Skip to content

Commit

Permalink
Automate backup-utils releases
Browse files Browse the repository at this point in the history
Script to automate backup-util releases using the existing conventions
and format.

The intention is to release with a single command:

```
./script/release 2.9.1
```
  • Loading branch information
rubiojr committed Apr 8, 2017
1 parent 05eb5d2 commit d9aeea6
Showing 1 changed file with 301 additions and 0 deletions.
301 changes: 301 additions & 0 deletions script/release
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
#!/usr/bin/env ruby
#/ Usage: release [--dry-run] <version>
#/
#/ Publish a backup-utils release:
#/ * Updates the package changelog
#/ * Bumps the backup-utils version if required
#/ * Creates the release pull request
#/ * Creates the release draft
#/ * Tags the release
#/ * Builds the release assets and uploads them
#/
#/ Notes:
#/ * Needs GH_RELEASE_TOKEN available in the environment.
#/ * Export GH_OWNER, GH_AUTHOR and GH_REPO if you want to tweak the build
#/ changelog or use a different owner/repo
#/ * Only pull requests labeled with bug or feature will show up in the
#/ release page and the changelog.
#/
# TODO: Auto-merge the release PR
# TODO: Tag the release
# TODO: Build and upload the binaries to the release draft
require 'json'
require 'net/http'
require 'time'
require 'erb'

API_HOST = ENV["GH_HOST"] || "api.github.com"
API_PORT = 443
GH_REPO = ENV["GH_REPO"] || "backup-utils"
GH_OWNER = ENV["GH_OWNER"] || "github"
GH_AUTHOR = ENV["GH_AUTHOR"] || "Sergio Rubio <[email protected]>"
DEB_PKG_NAME = "github-backup-utils"

CHANGELOG_TMPL = """
<%G= package_name %> (<%= package_version %>) UNRELEASED; urgency=medium
<%- changes.each do |ch| -%>
* <%= ch.strip.chomp %>
<% end -%>
-- <%= author %> <%= Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S %z') %>
"""

# Override Kernel.warn
def warn(msg)
unless $no_warn
Kernel.warn msg
end
end

def client
@http ||= begin
c = Net::HTTP.new(API_HOST, API_PORT)
c.use_ssl = true
c
end
end

def release_token
token = ENV["GH_RELEASE_TOKEN"]
raise "GH_RELEASE_TOKEN not set" if token.nil?

token
end

def get(path)
req = Net::HTTP::Get.new(path)
req['Authorization'] = "token #{release_token}"
client.request(req)
end

def post(path, body)
req = Net::HTTP::Post.new(path)
req['Authorization'] = "token #{release_token}"
req.body = body
client.request(req)
end

#
# tag "v0.0.1", "foo tag",
# "e1d0d72078f7fbef653e705bfb0fe018bd9c772e",
# "foolano", "test",
# "Foolano Garcia", "[email protected]"
def tag(name, message, sha, owner, repo, tagger_name, tagger_email)
r = {
"tag": name,
"message": "#{message}\n",
"object": sha,
"type": "commit",
"tagger": {
"name": tagger_name,
"email": tagger_email,
"date": Time.now.utc.iso8601.to_s
}
}.to_json
res = post("/repos/#{owner}/#{repo}/git/tags", r)

raise "Creating tag object failed (#{res.code})" unless res.kind_of? Net::HTTPSuccess

body = {
"ref": "refs/heads/#{name}",
"sha": sha
}.to_json
res = post("/repos/#{owner}/#{repo}/git/refs", body)

raise "Creating tag ref failed (#{res.code})" unless res.kind_of? Net::HTTPSuccess
end

def bug_or_feature?(issue_hash)
return true if issue_hash["labels"].find { |l| ["bug", "feature"].include?(l["name"]) }
false
end

def issue_from(owner, repo, issue)
res = get("/repos/#{owner}/#{repo}/issues/#{issue}")
raise "Issue ##{issue} not found in #{owner}/#{repo}" unless res.kind_of? Net::HTTPSuccess

JSON.parse(res.body)
end

def beautify_changes(changes, owner, repo)
c = []
changes.each do |ch|
if ch =~ /#(\d+)/
begin
j = issue_from(owner, repo, $1)
if bug_or_feature?(j)
c << "#{j['title']} ##{$1}"
end
rescue => e
warn "Warning: #{e.message}"
end
end
end

c
end

def changelog()
changes = `git log --pretty=oneline origin/stable...origin/master | grep "Merge pull request"`.lines
raise "Building the changelog failed" if $? != 0

changes
end

