circlemator 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c813019d9366200fd764c399fa6736d7e0d690e5
4
+ data.tar.gz: b2692e0b581a721cbe5854ba21cdb932552cbe32
5
+ SHA512:
6
+ metadata.gz: c0a99bfe944824eb9e5dffb63ab81c8352e7cc63fb42dd1f8229b8535144c6f9998c13a2dabab405f269fab975778e1defefb71866c9383a5d504f6a265414c3
7
+ data.tar.gz: 70629c129f63f0646a1a9d185b2d8b6da8377584b93c5dacf70eacaa77f2ecf8a2c333d481c17674e14e73c561b47d56a81919e541470b49a4b931a5771eba2e
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in circlemator.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Rainforest QA, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Circlemator
2
+
3
+ Circlemator is a bucket of tricks for working with CircleCI and Github
4
+ used internally at Rainforest QA.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'circlemator', require: false
12
+ ```
13
+
14
+ Then run `bundle` and check in the resulting Gemfile.lock. That should
15
+ be enough, really.
16
+
17
+ ## Usage
18
+
19
+ Circlemator tasks are designed to be added to your circle.yml file
20
+ like so:
21
+
22
+ ```yml
23
+ - bundle exec circlemator <task> [options]
24
+ ```
25
+
26
+ Different tasks require different options/placement in your
27
+ circle.yml.
28
+
29
+ ## Tasks
30
+
31
+ ### Cancel old builds
32
+
33
+ CircleCI starts a build every time you push to Github. That's usually
34
+ a good thing, but if you have a big test suite it can be annoying when
35
+ your build queue gets gummed up running builds on out-of-date
36
+ commits. To clear things up, the `cancel-old` task cancels all builds
37
+ that are not at the head of their branch. It should be in your
38
+ circle.yml before your tests are run but after the dependencies have
39
+ been fetched, for example:
40
+
41
+ ```yml
42
+ test:
43
+ pre:
44
+ - bundle exec circlemator cancel-old
45
+ ```
46
+
47
+ In order for this to work, you need the following environment variable
48
+ to be set in CircleCI:
49
+
50
+ - `CIRCLE_API_TOKEN`: Your CircleCI API token. (Can also be set with
51
+ the `-t` option.)
52
+
53
+ ### Self-merge release branch
54
+
55
+ Preamble: at Rainforest, our process for getting code into production
56
+ looks like this:
57
+
58
+ 1. Push to feature branch pull request.
59
+ 2. Run unit tests and get code review (repeat 1-2 as necessary).
60
+ 3. Merge feature branch to `develop`.
61
+ 4. Open release pull request from `develop` to `master`.
62
+ 5. Run unit tests + Rainforest against `develop`.
63
+ 6. Merge `develop` into `master` if everything's green.
64
+ 7. Deploy from `master`.
65
+
66
+ Out of these, steps 1, 2, 3, and 4 require manual intervention, but
67
+ everything else should be automatic! The `self-merge` task is designed
68
+ to take care of step 6 (the rest is handled by CircleCI out of the
69
+ box).
70
+
71
+ To use `self-merge`, add something like the following to your
72
+ circle.yml:
73
+
74
+ ```yml
75
+ deployment:
76
+ staging:
77
+ branch: develop
78
+ commands:
79
+ <any commands you would normally run>
80
+ - bundle exec circlemator self-merge --base-branch=master --compare-branch=develop
81
+ ```
82
+
83
+ Swap out `develop` and `master` as necessary to fit your workflow. Be
84
+ warned, the `circlemator` command should probably be the last command
85
+ in your deploy stage! (Otherwise you'll merge before your build is
86
+ done.)
87
+
88
+ `self-merge` will *only* run if there is an open pull request against
89
+ the base branch. That means you have a way to prevent automatic
90
+ shipping in exceptional circumstances: just don't open a release pull
91
+ request.
92
+
93
+ `self-merge` requires the following environment variable to be set:
94
+
95
+ - `GITHUB_AUTH_TOKEN`: A Github API auth token for a user with commit
96
+ access to your repo. (Can also be set with the `-g` option.)
97
+
98
+ Also, unfortunately branch protection cannot be enabled on your
99
+ `master` branch. (Contributions welcome for anyone who can think of a
100
+ workaround...)
101
+
102
+ ## Contributing
103
+
104
+ Bug reports and pull requests are welcome on GitHub at
105
+ https://github.com/rainforestapp/circlemator.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "circlemator"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'circlemator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "circlemator"
8
+ spec.version = Circlemator::VERSION
9
+ spec.authors = ["Emanuel Evans"]
10
+ spec.email = ["emanuel@rainforestqa.com"]
11
+
12
+ spec.summary = %q{A bucket of tricks for CircleCI and Github.}
13
+ spec.description = <<-EOF
14
+ A few utilities for CircleCI to improve your CI workflow.
15
+ EOF
16
+ spec.homepage = "https://github.com/rainforestapp/circlemator"
17
+ spec.license = "MIT"
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "httparty", "~> 0.13.7"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.10"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ end
data/exe/circlemator ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require_relative '../lib/circlemator/build_canceler'
4
+ require_relative '../lib/circlemator/self_merger'
5
+
6
+ options = {}
7
+ parser = OptionParser.new do |opts|
8
+ opts.banner = "Usage: circlemator (self-merge|cancel-old) [options]"
9
+
10
+ opts.on('-bBRANCH', '--base-branch=BRANCH', 'Base branch for merging') do |b|
11
+ options[:base_branch] = b
12
+ end
13
+
14
+ opts.on('-cBRANCH', '--compare-branch=BRANCH', 'Compare branch for merging') do |b|
15
+ options[:compare_branch] = b
16
+ end
17
+
18
+ opts.on('-gTOKEN', '--github-auth-token=TOKEN', 'Github auth token') do |t|
19
+ options[:github_auth_token] = t
20
+ end
21
+
22
+ opts.on('-tTOKEN', '--circle-api-token=TOKEN', 'CircleCI API token') do |t|
23
+ options[:circle_api_token] = t
24
+ end
25
+
26
+ opts.on('-h', '--help', 'Print this help message') do
27
+ puts opts
28
+ exit
29
+ end
30
+ end
31
+
32
+ parser.parse!
33
+
34
+ def require_env(var)
35
+ if !ENV[var] || ENV[var].empty?
36
+ puts "Environment variable '#{var}' is required"
37
+ exit 1
38
+ end
39
+
40
+ ENV[var]
41
+ end
42
+
43
+ def require_opt(opts, key)
44
+ if !opts[key]
45
+ puts "Option #{key.gsub(/_/, '-')} is required!"
46
+ exit 1
47
+ end
48
+ end
49
+
50
+ if !ENV['CIRCLECI']
51
+ puts 'Cirlemator should only be run from CircleCI'
52
+ exit 1
53
+ end
54
+
55
+ options[:user] = require_env 'CIRCLE_PROJECT_USERNAME'
56
+ options[:repo] = require_env 'CIRCLE_PROJECT_REPONAME'
57
+
58
+ case ARGV[0]
59
+ when 'self-merge'
60
+ options[:sha] = require_env 'CIRCLE_SHA1'
61
+ options[:github_auth_token] ||= ENV['GITHUB_AUTH_TOKEN']
62
+ require_opt options, :github_auth_token
63
+ require_opt options, :base_branch
64
+ require_opt options, :compare_branch
65
+
66
+ Circlemator::SelfMerger.new(options).merge!
67
+ when 'cancel-old'
68
+ options[:current_build] = require_env('CIRCLE_BUILD_NUM').to_i
69
+ options[:circle_api_token] ||= ENV['CIRCLE_API_TOKEN']
70
+ require_opt options, :circle_api_token
71
+
72
+ Circlemator::BuildCanceler.new(options).cancel_old_builds!
73
+ else
74
+ puts parser
75
+ end
76
+
77
+
78
+
79
+ # Local Variables:
80
+ # major-mode: ruby-mode
81
+ # End:
@@ -0,0 +1,54 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module Circlemator
5
+ class BuildCanceler
6
+ def initialize(user: , repo: , current_build: , circle_api_token: )
7
+ @user = user
8
+ @repo = repo
9
+ @current_build = current_build
10
+ @circle_api_token = circle_api_token
11
+ end
12
+
13
+ def cancel_old_builds!
14
+ resp = HTTParty.get "https://circleci.com/api/v1/project/#{@user}/#{@repo}",
15
+ circle_auth
16
+ check_response resp
17
+
18
+ builds = JSON.parse(resp.body)
19
+ .select { |b| %w(running scheduled queued not_running).include? b.fetch('status') }
20
+ .group_by { |b| b.fetch('branch') }
21
+ .flat_map { |_, group| group.sort_by { |b| b.fetch('build_num') }[0...-1] }
22
+ .map { |b| b.fetch('build_num') }
23
+
24
+ cancel_self = !!builds.delete(@current_build)
25
+
26
+ builds.each do |build_num|
27
+ resp = HTTParty.post "https://circleci.com/api/v1/project/#{@user}/#{@repo}/#{build_num}/cancel",
28
+ circle_auth
29
+ check_response resp
30
+ end
31
+
32
+ if cancel_self
33
+ puts "Daisy, Daisy, give me your answer, do..."
34
+ HTTParty.post "https://circleci.com/api/v1/project/#{@user}/#{@repo}/#{@current_build}/cancel",
35
+ circle_auth
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def circle_auth
42
+ {
43
+ query: { 'circle-token': @circle_api_token },
44
+ headers: { 'Accept' => 'application/json' },
45
+ }
46
+ end
47
+
48
+ def check_response(resp)
49
+ if resp.code != 200
50
+ raise "CircleCI API call failed: #{JSON.parse(resp.body)}"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,69 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module Circlemator
5
+ class SelfMerger
6
+ def initialize(user: ,
7
+ repo: ,
8
+ sha: ,
9
+ github_auth_token: ,
10
+ base_branch: ,
11
+ compare_branch: )
12
+ @user = user
13
+ @repo = repo
14
+ @sha = sha
15
+ @auth_token = github_auth_token
16
+ @base_branch = base_branch
17
+ @compare_branch = compare_branch
18
+ end
19
+
20
+ def merge!
21
+ pr_number, pr_url = find_pr
22
+ return if pr_number.nil? || pr_url.nil?
23
+
24
+ msg = "Auto-merge by Circlemator!"
25
+ response = HTTParty.put "#{pr_url}/merge",
26
+ body: { commit_message: msg, sha: @sha }.to_json,
27
+ basic_auth: github_auth
28
+ if response.code != 200
29
+ body = JSON.parse(response.body)
30
+ raise "Merge failed: #{body.fetch('message')}"
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def find_pr
37
+ response = HTTParty.get "https://api.github.com/repos/#{github_repo}/pulls",
38
+ query: { base: @base_branch },
39
+ basic_auth: github_auth
40
+ if response.code != 200
41
+ raise "Bad response from Github: #{response.inspect}"
42
+ end
43
+
44
+ prs = JSON.parse(response.body)
45
+ pr = prs.find do |pr|
46
+ pr.fetch('head').fetch('ref') == @compare_branch &&
47
+ pr.fetch('head').fetch('sha') == @sha &&
48
+ pr.fetch('base').fetch('ref') == @base_branch
49
+ end
50
+
51
+ if pr.nil?
52
+ puts 'No release PR. Not merging.'
53
+ return
54
+ end
55
+
56
+ [pr.fetch('number'), pr.fetch('url')]
57
+ end
58
+
59
+
60
+ def github_repo
61
+ "#{@user}/#{@repo}"
62
+ end
63
+
64
+
65
+ def github_auth
66
+ { username: @auth_token, password: 'x-oauth-basic' }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Circlemator
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ require "circlemator/version"
2
+
3
+ module Circlemator
4
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: circlemator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Emanuel Evans
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.13.7
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: " A few utilities for CircleCI to improve your CI workflow.\n"
56
+ email:
57
+ - emanuel@rainforestqa.com
58
+ executables:
59
+ - circlemator
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".ruby-version"
65
+ - Gemfile
66
+ - LICENSE
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - circlemator.gemspec
72
+ - exe/circlemator
73
+ - lib/circlemator.rb
74
+ - lib/circlemator/build_canceler.rb
75
+ - lib/circlemator/self_merger.rb
76
+ - lib/circlemator/version.rb
77
+ homepage: https://github.com/rainforestapp/circlemator
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.5.1
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: A bucket of tricks for CircleCI and Github.
101
+ test_files: []