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 a2414f7..caf51b8 100644 --- a/infraboxcli/__init__.py +++ b/infraboxcli/__init__.py @@ -2,14 +2,19 @@ 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 +from infraboxcli.dashboard import remotes +from infraboxcli.dashboard import local_config version = '0.6.4' @@ -94,13 +99,144 @@ 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) + # Project + parser_project = sub_parser.add_parser('project', help='Manage your project') + parser_project.add_argument('--project-name', required=False, type=str) + parser_project.set_defaults(project_command=True) + sub_project = parser_project.add_subparsers(dest='project') + + # Project list + parser_projects_list = sub_project.add_parser('list', help='Get a list of all your projects') + parser_projects_list.add_argument('--verbose', required=False, default=True, type=str2bool) + parser_projects_list.set_defaults(func=project.list_projects) + + # Project status + parser_projects_list = sub_project.add_parser('status', help='Get some info about your current project') + parser_projects_list.add_argument('--verbose', required=False, default=True, type=str2bool) + parser_projects_list.set_defaults(func=project.print_status) + + # Create project + parser_project_create = sub_project.add_parser('create', help='Create a new project') + parser_project_create.add_argument('--name', required=True, type=str, + help='Name of the project you want to create') + parser_project_create.add_argument('--type', required=True, type=str, + help='Name of the project { upload, github, gerrit } you want to create') + parser_project_create.add_argument('--public', required=False, default=False, action='store_true', + help='Make your project public') + parser_project_create.add_argument('--private', required=False, default=False, action='store_true', + help='Make your project private') + parser_project_create.set_defaults(func=project.create_project) + + parser_project_delete = sub_project.add_parser('delete', help='Delete a project') + parser_project_delete.add_argument('--name', required=False, type=str, + help='Name of the project you want to delete') + parser_project_delete.add_argument('--id', required=False, type=str, + help='Id of the project you want to delete') + parser_project_delete.set_defaults(func=project.delete_project) + + # Collaborators + parser_collaborators = sub_project.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_project.add_parser('secrets', help='Create or delete secrets') + sub_secrets = parser_secrets.add_subparsers() + + parser_list_secrets = sub_secrets.add_parser('list', help='Show all your secrets') + parser_list_secrets.add_argument('--verbose', required=False, default=True, type=str2bool) + parser_list_secrets.set_defaults(func=project.list_secrets) + + 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=False, type=str, + help='Name of the secret you want to delete') + parser_delete_secret.add_argument('--id', required=False, type=str, + help='Id of the secret you want to delete') + parser_delete_secret.set_defaults(func=project.delete_secret) + + # Tokens + parsers_project_tokens = sub_project.add_parser('tokens', 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) + + # Login + parser_login = sub_parser.add_parser('login', help='Login to infrabox') + parser_login.add_argument('remote_url', nargs='?', type=str, help='Name of remote') + 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) + + # Logout + parser_logout = sub_parser.add_parser('logout', help='Logout from current remote') + parser_logout.set_defaults(func=user.logout) + + # Config + parser_config = sub_parser.add_parser('config', help='Configure your infrabox') + sub_config = parser_config.add_subparsers(dest='config') + + parser_config_current_project = sub_config.add_parser('set-current-project', help='Set new current project') + parser_config_current_project.add_argument('project_name', nargs='?', type=str, help='Name of the project') + parser_config_current_project.set_defaults(func=local_config.set_current_project_name) + + # Remotes + parser_remotes = sub_parser.add_parser('remotes', help='Current remotes') + sub_remotes = parser_remotes.add_subparsers() + parser_remotes_list = sub_remotes.add_parser('list', help='Show your all remotes') + parser_remotes_list.add_argument('--verbose', required=False, default=True, type=str2bool) + parser_remotes_list.set_defaults(func=remotes.list_remotes) + # Parse args args = parser.parse_args() + # Prevent collision on `project-name` argument with `run`, `pull`, `push` commands + if 'project' in args or 'config' in args: + # Run command + args.func(args) + return + if 'version' in args: print('infraboxcli %s' % version) return @@ -151,3 +287,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..76e3e3a --- /dev/null +++ b/infraboxcli/dashboard/cli_client.py @@ -0,0 +1,51 @@ +import requests + +from infraboxcli.log import logger + +session = requests.Session() +connection_error_message = 'Can\'t connect to the remote. Please, check your connection or remote url.' + + +def get(url, headers=None, cookies_handler=None, verify=None, timeout=60): + try: + response = session.get(url, headers=headers, verify=verify, timeout=timeout) + except requests.ConnectionError: + logger.error(connection_error_message) + exit(1) + except Exception as e: + logger.error(e.message) + exit(1) + + if cookies_handler: + cookies_handler(url, session.cookies.get_dict()) + return response + + +def post(url, data, headers=None, cookies_handler=None, verify=None, timeout=60): + try: + response = session.post(url, json=data, headers=headers, verify=verify, timeout=timeout) + except requests.ConnectionError: + logger.error(connection_error_message) + exit(1) + except Exception as e: + logger.error(e.message) + exit(1) + + if cookies_handler: + cookies_handler(url, session.cookies.get_dict()) + return response + + +def delete(url, headers=None, cookies_handler=None, verify=None, timeout=60): + try: + response = session.delete(url, headers=headers, verify=verify, timeout=timeout) + except requests.ConnectionError: + logger.error(connection_error_message) + exit(1) + except Exception as e: + logger.error(e.message) + exit(1) + + if cookies_handler: + cookies_handler(url, session.cookies.get_dict()) + return response diff --git a/infraboxcli/dashboard/external.py b/infraboxcli/dashboard/external.py new file mode 100644 index 0000000..844fb93 --- /dev/null +++ b/infraboxcli/dashboard/external.py @@ -0,0 +1,75 @@ +from infraboxcli.log import logger +from pyinfrabox.utils import get_remote_url +from infraboxcli.dashboard import local_config + + +def save_user_token(url, cookies_dict): + config = local_config.get_config() + if config is None: + config = {} + + config.setdefault('remotes', {}) + + is_new_remote_or_null = False + remote_url = get_remote_url(url) + if remote_url not in config['remotes'] \ + or config['remotes'][remote_url] is None: + is_new_remote_or_null = True + + # Decide what are we going to do if user entered invalid username or password: + # either use `current_user_token` if it exists or raise an error + allow_login_if_current_user_token_is_set = False + + user_token = None + if 'token' not in cookies_dict: + if is_new_remote_or_null or not allow_login_if_current_user_token_is_set: + logger.error('Unauthorized: invalid username and/or password.') + exit(1) + else: + user_token = config['remotes'][remote_url]['current_user_token'] + else: + user_token = cookies_dict['token'] + + config['current_remote'] = remote_url + config['remotes'].setdefault(remote_url, {}) + config['remotes'][remote_url]['current_user_token'] = user_token + + local_config.save_config(config) + logger.info('Logged in successfully.') + + +def get_current_user_token(): + try: + config = local_config.get_config() + + current_remote = config['current_remote'] + if not current_remote: + raise + + current_user_token = config['remotes'][current_remote]['current_user_token'] + if current_user_token is None or not current_user_token: + raise + + return current_user_token + except: + logger.error('Could not load current user token. Please, log in.') + exit(1) + + +def delete_current_user_token(args=None): + try: + config = local_config.get_config() + + current_remote = config['current_remote'] + if not current_remote: + raise + + if not config['remotes'][current_remote]['current_user_token']: + return False + + config['remotes'][current_remote]['current_user_token'] = "" + local_config.save_config(config) + + return True + except: + return False diff --git a/infraboxcli/dashboard/local_config.py b/infraboxcli/dashboard/local_config.py new file mode 100644 index 0000000..7f8fb79 --- /dev/null +++ b/infraboxcli/dashboard/local_config.py @@ -0,0 +1,81 @@ +import json + +from os.path import expanduser +from infraboxcli.log import logger +from pyinfrabox.utils import safe_open_w + +config_file_path = '/.infrabox/config.json' +home = expanduser("~") + + +def set_current_project_name(args): + from infraboxcli.dashboard import project + all_projects = project.get_projects(args).json() + + project_exists = False + for project in all_projects: + if args.project_name == project['name']: + project_exists = True + break + + if not project_exists: + logger.error('Project with such a name does not exist.') + exit(1) + + try: + config = get_config() + + config['remotes'][get_current_remote_url()]['current_project'] = args.project_name + save_config(config) + + return True + except: + return False + + +def get_current_project_name(args): + try: + return get_config()['remotes'][get_current_remote_url()]['current_project'] + except: + return None + + +def get_current_remote_url(): + try: + return get_config()['current_remote'] + except: + return None + + +def get_all_remotes(): + try: + config = get_config() + + remotes = config['remotes'].keys() + if not remotes: + raise + + return remotes + except: + logger.error('No available remotes. Please, log in.') + exit(1) + + +def get_config(): + try: + with open(home + config_file_path, 'r') as config_file: + config = json.load(config_file) + + return config + except: + return None + + +def save_config(config): + try: + with safe_open_w(home + config_file_path) as config_file: + json.dump(config, config_file) + + return True + except: + return False diff --git a/infraboxcli/dashboard/project.py b/infraboxcli/dashboard/project.py new file mode 100644 index 0000000..ed02cec --- /dev/null +++ b/infraboxcli/dashboard/project.py @@ -0,0 +1,404 @@ +from infraboxcli.dashboard.cli_client import get, post, delete +from infraboxcli.dashboard.user import get_user_headers +import infraboxcli.env + +from infraboxcli.log import logger + +api_projects_endpoint_url = '/api/v1/projects/' +allowed_project_types = ['upload'] #TODO: add ['github', 'gitlab', 'gerrit'] + + +def check_project_is_set(args): + infraboxcli.env.check_env_cli_token(args) + + if args.project_name: + args.project_id = get_project_id_by_name(args) + + if args.project_id is not None: + if 'project_name_printed' not in args \ + and 'using_default_project' not in args: + logger.info('Project: {project_name}'.format(project_name=args.project_name)) + args.project_name_printed = True + + return True + + exit(1) + + +def get_projects(args): + infraboxcli.env.check_env_url(args) + + url = args.url + api_projects_endpoint_url + response = get(url, get_user_headers(), verify=args.ca_bundle, timeout=60) + + return response + + +def list_projects(args): + if args.verbose: + all_projects = get_projects(args).json() + + logger.info('Projects:') + msg = "" + for project in all_projects: + msg += 'Name: {}\nId: {}\nType: {}\nPublic: {}\n---\n'\ + .format(project['name'], project['id'], project['type'], project['public']) + logger.log(msg, print_header=False) + + +def print_status(args): + if args.verbose: + infraboxcli.env.check_env_cli_token(args) + + if args.project_name: + project = get_project_by_name(args) + elif args.project_id: + project = get_project_by_id(args) + + if project is None: + logger.error('Current project is not set.') + exit(1) + + num_collaborators = len(get_collaborators(args).json()) + num_tokens = len(get_project_tokens(args).json()) + num_secrets = len(get_secrets(args).json()) + + logger.info('Project status:') + msg = 'Name: {}\nId: {}\nType: {}\nPublic: {}\n---\n' \ + + 'Total collaborators: {}\nTotal tokens: {}\nTotal secrets: {}\n---\n' + logger.log(msg.format(project['name'], project['id'], project['type'], project['public'], + num_collaborators, num_tokens, num_secrets), print_header=False) + + +def create_project(args): + infraboxcli.env.check_env_url(args) + + if not args.private and not args.public: + logger.error('Specify if your project is going to be public or private, please.') + return + + if args.private and args.public: + logger.error('Project can\'t be public and private simultaneously. ' + + 'Choose only one option, please.') + return + + is_private_project = True + if args.public: + is_private_project = False + + args.type = args.type.lower() + if args.type not in allowed_project_types: + logger.error('Provided project type is not supported.' + + '\nAllowed project types are: [ {allowed_types} ]' + .format(allowed_types=', '.join(allowed_project_types))) + return + + url = args.url + api_projects_endpoint_url + + data = { + 'name': args.name, + 'type': args.type, + 'private': is_private_project + } + response = post(url, data=data, headers=get_user_headers(), verify=args.ca_bundle, timeout=60) + + if response.status_code != 200: + logger.error(response.json()['message']) + else: + logger.info(response.json()['message']) + + return response + + +def get_project_id_by_name(args): + all_projects = get_projects(args).json() + + for project in all_projects: + if args.project_name == project['name']: + return project['id'] + + logger.info('Project with such a name does not exist.') + return None + + +def get_project_name_by_id(args): + project = get_project_by_id(args) + if project: + return project['name'] + + +def get_project_by_id(args): + all_projects = get_projects(args).json() + + for project in all_projects: + if args.project_id == project['id']: + return project + + logger.info('Project with such an id does not exist.') + return None + + +def get_project_by_name(args): + project_id = get_project_id_by_name(args) + + if project_id: + args.project_id = project_id + return get_project_by_id(args) + + +def delete_project(args): + if args.id: + delete_project_by_id(args) + elif args.name: + delete_project_by_name(args) + else: + logger.error('Please, provide either token id or name.') + + +def delete_project_by_name(args): + infraboxcli.env.check_env_url(args) + + args.project_name = args.name + project_id = get_project_id_by_name(args) + + if not project_id: + return + + args.id = project_id + return delete_project_by_id(args) + + +def delete_project_by_id(args): + infraboxcli.env.check_env_url(args) + url = args.url + api_projects_endpoint_url + args.id + response = delete(url, headers=get_user_headers(), verify=args.ca_bundle, timeout=60) + + if response.status_code != 200: + logger.error(response.json()['message']) + else: + logger.info(response.json()['message']) + + return response + + +def get_collaborators(args): + check_project_is_set(args) + + url = args.url + api_projects_endpoint_url + args.project_id + '/collaborators' + response = get(url, get_user_headers(), verify=args.ca_bundle, timeout=60) + return response + + +def list_collaborators(args): + if args.verbose: + all_collaborators = get_collaborators(args).json() + + logger.info('Collaborators:') + msg = "" + for collaborator in all_collaborators: + msg += 'Username: %s' % collaborator['username']\ + + '\nE-mail: %s' % collaborator['email']\ + + '\n---\n' + logger.log(msg, print_header=False) + + +def add_collaborator(args): + check_project_is_set(args) + + url = args.url + api_projects_endpoint_url + args.project_id + '/collaborators' + data = { 'username': args.username } + response = post(url, data, get_user_headers(), verify=args.ca_bundle, timeout=60) + + logger.info(response.json()['message']) + return response + + +def remove_collaborator(args): + check_project_is_set(args) + + all_project_collaborators = get_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: + logger.info('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(), verify=args.ca_bundle, timeout=60) + + logger.info(response.json()['message']) + return response + + +def get_secrets(args): + check_project_is_set(args) + + url = args.url + api_projects_endpoint_url + args.project_id + '/secrets' + response = get(url, get_user_headers(), verify=args.ca_bundle, timeout=60) + + return response + + +def list_secrets(args): + if args.verbose: + all_secrets = get_secrets(args).json() + + logger.info('Secrects:') + msg = "" + for secret in all_secrets: + msg += 'Name: %s' % secret['name']\ + + '\nId: %s' % secret['id']\ + + '\n---\n' + logger.log(msg, print_header=False) + + +def get_secret_id_by_name(args): + all_secrets = get_secrets(args).json() + + for secret in all_secrets: + if args.name == secret['name']: + return secret['id'] + + logger.info('Secret with such a name does not exist.') + return None + + +def add_secret(args): + check_project_is_set(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(), verify=args.ca_bundle, timeout=60) + + logger.info(response.json()['message']) + return response + + +def delete_secret(args): + if args.id: + delete_secret_by_id(args) + elif args.name: + delete_secret_by_name(args) + else: + logger.error('Please, provide either token id or description.') + + +def delete_secret_by_name(args): + check_project_is_set(args) + + secret_id = get_secret_id_by_name(args) + + if not secret_id: + return + + args.id = secret_id + return delete_secret_by_id(args) + + +def delete_secret_by_id(args): + check_project_is_set(args) + + url = args.url + api_projects_endpoint_url + args.project_id + '/secrets/' + args.id + response = delete(url, get_user_headers(), verify=args.ca_bundle, timeout=60) + + logger.info(response.json()['message']) + return response + + +def get_project_tokens(args): + check_project_is_set(args) + + url = args.url + api_projects_endpoint_url + args.project_id + '/tokens' + response = get(url, get_user_headers(), verify=args.ca_bundle, timeout=60) + + return response + + +def list_project_tokens(args): + if args.verbose: + all_project_tokens = get_project_tokens(args).json() + + logger.info('Project tokens:') + msg = "" + for project_token in all_project_tokens: + msg += 'Description: %s' % project_token['description']\ + + '\nId: %s' % project_token['id']\ + + '\nScope push: %s' % project_token['scope_push']\ + + '\nScope pull: %s' % project_token['scope_pull']\ + + '\n---\n' + logger.log(msg, print_header=False) + + +def get_project_token_id_by_description(args): + all_project_tokens = get_project_tokens(args).json() + + for project_token in all_project_tokens: + if args.description == project_token['description']: + return project_token['id'] + + logger.info('Token with such a description does not exist.') + return None + + +def add_project_token(args): + check_project_is_set(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(), verify=args.ca_bundle, timeout=60) + + if response.status_code != 200: + logger.error(response.json()['message']) + return + + # Print project token to the CLI + logger.info('Authentication Token:' + + '\nPlease save your token at a secure place. We will not show it to you again.\n') + logger.log(response.json()['data']['token'], print_header=False) + + 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: + logger.error('Please, provide either token id or description.') + + +def delete_project_token_by_description(args): + check_project_is_set(args) + + token_id = get_project_token_id_by_description(args) + + if not token_id: + return + + args.id = token_id + return delete_project_token_by_id(args) + + +def delete_project_token_by_id(args): + check_project_is_set(args) + + url = args.url + api_projects_endpoint_url + args.project_id + '/tokens/' + args.id + response = delete(url, get_user_headers(), verify=args.ca_bundle, timeout=60) + + logger.info(response.json()['message']) + return response diff --git a/infraboxcli/dashboard/remotes.py b/infraboxcli/dashboard/remotes.py new file mode 100644 index 0000000..a8013b4 --- /dev/null +++ b/infraboxcli/dashboard/remotes.py @@ -0,0 +1,12 @@ +from infraboxcli.dashboard import local_config +from infraboxcli.log import logger + + +def list_remotes(args): + if args.verbose: + remotes = local_config.get_all_remotes() + + msg = ': ' + msg += '\n: '.join(remotes) + logger.info('Remotes:') + logger.log(msg, print_header=False) diff --git a/infraboxcli/dashboard/user.py b/infraboxcli/dashboard/user.py new file mode 100644 index 0000000..4f0a256 --- /dev/null +++ b/infraboxcli/dashboard/user.py @@ -0,0 +1,54 @@ +import getpass +import json + +from infraboxcli.log import logger +from infraboxcli.dashboard.cli_client import post, get +from infraboxcli.dashboard.external import get_current_user_token, save_user_token, delete_current_user_token +from pyinfrabox.utils import validate_url +import infraboxcli.env + + +api_endpoint_url = '/api/v1/' + +def get_user_token(): + return get_current_user_token() + +def get_user_headers(): + return {'Authorization': 'token %s' % get_user_token()} + +def login(args): + if args.remote_url: + args.url = args.remote_url + + if args.remote_url and not validate_url(args.remote_url): + logger.error('Invalid url.') + exit(1) + + infraboxcli.env.check_env_url(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 + + +def logout(args): + token_deleted = delete_current_user_token(args) + + if token_deleted: + logger.info('Successfully logged out.') + else: + logger.info('Already logged out.') diff --git a/infraboxcli/env.py b/infraboxcli/env.py index 90a4ad5..48b1c08 100644 --- a/infraboxcli/env.py +++ b/infraboxcli/env.py @@ -1,9 +1,30 @@ import os import jwt +import textwrap from infraboxcli.log import logger +from infraboxcli.dashboard.local_config import get_current_remote_url, get_current_project_name + + +def check_env_url(args): + if not args.url: + current_remote_url = get_current_remote_url() + if current_remote_url: + args.url = current_remote_url + return True + + error_msg = textwrap.dedent("\ + Remote URL is not specified. Either set INFRABOX_URL env var " + + "or specify an url via `--url` argument.") + logger.error(error_msg) + exit(1) + def check_env_cli_token(args): + check_env_url(args) + if __check_project_name_set(args): + return True + token = os.environ.get('INFRABOX_CLI_TOKEN', None) if not token: logger.error('INFRABOX_CLI_TOKEN env var must be set') @@ -13,3 +34,18 @@ def check_env_cli_token(args): t = jwt.decode(token, verify=False) args.project_id = t['project']['id'] + + return True + + +def __check_project_name_set(args): + # Use project name from config only if no extra project name was provided + if not args.project_name: + current_config_project_name = get_current_project_name(args) + if current_config_project_name: + args.project_name = current_config_project_name + args.using_default_project = True + + return args.project_name + + diff --git a/infraboxcli/log.py b/infraboxcli/log.py index fed7750..5a485b3 100644 --- a/infraboxcli/log.py +++ b/infraboxcli/log.py @@ -8,6 +8,9 @@ def __init__(self): def _print(self, color, s): print("%s[infrabox] %s%s" % (color, s, Fore.RESET)) + def log(self, s, print_header=True): + print("%s%s" % ("[infrabox] " if print_header else "", s)) + def info(self, s): self._print(Fore.BLUE, s) diff --git a/infraboxcli/run.py b/infraboxcli/run.py index ceaeeb3..c7ae645 100644 --- a/infraboxcli/run.py +++ b/infraboxcli/run.py @@ -319,8 +319,9 @@ def build_and_run_docker(args, job): cmd += ['-e', 'INFRABOX_CLI=true'] - for e in args.env: - cmd += ['-e', e] + if args.env: + for e in args.env: + cmd += ['-e', e] if args.env_file: cmd += ['--env-file', args.env_file] @@ -453,6 +454,15 @@ def run(args): # validate infrabox.json data = load_infrabox_json(args.infrabox_json) + if args.memory: + logger.warn('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: + logger.warn('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/pyinfrabox/utils.py b/pyinfrabox/utils.py index 0a543e9..77ac523 100644 --- a/pyinfrabox/utils.py +++ b/pyinfrabox/utils.py @@ -1,8 +1,19 @@ +import os +import errno + from builtins import int, range, str from past.builtins import basestring from pyinfrabox import ValidationError +try: + #python2 + from urlparse import urlparse +except: + #python3 + from urllib.parse import urlparse + + def check_text(t, path, allowEmpty=False): if not isinstance(t, basestring): raise ValidationError(path, "is not a string") @@ -49,3 +60,33 @@ def check_number(d, path): def check_color(d, path): if d not in ("red", "green", "blue", "yellow", "orange", "white", "black", "grey"): raise ValidationError(path, "not a valid value") + +def get_remote_url(url): + parsed_url = urlparse(url) + return parsed_url.scheme + '://' + parsed_url.netloc + +def validate_url(url): + try: + result = urlparse(url) + return result.scheme and result.netloc + except: + return False + +def mkdir_p(path): + """ + An implementation of `mkdir -p` UNIX command. + """ + try: + os.makedirs(path) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise + +def safe_open_w(path): + """ + Open 'path' variable for writing with creating all parent directories if needed. + """ + mkdir_p(os.path.dirname(path)) + return open(path, 'w') diff --git a/setup.py b/setup.py index cbbed82..35e6280 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',