New features:
- Update version schema and add version command - Enable user to limit results during run query - Enable user to query runs belongs to her
This commit is contained in:
Родитель
543114d8c7
Коммит
f8e2878dbe
|
@ -12,4 +12,4 @@ script:
|
|||
- pylint src
|
||||
- export
|
||||
- ./setup.py bdist_wheel
|
||||
- ./publish.sh
|
||||
- ./scripts/publish.sh
|
|
@ -0,0 +1,14 @@
|
|||
Micrsoft ADX A01 Automation System CLI
|
||||
======================================
|
||||
|
||||
|
||||
.. :changelog:
|
||||
|
||||
Release History
|
||||
===============
|
||||
|
||||
0.14
|
||||
++++
|
||||
|
||||
* New Features: Allow specify --last and --skip while query runs to limit the return data set.
|
||||
* New Features: Allow --me option while query runs to list runs belong to the current user only.
|
|
@ -13,4 +13,5 @@ version=${version/-py3-none-any.whl/}
|
|||
echo $version
|
||||
|
||||
az storage blob upload -c client -f $wheel_file -n archive/$wheel_file --validate-content --no-progress
|
||||
az storage blob url -c client -n archive/$wheel_file -otsv | az storage blob upload -c client -f /dev/stdin -n latest --validate-content --no-progress
|
||||
az storage blob url -c client -n archive/$wheel_file -otsv | tee ./blob_path
|
||||
az storage blob upload -c client -f ./blob_path -n latest --validate-content --no-progress
|
27
setup.py
27
setup.py
|
@ -1,20 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
from setuptools import setup
|
||||
|
||||
def get_version() -> str:
|
||||
if "TRAVIS" in os.environ:
|
||||
tag = os.environ.get('TRAVIS_TAG', None)
|
||||
if tag:
|
||||
return tag
|
||||
else:
|
||||
return f'0.0.0.{os.environ["TRAVIS_BUILD_NUMBER"]}'
|
||||
return f'0.0.0.{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}'
|
||||
ROOT_INIT = os.path.join(os.path.dirname(__file__), 'src', 'a01', '__init__.py')
|
||||
VERSION = os.environ.get('TRAVIS_TAG')
|
||||
|
||||
try:
|
||||
if VERSION:
|
||||
with open(ROOT_INIT, 'w') as file_handler:
|
||||
file_handler.write(f'__version__ = {VERSION}')
|
||||
else:
|
||||
with open(ROOT_INIT, 'r') as file_handler:
|
||||
line = file_handler.readline()
|
||||
VERSION = line.split('=')[1].strip()
|
||||
except (ValueError, IOError):
|
||||
print('Fail to pass version string.', file=sys.stderr, flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
VERSION = get_version()
|
||||
with open('HISTORY.rst', 'r', encoding='utf-8') as f:
|
||||
HISTORY = f.read()
|
||||
|
||||
CLASSIFIERS = [
|
||||
'Development Status :: 4 - Beta',
|
||||
|
@ -40,7 +47,7 @@ setup(
|
|||
name='adx-automation-cli',
|
||||
version=VERSION,
|
||||
description='ADX Automation CLI',
|
||||
long_description='Command line tools for ADX automation system',
|
||||
long_description=HISTORY,
|
||||
license='MIT',
|
||||
author='Microsoft Corporation',
|
||||
author_email='trdai@microsoft.com',
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
__version__ = '1.0.0+local'
|
|
@ -1,3 +1,11 @@
|
|||
from a01 import __version__
|
||||
from a01.cli import cmd
|
||||
|
||||
@cmd('version', desc='Print version information')
|
||||
def version() -> None:
|
||||
print(__version__)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
from a01.cli import setup_commands
|
||||
|
||||
|
|
|
@ -141,6 +141,22 @@ class AuthSettings(object):
|
|||
self.logger.exception(f'Fail to save the file {TOKEN_FILE}')
|
||||
raise
|
||||
|
||||
def get_user_name(self) -> str:
|
||||
"""Returns the name of current user. For a user account, it returns its user id. For a service principal, it
|
||||
returns its service principal ID."""
|
||||
if not self.has_login:
|
||||
print('You need to login. Usage: a01 login.')
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
return self.user_id
|
||||
except AuthenticationError:
|
||||
try:
|
||||
return self.service_principal_id
|
||||
except AuthenticationError:
|
||||
print("Fail to find user name. Tried both user id and service principal.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@staticmethod
|
||||
def _get_auth_context() -> adal.AuthenticationContext:
|
||||
return adal.AuthenticationContext(AUTHORITY_URL, api_version=None)
|
||||
|
@ -209,18 +225,3 @@ def whoami() -> None:
|
|||
print(AuthSettings().summary)
|
||||
except AuthenticationError:
|
||||
print('You need to login. Usage: a01 login.')
|
||||
|
||||
|
||||
def get_user_id() -> str:
|
||||
try:
|
||||
return AuthSettings().user_id
|
||||
except AuthenticationError:
|
||||
print('You need to login. Usage: a01 login.')
|
||||
sys.exit(1)
|
||||
|
||||
def get_service_principal_id() -> str:
|
||||
try:
|
||||
return AuthSettings().service_principal_id
|
||||
except AuthenticationError:
|
||||
print('You need to login. Usage: a01 login.')
|
||||
sys.exit(1)
|
||||
|
|
|
@ -66,3 +66,4 @@ class A01Config(configparser.ConfigParser): # pylint: disable=too-many-ancestor
|
|||
@property
|
||||
def endpoint_uri(self) -> str:
|
||||
return f'https://{self.endpoint}/api'
|
||||
# return 'http://127.0.0.1:5000/api'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import datetime
|
||||
import base64
|
||||
import urllib
|
||||
from typing import List, Tuple, Generator
|
||||
|
||||
import colorama
|
||||
|
@ -15,10 +16,11 @@ from a01.common import get_logger, A01Config, NAMESPACE
|
|||
class Run(object):
|
||||
logger = get_logger('Run')
|
||||
|
||||
def __init__(self, name: str, settings: dict, details: dict):
|
||||
def __init__(self, name: str, settings: dict, details: dict, owner: str = None):
|
||||
self.name = name
|
||||
self.settings = settings
|
||||
self.details = details
|
||||
self.owner = owner
|
||||
|
||||
self.id = None # pylint: disable=invalid-name
|
||||
self.creation = None
|
||||
|
@ -27,7 +29,8 @@ class Run(object):
|
|||
result = {
|
||||
'name': self.name,
|
||||
'settings': self.settings,
|
||||
'details': self.details
|
||||
'details': self.details,
|
||||
'owner': self.owner
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -71,6 +74,7 @@ class Run(object):
|
|||
result = Run(name=data['name'], settings=data['settings'], details=data['details'])
|
||||
result.id = data['id']
|
||||
result.creation = datetime.datetime.strptime(data['creation'], '%Y-%m-%dT%H:%M:%SZ')
|
||||
result.owner = data.get('owner', None)
|
||||
|
||||
return result
|
||||
|
||||
|
@ -91,7 +95,7 @@ class RunCollection(object):
|
|||
for run in self.runs:
|
||||
time = (run.creation - datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M PST')
|
||||
remark = run.details.get('remark', None) or run.settings.get('a01.reserved.remark', '')
|
||||
owner = run.details.get('creator', None) or run.details.get('a01.reserved.creator', '')
|
||||
owner = run.owner or run.details.get('creator', None) or run.details.get('a01.reserved.creator', '')
|
||||
|
||||
row = [run.id, run.name, time, remark, owner]
|
||||
if remark and remark.lower() == 'official':
|
||||
|
@ -105,13 +109,22 @@ class RunCollection(object):
|
|||
return 'Id', 'Name', 'Creation', 'Remark', 'Owner'
|
||||
|
||||
@classmethod
|
||||
def get(cls) -> 'RunCollection':
|
||||
def get(cls, **kwargs) -> 'RunCollection':
|
||||
try:
|
||||
resp = session.get(f'{cls.endpoint_uri()}/runs')
|
||||
url = f'{cls.endpoint_uri()}/runs'
|
||||
query = {}
|
||||
for key, value in kwargs.items():
|
||||
if value is not None:
|
||||
query[key] = value
|
||||
|
||||
if query:
|
||||
url = f'{url}?{urllib.parse.urlencode(query)}'
|
||||
|
||||
resp = session.get(url)
|
||||
resp.raise_for_status()
|
||||
|
||||
runs = [Run.from_dict(each) for each in resp.json()]
|
||||
runs = sorted(runs, key=lambda r: r.id)
|
||||
runs = sorted(runs, key=lambda r: r.id, reverse=True)
|
||||
|
||||
return RunCollection(runs)
|
||||
except HTTPError:
|
||||
|
|
|
@ -20,11 +20,12 @@ from kubernetes.client.models.v1_env_var import V1EnvVar
|
|||
from kubernetes.client.models.v1_env_var_source import V1EnvVarSource
|
||||
from kubernetes.client.models.v1_secret_key_selector import V1SecretKeySelector
|
||||
|
||||
import a01
|
||||
import a01.models
|
||||
from a01.common import get_logger, A01Config, COMMON_IMAGE_PULL_SECRET
|
||||
from a01.cli import cmd, arg
|
||||
from a01.communication import session
|
||||
from a01.auth import get_user_id, get_service_principal_id
|
||||
from a01.auth import AuthSettings, AuthenticationError
|
||||
from a01.output import output_in_table
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
@ -33,13 +34,27 @@ logger = get_logger(__name__) # pylint: disable=invalid-name
|
|||
|
||||
|
||||
@cmd('get runs', desc='Retrieve the runs.')
|
||||
def get_runs() -> None:
|
||||
@arg('owner', help='Query runs by owner.')
|
||||
@arg('me', help='Query runs created by me.')
|
||||
@arg('last', help='Returns the last NUMBER of records. Default: 20.')
|
||||
@arg('skip', help='Returns the records after skipping given number of records at the bottom. Default: 0.')
|
||||
def get_runs(me: bool = False, last: int = 20, skip: int = 0, owner: str = None) -> None: # pylint: disable=invalid-name
|
||||
try:
|
||||
runs = a01.models.RunCollection.get()
|
||||
if me and owner:
|
||||
raise ValueError('--me and --user are mutually exclusive.')
|
||||
elif me:
|
||||
owner = AuthSettings().get_user_name()
|
||||
|
||||
runs = a01.models.RunCollection.get(owner=owner, last=last, skip=skip)
|
||||
output_in_table(runs.get_table_view(), headers=runs.get_table_header())
|
||||
except ValueError as err:
|
||||
logger.error(err)
|
||||
sys.exit(1)
|
||||
except AuthenticationError as err:
|
||||
logger.error(err)
|
||||
print('You need to login. Usage: a01 login.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
@cmd('get run', desc='Retrieve a run')
|
||||
|
@ -106,8 +121,9 @@ def get_run(run_id: str, log: bool = False, recording: bool = False, recording_a
|
|||
def create_run(image: str, from_failures: str = None, live: bool = False, parallelism: int = 3, query: str = None,
|
||||
remark: str = '', email: bool = False, secret: str = None, mode: str = None,
|
||||
reset_run: str = None) -> None:
|
||||
auth = AuthSettings()
|
||||
remark = remark or ''
|
||||
creator = get_user_id() if email else get_service_principal_id()
|
||||
creator = auth.get_user_name()
|
||||
|
||||
try:
|
||||
if not reset_run:
|
||||
|
@ -119,7 +135,7 @@ def create_run(image: str, from_failures: str = None, live: bool = False, parall
|
|||
'a01.reserved.storageshare': 'k8slog',
|
||||
'a01.reserved.testquery': query,
|
||||
'a01.reserved.remark': remark,
|
||||
'a01.reserved.useremail': get_user_id() if email else '',
|
||||
'a01.reserved.useremail': auth.user_id if email else '',
|
||||
'a01.reserved.initparallelism': parallelism,
|
||||
'a01.reserved.livemode': str(live),
|
||||
'a01.reserved.testmode': mode,
|
||||
|
@ -127,8 +143,9 @@ def create_run(image: str, from_failures: str = None, live: bool = False, parall
|
|||
},
|
||||
details={
|
||||
'a01.reserved.creator': creator,
|
||||
'a01.reserved.client': 'A01 CLI'
|
||||
})
|
||||
'a01.reserved.client': f'CLI {a01.__version__}'
|
||||
},
|
||||
owner=creator)
|
||||
|
||||
# prune
|
||||
to_delete = [k for k, v in run_model.settings.items() if not v]
|
||||
|
|
Загрузка…
Ссылка в новой задаче