github_snap_builder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +64 -0
- data/README.md +35 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/github_snap_builder +5 -0
- data/bin/github_snap_builder_config_validator +17 -0
- data/bin/setup +8 -0
- data/configure +20 -0
- data/github_snap_builder.gemspec +45 -0
- data/lib/github_snap_builder/builder_implementations/docker.rb +87 -0
- data/lib/github_snap_builder/config.rb +143 -0
- data/lib/github_snap_builder/server.rb +254 -0
- data/lib/github_snap_builder/snap_builder.rb +89 -0
- data/lib/github_snap_builder/version.rb +3 -0
- data/lib/github_snap_builder.rb +69 -0
- data/sample-config.yaml +35 -0
- data/tests/builder_implementations/docker_test.rb +85 -0
- data/tests/config_test.rb +164 -0
- data/tests/snap_builder_test.rb +114 -0
- data/tests/test_helpers.rb +16 -0
- metadata +212 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3ab5c0008844156c54ae4bdca678c75f5c5834b985e5d22dfbfeb3798d6d592d
|
4
|
+
data.tar.gz: 1920c78ec7f5d9ae27d276dc4e9ca6a784a81f079a72dbc7d70c75aafab65cbd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: da93b02af100acc6fd4c5cae802e2edd0112cf03ac66207895d560a63668e4da96eb77e0e423adfc92ecfa5b01e1540fa98f59d0616d4bda18c85e83bdab34f9
|
7
|
+
data.tar.gz: 13cdd5e0a397785a243a75cf4dc46e0141552ce57c3575707585fa37d1714f2c4feabdf96034fd910cf62f575053a744e912f621e257c9cb95f931a094f22db4
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
snap-builder
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.6.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
github_snap_builder (0.1.0)
|
5
|
+
docker-api (~> 1.34)
|
6
|
+
jwt (~> 2.1)
|
7
|
+
octokit (~> 4.0)
|
8
|
+
rugged (~> 0.0)
|
9
|
+
sinatra (~> 2.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
addressable (2.5.2)
|
15
|
+
public_suffix (>= 2.0.2, < 4.0)
|
16
|
+
docker-api (1.34.2)
|
17
|
+
excon (>= 0.47.0)
|
18
|
+
multi_json
|
19
|
+
excon (0.62.0)
|
20
|
+
faraday (0.15.4)
|
21
|
+
multipart-post (>= 1.2, < 3)
|
22
|
+
jwt (2.1.0)
|
23
|
+
metaclass (0.0.4)
|
24
|
+
mocha (1.8.0)
|
25
|
+
metaclass (~> 0.0.1)
|
26
|
+
multi_json (1.13.1)
|
27
|
+
multipart-post (2.0.0)
|
28
|
+
mustermann (1.0.3)
|
29
|
+
octokit (4.13.0)
|
30
|
+
sawyer (~> 0.8.0, >= 0.5.3)
|
31
|
+
power_assert (1.1.3)
|
32
|
+
public_suffix (3.0.3)
|
33
|
+
rack (2.0.6)
|
34
|
+
rack-protection (2.0.5)
|
35
|
+
rack
|
36
|
+
rack-test (1.1.0)
|
37
|
+
rack (>= 1.0, < 3)
|
38
|
+
rake (12.3.2)
|
39
|
+
rugged (0.27.7)
|
40
|
+
sawyer (0.8.1)
|
41
|
+
addressable (>= 2.3.5, < 2.6)
|
42
|
+
faraday (~> 0.8, < 1.0)
|
43
|
+
sinatra (2.0.5)
|
44
|
+
mustermann (~> 1.0)
|
45
|
+
rack (~> 2.0)
|
46
|
+
rack-protection (= 2.0.5)
|
47
|
+
tilt (~> 2.0)
|
48
|
+
test-unit (3.3.0)
|
49
|
+
power_assert
|
50
|
+
tilt (2.0.9)
|
51
|
+
|
52
|
+
PLATFORMS
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
bundler (~> 2.0)
|
57
|
+
github_snap_builder!
|
58
|
+
mocha (~> 1.0)
|
59
|
+
rack-test (~> 1.0)
|
60
|
+
rake (~> 12.3)
|
61
|
+
test-unit (~> 3.2)
|
62
|
+
|
63
|
+
BUNDLED WITH
|
64
|
+
2.0.1
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# GithubSnapBuilder
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/github_snap_builder`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'github_snap_builder'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install github_snap_builder
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/github_snap_builder.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "github_snap_builder"
|
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(__FILE__)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'github_snap_builder/config'
|
4
|
+
|
5
|
+
if ARGV.length != 1
|
6
|
+
puts "Usage:"
|
7
|
+
puts " github_snap_builder_config_validator <config file>"
|
8
|
+
exit 0
|
9
|
+
end
|
10
|
+
|
11
|
+
config = GithubSnapBuilder::Config.new(File.read(ARGV[0]))
|
12
|
+
|
13
|
+
begin
|
14
|
+
config.validate
|
15
|
+
rescue GithubSnapBuilder::ConfigurationError => e
|
16
|
+
abort e.message
|
17
|
+
end
|
data/bin/setup
ADDED
data/configure
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/bin/sh -e
|
2
|
+
|
3
|
+
if [ "$(id -u)" -ne 0 ]; then
|
4
|
+
echo "This utility needs to run as root"
|
5
|
+
exit 1
|
6
|
+
fi
|
7
|
+
|
8
|
+
tmp_config="$(mktemp)"
|
9
|
+
cp "$SNAP_BUILDER_CONFIG" "$tmp_config"
|
10
|
+
|
11
|
+
editor "$tmp_config"
|
12
|
+
while ! "$SNAP/bin/ruby" "$SNAP/bin/github_snap_builder_config_validator" "$tmp_config"; do
|
13
|
+
echo "Press [ENTER] to try again"
|
14
|
+
read _
|
15
|
+
editor "$tmp_config"
|
16
|
+
done
|
17
|
+
|
18
|
+
mv "$tmp_config" "$SNAP_BUILDER_CONFIG"
|
19
|
+
|
20
|
+
snapctl restart github-snap-builder.github-snap-builder
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "github_snap_builder/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "github_snap_builder"
|
8
|
+
spec.version = GithubSnapBuilder::VERSION
|
9
|
+
spec.authors = ["Kyle Fazzari"]
|
10
|
+
spec.email = ["kyrofa@ubuntu.com"]
|
11
|
+
|
12
|
+
spec.summary = "Github app that builds and releases snaps for each pull request."
|
13
|
+
spec.homepage = "https://github.com/kyrofa/github-snap-builder"
|
14
|
+
spec.license = "GPL-3.0"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
27
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
28
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|snap)/}) }
|
29
|
+
end
|
30
|
+
spec.executables << "github_snap_builder"
|
31
|
+
spec.executables << "github_snap_builder_config_validator"
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.add_dependency 'sinatra', '~> 2.0'
|
35
|
+
spec.add_dependency 'jwt', '~> 2.1'
|
36
|
+
spec.add_dependency 'octokit', '~> 4.0'
|
37
|
+
spec.add_dependency 'rugged', '~> 0.0'
|
38
|
+
spec.add_dependency 'docker-api', '~> 1.34'
|
39
|
+
|
40
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
41
|
+
spec.add_development_dependency 'test-unit', '~> 3.2'
|
42
|
+
spec.add_development_dependency 'rack-test', '~> 1.0'
|
43
|
+
spec.add_development_dependency 'mocha', '~> 1.0'
|
44
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
45
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'docker'
|
4
|
+
require 'github_snap_builder'
|
5
|
+
|
6
|
+
module GithubSnapBuilder
|
7
|
+
class DockerBuilder
|
8
|
+
def initialize(logger, base)
|
9
|
+
@logger = logger
|
10
|
+
@base = base
|
11
|
+
|
12
|
+
begin
|
13
|
+
Docker.validate_version!
|
14
|
+
rescue Excon::Error::Socket
|
15
|
+
raise DockerVersionError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def build(project_directory)
|
20
|
+
# Snapcraft will detect if it's in a docker container and default to
|
21
|
+
# destructive mode.
|
22
|
+
run(['sh', '-c', "apt update -qq && snapcraft"], {
|
23
|
+
'Env' => ['SNAPCRAFT_MANAGED_HOST=yes'],
|
24
|
+
'WorkingDir' => '/snapcraft',
|
25
|
+
'HostConfig' => {
|
26
|
+
'Binds' => ["#{project_directory}:/snapcraft"],
|
27
|
+
'AutoRemove' => true,
|
28
|
+
}
|
29
|
+
})
|
30
|
+
end
|
31
|
+
|
32
|
+
def release(snap_path, token, channel)
|
33
|
+
run(['sh', '-c', "snapcraft login --with /token && snapcraft push #{File.basename(snap_path).shellescape} --release=#{channel.shellescape}"], {
|
34
|
+
'Env' => ['SNAPCRAFT_MANAGED_HOST=yes'],
|
35
|
+
'WorkingDir' => '/snapcraft',
|
36
|
+
'HostConfig' => {
|
37
|
+
'Binds' => ["#{File.dirname(snap_path)}:/snapcraft"],
|
38
|
+
'AutoRemove' => true,
|
39
|
+
}
|
40
|
+
}) do |container|
|
41
|
+
container.store_file("/token", token)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def run(command, options)
|
48
|
+
# Grab the image for this base
|
49
|
+
@logger.info 'Fetching image...'
|
50
|
+
image = get_image(@base)
|
51
|
+
options = {'Image' => image.id, 'Cmd' => command}.merge(options)
|
52
|
+
|
53
|
+
begin
|
54
|
+
# Create a new container
|
55
|
+
@logger.info 'Provisioning...'
|
56
|
+
container = Docker::Container.create options
|
57
|
+
|
58
|
+
# Give called the change to mess with container before running
|
59
|
+
# anything
|
60
|
+
yield container if block_given?
|
61
|
+
|
62
|
+
# Fire up the container and stream its logs
|
63
|
+
@logger.info 'Firing up...'
|
64
|
+
container.tap(&:start).attach({}, {read_timeout: 600}) do |stream, chunk|
|
65
|
+
if stream == :stdout
|
66
|
+
@logger.info chunk
|
67
|
+
else
|
68
|
+
@logger.error chunk
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if container.wait()['StatusCode'] != 0
|
73
|
+
@logger.error "Command exited non-zero: aborting"
|
74
|
+
raise DockerRunError, command
|
75
|
+
end
|
76
|
+
ensure
|
77
|
+
# This shouldn't be necessary given the AutoRemove option, but it's
|
78
|
+
# easy to ensure.
|
79
|
+
container.delete(force: true) unless container.nil?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_image(base)
|
84
|
+
Docker::Image.create('fromImage' => "kyrofa/github-snap-builder:#{base}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'github_snap_builder'
|
3
|
+
require 'github_snap_builder/snap_builder'
|
4
|
+
|
5
|
+
module GithubSnapBuilder
|
6
|
+
class Config
|
7
|
+
def initialize(config_file_contents)
|
8
|
+
@config = YAML.safe_load(config_file_contents) || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def github_webhook_secret
|
12
|
+
@config['github_webhook_secret']
|
13
|
+
end
|
14
|
+
|
15
|
+
def github_app_id
|
16
|
+
@config['github_app_id']
|
17
|
+
end
|
18
|
+
|
19
|
+
def github_app_private_key
|
20
|
+
@config['github_app_private_key']
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_type
|
24
|
+
@config['build_type']
|
25
|
+
end
|
26
|
+
|
27
|
+
def port
|
28
|
+
@config.fetch('port', 3000)
|
29
|
+
end
|
30
|
+
|
31
|
+
def bind
|
32
|
+
@config.fetch('bind', '0.0.0.0')
|
33
|
+
end
|
34
|
+
|
35
|
+
def repos
|
36
|
+
@repos ||= initialize_repos
|
37
|
+
end
|
38
|
+
|
39
|
+
def repos_include?(repo_name)
|
40
|
+
@config.fetch('repos', {}).include? repo_name
|
41
|
+
end
|
42
|
+
|
43
|
+
def repo(repo_name)
|
44
|
+
repos.each do | repo |
|
45
|
+
if repo.name == repo_name
|
46
|
+
return repo
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate
|
52
|
+
if @config.nil? or @config.empty?
|
53
|
+
raise ConfigurationError, "Config seems completely empty"
|
54
|
+
end
|
55
|
+
|
56
|
+
if github_webhook_secret.nil? || !github_webhook_secret.is_a?(String) || github_webhook_secret.empty?
|
57
|
+
raise ConfigurationFieldError, "github_webhook_secret"
|
58
|
+
end
|
59
|
+
|
60
|
+
if github_app_id.nil? || !github_app_id.is_a?(Integer) || github_app_id == 0
|
61
|
+
raise ConfigurationFieldError, "github_app_id"
|
62
|
+
end
|
63
|
+
|
64
|
+
if github_app_private_key.nil? || !github_app_private_key.is_a?(String) || github_app_private_key.empty?
|
65
|
+
raise ConfigurationFieldError, "github_app_private_key"
|
66
|
+
end
|
67
|
+
|
68
|
+
if build_type.nil? || !build_type.is_a?(String) || build_type.empty? || !SnapBuilder.supported_build_types.include?(build_type)
|
69
|
+
raise ConfigurationFieldError, "build_type"
|
70
|
+
end
|
71
|
+
|
72
|
+
if port.nil? || !port.is_a?(Integer) || port == 0
|
73
|
+
raise ConfigurationFieldError, "port"
|
74
|
+
end
|
75
|
+
|
76
|
+
if bind.nil? || !bind.is_a?(String) || bind.empty?
|
77
|
+
raise ConfigurationFieldError, "bind"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def valid?
|
82
|
+
begin
|
83
|
+
validate
|
84
|
+
rescue ConfigurationError
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
begin
|
89
|
+
repos.each do | repo |
|
90
|
+
repo.validate
|
91
|
+
end
|
92
|
+
rescue ConfigurationError
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def initialize_repos
|
102
|
+
repos = []
|
103
|
+
@config.fetch('repos', {}).each do | name, definition |
|
104
|
+
repos << RepoConfig.new(name, definition)
|
105
|
+
end
|
106
|
+
repos
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class RepoConfig
|
111
|
+
attr_reader :name, :channel, :token
|
112
|
+
|
113
|
+
def initialize(name, config)
|
114
|
+
@name = name
|
115
|
+
@channel = config.fetch('channel', 'edge')
|
116
|
+
@token = config['token']
|
117
|
+
end
|
118
|
+
|
119
|
+
def validate
|
120
|
+
if name.nil? || !name.is_a?(String) || name.empty?
|
121
|
+
raise ConfigurationFieldError, "repo name"
|
122
|
+
end
|
123
|
+
|
124
|
+
if channel.nil? || !channel.is_a?(String) || channel.empty?
|
125
|
+
raise ConfigurationFieldError, "#{name}'s channel"
|
126
|
+
end
|
127
|
+
|
128
|
+
if token.nil? || !token.is_a?(String) || token.empty?
|
129
|
+
raise ConfigurationFieldError, "#{name}'s token"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def valid?
|
134
|
+
begin
|
135
|
+
validate
|
136
|
+
rescue ConfigurationError
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
|
140
|
+
true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|