puppet-autostager 0.0.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 +7 -0
- data/.editorconfig +14 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTING.md +32 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +44 -0
- data/Rakefile +52 -0
- data/autostager.gemspec +36 -0
- data/bin/autostager +8 -0
- data/lib/autostager/cli.rb +34 -0
- data/lib/autostager/git_timeout.rb +6 -0
- data/lib/autostager/logger.rb +42 -0
- data/lib/autostager/pull_request.rb +123 -0
- data/lib/autostager/timeout.rb +29 -0
- data/lib/autostager/version.rb +5 -0
- data/lib/autostager.rb +173 -0
- data/script/bootstrap +10 -0
- data/script/functions +10 -0
- data/script/test +8 -0
- data/spec/functional/placeholder_spec.rb +3 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/unit/friction_spec.rb +26 -0
- metadata +260 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a5ed62a7740c05771fb4d9aaa8fc798fd81d8187
|
4
|
+
data.tar.gz: 8876fdfd9a24448647021ca1670a6f4e0460f6fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bb03600fe1631b813ec68a1c17b862f4ac3d00863784cb1ccbe5b50b7882e852b70b3153b2522a3515da59e22e0576a6c594856003bae689e04aa4096bba785b
|
7
|
+
data.tar.gz: 6ac3a4225687e4c17deeeba0d460386ac22858f5c5b300fa8356e5f457e084d5310e36f90fad61503c2402e265e59399ec35b28df5bc6ad2359eb7e555991da0
|
data/.editorconfig
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
; This file is for unifying the coding style for different editors and IDEs.
|
2
|
+
; Plugins are available for notepad++, emacs, vim, gedit,
|
3
|
+
; textmate, visual studio, and more.
|
4
|
+
;
|
5
|
+
; See http://editorconfig.org for details.
|
6
|
+
|
7
|
+
root = true
|
8
|
+
|
9
|
+
[*]
|
10
|
+
indent_style = space
|
11
|
+
indent_size = 2
|
12
|
+
end_of_line = lf
|
13
|
+
insert_final_newline = true
|
14
|
+
trim_trailing_whitespace = true
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Contributing
|
2
|
+
============
|
3
|
+
|
4
|
+
1. Fork it
|
5
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
6
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
7
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
8
|
+
5. Create new Pull Request
|
9
|
+
|
10
|
+
|
11
|
+
Diff churn
|
12
|
+
----------
|
13
|
+
|
14
|
+
Please minimize diff churn to enhance git history commands.
|
15
|
+
|
16
|
+
* Arrays should usually be multi-line with trailing commas.
|
17
|
+
|
18
|
+
Update `.rubocop.yml` if necessary to favor minimal churn.
|
19
|
+
|
20
|
+
|
21
|
+
Linear history
|
22
|
+
--------------
|
23
|
+
|
24
|
+
Use `git rebase upstream/master` to update your branch.
|
25
|
+
|
26
|
+
* You **must** force-push after rebasing.
|
27
|
+
* We **never force-push** to master.
|
28
|
+
|
29
|
+
The primary reason for this is to maintain a clean, linear history
|
30
|
+
via "fast-forward" merges to master.
|
31
|
+
A clean, linear history in master makes it easier
|
32
|
+
to troubleshoot regressions and follow the timeline.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Paul Morgan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Autostager
|
2
|
+
==========
|
3
|
+
|
4
|
+
Automatically stage a local directory based on Github pull request.
|
5
|
+
|
6
|
+
[](https://www.shippable.com/projects/53f217676ac7a5580608058a)
|
7
|
+
[](https://travis-ci.org/jumanjihouse/autostager)
|
8
|
+
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'puppet-autostager'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install puppet-autostager
|
24
|
+
|
25
|
+
|
26
|
+
Usage
|
27
|
+
-----
|
28
|
+
|
29
|
+
Create an access token as described at
|
30
|
+
https://github.com/octokit/octokit.rb#oauth-access-tokens
|
31
|
+
then export a few environment variables:
|
32
|
+
|
33
|
+
```
|
34
|
+
export repo_slug=ISEexchange/puppet
|
35
|
+
export access_token=<your 40-char token>
|
36
|
+
export base_dir=/tmp/puppet/environments
|
37
|
+
export debug=anything
|
38
|
+
```
|
39
|
+
|
40
|
+
Then run:
|
41
|
+
|
42
|
+
```
|
43
|
+
autostager
|
44
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# vim: set ts=2 sw=2 ai et:
|
3
|
+
|
4
|
+
# Does the current user have root privileges?
|
5
|
+
#
|
6
|
+
# @return [Boolean] true if user can exercise root privileges;
|
7
|
+
# false if user does not have privileges OR we're on a
|
8
|
+
# non-posix platform
|
9
|
+
def got_root?
|
10
|
+
# attempt to exercise privilege
|
11
|
+
Process::Sys.setuid(0)
|
12
|
+
true
|
13
|
+
rescue Errno::EPERM
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
abort 'Must not run as root' if got_root?
|
18
|
+
|
19
|
+
require 'bundler/gem_tasks'
|
20
|
+
require 'bundler/setup'
|
21
|
+
require 'bundler/settings'
|
22
|
+
require 'rspec/core/rake_task'
|
23
|
+
require 'rubocop/rake_task'
|
24
|
+
require 'fileutils'
|
25
|
+
|
26
|
+
task default: [:help]
|
27
|
+
|
28
|
+
desc 'Display the list of available rake tasks'
|
29
|
+
task :help do
|
30
|
+
system('rake -T')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Run unit tests before functional tests.
|
34
|
+
desc 'Run rspec tests'
|
35
|
+
task test: [
|
36
|
+
:rubocop,
|
37
|
+
:unit,
|
38
|
+
:functional,
|
39
|
+
]
|
40
|
+
|
41
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
42
|
+
t.pattern = 'spec/unit/**/*_spec.rb'
|
43
|
+
end
|
44
|
+
|
45
|
+
RSpec::Core::RakeTask.new(:functional) do |t|
|
46
|
+
t.pattern = 'spec/functional/**/*_spec.rb'
|
47
|
+
end
|
48
|
+
|
49
|
+
desc 'Run RuboCop'
|
50
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
51
|
+
t.options.unshift '--display-cop-names'
|
52
|
+
end
|
data/autostager.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'English'
|
5
|
+
require 'autostager/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'puppet-autostager'
|
9
|
+
spec.version = Autostager::VERSION
|
10
|
+
spec.authors = ['Paul Morgan']
|
11
|
+
spec.email = ['jumanjiman@gmail.com']
|
12
|
+
spec.description = 'Create a puppet enviroment for each pull request'
|
13
|
+
spec.summary = 'Create a puppet enviroment for each pull request'
|
14
|
+
spec.homepage = 'https://github.com/jumanjiman/autostager'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($RS)
|
18
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
23
|
+
spec.add_development_dependency 'friction'
|
24
|
+
spec.add_development_dependency 'minitest'
|
25
|
+
spec.add_development_dependency 'mocha'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'rspec-core'
|
28
|
+
spec.add_development_dependency 'rspec', '>= 3.0', '<= 4.0'
|
29
|
+
spec.add_development_dependency 'rspec-expectations'
|
30
|
+
spec.add_development_dependency 'rspec-mocks'
|
31
|
+
spec.add_development_dependency 'rubocop'
|
32
|
+
spec.add_development_dependency 'thor'
|
33
|
+
|
34
|
+
spec.add_dependency 'json'
|
35
|
+
spec.add_dependency 'octokit', '~> 3.0'
|
36
|
+
end
|
data/bin/autostager
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'autostager/logger'
|
3
|
+
|
4
|
+
module Autostager
|
5
|
+
# Start and run the autostager in a loop.
|
6
|
+
class CLI
|
7
|
+
include Autostager::Logger
|
8
|
+
|
9
|
+
# Entry point for the app.
|
10
|
+
# Stage pull requests on a 30-second loop.
|
11
|
+
def run
|
12
|
+
trap_interrupt
|
13
|
+
loop do
|
14
|
+
Autostager.run
|
15
|
+
log "Sleep for #{sleep_interval} seconds."
|
16
|
+
sleep sleep_interval
|
17
|
+
end
|
18
|
+
rescue Interrupt
|
19
|
+
log 'Exit on interrupt'
|
20
|
+
exit!(0)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sleep_interval
|
24
|
+
ENV['sleep_interval'] || 30
|
25
|
+
end
|
26
|
+
|
27
|
+
def trap_interrupt
|
28
|
+
Signal.trap('INT') do
|
29
|
+
log 'Exit on interrupt'
|
30
|
+
exit!(0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'syslog'
|
3
|
+
|
4
|
+
module Autostager
|
5
|
+
# Interface to Posix syslog.
|
6
|
+
module Logger
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Log a message to syslog at a specified priority level
|
10
|
+
#
|
11
|
+
# priority level must be one of:
|
12
|
+
#
|
13
|
+
# * :crit
|
14
|
+
# * :emerg emergency
|
15
|
+
# * :alert
|
16
|
+
# * :err
|
17
|
+
# * :warning
|
18
|
+
# * :notice
|
19
|
+
# * :info (default if level is not specified)
|
20
|
+
# * :debug
|
21
|
+
#
|
22
|
+
# @param msg [String] the message you want to send to syslog
|
23
|
+
# @param level [Symbol] the priority of the message
|
24
|
+
def log(msg, level = :info)
|
25
|
+
msg = safe(msg)
|
26
|
+
$stderr.puts "#{DateTime.now} #{msg}" if ENV.key?('debug')
|
27
|
+
Syslog.open($PROGRAM_NAME, Syslog::LOG_PID | Syslog::LOG_CONS) do |s|
|
28
|
+
s.send(level, '%s', msg)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Make a string safe for syslog.
|
33
|
+
# @see http://goo.gl/hcDiqu
|
34
|
+
#
|
35
|
+
# @param [String] A string that could be unsafe.
|
36
|
+
#
|
37
|
+
# @return [String] A safe copy of the original string.
|
38
|
+
def safe(str)
|
39
|
+
URI.unescape(str)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Autostager
|
4
|
+
# Convenience class to represent a PR in staging directory.
|
5
|
+
class PullRequest
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :web_url
|
8
|
+
attr_reader :number
|
9
|
+
attr_reader :branch
|
10
|
+
attr_reader :sha
|
11
|
+
attr_reader :clone_url
|
12
|
+
attr_reader :upstream_url
|
13
|
+
attr_reader :base_dir
|
14
|
+
|
15
|
+
include Autostager::Logger
|
16
|
+
|
17
|
+
def initialize(branch, clone_url, base_dir, name, upstream)
|
18
|
+
@branch = branch
|
19
|
+
@clone_url = clone_url
|
20
|
+
@base_dir = base_dir
|
21
|
+
@name = name
|
22
|
+
@upstream_url = upstream
|
23
|
+
|
24
|
+
@staging_dir = File.join base_dir, @name
|
25
|
+
end
|
26
|
+
|
27
|
+
def local_sha
|
28
|
+
Dir.chdir @staging_dir
|
29
|
+
`git log --pretty='%H' HEAD^1..`.chomp
|
30
|
+
end
|
31
|
+
|
32
|
+
def staged?
|
33
|
+
File.exist? @staging_dir
|
34
|
+
end
|
35
|
+
|
36
|
+
# How many commits is this branch behind?
|
37
|
+
#
|
38
|
+
# @param treeish [String] example: 'upstream/production'
|
39
|
+
#
|
40
|
+
# @return [Fixnum]
|
41
|
+
def behind(treeish)
|
42
|
+
Dir.chdir @staging_dir
|
43
|
+
`git log --oneline ..#{treeish}`.lines.count
|
44
|
+
end
|
45
|
+
|
46
|
+
# A threshold for how many commits a branch can be behind upstream.
|
47
|
+
#
|
48
|
+
# @note This number is based on the value of same name in
|
49
|
+
# spec/spec_helper.rb, so please update the value both
|
50
|
+
# here and in spec_helper.rb if you need to change the threshold.
|
51
|
+
#
|
52
|
+
# @return [Fixnum]
|
53
|
+
def behind_threshold
|
54
|
+
10
|
55
|
+
end
|
56
|
+
|
57
|
+
# Is this branch within a reasonable number of commits with upstream?
|
58
|
+
#
|
59
|
+
# @param treeish [String] example: 'upstream/production'
|
60
|
+
#
|
61
|
+
# @return [Boolean]
|
62
|
+
def up2date?(treeish)
|
63
|
+
behind(treeish) <= behind_threshold
|
64
|
+
end
|
65
|
+
|
66
|
+
# Fast-forward this branch to origin.
|
67
|
+
# @note This fails if the branch cannot be fast-forwarded.
|
68
|
+
#
|
69
|
+
# @return [Fixnum] exit status of `git rebase`
|
70
|
+
# @return [nil] if `git rebase` fails to exit.
|
71
|
+
def rebase
|
72
|
+
log "rebase origin/#{@branch}"
|
73
|
+
Dir.chdir @staging_dir
|
74
|
+
`git rebase origin/#{@branch} &> /dev/null`
|
75
|
+
status = $CHILD_STATUS.exitstatus
|
76
|
+
update_submodules
|
77
|
+
log "#{@branch} is at revision #{local_sha}"
|
78
|
+
status
|
79
|
+
end
|
80
|
+
|
81
|
+
def reset_hard
|
82
|
+
Dir.chdir @staging_dir
|
83
|
+
`git reset --hard origin/#{@branch} &> /dev/null`
|
84
|
+
update_submodules
|
85
|
+
end
|
86
|
+
|
87
|
+
def fetch
|
88
|
+
log 'git fetch'
|
89
|
+
Dir.chdir @staging_dir
|
90
|
+
add_upstream_remote
|
91
|
+
`git fetch --all --prune &> /dev/null`
|
92
|
+
end
|
93
|
+
|
94
|
+
def update_submodules
|
95
|
+
log "update submodules in #{@staging_dir}"
|
96
|
+
`git submodule sync &> /dev/null`
|
97
|
+
`git submodule update --init --recursive &> /dev/null`
|
98
|
+
end
|
99
|
+
|
100
|
+
def remote?(s)
|
101
|
+
Dir.chdir @staging_dir
|
102
|
+
remotes = `git remote -v`.split("\n")
|
103
|
+
urls = remotes.map { |r| r.split("\t").last.split(' ').first }.uniq
|
104
|
+
urls.include?(s)
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_upstream_remote
|
108
|
+
Dir.chdir @staging_dir
|
109
|
+
return if remote?(@upstream_url)
|
110
|
+
log 'add upstream remote'
|
111
|
+
`git remote add upstream #{@upstream_url} &> /dev/null`
|
112
|
+
`git fetch --prune upstream &> /dev/null`
|
113
|
+
end
|
114
|
+
|
115
|
+
def clone
|
116
|
+
log "clone to #{@staging_dir}"
|
117
|
+
FileUtils.mkdir_p @base_dir unless File.exist?(@base_dir)
|
118
|
+
`git clone -b #{@branch} #{@clone_url} #{@staging_dir} &> /dev/null`
|
119
|
+
Dir.chdir @staging_dir
|
120
|
+
update_submodules
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Autostager
|
4
|
+
# Meta-module to call the correct library method
|
5
|
+
# for the version of Ruby on which the autostager runs.
|
6
|
+
module Timeout
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# The ruby timeout.rb library is unreliable on 1.8.7,
|
10
|
+
# so we use a gem to provide timeouts if this script
|
11
|
+
# is running on 1.8.7.
|
12
|
+
# @see http://ph7spot.com/musings/system-timer
|
13
|
+
#
|
14
|
+
# @param seconds [Fixnum] Expiration time.
|
15
|
+
# @param err [StandardErr] Raise err on expiration.
|
16
|
+
# @param block [Block] Code that is subject to expiration.
|
17
|
+
#
|
18
|
+
# @return [nil]
|
19
|
+
def timeout(seconds, err, &block)
|
20
|
+
if RUBY_VERSION =~ /1.8/
|
21
|
+
require 'system_timer'
|
22
|
+
SystemTimer.timeout_after(seconds, err) { block.call }
|
23
|
+
else
|
24
|
+
require 'timeout'
|
25
|
+
::Timeout.timeout(seconds, err) { block.call }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/autostager.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'English'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'autostager/cli'
|
5
|
+
require 'autostager/git_timeout'
|
6
|
+
require 'autostager/logger'
|
7
|
+
require 'autostager/pull_request'
|
8
|
+
require 'autostager/timeout'
|
9
|
+
require 'autostager/version'
|
10
|
+
require 'octokit'
|
11
|
+
require 'pp'
|
12
|
+
|
13
|
+
# Top-level module namespace.
|
14
|
+
module Autostager
|
15
|
+
module_function
|
16
|
+
|
17
|
+
extend Autostager::Logger
|
18
|
+
|
19
|
+
def access_token
|
20
|
+
ENV['access_token']
|
21
|
+
end
|
22
|
+
|
23
|
+
# Convert a string into purely alphanumeric characters
|
24
|
+
def alphafy(a_string)
|
25
|
+
a_string.gsub(/[^a-z0-9_]/i, '_')
|
26
|
+
end
|
27
|
+
|
28
|
+
# rubocop:disable MethodLength
|
29
|
+
def stage_upstream
|
30
|
+
default_branch = @client.repo(repo_slug).default_branch
|
31
|
+
log "===> begin #{default_branch}"
|
32
|
+
p = Autostager::PullRequest.new(
|
33
|
+
default_branch,
|
34
|
+
authenticated_url("https://github.com/#{repo_slug}"),
|
35
|
+
base_dir,
|
36
|
+
default_branch,
|
37
|
+
authenticated_url("https://github.com/#{repo_slug}"),
|
38
|
+
)
|
39
|
+
p.clone unless p.staged?
|
40
|
+
p.fetch
|
41
|
+
return if p.rebase
|
42
|
+
|
43
|
+
# fast-forward failed, so raise awareness.
|
44
|
+
@client.create_issue(
|
45
|
+
repo_slug,
|
46
|
+
"Failed to fast-forward #{default_branch} branch",
|
47
|
+
':bangbang: This probably means somebody force-pushed to the branch.',
|
48
|
+
)
|
49
|
+
end
|
50
|
+
# rubocop:enable MethodLength
|
51
|
+
|
52
|
+
# rubocop:disable MethodLength
|
53
|
+
def process_pull(pr)
|
54
|
+
log "===> #{pr.number} #{clone_dir(pr)}"
|
55
|
+
p = Autostager::PullRequest.new(
|
56
|
+
pr.head.ref,
|
57
|
+
authenticated_url(pr.head.repo.clone_url),
|
58
|
+
base_dir,
|
59
|
+
clone_dir(pr),
|
60
|
+
authenticated_url(pr.base.repo.clone_url),
|
61
|
+
)
|
62
|
+
if p.staged?
|
63
|
+
p.fetch
|
64
|
+
if pr.head.sha != p.local_sha
|
65
|
+
p.reset_hard
|
66
|
+
add_comment = true
|
67
|
+
else
|
68
|
+
log "nothing to do on #{pr.number} #{staging_dir(pr)}"
|
69
|
+
add_comment = false
|
70
|
+
end
|
71
|
+
comment_or_close(p, pr, add_comment)
|
72
|
+
else
|
73
|
+
p.clone
|
74
|
+
comment_or_close(p, pr)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# rubocop:enable MethodLength
|
78
|
+
|
79
|
+
# rubocop:disable MethodLength
|
80
|
+
def comment_or_close(p, pr, add_comment = true)
|
81
|
+
if p.up2date?("upstream/#{pr.base.repo.default_branch}")
|
82
|
+
if add_comment
|
83
|
+
comment = format(
|
84
|
+
':bell: Staged `%s` at revision %s on %s',
|
85
|
+
clone_dir(pr),
|
86
|
+
p.local_sha,
|
87
|
+
Socket.gethostname,
|
88
|
+
)
|
89
|
+
client.add_comment repo_slug, pr.number, comment
|
90
|
+
log comment
|
91
|
+
end
|
92
|
+
else
|
93
|
+
comment = format(
|
94
|
+
':boom: Unstaged since %s is dangerously behind upstream.',
|
95
|
+
clone_dir(pr),
|
96
|
+
)
|
97
|
+
FileUtils.rm_rf staging_dir, secure: true
|
98
|
+
client.add_comment repo_slug, pr.number, comment
|
99
|
+
client.close_issue repo_slug, pr.number
|
100
|
+
log comment
|
101
|
+
end
|
102
|
+
end
|
103
|
+
# rubocop:enable MethodLength
|
104
|
+
|
105
|
+
def authenticated_url(s)
|
106
|
+
s.dup.sub!(%r{^(https://)(.*)}, '\1' + access_token + '@\2')
|
107
|
+
end
|
108
|
+
|
109
|
+
def base_dir
|
110
|
+
ENV['base_dir'] || '/opt/puppet/environments'
|
111
|
+
end
|
112
|
+
|
113
|
+
def clone_dir(pr)
|
114
|
+
alphafy(pr.head.label)
|
115
|
+
end
|
116
|
+
|
117
|
+
def staging_dir(pr)
|
118
|
+
File.join base_dir, clone_dir(pr)
|
119
|
+
end
|
120
|
+
|
121
|
+
def repo_slug
|
122
|
+
ENV['repo_slug']
|
123
|
+
end
|
124
|
+
|
125
|
+
def client
|
126
|
+
@client ||= Octokit::Client.new(access_token: access_token)
|
127
|
+
end
|
128
|
+
|
129
|
+
# A list of directories we never discard.
|
130
|
+
def safe_dirs
|
131
|
+
[
|
132
|
+
'.',
|
133
|
+
'..',
|
134
|
+
'production',
|
135
|
+
]
|
136
|
+
end
|
137
|
+
|
138
|
+
# rubocop:disable MethodLength
|
139
|
+
def run
|
140
|
+
Octokit.auto_paginate = true
|
141
|
+
user = client.user
|
142
|
+
user.login
|
143
|
+
|
144
|
+
# Get open PRs.
|
145
|
+
prs = client.pulls(repo_slug)
|
146
|
+
|
147
|
+
# Set of PR clone dirs.
|
148
|
+
new_clones = prs.map { |pr| clone_dir(pr) }
|
149
|
+
|
150
|
+
# Discard directories that do not have open PRs.
|
151
|
+
if File.exist?(base_dir)
|
152
|
+
discard_dirs = Dir.entries(base_dir) - safe_dirs - new_clones
|
153
|
+
discard_dirs.map { |d| File.join(base_dir, d) }.each do |dir|
|
154
|
+
log "===> Unstage #{dir} since PR is closed."
|
155
|
+
FileUtils.rm_rf dir, secure: true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Process current PRs.
|
160
|
+
Autostager::Timeout.timeout(120, GitTimeout) do
|
161
|
+
prs.each { |pr| process_pull pr }
|
162
|
+
end
|
163
|
+
|
164
|
+
# Handle the default branch differently because
|
165
|
+
# we only ever rebase, never force-push.
|
166
|
+
stage_upstream
|
167
|
+
rescue Octokit::Unauthorized => e
|
168
|
+
$stderr.puts e.message
|
169
|
+
$stderr.puts 'Did you export "access_token" and "repo_slug"?'
|
170
|
+
exit(1)
|
171
|
+
end
|
172
|
+
# rubocop:enable MethodLength
|
173
|
+
end
|
data/script/bootstrap
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
set -e
|
3
|
+
|
4
|
+
# Import smitty.
|
5
|
+
. script/functions
|
6
|
+
|
7
|
+
git remote show upstream &> /dev/null || smitty git remote add upstream https://github.com/jumanjihouse/autostager.git
|
8
|
+
|
9
|
+
rm -f Gemfile.lock
|
10
|
+
smitty bundle update 2> /dev/null || smitty bundle install --path=~/.bundle
|
data/script/functions
ADDED
data/script/test
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'minitest'
|
3
|
+
require 'mocha/setup'
|
4
|
+
require 'English'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
RSpec.configure do |c|
|
9
|
+
c.mock_with :mocha
|
10
|
+
c.color = true
|
11
|
+
c.formatter = 'doc'
|
12
|
+
|
13
|
+
# Allow both "should" and "expect" syntax.
|
14
|
+
# https://www.relishapp.com/rspec/rspec-expectations/docs/syntax-configuration
|
15
|
+
c.expect_with :rspec do |e|
|
16
|
+
e.syntax = [:should, :expect]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fail overall as soon as first test fails.
|
20
|
+
# Fail fast to reduce duration of test runs.
|
21
|
+
# IOW get out of the way so that the next pull request gets tested.
|
22
|
+
c.fail_fast = true
|
23
|
+
|
24
|
+
# Make it easy for spec tests to find fixtures.
|
25
|
+
c.add_setting :fixture_path, default: nil
|
26
|
+
c.fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'friction'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
# Monkey-patch kernel to capture stdout
|
7
|
+
# as workaround for friction gem. bleh.
|
8
|
+
#
|
9
|
+
# @todo submit PR against friction.
|
10
|
+
module Kernel
|
11
|
+
def capture_stdout
|
12
|
+
real_out, out = $stdout, StringIO.new
|
13
|
+
$stdout = out
|
14
|
+
yield
|
15
|
+
return out.string
|
16
|
+
ensure
|
17
|
+
$stdout = real_out
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'contributor friction' do
|
22
|
+
it 'there should not be any' do
|
23
|
+
out = capture_stdout { Friction.run! }
|
24
|
+
out.should =~ /Everything is in order/
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: puppet-autostager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Morgan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: friction
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mocha
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-core
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
- - <=
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '4.0'
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '3.0'
|
114
|
+
- - <=
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '4.0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rspec-expectations
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: rspec-mocks
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: rubocop
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - '>='
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - '>='
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: thor
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: json
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - '>='
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
type: :runtime
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - '>='
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
- !ruby/object:Gem::Dependency
|
188
|
+
name: octokit
|
189
|
+
requirement: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ~>
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '3.0'
|
194
|
+
type: :runtime
|
195
|
+
prerelease: false
|
196
|
+
version_requirements: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - ~>
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '3.0'
|
201
|
+
description: Create a puppet enviroment for each pull request
|
202
|
+
email:
|
203
|
+
- jumanjiman@gmail.com
|
204
|
+
executables:
|
205
|
+
- autostager
|
206
|
+
extensions: []
|
207
|
+
extra_rdoc_files: []
|
208
|
+
files:
|
209
|
+
- .editorconfig
|
210
|
+
- .gitignore
|
211
|
+
- .rubocop.yml
|
212
|
+
- .travis.yml
|
213
|
+
- CONTRIBUTING.md
|
214
|
+
- Gemfile
|
215
|
+
- LICENSE.txt
|
216
|
+
- README.md
|
217
|
+
- Rakefile
|
218
|
+
- autostager.gemspec
|
219
|
+
- bin/autostager
|
220
|
+
- lib/autostager.rb
|
221
|
+
- lib/autostager/cli.rb
|
222
|
+
- lib/autostager/git_timeout.rb
|
223
|
+
- lib/autostager/logger.rb
|
224
|
+
- lib/autostager/pull_request.rb
|
225
|
+
- lib/autostager/timeout.rb
|
226
|
+
- lib/autostager/version.rb
|
227
|
+
- script/bootstrap
|
228
|
+
- script/functions
|
229
|
+
- script/test
|
230
|
+
- spec/functional/placeholder_spec.rb
|
231
|
+
- spec/spec_helper.rb
|
232
|
+
- spec/unit/friction_spec.rb
|
233
|
+
homepage: https://github.com/jumanjiman/autostager
|
234
|
+
licenses:
|
235
|
+
- MIT
|
236
|
+
metadata: {}
|
237
|
+
post_install_message:
|
238
|
+
rdoc_options: []
|
239
|
+
require_paths:
|
240
|
+
- lib
|
241
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
242
|
+
requirements:
|
243
|
+
- - '>='
|
244
|
+
- !ruby/object:Gem::Version
|
245
|
+
version: '0'
|
246
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - '>='
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
251
|
+
requirements: []
|
252
|
+
rubyforge_project:
|
253
|
+
rubygems_version: 2.1.11
|
254
|
+
signing_key:
|
255
|
+
specification_version: 4
|
256
|
+
summary: Create a puppet enviroment for each pull request
|
257
|
+
test_files:
|
258
|
+
- spec/functional/placeholder_spec.rb
|
259
|
+
- spec/spec_helper.rb
|
260
|
+
- spec/unit/friction_spec.rb
|