def run(self): if not type(self)._command_runners: # avoid circular dependency between this module and command_runners type(self)._command_runners = utils.import_module('devassistant.command_runners') for cr in type(self)._command_runners.command_runners: if cr.matches(self): return cr.run(self) raise exceptions.CommandException('No runner for command "{ct}: {c}".'.\ format(ct=self.comm_type, c=self.comm))
def format_deep(self, eval_expressions=True): """Formats command input of this command as a Python structure (list/dict/str). Args: eval_expressions: if False, format_deep will only substitute variables, but won't evaluate expressions """ if not self._lang: # avoid circular dependency between this module and lang type(self)._lang = utils.import_module('devassistant.lang') return self._format_deep_recursive(struct=self.comm, eval_expressions=eval_expressions)
class DockerHelper(object): try: _docker_module = utils.import_module('docker') except: _docker_module = None @classmethod def is_available(cls): return cls._docker_module is not None @property def errors(cls): try: return cls._docker_module.errors except AttributeError: return None @classmethod def get_client(cls): try: return cls._docker_module.Client() except AttributeError: return None @classmethod def docker_service_running(cls): try: ClHelper.run_command('systemctl status docker') return True except exceptions.ClException: return False @classmethod def docker_service_enable_and_run(cls): # TODO: add some conditionals for various platforms logger.info('Enabling and running docker service ...') try: cmd_str = 'bash -c "systemctl enable docker && systemctl start docker"' ClHelper.run_command(cmd_str, as_user='******') except exceptions.ClException: raise exceptions.CommandException( 'Failed to enable and run docker service.') # we need to wait until /var/run/docker.sock is created # let's wait for 30 seconds logger.info( 'Waiting for /var/run/docker.sock to be created (max 15 seconds) ...' ) success = False for i in range(0, 30): time.sleep(i * 0.5) try: ClHelper.run_command('ls /var/run/docker.sock') success = True break except exceptions.ClException: pass if not success: logger.warning( '/var/run/docker.sock doesn\'t exist, docker will likely not work!' ) @classmethod def docker_group_active(cls): logger.debug( 'Determining if current user has active "docker" group ...') # we have to run cl command, too see if the user has already re-logged # after being added to docker group, so that he can effectively use it if 'docker' in ClHelper.run_command('groups').split(): logger.debug('Current user is in "docker" group.') return True else: logger.debug('Current user is not in "docker" group.') return False @classmethod def user_in_docker_group(cls, username): if grp: return username in grp.getgrnam('docker').gr_mem else: # NotUNIX return False @classmethod def add_user_to_docker_group(cls, username): try: logger.info('Adding {0} to group docker ...'.format(username)) ClHelper.run_command( 'bash -c "usermod -a -G docker {0}"'.format(username), as_user='******') except exceptions.ClException as e: msg = 'Failed to add user to "docker" group: {0}'.format(e.output) raise exceptions.CommandException(msg)
class GitHubAuth(object): """Only use the github_authenticated decorator from the class. The other methods should be consider private; they expect certain order of calling, so calling them ad-hoc may break something. """ _user = None _token = None try: _gh_module = utils.import_module('github') except: _gh_module = None @classmethod def _github_token(cls, login): if not cls._token: try: cls._token = ClHelper.run_command("git config github.token.{login}".format( login=login)) except exceptions.ClException: pass # token is not available yet return cls._token @classmethod def _get_github_user(cls, login): if not cls._user: try: # try logging with token token = cls._github_token(login) gh = cls._gh_module.Github(login_or_token=token) cls._user = gh.get_user() # try if the authentication was successful cls._user.login except cls._gh_module.GithubException: # if the token was set, it was wrong, so make sure it's reset cls._token = None # try login with username/password 3 times cls._user = cls._try_login_with_password_ntimes(login, 3) if cls._user is not None: cls._github_create_auth() # create auth for future use return cls._user @classmethod def _try_login_with_password_ntimes(cls, login, ntimes): user = None for i in range(0, ntimes): password = DialogHelper.ask_for_password( prompt='Github Password for {username}:'.format(username=login)) # user pressed Ctrl + D if password is None: break gh = cls._gh_module.Github(login_or_token=login, password=password) user = gh.get_user() try: user.login break # if user.login doesn't raise, authentication has been successful except cls._gh_module.GithubException as e: user = None msg = 'Wrong Github username or password; message from Github: {0}\n'.\ format(e.data.get('message', 'Unknown authentication error')) msg += 'Try again or press {0} to abort.' if current_run.UI == 'cli': msg = msg.format('Ctrl + D') else: msg = msg.format('"Cancel"') logger.warning(msg) return user @classmethod def _github_create_auth(cls): """ Store token into ~/.gitconfig. Note: this uses cls._user.get_authorizations(), which only works if cls._user was authorized by login/password, doesn't work for token auth (TODO: why?). If token is not defined then store it into ~/.gitconfig file """ if not cls._token: try: auth = None for a in cls._user.get_authorizations(): if a.note == 'DevAssistant': auth = a if not auth: auth = cls._user.create_authorization( scopes=['repo', 'user', 'admin:public_key'], note="DevAssistant") ClHelper.run_command("git config --global github.token.{login} {token}".format( login=cls._user.login, token=auth.token)) ClHelper.run_command("git config --global github.user.{login} {login}".format( login=cls._user.login)) except cls._gh_module.GithubException as e: logger.warning('Creating authorization failed: {0}'.format(e)) @classmethod def _github_create_ssh_key(cls): """Creates a local ssh key, if it doesn't exist already, and uploads it to Github.""" try: login = cls._user.login pkey_path = '{home}/.ssh/{keyname}'.format(home=os.path.expanduser('~'), keyname=settings.GITHUB_SSH_KEYNAME.format(login=login)) # generate ssh key only if it doesn't exist if not os.path.exists(pkey_path): ClHelper.run_command('ssh-keygen -t rsa -f {pkey_path}\ -N \"\" -C \"DevAssistant\"'.\ format(pkey_path=pkey_path)) ClHelper.run_command('ssh-add {pkey_path}'.format(pkey_path=pkey_path)) public_key = ClHelper.run_command('cat {pkey_path}.pub'.format(pkey_path=pkey_path)) cls._user.create_key("DevAssistant", public_key) except exceptions.ClException as e: msg = 'Couldn\'t create a new ssh key: {e}'.format(e) raise exceptions.CommandException(msg) @classmethod def _create_ssh_config_entry(cls): # TODO: some duplication with _ssh_key_needs_config_entry, maybe refactor a bit ssh_config = os.path.expanduser('~/.ssh/config') fh = os.fdopen(os.open(ssh_config, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600), 'a') fh.write(settings.GITHUB_SSH_CONFIG.format( login=cls._user.login, keyname=settings.GITHUB_SSH_KEYNAME.format(login=cls._user.login))) fh.close() @classmethod def _github_ssh_key_exists(cls): """Returns True if any key on Github matches a local key, else False.""" remote_keys = map(lambda k: k._key, cls._user.get_keys()) found = False pubkey_files = glob.glob(os.path.expanduser('~/.ssh/*.pub')) for rk in remote_keys: for pkf in pubkey_files: local_key = open(pkf).read() # in PyGithub 1.23.0, remote key is an object, not string rkval = rk if isinstance(rk, six.string_types) else rk.value # don't use "==" because we have comments etc added in public_key if rkval in local_key: found = True break return found @classmethod def _ssh_key_needs_config_entry(cls): if getpass.getuser() != cls._user.login: ssh_config = os.path.expanduser('~/.ssh/config') user_github_string = 'github.com-{0}'.format(cls._user.login) needs_to_add_config_entry = True if os.path.isfile(ssh_config): fh = open(ssh_config) config_content = fh.read() if user_github_string in config_content: needs_to_add_config_entry = False fh.close() return needs_to_add_config_entry return False @classmethod def github_authenticated(cls, func): """Does user authentication, creates SSH keys if needed and injects "_user" attribute into class/object bound to the decorated function. Don't call any other methods of this class manually, this should be everything you need. """ def inner(func_cls, *args, **kwargs): if not cls._gh_module: logger.warning('PyGithub not installed, skipping Github auth procedures.') elif not func_cls._user: # authenticate user, possibly also creating authentication for future use func_cls._user = cls._get_github_user(kwargs['login']) if func_cls._user is None: msg = 'Github authentication failed, skipping Github command.' logger.warning(msg) return (False, msg) # create an ssh key for pushing if we don't have one if not cls._github_ssh_key_exists(): cls._github_create_ssh_key() # next, create ~/.ssh/config entry for the key, if system username != GH login if cls._ssh_key_needs_config_entry(): cls._create_ssh_config_entry() return func(func_cls, *args, **kwargs) return inner
class GitHubAuth(object): """Only use the github_authenticated decorator from the class. The other methods should be consider private; they expect certain order of calling, so calling them ad-hoc may break something. """ _user = None _token = None try: _gh_module = utils.import_module('github') _gh_exceptions = utils.import_module('github.GithubException') except: _gh_module = None _gh_exceptions = None @classmethod def _github_token(cls, login): if not cls._token: try: cls._token = ClHelper.run_command( "git config github.token.{login}".format(login=login), log_secret=True) except exceptions.ClException: pass # token is not available yet return cls._token @classmethod def _get_github_user(cls, login, ui): if not cls._github_token(login): cls._user = cls._try_login_with_password_ntimes(login, 3, ui) else: try: # try logging with token token = cls._github_token(login) gh = cls._gh_module.Github(login_or_token=token) user = gh.get_user() user.login # throws unless the authentication was successful cls._user = user except cls._gh_module.GithubException: # if the token was set, it was wrong, so make sure it's reset cls._token = None cls._user = cls._try_login_with_password_ntimes(login, 3, ui) if cls._token is None: cls._github_create_authorization(ui) return cls._user @classmethod def _github_create_authorization(cls, ui): try: cls._user.login cls._github_create_simple_authorization() except cls._gh_exceptions.TwoFactorException: cls._github_create_twofactor_authorization(ui) except cls._gh_exceptions.GithubException: raise @classmethod def _try_login_with_password_ntimes(cls, login, ntimes, ui): user = None for i in range(0, ntimes): password = DialogHelper.ask_for_password( ui, prompt='Github Password for {username}:'.format( username=login)) # user pressed Ctrl + D if password is None: break gh = cls._gh_module.Github(login_or_token=login, password=password) user = gh.get_user() try: user.login break # if user.login doesn't raise, authentication has been successful except cls._gh_exceptions.TwoFactorException: break # two-factor auth is used except cls._gh_module.GithubException as e: user = None msg = 'Wrong Github username or password; message from Github: {0}\n'.\ format(e.data.get('message', 'Unknown authentication error')) msg += 'Try again or press {0} to abort.' if ui == 'cli': msg = msg.format('Ctrl + D') else: msg = msg.format('"Cancel"') logger.warning(msg) return user @classmethod def _github_create_twofactor_authorization(cls, ui): """Create an authorization for a GitHub user using two-factor authentication. Unlike its non-two-factor counterpart, this method does not traverse the available authentications as they are not visible until the user logs in. Please note: cls._user's attributes are not accessible until the authorization is created due to the way (py)github works. """ try: try: # This is necessary to trigger sending a 2FA key to the user auth = cls._user.create_authorization() except cls._gh_exceptions.GithubException: onetime_pw = DialogHelper.ask_for_password( ui, prompt='Your one time password:'******'repo', 'user', 'admin:public_key'], note="DevAssistant", onetime_password=onetime_pw) cls._user = cls._gh_module.Github( login_or_token=auth.token).get_user() logger.debug( 'Two-factor authorization for user "{0}" created'.format( cls._user.login)) cls._github_store_authorization(cls._user, auth) logger.debug('Two-factor authorization token stored') except cls._gh_exceptions.GithubException as e: logger.warning( 'Creating two-factor authorization failed: {0}'.format(e)) @classmethod def _github_create_simple_authorization(cls): """Create a GitHub authorization for the given user in case they don't already have one. """ try: auth = None for a in cls._user.get_authorizations(): if a.note == 'DevAssistant': auth = a if not auth: auth = cls._user.create_authorization( scopes=['repo', 'user', 'admin:public_key'], note="DevAssistant") cls._github_store_authorization(cls._user, auth) except cls._gh_exceptions.GithubException as e: logger.warning('Creating authorization failed: {0}'.format(e)) @classmethod def _github_store_authorization(cls, user, auth): """Store an authorization token for the given GitHub user in the git global config file. """ ClHelper.run_command( "git config --global github.token.{login} {token}".format( login=user.login, token=auth.token), log_secret=True) ClHelper.run_command( "git config --global github.user.{login} {login}".format( login=user.login)) @classmethod def _start_ssh_agent(cls): """Starts ssh-agent and returns the environment variables related to it""" env = dict() stdout = ClHelper.run_command('ssh-agent -s') lines = stdout.split('\n') for line in lines: if not line or line.startswith('echo '): continue line = line.split(';')[0] parts = line.split('=') if len(parts) == 2: env[parts[0]] = parts[1] return env @classmethod def _github_create_ssh_key(cls): """Creates a local ssh key, if it doesn't exist already, and uploads it to Github.""" try: login = cls._user.login pkey_path = '{home}/.ssh/{keyname}'.format( home=os.path.expanduser('~'), keyname=settings.GITHUB_SSH_KEYNAME.format(login=login)) # generate ssh key only if it doesn't exist if not os.path.exists(pkey_path): ClHelper.run_command('ssh-keygen -t rsa -f {pkey_path}\ -N \"\" -C \"DevAssistant\"'.format( pkey_path=pkey_path)) try: ClHelper.run_command( 'ssh-add {pkey_path}'.format(pkey_path=pkey_path)) except exceptions.ClException: # ssh agent might not be running env = cls._start_ssh_agent() ClHelper.run_command( 'ssh-add {pkey_path}'.format(pkey_path=pkey_path), env=env) public_key = ClHelper.run_command( 'cat {pkey_path}.pub'.format(pkey_path=pkey_path)) cls._user.create_key("DevAssistant", public_key) except exceptions.ClException as e: msg = 'Couldn\'t create a new ssh key: {0}'.format(e) raise exceptions.CommandException(msg) @classmethod def _create_ssh_config_entry(cls): ssh_config = os.path.expanduser('~/.ssh/config') fh = os.fdopen( os.open(ssh_config, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600), 'a') fh.write( settings.GITHUB_SSH_CONFIG.format( login=cls._user.login, keyname=settings.GITHUB_SSH_KEYNAME.format( login=cls._user.login))) fh.close() @classmethod def _github_ssh_key_exists(cls): """Returns True if any key on Github matches a local key, else False.""" remote_keys = map(lambda k: k._key, cls._user.get_keys()) found = False pubkey_files = glob.glob(os.path.expanduser('~/.ssh/*.pub')) for rk in remote_keys: for pkf in pubkey_files: local_key = io.open(pkf, encoding='utf-8').read() # in PyGithub 1.23.0, remote key is an object, not string rkval = rk if isinstance(rk, six.string_types) else rk.value # don't use "==" because we have comments etc added in public_key if rkval in local_key: found = True break return found @classmethod def _ssh_key_needs_config_entry(cls): if getpass.getuser() != cls._user.login: ssh_config = os.path.expanduser('~/.ssh/config') user_github_string = 'github.com-{0}'.format(cls._user.login) needs_to_add_config_entry = True if os.path.isfile(ssh_config): fh = open(ssh_config) config_content = fh.read() if user_github_string in config_content: needs_to_add_config_entry = False fh.close() return needs_to_add_config_entry return False @classmethod def github_authenticated(cls, func): """Does user authentication, creates SSH keys if needed and injects "_user" attribute into class/object bound to the decorated function. Don't call any other methods of this class manually, this should be everything you need. """ def inner(func_cls, *args, **kwargs): if not cls._gh_module: logger.warning( 'PyGithub not installed, skipping Github auth procedures.') elif not func_cls._user: # authenticate user, possibly also creating authentication for future use login = kwargs['login'].encode( utils.defenc) if not six.PY3 else kwargs['login'] func_cls._user = cls._get_github_user(login, kwargs['ui']) if func_cls._user is None: msg = 'Github authentication failed, skipping Github command.' logger.warning(msg) return (False, msg) # create an ssh key for pushing if we don't have one if not cls._github_ssh_key_exists(): cls._github_create_ssh_key() # next, create ~/.ssh/config entry for the key, if system username != GH login if cls._ssh_key_needs_config_entry(): cls._create_ssh_config_entry() return func(func_cls, *args, **kwargs) return inner
def load_command_runners(cls): if not cls.command_runners: cls.command_runners = utils.import_module('devassistant.command_runners') return cls.command_runners
class GitHubAuth(object): """Only use the github_authenticated decorator from the class. The other methods should be consider private; they expect certain order of calling, so calling them ad-hoc may break something. """ _user = None _token = None try: _gh_module = utils.import_module('github') except: _gh_module = None @classmethod def _github_token(cls, login): if not cls._token: try: cls._token = ClHelper.run_command("git config github.token.{login}".format( login=login)) except exceptions.ClException: pass # token is not available yet return cls._token @classmethod def _get_github_user(cls, login): if not cls._user: try: # try logging with token token = cls._github_token(login) gh = cls._gh_module.Github(login_or_token=token) cls._user = gh.get_user() # try if the authentication was successful cls._user.login except cls._gh_module.GithubException: # if the token was set, it was wrong, so make sure it's reset cls._token = None # login with username/password password = DialogHelper.ask_for_password( prompt='Github Password for {username}:'.format(username=login)) gh = cls._gh_module.Github(login_or_token=login, password=password) cls._user = gh.get_user() try: cls._user.login cls._github_create_auth() # create auth for future use except cls._gh_module.GithubException as e: msg = 'Wrong username or password\nGitHub exception: {0}'.format(e) # reset cls._user to None, so that we don't use it if calling this multiple times cls._user = None raise exceptions.CommandException(msg) return cls._user @classmethod def _github_create_auth(cls): """ Store token into ~/.gitconfig. If token is not defined then store it into ~/.gitconfig file """ if not cls._token: try: auth = cls._user.create_authorization(scopes=['repo', 'user'], note="DeveloperAssistant") ClHelper.run_command("git config --global github.token.{login} {token}".format( login=cls._user.login, token=auth.token)) ClHelper.run_command("git config --global github.user.{login} {login}".format( login=cls._user.login)) except cls._gh_module.GithubException as e: logger.warning('Creating authorization failed: {0}'.format(e)) @classmethod def _github_create_ssh_key(cls): try: login = cls._user.login pkey_path = '{home}/.ssh/{keyname}'.format(home=os.path.expanduser('~'), keyname=settings.GITHUB_SSH_KEYNAME.format(login=login)) # TODO: handle situation where {pkey_path} exists, but it's not registered on GH # generate ssh key ClHelper.run_command('ssh-keygen -t rsa -f {pkey_path}\ -N \"\" -C \"DevAssistant\"'.\ format(pkey_path=pkey_path)) ClHelper.run_command('ssh-add {pkey_path}'.format(pkey_path=pkey_path)) public_key = ClHelper.run_command('cat {pkey_path}.pub'.format(pkey_path=pkey_path)) cls._user.create_key("devassistant", public_key) except exceptions.ClException as e: msg = 'Couldn\'t create a new ssh key: {e}'.format(e) raise exceptions.CommandException(msg) @classmethod def _create_ssh_config_entry(cls): # TODO: some duplication with _ssh_key_needs_config_entry, maybe refactor a bit ssh_config = os.path.expanduser('~/.ssh/config') fh = os.fdopen(os.open(ssh_config, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0o600), 'a') fh.write(settings.GITHUB_SSH_CONFIG.format( login=cls._user.login, keyname=settings.GITHUB_SSH_KEYNAME.format(login=cls._user.login))) fh.close() @classmethod def _github_ssh_key_exists(cls): remote_keys = map(lambda k: k._key, cls._user.get_keys()) found = False pubkey_files = glob.glob(os.path.expanduser('~/.ssh/*.pub')) for rk in remote_keys: for pkf in pubkey_files: local_key = open(pkf).read() # don't use "==" because we have comments etc added in public_key # in PyGithub 1.23.0, remote key is an object, not string rkval = rk if isinstance(rk, six.string_types) else rk.value if rkval in local_key: found = True break return found @classmethod def _ssh_key_needs_config_entry(cls): if getpass.getuser() != cls._user.login: ssh_config = os.path.expanduser('~/.ssh/config') user_github_string = 'github.com-{0}'.format(cls._user.login) needs_to_add_config_entry = True if os.path.isfile(ssh_config): fh = open(ssh_config) config_content = fh.read() if user_github_string in config_content: needs_to_add_config_entry = False fh.close() return needs_to_add_config_entry return False @classmethod def github_authenticated(cls, func): """Does user authentication, creates SSH keys if needed and injects "_user" attribute into class/object bound to the decorated function. Don't call any other methods of this class manually, this should be everything you need. """ def inner(func_cls, *args, **kwargs): if not cls._gh_module: logger.warning('PyGithub not installed, skipping github authentication procedures.') elif not func_cls._user: # authenticate user, possibly also creating authentication for future use func_cls._user = cls._get_github_user(kwargs['login']) # create an ssh key for pushing if we don't have one if not cls._github_ssh_key_exists(): cls._github_create_ssh_key() # next, create ~/.ssh/config entry for the key, if system username != GH login if cls._ssh_key_needs_config_entry(): cls._create_ssh_config_entry() return func(func_cls, *args, **kwargs) return inner
def load_command_runners(cls): if not cls.command_runners: cls.command_runners = utils.import_module( 'devassistant.command_runners') return cls.command_runners
class GitHubCommandRunner(CommandRunner): _user = None try: _gh_module = utils.import_module('github') except: _gh_module = None _required_yaml_args = {'default': ['login', 'reponame'], 'create_repo': ['login', 'reponame', 'private'], 'create_and_push': ['login', 'reponame', 'private'], 'create_fork': ['login', 'repo_url'], 'push': []} @classmethod def matches(cls, c): return c.comm_type == 'github' @classmethod def run(cls, c): """Arguments given to 'github' command may be: - Just a string (action), which implies that all the other arguments are deducted from global context and local system. - List containing a string (action) as a first item and rest of the args in a dict. (args not specified in the dict are taken from global context. Possible arguments: - login - taken from 'github' or system username - represents Github login - reponame - taken from 'name' (first applies os.path.basename) - repo to operate on """ comm, kwargs = cls.format_args(c) if not cls._gh_module: logger.warning('PyGithub not installed, cannot execute github command.') return [False, ''] # we pass arguments as kwargs, so that the auth decorator can easily query them # NOTE: these are not the variables from global context, but rather what # cls.format_args returned if comm == 'create_repo': ret = cls._github_create_repo(**kwargs) elif comm == 'push': ret = cls._github_push() elif comm == 'create_and_push': ret = cls._github_create_and_push(**kwargs) elif comm == 'add_remote_origin': ret = cls._github_add_remote_origin(**kwargs) elif comm == 'create_fork': ret = cls._github_fork(**kwargs) else: raise exceptions.CommandException('Unknown command type {ct}.'.format(ct=c.comm_type)) return ret @classmethod def format_args(cls, c): args = c.input_res if isinstance(args, list): comm = args[0] args_rest = args[1] else: comm = args args_rest = {} # find out what arguments we will need kwargs = {} req_kwargs = cls._required_yaml_args.get(comm, cls._required_yaml_args['default']) for k in req_kwargs: kwargs[k] = getattr(cls, '_guess_' + k)(args_rest.get(k), c.kwargs) return comm, kwargs @classmethod def _guess_login(cls, explicit, ctxt): """Get github login, either from explicitly given string or 'github' global variable or from local username. Args: ctxt: global context Returns: guessed github login """ return explicit or ctxt.get('github', None) or getpass.getuser() @classmethod def _guess_reponame(cls, explicit, ctxt): """Extract reponame, either from explicitly given string or from 'name' global variable, which is possibly a path. Args: ctxt: global context Returns: guessed reponame """ name = explicit if not name: name = os.path.basename(ctxt.get('name', '')) if not name: raise exceptions.CommandException('Cannot guess Github reponame - no argument given' 'and there is no "name" variable.') return name @classmethod def _guess_repo_url(cls, explicit, ctxt): """Get repo to fork in form of '<login>/<reponame>' from explicitly given string or global variable 'url'. Args: ctxt: global context Returns: guessed fork reponame """ url = explicit or ctxt.get('url') if not url: raise exceptions.CommandException('Cannot guess name of Github repo to fork - no' 'argument given and there is no "url" variable.') url = url[:-4] if url.endswith('.git') else url # if using git@github:username/reponame.git, strip the stuff before ":" url = url.split(':')[-1] return '/'.join(url.split('/')[-2:]) @classmethod def _guess_private(cls, explicit, ctxt): return bool(explicit or ctxt.get('github_private') or False) @classmethod def _github_push(cls): try: ret = ClHelper.run_command("git push -u origin master") logger.info('Source code was successfully pushed.') return (True, ret) except exceptions.ClException as e: logger.warning('Problem pushing source code: {0}'.format(e.output)) return (False, e.output) @classmethod @GitHubAuth.github_authenticated def _github_add_remote_origin(cls, **kwargs): """Note: the kwargs are not the global context here, but what cls.format_args returns.""" reponame = kwargs['reponame'] login = kwargs['login'] # if system username != GH login, we need to use [email protected]{login}:... # else just [email protected]:... dash_login = '' if getpass.getuser() != login: dash_login = '******' + login try: logger.info('Adding Github repo as git remote ...') ret = ClHelper.run_command("git remote add origin [email protected]{dl}:{l}/{r}.git".\ format(dl=dash_login, l=login, r=reponame)) logger.info('Successfully added Github repo as git remote.') return (True, ret) except exceptions.ClException as e: logger.warning('Problem adding Github repo as git remote: {0}.'.format(e.output)) return (False, e.output) @classmethod @GitHubAuth.github_authenticated def _github_create_repo(cls, **kwargs): """Create repo on GitHub. Note: the kwargs are not the global context here, but what cls.format_args returns. If repository already exists then CommandException will be raised. Raises: devassistant.exceptions.CommandException on error """ reponame = kwargs['reponame'] if reponame in map(lambda x: x.name, cls._user.get_repos()): msg = 'Failed to create Github repo: {0}/{1} alread exists.'.\ format(cls._user.login, reponame) logger.warning(msg) return (False, msg) else: msg = '' success = False try: new_repo = cls._user.create_repo(reponame, private=kwargs['private']) msg = new_repo.clone_url success = True except cls._gh_module.GithubException as e: gh_errs = e.data.get('errors', []) gh_errs = '; '.join(map(lambda err: err.get('message', ''), gh_errs)) msg = 'Failed to create GitHub repo. This sometime happens when you delete ' msg += 'a repo and then you want to create the same one immediately. If that\'s ' msg += 'the case, wait for few minutes and then try again.\n' msg += 'Github errors: ' + gh_errs except BaseException as e: msg = 'Failed to create Github repo: {0}'.\ format(getattr(e, 'message', 'Unknown error')) if success: logger.info('Your new repository: {0}'.format(new_repo.html_url)) else: logger.warning(msg) return (success, msg) @classmethod @GitHubAuth.github_authenticated def _github_add_remote_and_push(cls, **kwargs): """Add a remote and push to GitHub. As this is not a callable subcommand of this command runner, it doesn't emit any informative logging messages on its own, only messages emitted by called methods. Note: the kwargs are not the global context here, but what cls.format_args returns. """ ret = cls._github_add_remote_origin(**kwargs) if ret[0]: ret = cls._github_push() return ret @classmethod @GitHubAuth.github_authenticated def _github_create_and_push(cls, **kwargs): """Note: the kwargs are not the global context here, but what cls.format_args returns.""" # we assume we're in the project directory logger.info('Registering your {priv}project on GitHub as {login}/{repo}...'.\ format(priv='private ' if kwargs['private'] else '', login=kwargs['login'], repo=kwargs['reponame'])) ret = cls._github_create_repo(**kwargs) if ret[0]: # on success push the sources ret = cls._github_add_remote_and_push(**kwargs) return ret @classmethod @GitHubAuth.github_authenticated def _github_fork(cls, **kwargs): """Create a fork of repo from kwargs['fork_repo']. Note: the kwargs are not the global context here, but what cls.format_args returns. Raises: devassistant.exceptions.CommandException on error """ timeout = 300 # 5 minutes fork_login, fork_reponame = kwargs['repo_url'].split('/') logger.info('Forking {repo} for user {login} on Github ...'.\ format(login=kwargs['login'], repo=kwargs['repo_url'])) success = False msg = '' try: repo = cls._gh_module.Github().get_user(fork_login).get_repo(fork_reponame) fork = cls._user.create_fork(repo) while timeout > 0: time.sleep(5) timeout -= 5 try: fork.get_contents('/') # This function doesn't throw an exception when clonable success = True break except cls._gh_module.GithubException as e: if 'is empty' not in str(e): raise e msg = fork.ssh_url except cls._gh_module.GithubException as e: msg = 'Failed to create Github fork with error: {err}'.format(err=e) except BaseException as e: msg = 'Exception while forking GH repo: {0}'.\ format(getattr(e, 'message', 'Unknown error')) if success: logger.info('Fork is ready at {url}.'.format(url=fork.html_url)) else: logger.warning(msg) return (success, msg)