class PushChangelogCommand(CommandProcessor): """Implements push_changelog_to_gist.""" def __init__(self, factory, options, **kwargs): super(PushChangelogCommand, self).__init__(factory, options, **kwargs) check_options_set(options, ['build_changelog_gist_url', 'git_branch']) if not options.changelog_path: options.changelog_path = os.path.join( self.get_output_dir(command=BUILD_CHANGELOG_COMMAND), 'changelog.md') check_path_exists(options.changelog_path, why='changelog_path') self.__git = GitRunner(options) def _do_command(self): options = self.options gist_url = options.build_changelog_gist_url index = gist_url.rfind('/') if index < 0: index = gist_url.rfind(':') # ssh gist gist_id = gist_url[index + 1:] git_dir = os.path.join(self.get_input_dir(), gist_id) if not os.path.exists(git_dir): logging.debug('Cloning gist from %s', gist_url) ensure_dir_exists(os.path.dirname(git_dir)) self.git_run_with_retries(os.path.dirname(git_dir), 'clone ' + gist_url) else: logging.debug('Updating gist in "%s"', git_dir) self.git_run_with_retries(git_dir, 'fetch origin master') self.git_run_with_retries(git_dir, 'checkout master') dest_path = os.path.join(git_dir, '%s-raw-changelog.md' % options.git_branch) logging.debug('Copying "%s" to "%s"', options.changelog_path, dest_path) shutil.copyfile(options.changelog_path, dest_path) self.git_run_with_retries(git_dir, 'add ' + os.path.basename(dest_path)) self.__git.check_commit_or_no_changes( git_dir, '-a -m "Updated %s"' % os.path.basename(dest_path)) logging.debug('Pushing back gist') self.git_run_with_retries(git_dir, 'push -f origin master') # For some reason gist.github.com seems to have a lot of connection timeout # errors, which we don't really see with normal github. I'm not sure why, but # let's just retry and see if that helps. # Retry every 2^n seconds (with a maximum of 16 seconds), giving up after 2 minutes. @retry(stop_max_delay=120000, wait_exponential_multiplier=1000, wait_exponential_max=16000) def git_run_with_retries(self, git_dir, command, **kwargs): self.__git.check_run(git_dir, command, **kwargs)
class PushChangelogCommand(CommandProcessor): """Implements push_changelog_to_gist.""" def __init__(self, factory, options, **kwargs): super(PushChangelogCommand, self).__init__(factory, options, **kwargs) check_options_set(options, ['build_changelog_gist_url', 'git_branch']) if not options.changelog_path: options.changelog_path = os.path.join( self.get_output_dir(command=BUILD_CHANGELOG_COMMAND), 'changelog.md') check_path_exists(options.changelog_path, why='changelog_path') self.__git = GitRunner(options) def _do_command(self): options = self.options gist_url = options.build_changelog_gist_url index = gist_url.rfind('/') if index < 0: index = gist_url.rfind(':') # ssh gist gist_id = gist_url[index + 1:] git_dir = os.path.join(self.get_input_dir(), gist_id) if not os.path.exists(git_dir): logging.debug('Cloning gist from %s', gist_url) ensure_dir_exists(os.path.dirname(git_dir)) self.__git.check_run(os.path.dirname(git_dir), 'clone ' + gist_url) else: logging.debug('Updating gist in "%s"', git_dir) self.__git.check_run(git_dir, 'fetch origin master') self.__git.check_run(git_dir, 'checkout master') dest_path = os.path.join(git_dir, '%s-raw-changelog.md' % options.git_branch) logging.debug('Copying "%s" to "%s"', options.changelog_path, dest_path) shutil.copyfile(options.changelog_path, dest_path) self.__git.check_run(git_dir, 'add ' + os.path.basename(dest_path)) self.__git.check_commit_or_no_changes( git_dir, '-a -m "Updated %s"' % os.path.basename(dest_path)) logging.debug('Pushing back gist') self.__git.check_run(git_dir, 'push -f origin master')
class PushChangelogCommand(CommandProcessor): """Implements push_changelog_to_gist.""" def __init__(self, factory, options, **kwargs): super(PushChangelogCommand, self).__init__(factory, options, **kwargs) check_options_set( options, ['build_changelog_gist_url', 'git_branch']) if not options.changelog_path: options.changelog_path = os.path.join( self.get_output_dir(command=BUILD_CHANGELOG_COMMAND), 'changelog.md') check_path_exists(options.changelog_path, why='changelog_path') self.__git = GitRunner(options) def _do_command(self): options = self.options gist_url = options.build_changelog_gist_url index = gist_url.rfind('/') if index < 0: index = gist_url.rfind(':') # ssh gist gist_id = gist_url[index + 1:] git_dir = os.path.join(self.get_input_dir(), gist_id) if not os.path.exists(git_dir): logging.debug('Cloning gist from %s', gist_url) ensure_dir_exists(os.path.dirname(git_dir)) self.__git.check_run(os.path.dirname(git_dir), 'clone ' + gist_url) else: logging.debug('Updating gist in "%s"', git_dir) self.__git.check_run(git_dir, 'fetch origin master') self.__git.check_run(git_dir, 'checkout master') dest_path = os.path.join( git_dir, '%s-raw-changelog.md' % options.git_branch) logging.debug('Copying "%s" to "%s"', options.changelog_path, dest_path) shutil.copyfile(options.changelog_path, dest_path) self.__git.check_run(git_dir, 'add ' + os.path.basename(dest_path)) self.__git.check_commit_or_no_changes( git_dir, '-a -m "Updated %s"' % os.path.basename(dest_path)) logging.debug('Pushing back gist') self.__git.check_run(git_dir, 'push -f origin master')
class InitiateReleaseBranchCommand(RepositoryCommandProcessor): def __init__(self, factory, options, **kwargs): super(InitiateReleaseBranchCommand, self).__init__(factory, options, **kwargs) check_options_set(options, ["spinnaker_version"]) self.__git = GitRunner(options) def _do_repository(self, repository): git_dir = repository.git_dir branch = self.options.spinnaker_version logging.debug('Checking for branch="%s" in "%s"', branch, git_dir) remote_branches = [ line.strip() for line in self.__git.check_run(git_dir, "branch -r").split("\n") ] if "origin/" + branch in remote_branches: if self.options.skip_existing: logging.info( 'Branch "%s" already exists in "%s" -- skip', branch, repository.origin, ) return elif self.options.delete_existing: logging.warning( 'Branch "%s" already exists in "%s" -- delete', branch, repository.origin, ) self.__git.delete_branch_on_origin(git_dir, branch) else: raise_and_log_error( ConfigError( 'Branch "{branch}" already exists in "{repo}"'.format( branch=branch, repo=repository.name), cause="branch_exists", )) logging.info('Creating and pushing branch "%s" to "%s"', branch, repository.origin) self.__git.check_run(git_dir, "checkout -b " + branch) self.__git.push_branch_to_origin(git_dir, branch)
class InitiateReleaseBranchCommand(RepositoryCommandProcessor): def __init__(self, factory, options, **kwargs): super(InitiateReleaseBranchCommand, self).__init__( factory, options, **kwargs) check_options_set(options, ['spinnaker_version']) self.__git = GitRunner(options) def _do_repository(self, repository): git_dir = repository.git_dir branch = self.options.spinnaker_version logging.debug('Checking for branch="%s" in "%s"', branch, git_dir) remote_branches = [ line.strip() for line in self.__git.check_run(git_dir, 'branch -r').split('\n')] if 'origin/' + branch in remote_branches: if self.options.skip_existing: logging.info('Branch "%s" already exists in "%s" -- skip', branch, repository.origin) return elif self.options.delete_existing: logging.warning('Branch "%s" already exists in "%s" -- delete', branch, repository.origin) self.__git.delete_branch_on_origin(git_dir, branch) else: raise_and_log_error( ConfigError( 'Branch "{branch}" already exists in "{repo}"'.format( branch=branch, repo=repository.name), cause='branch_exists')) logging.info('Creating and pushing branch "%s" to "%s"', branch, repository.origin) self.__git.check_run(git_dir, 'checkout -b ' + branch) self.__git.push_branch_to_origin(git_dir, branch)
class PublishSpinnakerCommand(CommandProcessor): """"Implements the publish_spinnaker command.""" # pylint: disable=too-few-public-methods def __init__(self, factory, options, **kwargs): super(PublishSpinnakerCommand, self).__init__(factory, options, **kwargs) check_options_set(options, [ 'spinnaker_version', 'spinnaker_release_alias', 'bom_version', 'changelog_gist_url', 'github_owner', 'min_halyard_version' ]) major, minor, _ = self.options.spinnaker_version.split('.') self.__branch = 'release-{major}.{minor}.x'.format(major=major, minor=minor) options_copy = copy.copy(options) self.__bom_scm = BomSourceCodeManager(options_copy, self.get_input_dir()) self.__hal = HalRunner(options) self.__git = GitRunner(options) self.__hal.check_property('spinnaker.config.input.bucket', options.halyard_bom_bucket) if options.only_repositories: self.__only_repositories = options.only_repositories.split(',') else: self.__only_repositories = [] options_copy.git_branch = self.__branch self.__branch_scm = BranchSourceCodeManager(options_copy, self.get_input_dir()) def push_branches_and_tags(self, bom): """Update the release branches and tags in each of the BOM repositires.""" logging.info('Tagging each of the BOM service repos') bom_scm = self.__bom_scm branch_scm = self.__branch_scm # Run in two passes so we dont push anything if we hit a problem # in the tagging pass. Since we are spread against multiple repositiories, # we cannot do this atomically. The two passes gives us more protection # from a partial push due to errors in a repo. names_to_push = set([]) for which in ['tag', 'push']: for name, spec in bom['services'].items(): if name in ['monitoring-third-party', 'defaultArtifact']: # Ignore this, it is redundant to monitoring-daemon continue if name == 'monitoring-daemon': name = 'spinnaker-monitoring' if self.__only_repositories and name not in self.__only_repositories: logging.debug('Skipping %s because of --only_repositories', name) continue if spec is None: logging.warning('HAVE bom.services.%s = None', name) continue repository = bom_scm.make_repository_spec(name) bom_scm.ensure_local_repository(repository) version = bom_scm.determine_repository_version(repository) if which == 'tag': added = self.__branch_and_tag_repository( repository, self.__branch, version) if added: names_to_push.add(name) else: self.__push_branch_and_maybe_tag_repository( repository, self.__branch, version, name in names_to_push) additional_repositories = list(SPINNAKER_PROCESS_REPOSITORY_NAMES) for name in additional_repositories: if self.__only_repositories and name not in self.__only_repositories: logging.debug('Skipping %s because of --only_repositories', name) continue repository = branch_scm.make_repository_spec(name) branch_scm.ensure_local_repository(repository) git_summary = self.__git.collect_repository_summary( repository.git_dir) version = git_summary.version if self.__branch_and_tag_repository(repository, self.__branch, version): self.__push_branch_and_maybe_tag_repository( repository, self.__branch, version, True) def __already_have_tag(self, repository, tag): """Determine if we already have the tag in the repository.""" git_dir = repository.git_dir existing_commit = self.__git.query_commit_at_tag(git_dir, tag) if not existing_commit: return False want_commit = self.__git.query_local_repository_commit_id(git_dir) if want_commit == existing_commit: logging.debug('Already have "%s" at %s', tag, want_commit) return True raise_and_log_error( ConfigError( '"{tag}" already exists in "{repo}" at commit {have}, not {want}' .format(tag=tag, repo=git_dir, have=existing_commit, want=want_commit))) def __branch_and_tag_repository(self, repository, branch, version): """Create a branch and/or verison tag in the repository, if needed.""" tag = 'version-' + version if self.__already_have_tag(repository, tag): return False self.__git.check_run(repository.git_dir, 'tag ' + tag) return True def __push_branch_and_maybe_tag_repository(self, repository, branch, version, also_tag): """Push the branch and verison tag to the origin.""" tag = 'version-' + version self.__git.push_branch_to_origin(repository.git_dir, branch) if also_tag: self.__git.push_tag_to_origin(repository.git_dir, tag) else: logging.info('%s was already tagged with "%s" -- skip', repository.git_dir, tag) def _do_command(self): """Implements CommandProcessor interface.""" options = self.options spinnaker_version = options.spinnaker_version options_copy = copy.copy(options) options_copy.git_branch = 'master' # push to master in spinnaker.github.io publish_changelog_command = PublishChangelogFactory().make_command( options_copy) changelog_gist_url = options.changelog_gist_url # Make sure changelog exists already. # If it does not then fail. try: logging.debug('Verifying changelog ready at %s', changelog_gist_url) urlopen(changelog_gist_url) except HTTPError: logging.error(exception_to_message) raise_and_log_error( ConfigError( 'Changelog gist "{url}" must exist before publising a release.' .format(url=changelog_gist_url), cause='ChangelogMissing')) bom = self.__hal.retrieve_bom_version(self.options.bom_version) bom['version'] = spinnaker_version bom_path = os.path.join(self.get_output_dir(), spinnaker_version + '.yml') write_to_path(yaml.safe_dump(bom, default_flow_style=False), bom_path) self.__hal.publish_bom_path(bom_path) self.push_branches_and_tags(bom) self.__hal.publish_spinnaker_release(spinnaker_version, options.spinnaker_release_alias, changelog_gist_url, options.min_halyard_version) logging.info('Publishing changelog') publish_changelog_command()
class PublishSpinnakerCommand(CommandProcessor): """"Implements the publish_spinnaker command.""" # pylint: disable=too-few-public-methods def __init__(self, factory, options, **kwargs): super(PublishSpinnakerCommand, self).__init__(factory, options, **kwargs) check_options_set(options, [ 'spinnaker_version', 'bom_version', 'github_owner', 'min_halyard_version' ]) options_copy = copy.copy(options) self.__scm = BomSourceCodeManager(options_copy, self.get_input_dir()) self.__hal = HalRunner(options) self.__git = GitRunner(options) self.__hal.check_property('spinnaker.config.input.bucket', options.halyard_bom_bucket) def push_branches_and_tags(self, bom): """Update the release branches and tags in each of the BOM repositires.""" major, minor, _ = self.options.spinnaker_version.split('.') branch = 'release-{major}.{minor}.x'.format(major=major, minor=minor) logging.info('Tagging each of the BOM service repos') # Run in two passes so we dont push anything if we hit a problem # in the tagging pass. Since we are spread against multiple repositiories, # we cannot do this atomically. The two passes gives us more protection # from a partial push due to errors in a repo. for which in ['tag', 'push']: for name, spec in bom['services'].items(): if name in ['monitoring-third-party', 'defaultArtifact']: # Ignore this, it is redundant to monitoring-daemon continue if spec is None: logging.warning('HAVE bom.services.%s = None', name) continue if name == 'monitoring-daemon': name = 'spinnaker-monitoring' repository = self.__scm.make_repository_spec(name) self.__scm.ensure_local_repository(repository) if which == 'tag': self.__branch_and_tag_repository(repository, branch) else: self.__push_branch_and_tag_repository(repository, branch) def __branch_and_tag_repository(self, repository, branch): """Create a branch and/or verison tag in the repository, if needed.""" source_info = self.__scm.lookup_source_info(repository) tag = 'version-' + source_info.summary.version self.__git.check_run(repository.git_dir, 'tag ' + tag) def __push_branch_and_tag_repository(self, repository, branch): """Push the branch and verison tag to the origin.""" source_info = self.__scm.lookup_source_info(repository) tag = 'version-' + source_info.summary.version self.__git.push_branch_to_origin(repository.git_dir, branch) self.__git.push_tag_to_origin(repository.git_dir, tag) def _do_command(self): """Implements CommandProcessor interface.""" options = self.options spinnaker_version = options.spinnaker_version bom = self.__hal.retrieve_bom_version(self.options.bom_version) bom['version'] = spinnaker_version self.push_branches_and_tags(bom) bom_path = os.path.join(self.get_output_dir(), spinnaker_version + '.yml') changelog_base_url = 'https://www.spinnaker.io/%s' % options.github_owner changelog_filename = '%s-changelog' % spinnaker_version.replace( '.', '-') changelog_uri = '%s/community/releases/versions/%s' % ( changelog_base_url, changelog_filename) write_to_path(yaml.dump(bom, default_flow_style=False), bom_path) self.__hal.publish_spinnaker_release(spinnaker_version, options.spinnaker_release_alias, changelog_uri, options.min_halyard_version)
class PublishSpinnakerCommand(CommandProcessor): """"Implements the publish_spinnaker command.""" # pylint: disable=too-few-public-methods def __init__(self, factory, options, **kwargs): super(PublishSpinnakerCommand, self).__init__(factory, options, **kwargs) check_options_set(options, [ 'spinnaker_version', 'bom_version', 'changelog_gist_url', 'github_owner', 'min_halyard_version' ]) options_copy = copy.copy(options) self.__scm = BomSourceCodeManager(options_copy, self.get_input_dir()) self.__hal = HalRunner(options) self.__git = GitRunner(options) self.__hal.check_property('spinnaker.config.input.bucket', options.halyard_bom_bucket) self.__only_repositories = options.only_repositories.split(',') def push_branches_and_tags(self, bom): """Update the release branches and tags in each of the BOM repositires.""" major, minor, _ = self.options.spinnaker_version.split('.') branch = 'release-{major}.{minor}.x'.format(major=major, minor=minor) logging.info('Tagging each of the BOM service repos') # Run in two passes so we dont push anything if we hit a problem # in the tagging pass. Since we are spread against multiple repositiories, # we cannot do this atomically. The two passes gives us more protection # from a partial push due to errors in a repo. for which in ['tag', 'push']: for name, spec in bom['services'].items(): if name in ['monitoring-third-party', 'defaultArtifact']: # Ignore this, it is redundant to monitoring-daemon continue if name == 'monitoring-daemon': name = 'spinnaker-monitoring' if self.__only_repositories and name not in self.__only_repositories: logging.debug('Skipping %s because of --only_repositories', name) continue if spec is None: logging.warning('HAVE bom.services.%s = None', name) continue repository = self.__scm.make_repository_spec(name) self.__scm.ensure_local_repository(repository) if which == 'tag': self.__branch_and_tag_repository(repository, branch) else: self.__push_branch_and_tag_repository(repository, branch) def __branch_and_tag_repository(self, repository, branch): """Create a branch and/or verison tag in the repository, if needed.""" version = self.__scm.determine_repository_version(repository) tag = 'version-' + version self.__git.check_run(repository.git_dir, 'tag ' + tag) def __push_branch_and_tag_repository(self, repository, branch): """Push the branch and verison tag to the origin.""" source_info = self.__scm.lookup_source_info(repository) tag = 'version-' + source_info.summary.version self.__git.push_branch_to_origin(repository.git_dir, branch) self.__git.push_tag_to_origin(repository.git_dir, tag) def _do_command(self): """Implements CommandProcessor interface.""" options = self.options spinnaker_version = options.spinnaker_version publish_changelog_command = PublishChangelogFactory().make_command( options) changelog_gist_url = options.changelog_gist_url # Make sure changelog exists already. # If it does not then fail. try: logging.debug('Verifying changelog ready at %s', changelog_gist_url) urllib2.urlopen(changelog_gist_url) except urllib2.HTTPError as error: logging.error(error.message) raise_and_log_error( ConfigError( 'Changelog gist "{url}" must exist before publising a release.' .format(url=changelog_gist_url), cause='ChangelogMissing')) bom = self.__hal.retrieve_bom_version(self.options.bom_version) bom['version'] = spinnaker_version bom_path = os.path.join(self.get_output_dir(), spinnaker_version + '.yml') write_to_path(yaml.dump(bom, default_flow_style=False), bom_path) self.push_branches_and_tags(bom) self.__hal.publish_spinnaker_release(spinnaker_version, options.spinnaker_release_alias, changelog_gist_url, options.min_halyard_version) logging.info('Publishing changelog') publish_changelog_command()
class PublishSpinnakerCommand(CommandProcessor): """"Implements the publish_spinnaker command.""" # pylint: disable=too-few-public-methods def __init__(self, factory, options, **kwargs): super(PublishSpinnakerCommand, self).__init__(factory, options, **kwargs) check_options_set(options, [ 'spinnaker_version', 'spinnaker_release_alias', 'bom_version', 'changelog_gist_url', 'github_owner', 'min_halyard_version' ]) major, minor, _ = self.options.spinnaker_version.split('.') self.__branch = 'release-{major}.{minor}.x'.format( major=major, minor=minor) options_copy = copy.copy(options) self.__bom_scm = BomSourceCodeManager(options_copy, self.get_input_dir()) self.__hal = HalRunner(options) self.__git = GitRunner(options) self.__hal.check_property( 'spinnaker.config.input.bucket', options.halyard_bom_bucket) if options.only_repositories: self.__only_repositories = options.only_repositories.split(',') else: self.__only_repositories = [] options_copy.git_branch = self.__branch self.__branch_scm = BranchSourceCodeManager( options_copy, self.get_input_dir()) def push_branches_and_tags(self, bom): """Update the release branches and tags in each of the BOM repositires.""" logging.info('Tagging each of the BOM service repos') bom_scm = self.__bom_scm branch_scm = self.__branch_scm # Run in two passes so we dont push anything if we hit a problem # in the tagging pass. Since we are spread against multiple repositiories, # we cannot do this atomically. The two passes gives us more protection # from a partial push due to errors in a repo. names_to_push = set([]) for which in ['tag', 'push']: for name, spec in bom['services'].items(): if name in ['monitoring-third-party', 'defaultArtifact']: # Ignore this, it is redundant to monitoring-daemon continue if name == 'monitoring-daemon': name = 'spinnaker-monitoring' if self.__only_repositories and name not in self.__only_repositories: logging.debug('Skipping %s because of --only_repositories', name) continue if spec is None: logging.warning('HAVE bom.services.%s = None', name) continue repository = bom_scm.make_repository_spec(name) bom_scm.ensure_local_repository(repository) version = bom_scm.determine_repository_version(repository) if which == 'tag': added = self.__branch_and_tag_repository( repository, self.__branch, version) if added: names_to_push.add(name) else: self.__push_branch_and_maybe_tag_repository( repository, self.__branch, version, name in names_to_push) additional_repositories = list(SPINNAKER_PROCESS_REPOSITORY_NAMES) for name in additional_repositories: if self.__only_repositories and name not in self.__only_repositories: logging.debug('Skipping %s because of --only_repositories', name) continue repository = branch_scm.make_repository_spec(name) branch_scm.ensure_local_repository(repository) git_summary = self.__git.collect_repository_summary(repository.git_dir) version = git_summary.version if self.__branch_and_tag_repository( repository, self.__branch, version): self.__push_branch_and_maybe_tag_repository( repository, self.__branch, version, True) def __already_have_tag(self, repository, tag): """Determine if we already have the tag in the repository.""" git_dir = repository.git_dir existing_commit = self.__git.query_commit_at_tag(git_dir, tag) if not existing_commit: return False want_commit = self.__git.query_local_repository_commit_id(git_dir) if want_commit == existing_commit: logging.debug('Already have "%s" at %s', tag, want_commit) return True raise_and_log_error( ConfigError( '"{tag}" already exists in "{repo}" at commit {have}, not {want}' .format(tag=tag, repo=git_dir, have=existing_commit, want=want_commit))) return False # not reached def __branch_and_tag_repository(self, repository, branch, version): """Create a branch and/or version tag in the repository, if needed.""" tag = 'version-' + version if self.__already_have_tag(repository, tag): return False self.__git.check_run(repository.git_dir, 'tag ' + tag) return True def __push_branch_and_maybe_tag_repository(self, repository, branch, version, also_tag): """Push the branch and version tag to the origin.""" tag = 'version-' + version self.__git.push_branch_to_origin(repository.git_dir, branch) if also_tag: self.__git.push_tag_to_origin(repository.git_dir, tag) else: logging.info('%s was already tagged with "%s" -- skip', repository.git_dir, tag) def _do_command(self): """Implements CommandProcessor interface.""" options = self.options spinnaker_version = options.spinnaker_version options_copy = copy.copy(options) options_copy.git_branch = 'master' # push to master in spinnaker.github.io publish_changelog_command = PublishChangelogFactory().make_command( options_copy) changelog_gist_url = options.changelog_gist_url # Make sure changelog exists already. # If it does not then fail. try: logging.debug('Verifying changelog ready at %s', changelog_gist_url) urlopen(changelog_gist_url) except HTTPError: logging.error(exception_to_message) raise_and_log_error( ConfigError( 'Changelog gist "{url}" must exist before publising a release.' .format(url=changelog_gist_url), cause='ChangelogMissing')) bom = self.__hal.retrieve_bom_version(self.options.bom_version) bom['version'] = spinnaker_version bom_path = os.path.join(self.get_output_dir(), spinnaker_version + '.yml') write_to_path(yaml.safe_dump(bom, default_flow_style=False), bom_path) self.__hal.publish_bom_path(bom_path) self.push_branches_and_tags(bom) self.__hal.publish_spinnaker_release( spinnaker_version, options.spinnaker_release_alias, changelog_gist_url, options.min_halyard_version) logging.info('Publishing changelog') publish_changelog_command()