diff --git a/.gitignore b/.gitignore index 7d77160..47076d2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ __pycache__/ *.swp +.idea/ + # C extensions *.so @@ -102,3 +104,6 @@ ENV/ # mypy .mypy_cache/ + +# exuberant ctags +tags diff --git a/infraboxcli/__init__.py b/infraboxcli/__init__.py index f622d61..69145e9 100644 --- a/infraboxcli/__init__.py +++ b/infraboxcli/__init__.py @@ -2,14 +2,17 @@ import os import sys -from infraboxcli.push import push -from infraboxcli.run import run from infraboxcli.graph import graph -from infraboxcli.validate import validate +from infraboxcli.init import init from infraboxcli.list_jobs import list_jobs from infraboxcli.log import logger -from infraboxcli.init import init from infraboxcli.pull import pull +from infraboxcli.push import push +from infraboxcli.run import run +from infraboxcli.validate import validate + +from infraboxcli.dashboard import user +from infraboxcli.dashboard import project version = '0.6.3' @@ -90,10 +93,73 @@ def main(): parser_run.add_argument("--local-cache", required=False, type=str, default="/tmp/{}/infrabox/local-cache".format(username), help="Path to the local cache") - + parser_run.add_argument("--memory", required=False, type=float, + help="Override a memory limit for your job") + parser_run.add_argument("--cpu", required=False, type=float, + help="Override a cpu limit for your job") parser_run.set_defaults(no_rm=False) parser_run.set_defaults(func=run) + # Collaborators + parser_collaborators = sub_parser.add_parser('collaborators', help='Add or remove collaborators for your project') + sub_collaborators = parser_collaborators.add_subparsers() + + parser_list_collaborators = sub_collaborators.add_parser('list', help='Show collaborators list') + parser_list_collaborators.add_argument('--verbose', required=False, default=True, type=str2bool) + parser_list_collaborators.set_defaults(func=project.list_collaborators) + + parser_add_collaborator = sub_collaborators.add_parser('add', help='Add a collaborator') + parser_add_collaborator.add_argument('--username', required=True, type=str, + help='Username of the collaborator you want to add') + parser_add_collaborator.set_defaults(func=project.add_collaborator) + + parser_remove_collaborator = sub_collaborators.add_parser('remove', help='Remove a collaborator') + parser_remove_collaborator.add_argument('--username', required=True, type=str, + help='Username of the collaborator you want to remove') + parser_remove_collaborator.set_defaults(func=project.remove_collaborator) + + # Secrets + parser_secrets = sub_parser.add_parser('secrets', help='Create or delete secrets') + sub_secrets = parser_secrets.add_subparsers() + + parser_create_secret = sub_secrets.add_parser('create', help='Create a secret') + parser_create_secret.add_argument('--name', required=True, type=str, help='Name of the secret') + parser_create_secret.add_argument('--value', required=True, type=str, help='Value of the secret') + parser_create_secret.set_defaults(func=project.add_secret) + + parser_delete_secret = sub_secrets.add_parser('delete', help='Delete a secret') + parser_delete_secret.add_argument('--name', required=True, type=str, help='Name of the secret you want to delete') + parser_delete_secret.set_defaults(func=project.delete_secret) + + # Tokens + parsers_project_tokens = sub_parser.add_parser('project-token', help='Manage your project tokens') + sub_project_tokens = parsers_project_tokens.add_subparsers() + + parser_list_project_tokens = sub_project_tokens.add_parser('list', help='Show all your project tokens') + parser_list_project_tokens.add_argument('--verbose', required=False, default=True, type=str2bool) + parser_list_project_tokens.set_defaults(func=project.list_project_tokens) + + parser_add_project_token = sub_project_tokens.add_parser('create', help='Create a project token') + parser_add_project_token.add_argument('--description', required=True, type=str, + help='Description of the project token you want to create') + #TODO when scope push/pull functionality is implemented, uncomment following 2 lines + #parser_add_project_token.add_argument('--scope_push', required=False, default=True, type=str2bool, help='Scope push') + #parser_add_project_token.add_argument('--scope_pull', required=False, default=True, type=str2bool, help='Scope pull') + parser_add_project_token.set_defaults(func=project.add_project_token) + + parser_remove_project_token = sub_project_tokens.add_parser('delete', help='Delete a project token') + parser_remove_project_token.add_argument('--id', required=False, type=str, + help='Id of the project token you want to delete') + parser_remove_project_token.add_argument('--description', required=False, type=str, + help='Description of the project token you want to delete') + parser_remove_project_token.set_defaults(func=project.delete_project_token) + + # User + parser_login = sub_parser.add_parser('login', help='Login to infrabox') + parser_login.add_argument('--email', required=False, default=None, type=str, help='Email of the user') + parser_login.add_argument('--password', required=False, default=None, type=str, help='Password of the user') + parser_login.set_defaults(func=user.login) + # Parse args args = parser.parse_args() @@ -139,3 +205,12 @@ def main(): # Run command args.func(args) + + +def str2bool(v): + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') diff --git a/infraboxcli/dashboard/__init__.py b/infraboxcli/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/infraboxcli/dashboard/cli_client.py b/infraboxcli/dashboard/cli_client.py new file mode 100644 index 0000000..f789016 --- /dev/null +++ b/infraboxcli/dashboard/cli_client.py @@ -0,0 +1,24 @@ +import requests + +session = requests.Session() + + +def get(url, headers=None, cookies_handler=None): + response = session.get(url, headers=headers) + if cookies_handler: + cookies_handler(session.cookies.get_dict()) + return response + + +def post(url, data, headers=None, cookies_handler=None): + response = session.post(url, json=data, headers=headers) + if cookies_handler: + cookies_handler(session.cookies.get_dict()) + return response + + +def delete(url, headers=None, cookies_handler=None): + response = session.delete(url, headers=headers) + if cookies_handler: + cookies_handler(session.cookies.get_dict()) + return response \ No newline at end of file diff --git a/infraboxcli/dashboard/external.py b/infraboxcli/dashboard/external.py new file mode 100644 index 0000000..297dfe4 --- /dev/null +++ b/infraboxcli/dashboard/external.py @@ -0,0 +1,43 @@ +import pickle +from os.path import expanduser + +home = expanduser("~") + +def save_user_token(dict): + try: + f = open(home + '/.infra.data', 'rb') + obj = pickle.load(f) + f.close() + + except: + obj = {} + + obj['current_user_token'] = dict['token'] + + f = open(home + '/.infra.data', 'wb') + pickle.dump(obj, f) + f.close() + + +def load_current_user_token(): + try: + f = open(home + '/.infra.data', 'rb') + obj = pickle.load(f) + f.close() + curr_token = obj['current_user_token'] + except: + raise EnvironmentError('Could not load current user token. Please log in') + + return curr_token + + +def load_current_project_token(): + try: + f = open(home + '/.infra.data', 'rb') + obj = pickle.load(f) + f.close() + curr_token = obj['current_project_token'] + except: + raise EnvironmentError('Could not load current project token. Please set current project') + + return curr_token \ No newline at end of file diff --git a/infraboxcli/dashboard/project.py b/infraboxcli/dashboard/project.py new file mode 100644 index 0000000..75b52eb --- /dev/null +++ b/infraboxcli/dashboard/project.py @@ -0,0 +1,166 @@ +from infraboxcli.dashboard.cli_client import get, post, delete +from infraboxcli.dashboard.user import get_user_headers +import infraboxcli.env + +api_projects_endpoint_url = '/api/v1/projects/' + + +def delete_project(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + response = get(url, get_user_headers()) + + return response + + +def list_collaborators(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + '/collaborators' + response = get(url, get_user_headers()) + + if args.verbose: + print('=== Collaborators ===') + for collaborator in response.json(): + print('Username: %s' % collaborator['username']) + print('E-mail: %s' % collaborator['email']) + print('---') + + return response + + +def add_collaborator(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + '/collaborators' + data = { 'username': args.username } + + response = post(url, data, get_user_headers()) + print(response.json()['message']) + + return response + + +def remove_collaborator(args): + infraboxcli.env.check_env_cli_token(args) + + args.verbose = False + all_project_collaborators = list_collaborators(args).json() + collaborator_id = None + for collaborator in all_project_collaborators: + if collaborator['username'] == args.username: + collaborator_id = collaborator['id'] + break + + if collaborator_id is None: + print('Specified user is not in collaborators list.') + return + + url = args.url + api_projects_endpoint_url + args.project_id + '/collaborators/' + collaborator_id + response = delete(url, get_user_headers()) + print(response.json()['message']) + + return response + + +def add_secret(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + '/secrets' + data = {'name': args.name, 'value': args.value} + + response = post(url, data, get_user_headers()) + + return response + + +def delete_secret(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + '/secrets/' + args.name + response = delete(url, get_user_headers()) + + return response + + +def list_project_tokens(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + '/tokens' + + response = get(url, get_user_headers()) + if args.verbose: + print('=== Project tokens ===') + for project_token in response.json(): + print('Description: %s' % project_token['description']) + print('Id: %s' % project_token['id']) + print('Scope push: %s' % project_token['scope_push']) + print('Scope pull: %s' % project_token['scope_pull']) + print('---') + + return response + + +def get_project_token_id_by_description(args): + args.verbose = False + all_project_tokens = list_project_tokens(args).json() + + for project_token in all_project_tokens: + if args.description == project_token['description']: + return project_token['id'] + + return None + + +def add_project_token(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + '/tokens' + + data = { + 'description': args.description, + #TODO when scope push/pull functionality is implemented, + # delete following 2 lines and uncomment next 2 lines + 'scope_push': True, + 'scope_pull': True + #'scope_push': args.scope_push, + #'scope_pull': args.scope_pull + } + + response = post(url, data, get_user_headers()) + + if response.status_code != 200: + print(response.json()['message']) + return + + # Print project token to the CLI + print('=== Authentication Token ===') + print('Please save your token at a secure place. We will not show it to you again.\n\n') + print(response.json()['data']['token']) + + return response + + +def delete_project_token(args): + if args.id: + delete_project_token_by_id(args) + elif args.description: + delete_project_token_by_description(args) + else: + print('Please, provide either token id or description.') + + +def delete_project_token_by_description(args): + infraboxcli.env.check_env_cli_token(args) + token_id = get_project_token_id_by_description(args) + + if not token_id: + print('Token with such a description does not exist.') + return + + args.id = token_id + return delete_project_token_by_id(args) + + +def delete_project_token_by_id(args): + infraboxcli.env.check_env_cli_token(args) + url = args.url + api_projects_endpoint_url + args.project_id + '/tokens/' + args.id + response = delete(url, get_user_headers()) + + print(response.json()['message']) + + return response diff --git a/infraboxcli/dashboard/user.py b/infraboxcli/dashboard/user.py new file mode 100644 index 0000000..6e26431 --- /dev/null +++ b/infraboxcli/dashboard/user.py @@ -0,0 +1,32 @@ +import getpass +import json + +from infraboxcli.dashboard.cli_client import post, get +from infraboxcli.dashboard.external import load_current_user_token, save_user_token + +api_endpoint_url = '/api/v1/' + +def get_user_token(): + return load_current_user_token() + +def get_user_headers(): + return {'Authorization': 'token %s' % get_user_token()} + +def login(args): + email = args.email + password = args.password + + if email is None: + email = raw_input("Email: ") + # Don't allow to pass password without email + password = None + + if password is None: + password = getpass.getpass('Password: ') + + data = { "email": email, "password": password} + + url = args.url + api_endpoint_url + 'account/login' + response = post(url, data, cookies_handler=save_user_token) + + return response diff --git a/infraboxcli/run.py b/infraboxcli/run.py index 8620609..205cdf5 100644 --- a/infraboxcli/run.py +++ b/infraboxcli/run.py @@ -447,6 +447,15 @@ def run(args): # validate infrabox.json data = load_infrabox_json(args.infrabox_json) + if args.memory: + print('WARNING: only int resource limits are supported right now. Using rounded int instead of provided value.') + for job in data['jobs']: + job['resources']['limits']['memory'] = int(args.memory) + if args.cpu: + print('WARNING: only int resource limits are supported right now. Using rounded int instead of provided value.') + for job in data['jobs']: + job['resources']['limits']['cpu'] = int(args.cpu) + jobs = get_job_list(data, args, infrabox_context=args.project_root) if not args.job_name: diff --git a/setup.py b/setup.py index 1023d82..1852818 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ def readme(): author='infrabox', license='MIT', packages=['infraboxcli', + 'infraboxcli.dashboard', 'pyinfrabox', 'pyinfrabox.infrabox', 'pyinfrabox.badge',