Skip to content

Commit

Permalink
feat(promote): Push tags and branches to remote repos during publicat…
Browse files Browse the repository at this point in the history
…ion. (#1488)
  • Loading branch information
jtk54 committed Mar 21, 2017
1 parent 1b70ccd commit 5a5e822
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 102 deletions.
1 change: 1 addition & 0 deletions dev/annotate_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ def main(cls):
options = parser.parse_args()

annotator = cls(options)
annotator.checkout_branch()
annotator.parse_git_tree()
annotator.tag_head()
annotator.delete_unwanted_tags()
Expand Down
1 change: 0 additions & 1 deletion dev/build_prevalidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ def main():
__annotate_component(annotator, 'halyard')

bom_generator = BomGenerator(options)
bom_generator.checkout_branch()
bom_generator.determine_and_tag_versions()
if options.container_builder == 'gcb':
bom_generator.write_container_builder_gcr_config()
Expand Down
2 changes: 2 additions & 0 deletions dev/generate_bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ def publish_microservice_configs(self):
def determine_and_tag_versions(self):
for comp in self.COMPONENTS:
self.path = os.path.join(self.__base_dir, comp)
self.checkout_branch()
self.parse_git_tree()
self.__changelog_start_hashes[comp] = self.current_version.hash
version_bump = self.tag_head()
Expand All @@ -316,6 +317,7 @@ def main(cls):
options.container_builder))

bom_generator.write_bom()
bom_generator.publish_boms()
bom_generator.publish_microservice_configs()
bom_generator.generate_changelog()

Expand Down
101 changes: 0 additions & 101 deletions dev/promote_bom.py

This file was deleted.

186 changes: 186 additions & 0 deletions dev/publish_bom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/usr/bin/python
#
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import datetime
import os
import socket
import sys
import yaml

from github import Github
from github.Gist import Gist
from github.InputFileContent import InputFileContent

from generate_bom import BomGenerator
from spinnaker.run import check_run_quick, run_quick

SERVICES = 'services'
VERSION = 'version'

COMPONENTS = [
'clouddriver',
'deck',
'echo',
'front50',
'gate',
'igor',
'orca',
'rosco',
'fiat',
'spinnaker-monitoring',
'spinnaker'
]

class BomPublisher(BomGenerator):

def __init__(self, options):
self.__rc_version = options.rc_version
self.__bom_dict = {}
self.__release_version = options.release_version
self.__github_publisher = options.github_publisher
self.__changelog_file = options.changelog_file
self.__github_token = options.github_token
self.__gist_user = options.gist_user
self.__github = Github(self.__gist_user, self.__github_token)
self.__patch_release = options.patch_release
self.__alias = options.bom_alias # Flag inherited from BomGenerator.
super(BomPublisher, self).__init__(options)

def unpack_bom(self):
"""Load the release candidate BOM into memory.
"""
bom_yaml_string = run_quick('hal versions bom {0} --color false'
.format(self.__rc_version), echo=False).stdout.strip()
print bom_yaml_string
self.__bom_dict = yaml.load(bom_yaml_string)
print self.__bom_dict

def publish_release_bom(self):
"""Read, update, and publish a release candidate BOM.
"""
new_bom_file = '{0}.yml'.format(self.__release_version)
self.__bom_dict[VERSION] = self.__release_version
self.write_bom_file(new_bom_file, self.__bom_dict)
self.publish_bom(new_bom_file)
# Re-write the 'latest' Spinnaker version.
# TODO(jacobkiefer): Update 'available versions' with Halyard when that feature is ready.
if self.__alias:
alias_file = '{0}.yml'.format(self.__alias)
self.write_bom_file(alias_file, self.__bom_dict)
self.publish_bom(alias_file)

