def __init__(self, repo_owner, repo, environment): self.output = OutputHelper() if all([repo_owner, repo, environment]): self._repo_owner = repo_owner self._repo = repo self._environment = environment.upper() else: exit('\nMissing github param\n\nABORTING!\n\n') self._url_github_api = self._get_url_github_api( HOST_GITHUB, repo_owner, repo) self._token_string = self._get_token_string(ACCESS_TOKEN) url = self._get_url_github_api_tags(self._url_github_api, self._token_string) req = self._get_tags(url) tags = req.json() self._max_comparisons = self._get_max_comparisons(tags) self._latest_tags = self._get_latest_tags(tags) self._last_tag = self._get_last_tag() self._last_tag_version = self._last_tag[VERS]
def __init__(self, host, bugzilla_username, bugzilla_password): self.output = OutputHelper() self.host = host self.username = bugzilla_username self.password = bugzilla_password self.token = self.get_token(host)
def __init__(self, repo='', product='', release_num='', environment=''): if all([repo, product, release_num, environment]): self.repo = repo self.product = product self.release_num = release_num self.environment = environment else: self.set_args() self.api_url = self.get_api_url() self.tags = self.get_tags() self.num_comparisons = self.get_num_comparisons(self.tags) self.latest_tags = self.get_latest_tags() self.output = OutputHelper()
class BugzillaRESTAPI(object): """"Used for CRUD operations against Bugzilla REST API Currently only supports authentication and create NEW. TODO: add bug update Use against URL_BUGZILLA_DEV to test. """ def __init__(self, host, bugzilla_username, bugzilla_password): self.output = OutputHelper() self.host = host self.username = bugzilla_username self.password = bugzilla_password self.token = self.get_token(host) def get_json(self, release_num, product, environment, status, description): """Create bugzilla JSON to POST to REST API. Returns: JSON string """ data = { 'product':'Mozilla Services', 'component':'General', 'version':'unspecified', 'op_sys':'All', 'rep_platform':'All' } short_desc = 'Please deploy {} {} to {}'.format( release_num, product, environment) data.update( {'short_desc': short_desc, 'description': description, 'status': status} ) return data # TODO(rpapa): it would save authentication time to cache token for re-use # add a try / except & query a new one if expired def get_token(self, host): """Fetch and return bugzilla token. Returns: string token """ url = '{}/rest/login?login={}&password={}'.format( host, self.username, self.password) req = requests.get(url) decoded = json.loads(req.text) try: if 'token' not in decoded: raise InvalidCredentials except InvalidCredentials: err_header = self.output.get_header('BUGZILLA ERROR') err_msg = '{}\n{}\n{}\n\n'.format( err_header, decoded['message'], decoded['documentation']) sys.exit(err_msg) else: return decoded['token'] def create_bug( self, release_num, product, environment, status, description): """Create bugzilla bug with description Note: On bugzilla-dev - available status: UNCONFIRMED, NEW, ASSIGNED, RESOLVED Returns: json string to POST to REST API """ url = '{}/rest/bug?token={}'.format(self.host, self.token) req = requests.post(url) data = self.get_json( release_num, product, environment, status, description) print data headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} req = requests.post(url, data=json.dumps(data), headers=headers) print 'CREATE BUG: {}'.format(req.status_code) return req.text
class GithubAPI(object): """Used for GET operations against github API. """ def __init__(self, repo='', product='', release_num='', environment=''): if all([repo, product, release_num, environment]): self.repo = repo self.product = product self.release_num = release_num self.environment = environment else: self.set_args() self.api_url = self.get_api_url() self.tags = self.get_tags() self.num_comparisons = self.get_num_comparisons(self.tags) self.latest_tags = self.get_latest_tags() self.output = OutputHelper() def set_args(self): parser = argparse.ArgumentParser( description='Scripts for creating / updating deployment tickets in \ Bugzilla', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( '-r', '--repo', default='mozilla-services', required=True) parser.add_argument( '-p', '--product', help='Example: loop-server', required=True) # @TODO: Need to add code to restrict query to <= release_num parser.add_argument( '-n', '--release-num', help='Example: 0.14.3', required=True) parser.add_argument( '-e', '--environment', help='Enter: stage, prod', default='stage', required=False) args = vars(parser.parse_args()) self.repo = args['repo'] self.product = args['product'] self.release_num = args['release_num'] self.environment = args['environment'] def get_api_url(self): """Return github API URL as string""" url = 'https://api.{}/repos/{}/{}/git/'.format(HOST_GITHUB, \ self.repo, self.product) return url def get_url_data(self, url): req = requests.get(url) try: if 'Not Found' in req.text: raise NotFoundError except NotFoundError: err_header = self.output.get_header('ERROR') err_msg = '{}\nNot found at: \n{}\nABORTING!\n\n'.format( err_header, self.api_url) sys.exit(err_msg) else: return json.loads(req.text) def get_tags(self): """Get all tags as json from Github API.""" return self.get_url_data(self.api_url + 'refs/tags') def get_tag(self, sha): """Get a specific tag's data from Github API.""" return self.get_url_data(self.api_url + 'tags/' + sha) def get_latest_tags(self): """Github API can only return all tags, but we only want the latest. Return: list of lists containing: [release_num, git sha] for last tags """ start = len(self.tags) - self.num_comparisons tags = self.tags latest = [] for i in xrange(len(tags)): if i >= start: parts = tags[i]['ref'].split('/') release_num = parts[2] sha = tags[i]['object']['sha'] tag = [release_num, sha] latest.append(tag) return latest def get_url_tag_release(self, release_num): """Return github tag release URL as string""" url = 'https://{}/{}/{}/releases/tag/{}'.format( HOST_GITHUB, self.repo, self.product, release_num ) return url def get_url_tag_commit(self, git_sha): """Return github tag commit SHA URL as string""" url = 'https://{}/{}/{}/commit/{}'.format( HOST_GITHUB, self.repo, self.product, git_sha ) return url def get_comparison(self, start, end): """Return github compare URL as string""" return 'https://{}/{}/{}/compare/{}...{}'.format(HOST_GITHUB, \ self.repo, self.product, start, end) + '\n' def get_num_comparisons(self, tags): """Display up to: MAX_COMPARISONS_TO_SHOW (if we have that many tags). If not, display comparisons of all tags. Returns: integer - num of github release comparisons to display """ count = len(tags) if count >= MAX_COMPARISONS_TO_SHOW: return MAX_COMPARISONS_TO_SHOW return count def get_changelog(self, commit_sha): """"Parse CHANGELOG for latest tag. Return: String with log from latest tag. """ url = 'https://{}/{}/{}/' + commit_sha + '/CHANGELOG' url = url.format(HOST_GITHUB_RAW, self.repo, self.product) req = requests.get(url) lines = req.text first = self.latest_tags[self.num_comparisons - 1][VERS] last = self.latest_tags[self.num_comparisons - 2][VERS] flag = False log = '' for line in lines.splitlines(): if first in line: flag = True if last in line: flag = False if flag: log += line + '\n' return log def get_release_notes(self): """Constructs release notes for Bugzilla service deployment ticket. Returns: String - with release notes """ notes = self.output.get_header('RELEASE NOTES') notes += 'https://{}/{}/{}/releases'.format(HOST_GITHUB, \ self.repo, self.product) + '\n' notes += self.output.get_sub_header('COMPARISONS') notes += self.get_comparison(self.latest_tags[0][VERS], self.latest_tags[1][VERS]) if len(self.latest_tags) >= (MAX_COMPARISONS_TO_SHOW - 1): notes += self.get_comparison(self.latest_tags[1][VERS], self.latest_tags[2][VERS]) if len(self.latest_tags) >= MAX_COMPARISONS_TO_SHOW: notes += self.get_comparison(self.latest_tags[2][VERS], self.latest_tags[3][VERS]) tag_data = self.get_tag(self.latest_tags[3][SHA]) notes += self.output.get_sub_header('TAGS') notes += self.get_url_tag_release(self.latest_tags[3][VERS]) + '\n' notes += self.get_url_tag_commit(tag_data["object"]["sha"]) + '\n' changelog = self.get_changelog(tag_data["object"]["sha"]) if changelog: notes += self.output.get_sub_header('CHANGELOG') notes += changelog return notes
class ReleaseNotes(object): """Used for GET operations against github API.""" def __init__(self, repo_owner, repo, environment): self.output = OutputHelper() if all([repo_owner, repo, environment]): self._repo_owner = repo_owner self._repo = repo self._environment = environment.upper() else: exit('\nMissing github param\n\nABORTING!\n\n') self._url_github_api = self._get_url_github_api( HOST_GITHUB, repo_owner, repo) self._token_string = self._get_token_string(ACCESS_TOKEN) url = self._get_url_github_api_tags(self._url_github_api, self._token_string) req = self._get_tags(url) tags = req.json() self._max_comparisons = self._get_max_comparisons(tags) self._latest_tags = self._get_latest_tags(tags) self._last_tag = self._get_last_tag() self._last_tag_version = self._last_tag[VERS] @property def last_tag(self): return self._last_tag_version def _get_last_tag(self): """Return last tag""" return self._latest_tags[self._max_comparisons - 1] def _get_token_string(self, access_token): """Return access_token as url param (if exists)""" if access_token: return '?access_token={0}'.format(access_token) return '' def _get_url_github(self, host_github, repo_owner, repo): """Return github root URL as string""" return 'https://{0}/{1}/{2}'.format(host_github, repo_owner, repo) def _get_url_github_api(self, host_github, repo_owner, repo): """Return github API URL as string""" return 'https://api.{0}/repos/{1}/{2}/git'.format( host_github, repo_owner, repo) def _get_url_github_api_tags(self, url_github_api, token_string): return '{0}/refs/tags{1}'.format(url_github_api, token_string) def _get_url_changelog(self, url_github_raw, commit_sha, filename): return '{0}/{1}/{2}'.format(url_github_raw, commit_sha, filename) def _url_last_tag(self, url_github_api, last_tag_sha, token_string): return '{0}/tags/{1}{2}'.format(url_github_api, last_tag_sha, token_string) def _url_comparison(self, url_github, start, end): return '{0}/compare/{1}...{2}'.format(url_github, start, end) def _url_tag(self, url_github, tag_version): return '{0}/releases/tag/{1}'.format(url_github, tag_version) def _url_tag_commit(self, url_github, commit_sha): return '{0}/commit/{1}'.format(url_github, commit_sha) def _url_releases(self, url_github): return '{0}/releases'.format(url_github) def _get_max_comparisons(self, tags): """Calculates max comparisons to show Note: Display up to MAX_COMPARISONS_TO_SHOW (or less) Returns: integer - num of github release comparisons to display """ count = len(tags) if count >= MAX_COMPARISONS_TO_SHOW: return MAX_COMPARISONS_TO_SHOW else: return count def _get_tags(self, url): """Get all tags as json from Github API.""" req = requests.get(url) try: if 'Not Found' in req.text: raise NotFoundError except NotFoundError: err_header = self.output.get_header('ERROR') err_msg = '{0}\nNothing found at: \n{1}\nABORTING!\n\n'.format( err_header, url) sys.exit(err_msg) else: return req def _parse_tag(self, tag): """Parse a tag object for the data we want Return: list of desired elements """ parts = tag['ref'].split('/') release_num = parts[2] sha = tag['object']['sha'] type = tag['object']['type'] url = tag['object']['url'] + self._token_string creation_date = self._get_commit_date(url) self.output.log((release_num, creation_date)) return [release_num, sha, type, url, creation_date] def _get_latest_tags(self, tags): """Github API returns all tags indiscriminately, but we only want the latest. Return: list of lists containing: [release_num, sha, type, url, creation_date] for latest tags """ self.output.log('Retrieve all tags', True) start = len(tags) - self._max_comparisons tags_unsorted = [] for i in range(len(tags)): tag = self._parse_tag(tags[i]) tags_unsorted.append(tag) self.output.log('Sort tags by commit date', True) tags_sorted = sorted(tags_unsorted, key=lambda tags_sorted: tags_sorted[4]) self.output.log('DONE!') latest = [] self.output.log('Get last tags from sorted list', True) for i in range(len(tags_sorted)): if i >= start: latest.append(tags_sorted[i]) self.output.log(tags_sorted[i]) self.output.log(latest) return latest def _get_commit_sha(self): """Return tag commit sha as string. Note: Varies depending on object type: type='tag' or 'commit' type='tag' requires a secondary call to retrieve commit url""" last_tag = self._last_tag if last_tag[TYPE] == 'tag': url = self._url_last_tag(self._url_github_api, last_tag[SHA], self._token_string) req = self._get_tags(url) return req.json()['object']['sha'] else: return last_tag[SHA] def _get_commit_date(self, url): """Return tag or commit creation date as string.""" req = self._get_tags(url) if 'git/tags' in url: return req.json()['tagger']['date'].split('T')[0] else: return req.json()['committer']['date'].split('T')[0] def _get_changelog(self, commit_sha): """"Parse and return CHANGELOG for latest tag as string""" url_github_raw = self._get_url_github(HOST_GITHUB_RAW, self._repo_owner, self._repo) for filename in CHANGELOG_FILENAMES: url = self._get_url_changelog(url_github_raw, commit_sha, filename) req = requests.get(url) try: if 'Not Found' in req.text: raise NotFoundError except NotFoundError: pass else: break if req.text == 'Not Found': return '' lines = req.text # parse out release notes for this release only # only works if version numbers in changelog appear exactly # as they are tagged vers_latest = self._latest_tags[self._max_comparisons - 1][VERS] vers_previous = self._latest_tags[self._max_comparisons - 2][VERS] flag = False log = '' for line in lines.splitlines(): if vers_latest in line: flag = True if vers_previous in line: flag = False if flag: log += line + '\n' return log def _get_section_release_notes(self, url_github): """Return bugzilla release notes with header as string""" notes = self.output.get_header('RELEASE NOTES') notes += self._url_releases(url_github) + '\n' return notes def _get_section_comparisons(self, url_github): """Return release notes - COMPARISONS section as string""" notes = self.output.get_sub_header('COMPARISONS') for i in range(0, self._max_comparisons - 1): start = self._latest_tags[i][VERS] end = self._latest_tags[i + 1][VERS] notes += self._url_comparison(url_github, start, end) + '\n' self.output.log('comparisons section - DONE!') return notes def _get_section_tags(self, url_github): """Return release notes - TAGS section as string""" commit_sha = self._get_commit_sha() notes = self.output.get_sub_header('TAGS') notes += self._url_tag( url_github, self._latest_tags[self._max_comparisons - 1][VERS]) + '\n' notes += self._url_tag_commit(url_github, commit_sha) + '\n' notes += self._get_section_changelog(commit_sha) self.output.log('tags section - DONE!') return notes def _get_section_changelog(self, commit_sha): """Return release notes - CHANGELOG section as string""" changelog = self._get_changelog(commit_sha) self.output.log('changelog section - DONE!') if changelog: return self.output.get_sub_header('CHANGELOG') + changelog else: return '' def get_release_notes(self): """Return release notes for Bugzilla deployment ticket as string""" url_github = self._get_url_github(HOST_GITHUB, self._repo_owner, self._repo) self.output.log('Create release notes', True) notes = self._get_section_release_notes(url_github) notes += self._get_section_comparisons(url_github) notes += self._get_section_tags(url_github) return notes