diff --git a/Makefile b/Makefile index ff00ef1852..36dce3219b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ NAME=redash VERSION=`python ./manage.py version` FULL_VERSION=$(VERSION)+b$(CIRCLE_BUILD_NUM) +BASE_VERSION=$(shell python ./manage.py version | cut -d + -f 1) # VERSION gets evaluated every time it's referenced, therefore we need to use VERSION here instead of FULL_VERSION. FILENAME=$(CIRCLE_ARTIFACTS)/$(NAME).$(VERSION).tar.gz @@ -15,7 +16,7 @@ pack: tar -zcv -f $(FILENAME) --exclude=".git*" --exclude="*.pyc" --exclude="*.pyo" --exclude="venv" --exclude="rd_ui/node_modules" --exclude="rd_ui/dist/bower_components" --exclude="rd_ui/app" * upload: - python bin/upload_version.py $(VERSION) $(FILENAME) + python bin/release_manager.py $(CIRCLE_SHA1) $(BASE_VERSION) $(FILENAME) test: nosetests --with-coverage --cover-package=redash tests/*.py diff --git a/bin/latest_release.py b/bin/latest_release.py deleted file mode 100755 index 1c24f68153..0000000000 --- a/bin/latest_release.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -import sys -import requests - -if __name__ == '__main__': - response = requests.get('https://api.github.com/repos/EverythingMe/redash/releases') - - if response.status_code != 200: - exit("Failed getting releases (status code: %s)." % response.status_code) - - sorted_releases = sorted(response.json(), key=lambda release: release['id'], reverse=True) - - latest_release = sorted_releases[0] - asset_url = latest_release['assets'][0]['url'] - filename = latest_release['assets'][0]['name'] - - wget_command = 'wget --header="Accept: application/octet-stream" %s -O %s' % (asset_url, filename) - - if '--url-only' in sys.argv: - print asset_url - elif '--wget' in sys.argv: - print wget_command - else: - print "Latest release: %s" % latest_release['tag_name'] - print latest_release['body'] - - print "\nTarball URL: %s" % asset_url - print 'wget: %s' % (wget_command) - - diff --git a/bin/release_manager.py b/bin/release_manager.py new file mode 100644 index 0000000000..0238325eef --- /dev/null +++ b/bin/release_manager.py @@ -0,0 +1,130 @@ +import os +import sys +import json +import re +import subprocess +import requests + +github_token = os.environ['GITHUB_TOKEN'] +auth = (github_token, 'x-oauth-basic') +repo = 'EverythingMe/redash' + +def _github_request(method, path, params=None, headers={}): + if not path.startswith('https://api.github.com'): + url = "https://api.github.com/{}".format(path) + else: + url = path + + if params is not None: + params = json.dumps(params) + + response = requests.request(method, url, data=params, auth=auth) + return response + +def exception_from_error(message, response): + return Exception("({}) {}: {}".format(response.status_code, message, response.json().get('message', '?'))) + +def rc_tag_name(version): + return "v{}-rc".format(version) + +def get_rc_release(version): + tag = rc_tag_name(version) + response = _github_request('get', 'repos/{}/releases/tags/{}'.format(repo, tag)) + + if response.status_code == 404: + return None + elif response.status_code == 200: + return response.json() + + raise exception_from_error("Unknown error while looking RC release: ", response) + +def create_release(version, commit_sha): + tag = rc_tag_name(version) + + params = { + 'tag_name': tag, + 'name': "{} - RC".format(version), + 'target_commitish': commit_sha, + 'prerelease': True + } + + response = _github_request('post', 'repos/{}/releases'.format(repo), params) + + if response.status_code != 201: + raise exception_from_error("Failed creating new release", response) + + return response.json() + +def upload_asset(release, filepath): + upload_url = release['upload_url'].replace('{?name}', '') + filename = filepath.split('/')[-1] + + with open(filepath) as file_content: + headers = {'Content-Type': 'application/gzip'} + response = requests.post(upload_url, file_content, params={'name': filename}, headers=headers, auth=auth, verify=False) + + if response.status_code != 201: # not 200/201/... + raise exception_from_error('Failed uploading asset', response) + + return response + +def remove_previous_builds(release): + for asset in release['assets']: + response = _github_request('delete', asset['url']) + if response.status_code != 204: + raise exception_from_error("Failed deleting asset", response) + +def get_changelog(commit_sha): + latest_release = _github_request('get', 'repos/{}/releases/latest'.format(repo)) + if latest_release.status_code != 200: + raise exception_from_error('Failed getting latest release', latest_release) + + latest_release = latest_release.json() + previous_sha = latest_release['target_commitish'] + + args = ['git', '--no-pager', 'log', '--merges', '--grep', 'Merge pull request', '--pretty=format:"%h|%s|%b|%p"', '{}...{}'.format(previous_sha, commit_sha)] + log = subprocess.check_output(args) + changes = ["Changes since {}:".format(latest_release['name'])] + + for line in log.split('\n'): + try: + sha, subject, body, parents = line[1:-1].split('|') + except ValueError: + continue + + try: + pull_request = re.match("Merge pull request #(\d+)", subject).groups()[0] + pull_request = " #{}".format(pull_request) + except Exception, ex: + pull_request = "" + + author = subprocess.check_output(['git', 'log', '-1', '--pretty=format:"%an"', parents.split(' ')[-1]])[1:-1] + + changes.append("{}{}: {} ({})".format(sha, pull_request, body.strip(), author)) + + return "\n".join(changes) + +def update_release(version, build_filepath, commit_sha): + try: + release = get_rc_release(version) or create_release(version, commit_sha) + print "Using release id: {}".format(release['id']) + + remove_previous_builds(release) + response = upload_asset(release, build_filepath) + + changelog = get_changelog(commit_sha) + + response = _github_request('patch', release['url'], {'body': changelog}) + if response.status_code != 200: + raise exception_from_error("Failed updating release description", response) + + except Exception, ex: + print ex + +if __name__ == '__main__': + commit_sha = sys.argv[1] + version = sys.argv[2] + filepath = sys.argv[3] + + # TODO: make sure running from git directory & remote = repo + update_release(version, filepath, commit_sha) diff --git a/bin/upload_version.py b/bin/upload_version.py deleted file mode 100644 index a1d641a767..0000000000 --- a/bin/upload_version.py +++ /dev/null @@ -1,46 +0,0 @@ -#!python -import os -import sys -import json -import requests -import subprocess - - -def capture_output(command): - proc = subprocess.Popen(command, stdout=subprocess.PIPE) - return proc.stdout.read() - - -if __name__ == '__main__': - version = sys.argv[1] - filepath = sys.argv[2] - filename = filepath.split('/')[-1] - github_token = os.environ['GITHUB_TOKEN'] - auth = (github_token, 'x-oauth-basic') - commit_sha = os.environ['CIRCLE_SHA1'] - - commit_body = capture_output(["git", "log", "--format=%b", "-n", "1", commit_sha]) - file_md5_checksum = capture_output(["md5sum", filepath]).split()[0] - file_sha256_checksum = capture_output(["sha256sum", filepath]).split()[0] - version_body = "%s\n\nMD5: %s\nSHA256: %s" % (commit_body, file_md5_checksum, file_sha256_checksum) - - params = json.dumps({ - 'tag_name': 'v{0}'.format(version), - 'name': 're:dash v{0}'.format(version), - 'body': version_body, - 'target_commitish': commit_sha, - 'prerelease': True - }) - - response = requests.post('https://api.github.com/repos/everythingme/redash/releases', - data=params, - auth=auth) - - upload_url = response.json()['upload_url'] - upload_url = upload_url.replace('{?name}', '') - - with open(filepath) as file_content: - headers = {'Content-Type': 'application/gzip'} - response = requests.post(upload_url, file_content, params={'name': filename}, auth=auth, - headers=headers, verify=False) -