def publish_changelog_gist(self):
"""Publish the changelog as a github gist.
"""
description = 'Changelog for Spinnaker {0}'.format(self.__release_version)
with open(self.__changelog_file, 'r') as clog:
raw_content_lines = clog.readlines()
spinnaker_version = '# Spinnaker {0}\n'.format(self.__release_version)
# Re-write the correct Spinnaker version at the top of the changelog.
# Also add some identifying information.
raw_content_lines[0] = spinnaker_version
timestamp = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())
signature = '\n\nGenerated by {0} at {1}'.format(socket.gethostname(), timestamp)
raw_content_lines.append(signature)
content = InputFileContent(''.join(raw_content_lines))
filename = os.path.basename(self.__changelog_file)
gist = self.__github.get_user().create_gist(True, {filename: content}, description=description)
print ('Wrote changelog to Gist at https://gist.github.com/{user}/{id}'
.format(user=self.__gist_user, id=gist.id))

def push_branch_and_tags(self):
"""Creates a release branch and pushes tags to the microservice repos owned by --github_publisher.
A private key that has access to --github_publisher's github repos needs added
to a running ssh-agent on the machine this script will run on:
> <copy or rsync the key to the vm>
> eval `ssh-agent`
> ssh-add ~/.ssh/<key with access to github repos>
"""
major, minor, _ = self.__release_version.split('.')

# The stable branch will look like <major>.<minor>.X since nebula
# enforces restrictions on what branches it does releases from.
# https://github.com/nebula-plugins/nebula-release-plugin#extension-provided
stable_branch = '.'.join([major, minor, 'X'])
for comp in COMPONENTS:
if self.__patch_release:
check_run_quick('git -C {0} checkout {1}'.format(comp, stable_branch))
else:
# Create new release branch.
check_run_quick('git -C {0} checkout -b {1}'.format(comp, stable_branch))

version_tag_build = ''
if comp == 'spinnaker-monitoring':
version_tag_build = 'version-{0}'.format(self.__bom_dict[SERVICES]['monitoring-daemon'][VERSION])
else:
version_tag_build = 'version-{0}'.format(self.__bom_dict[SERVICES][comp][VERSION])

last_dash = version_tag_build.rindex('-')
version_tag = version_tag_build[:last_dash]
repo_to_push = ('git@github.com:{owner}/{comp}.git'
.format(owner=self.__github_publisher, comp=comp))
check_run_quick('git -C {comp} remote add release {url}'
.format(comp=comp, url=repo_to_push))
check_run_quick('git -C {comp} push release {branch}'
.format(comp=comp, branch=stable_branch))

repo = self.__github.get_repo('{owner}/{comp}'.format(owner=self.__github_publisher, comp=comp))
paginated_tags = repo.get_tags()
tag_names = [tag.name for tag in paginated_tags]
if version_tag not in tag_names:
# The tag doesn't exist and we need to push a tag.
print ('pushing version tag {tag} to {owner}/{comp}'
.format(tag=version_tag, owner=self.__github_publisher, comp=comp))
check_run_quick('git -C {comp} push release {tag}'
.format(comp=comp, tag=version_tag))

@classmethod
def main(cls):
parser = argparse.ArgumentParser()
cls.init_argument_parser(parser)
options = parser.parse_args()

bom_publisher = cls(options)
bom_publisher.unpack_bom()
bom_publisher.publish_changelog_gist()
bom_publisher.push_branch_and_tags()
bom_publisher.publish_release_bom()

@classmethod
def init_argument_parser(cls, parser):
"""Initialize command-line arguments."""
parser.add_argument('--changelog_file', default='', required=True,
help='The changelog to publish during this publication.')
parser.add_argument('--github_token', default='', required=True,
help="The GitHub user token with scope='gists' to write gists.")
parser.add_argument('--gist_user', default='', required=True,
help="The GitHub user to write gists as.")
parser.add_argument('--rc_version', default='', required=True,
help='The version of the Spinnaker release candidate we are publishing.')
parser.add_argument('--release_version', default='', required=True,
help="The version for the new Spinnaker release. This needs to be of the form 'X.Y.Z'.")
parser.add_argument('--github_publisher', default='', required=True,
help="The owner of the remote repo the branch and tag are pushed to for each component.")
parser.add_argument('--patch_release', default=False, action='store_true',
help='Make a patch release.')
super(BomPublisher, cls).init_argument_parser(parser)

if __name__ == '__main__':
sys.exit(BomPublisher.main())

0 comments on commit 5a5e822

Please sign in to comment.