def sync_updates(repo, push=False): """Updates the local `repo` with origin, pulling and push when necessary. True is returned when any changes were made (aside fetch), otherwise False. Args: repo (git.objects.Repo): The git repository to sync updates on push (bool): True to push local changes, False to only pull Returns: bool: True if `repo` was sync'd, False otherwise """ ref = repo.remotes.origin.refs[0].name repo_dir = repo.working_dir branch = repo.active_branch io.info('fetching remote for "%s"@:%s' % ( os.path.split(repo_dir)[-1], branch, )) repo.remotes.origin.fetch() ahead, behind = helpers.get_ahead_behind_count(repo) is_dirty = repo.is_dirty() if not ahead and not behind and not is_dirty: return False io.info('(found) [ahead: %s, behind: %s] [dirty: %s]' % ( ahead, behind, is_dirty, )) # possible merge conflicts if (ahead and behind) or (behind and is_dirty): raise FetchRemoteUnknownNextStep( 'possible merge conflict. please manually resolve') # we're ahead so let's push these changes up if ahead and push: io.warn('ahead, pushing local changes to %s' % (ref, )) repo.remotes.origin.push() # we're behind so let's pull changes down if behind: io.warn('behind, pulling changes from %s' % (ref, )) repo.remotes.origin.pull() return True
def _get_artifact(self, deployment_repo, dockerhub_auth, env): io.info('retrieving image tags for "%s" from dockerhub' % (self.service_name, )) artifacts = dockerhub.list_image_tags(dockerhub_auth, self.service_name) artifacts = artifacts[:Bump.MAX_ARTIFACTS_SHOWN] if not artifacts: io.err('no artifacts found "%s/%s"' % ( self.config.get('dockerhub')['organization'], self.service_name, )) return None return self._prompt_artifact_selection(self.service_name, self.artifact_key, deployment_repo, env, artifacts)
def run(self): io.info('performing git sync across all specified repositories...') for deployment_repo in self.config.get('terraformRepositories'): git_ext.sync_updates(deployment_repo['git']) deployment_repo['tfvars'].load( ) # sync_updates may have changed tfvars. env = self.env or deployment_repo['defaultEnvironment'] table_data = self._build_status_table(deployment_repo, env) docker_org = self.config.get('dockerhub')['organization'] io.info('displaying "%s" active&inactive services on "%s"' % ( docker_org, env, )) io.print_table(table_data, 'current %s artifacts' % (env, )) io.warn('only dockerized services are shown here (i.e. no lambda)')
def tag_commit(repo, tag_name, message, ref='HEAD'): """Creates a tag to the `ref` of the given git `repo`. Args: repo (git.objects.Repo): The git repository commit changes to tag_name (str): The name of the tag message (str): The tag message body ref (Optional[str, git.objects.Commit]): The commit to tag on Returns: bool: True if a tag was successfully created """ repo.create_tag(tag_name, ref=ref, message=message) io.info('created tag: (name) %s' % (tag_name, )) return True
def run(self): io.info('authenticating "%s" against dockerhub' % (self.config.get('dockerhub')['organization'], )) # Authenticate against DockerHub for artifact access. dockerhub_auth = dockerhub.DockerHubAuthentication( self.config.get('dockerhub')['username'], self.config.get('dockerhub')['password'], self.config.get('dockerhub')['organization'], ) # Determine the deployment repo we want to make changes to. deployment_repo = TFVarsHelpers.find_deployment_repo( self.service_name, self.config.get('terraformRepositories')) if not deployment_repo: io.err('could not find service %r' % (self.service_name, )) return None # Determine the environment and safe guard based on active branch. env = self.env or deployment_repo['defaultEnvironment'] active_branch = deployment_repo['git'].active_branch.name if active_branch != deployment_repo['defaultGitBranch']: raise InvalidOperatingBranch(active_branch) # Select the artifact we want to bump with. artifact = self._get_artifact(deployment_repo, dockerhub_auth, env) if artifact is None: # An artifact wasn't selected, end command. return None git_ext.sync_updates(deployment_repo['git']) deployment_repo['tfvars'].load( ) # Reload tfvars in case the sync introduced new changes. # Update deployment repo and bump artifact. self._commit_bump(dockerhub_auth, deployment_repo, artifact, env) # Push changes up to GitHub to trigger changes in the build pipeline. if self.should_push: git_ext.push_commits(deployment_repo['git']) else: io.warn('commit to tfvars was NOT pushed to remote!') io.warn( "it's your responsibility to bundle changes and explicitly push" )
def _prompt_artifact_selection(self, service_name, artifact_key, deployment_repo, env, artifacts): current_image = deployment_repo['tfvars'].get(artifact_key, env) io.info('found artifacts for "%s/%s"' % ( self.config.get('dockerhub')['organization'], service_name, )) table_data = [ ( 'id', 'tag name (* = current)', 'created at', 'size', ), ] for i, artifact in enumerate(artifacts, 1): created_at = datetime.strptime(artifact['last_updated'], '%Y-%m-%dT%H:%M:%S.%fZ') created_at = pretty_print_datetime(created_at) image_size = humanize.naturalsize(artifact['full_size']) image_name = artifact['name'] if image_name in current_image: # indicate the current artifact. image_name += ' *' table_data.append(( str(i), image_name, created_at, image_size, )) io.print_table(table_data, 'recent artifacts') # Handle the case where the selected artifact is the current artifact. selected_artifact = io.collect_input( 'select the artifact you want to use [q]:', artifacts) if selected_artifact and selected_artifact['name'] in current_image: io.err('selected artifact is already the current active artifact') return None return selected_artifact
def commit_changes(repo, commit_message): """Commits all changes (staged and untracked) in a single commit. Args: repo (git.objects.Repo): The git repository to commit changes to commit_message (str): The commit message to use Returns: bool: True if a commit was made, False otherwise """ if not repo.is_dirty(): return False repo.git.add(u=True) actor = Actor(const.COMMIT_AUTHOR_NAME, email=const.COMMIT_AUTHOR_EMAIL) commit = repo.index.commit(commit_message, author=actor, committer=actor) io.info('commit message: "%s"' % (commit_message.split('\n')[0]), ) io.info('created commit: (id) %s' % (commit.name_rev, )) return True
def commit_empty_changes(repo, commit_message): """Similar to `git_extensions.extensions.commit_changes` except --allow-empty. Args: repo (git.objects.Repo): The git repository to commit changes to commit_message (str): The commit message to use Returns: git.objects.Commit: The commit that was just created, None if failed """ if repo.is_dirty(): return None repo.git.commit(*shlex.split('--allow-empty -m "%s" --author "%s"' % ( commit_message, const.COMMIT_AUTHOR_NAME, ))) commit = repo.iter_commits().next() io.info('commit message: "%s"' % (commit_message.split('\n')[0], )) io.info('created commit: (id) %s' % (commit.name_rev, )) return commit
def _commit_bump(self, dockerhub_auth, deployment_repo, artifact, env): image_abspath = dockerhub.build_image_abspath(dockerhub_auth, self.service_name, artifact['name']) io.info('updating "%s"' % (image_abspath, )) deployment_repo['tfvars'].set(self.artifact_key, image_abspath, env) deployment_repo['tfvars'].save() commit_message = git_ext.generate_service_bump_commit_message( deployment_repo['git'], self.service_name, env, artifact['name'], ) did_commit = git_ext.commit_changes(deployment_repo['git'], commit_message) if not did_commit: raise NoChangesEmptyCommit('"%s" has nothing to commit' % (deployment_repo['git'].working_dir, )) if env == 'production': git_ext.tag_commit(deployment_repo['git'], git_ext.generate_deploy_commit_tag(), commit_message)
def push_commits(repo): io.info('pushing changes to %s' % (repo.remotes.origin.refs[0].name, )) repo.git.push('origin', 'master', tags=True) io.ok('successfully pushed changes to %s' % (repo.remotes.origin.refs[0].name, ))