def build_changelog(changes, package_name, package_version, author, owner, repo)
ERB.new(CHANGELOG_TMPL, nil, "-").result(binding)
end

def update_changelog(changes, name, version, author, owner, repo, path = "debian/changelog")
raise "debian/changelog not found" unless File.exist?(path)
File.open("#{path}.new", 'w') do |f|
f.puts build_changelog changes, name, version, author, owner, repo
f.puts(File.read(path))
end
File.rename("#{path}.new", path)
end

def create_release(tag_name, branch, owner, repo, rel_name, rel_body, draft = true)
body = {
"tag_name": tag_name,
"target_commitish": branch,
"name": rel_name,
"body": rel_body,
"draft": draft,
"prerelease": false
}.to_json
res = post("/repos/#{owner}/#{repo}/releases", body)
end

def list_releases(owner, repo)
res = get("/repos/#{owner}/#{repo}/releases")
raise "Error retrieving releases" unless res.kind_of? Net::HTTPSuccess

JSON.parse(res.body)
end

def release_available?(owner, repo, tag_name)
releases = list_releases owner, repo
return true if releases.find { |r| r["tag_name"] == tag_name }

false
end

def bump_version(new_version, path = "share/github-backup-utils/version")
current_version = Gem::Version.new(File.read(path).strip.chomp)
if Gem::Version.new(new_version) < current_version
raise "New version should be newer than #{current_version}"
end
File.open("#{path}.new", 'w') do |f|
f.puts new_version
end
File.rename("#{path}.new", path)
end

def push_release_branch(version, changes)
if !system("git checkout --quiet -b release-#{version}")
raise "Creating release branch failed"
end

if !system("git commit --quiet -m 'Bump version: #{version}\n#{changes}' debian/changelog share/github-backup-utils/version")
raise "Error commiting changelog and version"
end

if !system("git push --quiet origin release-#{version}")
raise "Failed pushing the release branch"
end
end

def create_release_pr(version, owner, repo, release_body)
body = {
"title": "Bump version: #{version}",
"body": release_body,
"head": "release-#{version}",
"base": "master"
}.to_json
res = post("/repos/#{owner}/#{repo}/pulls", body)
raise "Creating release PR failed (#{res.code})" unless res.kind_of? Net::HTTPSuccess
end

def can_auth?
!ENV["GH_RELEASE_TOKEN"].nil?
end

args = ARGV.dup
dry_run = false
if args.include?("--dry-run")
dry_run = true
args.delete '--dry-run'
end

if args.include?("--no-warn")
$no_warn = true
args.delete '--no-warn'
end

if args.size < 1
$stderr.puts "Usage: release [--dry-run] <version>"
exit 1
end

begin
version = Gem::Version.new(args[0])
rescue ArgumentError
$stderr.puts "Error parsing version #{args[0]}"
exit 1
end

release_changes = []
release_a = false
if dry_run
if can_auth?
release_changes = beautify_changes(changelog, GH_OWNER, GH_REPO)
release_a = release_available?(GH_OWNER, GH_REPO, "v#{version}")
puts "Existing release: #{release_a}"
end
puts "New version: #{version}"
puts "Owner: #{GH_OWNER}"
puts "Repo: #{GH_REPO}"
puts "Author: #{GH_AUTHOR}"
puts "Token: #{ENV['GH_RELEASE_TOKEN'] && "set" || "unset"}"
if can_auth?
puts "Changelog:"
release_changes.each { |c| puts " * #{c}"}
end
exit
end

if release_a
$stderr.puts "Release #{version} already exists."
exit 1
end

`git fetch --quiet origin --prune`
branches = `git branch --all | grep release-#{version}$`
if !branches.empty?
$stderr.puts "Release branch release-#{version} already exists."
$stderr.puts "Branches found:"
branches.each_line { |l| puts "* #{l.strip.chomp}" }
exit 1
end

puts "Bumping version to #{version}"
bump_version(version)

puts "Updating changelog"
update_changelog release_changes, DEB_PKG_NAME, version, GH_AUTHOR, GH_OWNER, GH_REPO

puts "Creating release"
release_title = "GitHub Enterprise Backup Utilities v#{version}"
release_body = "Includes general improvements, bug fixes and support for GitHub Enterprise v#{version}"
release_changes.each do |c|
release_body += "\n* #{c}"
end
create_release "v#{version}", "master", GH_OWNER, GH_REPO, release_title, release_body

puts "Creating and publishing the release branch"
push_release_branch(version, release_changes)
create_release_pr(version, GH_OWNER, GH_REPO, release_body)
puts "Released!"

0 comments on commit d9aeea6

Please sign in to comment.