baps 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 26742e6633e6fd17a446096d5205ea9d845fa4f0
4
+ data.tar.gz: 89c927289e60d5e8d017dfa929431a03d4b24088
5
+ SHA512:
6
+ metadata.gz: 63acc006bf8feec859a0edc6d407e59556daefc73446d655d45fe7f39afeb2be96337378fc16a0e2f0aecc4352d6198512450fdbd5286cc9474ec8a8e6a53c93
7
+ data.tar.gz: 08977962a992b220517cba693876ae2b0847d97d18a5aa2f16bdeef27bb9c3476c6911ec32aa3d2b54975c42efbece0a32e79df3386c5c8f14ac947aec69e4a5
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1 @@
1
+ 2.3.1
@@ -0,0 +1,8 @@
1
+ FROM ruby:2.3
2
+
3
+ # throw errors if Gemfile has been modified since Gemfile.lock
4
+ RUN bundle config --global frozen 1
5
+
6
+ COPY . /app
7
+ WORKDIR /app
8
+ RUN bundle install
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test, :development do
6
+ gem 'pry'
7
+ end
@@ -0,0 +1,32 @@
1
+ # BAPS - Build and Packaging System
2
+ Define your build using provided DSL and `baps` will take care of building
3
+ the given software in a clean room environment, package it as `deb` and push to packagecloud.
4
+
5
+ ## Usage
6
+ `exe/baps -p ./definitions`
7
+ This will go through every definition file in the given `./definitions` folder and if there's at least
8
+ one definition that needs to be executed `baps` will pull prebuilt Docker image, copy the content of `./definitions`
9
+ folder to it and fire a container based on the image for each definition needed to be executed. Inside the container
10
+ `baps` will execute the given definition using `exe/baps -p ./definitions/path_to_definition_file.rb`
11
+
12
+ If you don't want a clean room environment i.e not execute your definition in Docker you can directly use `exe/baps --no-docker -p ./definitions/path_to_definition_file.rb`.
13
+
14
+ ## Example
15
+ An example definition to build `pmtud` from a remote git repository:
16
+ ```
17
+ deb do
18
+ name 'pmtud'
19
+ version '0.7'
20
+ dependencies %w(libpcap0.8 libnetfilter-log1)
21
+ description 'Path MTU daemon - broadcast lost ICMP packets on ECMP networks'
22
+
23
+ build do
24
+ source git: 'https://github.com/Shopify/pmtud.git', branch: "v#{@version}"
25
+ dependencies %w(libnetfilter-log-dev libnfnetlink-dev libpcap-dev)
26
+ run 'make pmtud BUILD=release CC=gcc'
27
+ run "install -d 0700 #{deb_dir}/usr/local/bin"
28
+ run "install pmtud #{deb_dir}/usr/local/bin"
29
+ end
30
+ end
31
+ ```
32
+ More examples can be found at https://github.com/Shopify/shopify-open-source-builds
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'baps/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "baps"
8
+ spec.version = Baps::VERSION
9
+ spec.authors = ["Elvin Efendi"]
10
+ spec.email = ["elvin.efendiyev@gmail.com"]
11
+
12
+ spec.summary = %q{Build and Packaging System Edit.}
13
+ spec.description = %q{Define your build using provided DSL and baps will take care of building the given software in a clean room environment, package it as deb and push to packagecloud.}
14
+ spec.homepage = ""
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_runtime_dependency 'fpm'
24
+ spec.add_runtime_dependency 'packagecloud-ruby'
25
+ spec.add_runtime_dependency 'docker-api'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.13"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'bundler/setup'
7
+ require 'baps'
8
+ require 'pry'
9
+
10
+ binding.pry
@@ -0,0 +1,15 @@
1
+ machine:
2
+ services:
3
+ - docker
4
+
5
+ test:
6
+ override:
7
+ - bundle exec rake test
8
+
9
+ deployment:
10
+ production:
11
+ branch: master
12
+ commands:
13
+ - docker build --rm=false -t elvinefendi/baps .
14
+ - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
15
+ - docker push elvinefendi/baps
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'bundler/setup'
7
+ require 'baps'
8
+ require 'baps/executor'
9
+ require 'optparse'
10
+
11
+ options = { use_docker: true }
12
+ OptionParser.new do |opts|
13
+ opts.banner = 'Usage: exe/baps [options]. Either -d or -f has to be provided.'
14
+
15
+ opts.on('-p', '--path PATH', 'Path to a directory with definitions or absolute path of definition file') do |v|
16
+ options[:path] = v
17
+ end
18
+
19
+ opts.on("--no-docker", "Path to a definition file") do
20
+ options[:use_docker] = false
21
+ end
22
+ end.parse!
23
+
24
+ executor = Baps::Executor.new(options)
25
+ executor.execute
@@ -0,0 +1,24 @@
1
+ require 'baps/build'
2
+ require 'baps/command'
3
+ require 'baps/deb'
4
+ require 'baps/definition'
5
+ require 'baps/executor'
6
+ require 'baps/packagecloud_api'
7
+ require 'baps/version'
8
+
9
+ require 'docker'
10
+
11
+ module Baps
12
+ extend self
13
+
14
+ TIMEOUT = 5*60
15
+ Excon.defaults[:write_timeout] = TIMEOUT
16
+ Excon.defaults[:read_timeout] = TIMEOUT
17
+
18
+ class ChecksumError < StandardError; end
19
+ class ExecutorError < StandardError; end
20
+
21
+ def packagecloud
22
+ @@packagecloud ||= PackagecloudAPI.new('shopify', ENV['PACKAGECLOUD_TOKEN'])
23
+ end
24
+ end
@@ -0,0 +1,82 @@
1
+ require 'tmpdir'
2
+
3
+ module Baps
4
+ class Build
5
+ def initialize(name, version, &block)
6
+ raise StandardError.new('Nothing defined') unless block_given?
7
+ @name = name
8
+ @version = version
9
+ @commands = []
10
+ instance_eval(&block)
11
+ @commands.unshift(Command.new("apt-get install -y -q #{dependencies.join(' ')}")) if dependencies
12
+ end
13
+
14
+ def start
15
+ Dir.chdir(dir) do
16
+ unpack_source
17
+ @commands.each(&:execute)
18
+ end
19
+ end
20
+
21
+ def deb_dir
22
+ @deb_dir ||= File.join(dir, "debian-#{Time.now.utc.to_i}")
23
+ end
24
+
25
+ def dir
26
+ @dir ||= Dir.mktmpdir("baps-#{@name}-#{@version}-")
27
+ end
28
+
29
+ private
30
+
31
+ def unpack_source
32
+ return unless source
33
+ if source[:git]
34
+ Command.new("git clone #{source[:git]} ./").execute
35
+ if source[:branch]
36
+ Command.new("git reset --hard #{source[:branch]}").execute
37
+ end
38
+ elsif source[:remote_tar]
39
+ path = "#{@name}-#{@version}.tar.gz"
40
+ IO.copy_stream(open(source[:remote_tar]), path)
41
+ content = File.read(path)
42
+ if sha256sum
43
+ raise ChecksumError, 'sha256sum cheksum did not match.' unless Digest::SHA256.hexdigest(content) == sha256sum
44
+ end
45
+ if md5sum
46
+ raise ChecksumError, 'md5sum cheksum did not match.' unless Digest::MD5.hexdigest(content) == md5sum
47
+ end
48
+ unpack_tar(path)
49
+ elsif source[:local_tar]
50
+ unpack_tar(File.join('baps-definitions', "#{@name}-#{@version}.tar.gz"))
51
+ end
52
+ end
53
+
54
+ def run(cmd)
55
+ @commands << Command.new(cmd)
56
+ end
57
+
58
+ def unpack_tar(path)
59
+ cmd = Command.new("tar -tf #{path}")
60
+ cmd.execute
61
+ dirs = cmd.stdout.split("\n")
62
+ have_root_dir = true
63
+ i = 0
64
+ while have_root_dir && i < dirs.size - 1
65
+ curr_dir = dirs[i].split('/').first
66
+ next_dir = dirs[i+1].split('/').first
67
+ have_root_dir = (curr_dir == next_dir)
68
+ i +=1
69
+ end
70
+ Command.new("tar -xzf #{path} -C . --strip #{have_root_dir ? 1 : 0}").execute
71
+ ensure
72
+ FileUtils.rm(path) if File.exist?(path)
73
+ end
74
+
75
+ %i(source dependencies sha256sum md5sum).each do |method|
76
+ define_method(method) do |value = nil|
77
+ return instance_variable_get("@#{method}") if value.nil?
78
+ instance_variable_set("@#{method}", value)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,53 @@
1
+ require 'open3'
2
+
3
+ module Baps
4
+ class Command
5
+ attr_reader :command, :exit_status, :stdout, :stderr
6
+
7
+ def initialize(command)
8
+ @command = command
9
+ @stdout, @stderr = String.new, String.new
10
+ end
11
+
12
+ def on_stdout(data)
13
+ @stdout += data
14
+ end
15
+
16
+ def on_stderr(data)
17
+ @stderr += data
18
+ end
19
+
20
+ def exit_status=(value)
21
+ @exit_status = value
22
+ if @exit_status > 0
23
+ message = ''
24
+ message += "#{command} exit status: #{@exit_status}\n"
25
+ message += "#{command} stdout: " + (stdout.strip.empty? ? "Nothing written" : stdout.strip) + "\n"
26
+ message += "#{command} stderr: " + (stderr.strip.empty? ? 'Nothing written' : stderr.strip) + "\n"
27
+ raise StandardError, message
28
+ end
29
+ end
30
+
31
+ def execute
32
+ puts "Executing: #{command}"
33
+ Open3.popen3(command) do |_stdin, stdout, stderr, wait_thr|
34
+ stdout_thread = Thread.new do
35
+ while (line = stdout.gets) do
36
+ on_stdout(line)
37
+ end
38
+ end
39
+
40
+ stderr_thread = Thread.new do
41
+ while (line = stderr.gets) do
42
+ on_stderr(line)
43
+ end
44
+ end
45
+
46
+ stdout_thread.join
47
+ stderr_thread.join
48
+
49
+ self.exit_status = wait_thr.value.to_i
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ module Baps
2
+ class Deb
3
+ def initialize(&block)
4
+ raise StandardError.new('Nothing defined') unless block_given?
5
+ instance_eval(&block)
6
+ end
7
+
8
+ def already_exist?
9
+ Baps.packagecloud.package_exist?(name: name, version: version)
10
+ end
11
+
12
+ def create
13
+ build.start
14
+ package
15
+ publish
16
+ end
17
+
18
+ private
19
+
20
+ def publish
21
+ file = Dir['*.deb'].first
22
+ Baps.packagecloud.publish_package(file)
23
+ end
24
+
25
+ def package
26
+ command = "fpm -s dir -C #{build.deb_dir} -v #{version} -t deb "\
27
+ "--description '#{description}' --vendor '' "\
28
+ "-n #{name} --deb-priority #{priority or 'optional'} --deb-compression bzip2"
29
+ command << " #{dependencies.map { |d| "-d '#{d}'" }.join(' ')}" if dependencies
30
+ command << " --replaces '#{replaces}'" if replaces
31
+ command << " #{targets.join(' ')}" if targets
32
+ # TODO use programming API
33
+ # as described in https://github.com/jordansissel/fpm/blob/master/examples/api/dir-to-deb-rpm-with-init.rb
34
+ Command.new(command).execute
35
+ end
36
+
37
+ def build(&block)
38
+ return @build unless block_given?
39
+ @build = Build.new(name, version, &block)
40
+ end
41
+
42
+ %i(name version dependencies description targets priority replaces).each do |method|
43
+ define_method(method) do |value = nil|
44
+ return instance_variable_get("@#{method}") if value.nil?
45
+ instance_variable_set("@#{method}", value)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ module Baps
2
+ class Definition
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ instance_eval(File.read(@path), @path)
8
+ end
9
+
10
+ def execute
11
+ puts "Executing #{@path}"
12
+ deb.create
13
+ end
14
+
15
+ def executed?
16
+ deb.already_exist?
17
+ end
18
+
19
+ private
20
+
21
+ def deb(&block)
22
+ return @deb unless block_given?
23
+ @deb = Deb.new(&block)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,66 @@
1
+ module Baps
2
+ class Executor
3
+ DOCKER_IMAGE_ID = 'elvinefendi/baps'
4
+
5
+ def initialize(path:, use_docker: true)
6
+ raise ArgumentError.new('Given path does not exist.') unless File.exist?(path)
7
+ @path = File.expand_path(path)
8
+ @use_docker = use_docker
9
+ @definitions = definitions_from_directory if File.directory?(@path)
10
+ @definitions = [Definition.new(@path)] if File.file?(@path)
11
+ end
12
+
13
+
14
+ def execute
15
+ if !@definitions || @definitions.empty?
16
+ puts 'No definition to execute'
17
+ return
18
+ end
19
+
20
+ return @definitions.each(&:execute) unless @use_docker
21
+
22
+ errors = {}
23
+ @definitions.each do |definition|
24
+ container = base_container.run("exe/baps -p #{path_in_docker} --no-docker")
25
+ container.attach { |stream, chunk| puts "#{stream}: #{chunk}" }
26
+ if (code = container.wait(Baps::TIMEOUT)['StatusCode']) > 0
27
+ errors[definition] = code
28
+ end
29
+ end
30
+ unless errors.empty?
31
+ raise ExecutorError, errors.map { |d, c| "#{d.path} exited with #{c}" }.join("\n")
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def base_container
38
+ return @base_container if @base_container
39
+ @base_container = image.run('apt-get update -y -q', 'Env' => ["PACKAGECLOUD_TOKEN=#{ENV['PACKAGECLOUD_TOKEN']}"])
40
+ @base_container.attach { |stream, chunk| puts "#{stream}: #{chunk}" }
41
+ @base_container
42
+ end
43
+
44
+ def image
45
+ return @image if @image
46
+ begin
47
+ @image = Docker::Image.get(DOCKER_IMAGE_ID)
48
+ rescue Docker::Error::NotFoundError
49
+ @image = Docker::Image.create('fromImage' => DOCKER_IMAGE_ID)
50
+ end
51
+ @image = @image.insert_local('localPath' => @path, 'outputPath' => path_in_docker)
52
+ end
53
+
54
+ def path_in_docker
55
+ "/baps-definitions/#{File.basename(@path)}"
56
+ end
57
+
58
+ def definitions_from_directory
59
+ Dir[File.join(@path, '**/*.rb')].map do |path|
60
+ definition = Definition.new(path)
61
+ next if definition.executed?
62
+ definition
63
+ end.compact
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,28 @@
1
+ require 'packagecloud'
2
+
3
+ module Baps
4
+ class PackagecloudAPI
5
+ def initialize(username, token)
6
+ connection = Packagecloud::Connection.new('https', 'packages.shopify.io')
7
+ credentials = Packagecloud::Credentials.new(username, token)
8
+ @client = Packagecloud::Client.new(credentials, "packagecloud-ruby #{Packagecloud::VERSION}", connection)
9
+ end
10
+
11
+ def publish_package(file)
12
+ package = Packagecloud::Package.new(file: file)
13
+ result = @client.put_package('public', package, 'ubuntu/trusty')
14
+ if result.succeeded
15
+ puts "Package #{file} has been pushed to packagecloud"
16
+ else
17
+ puts "Could not push #{file} to packagecoud: #{result.response}"
18
+ end
19
+ end
20
+
21
+ def package_exist?(name:, version:, arch: 'amd64', type: 'deb')
22
+ url = "/api/v1/repos/shopify/public/package/#{type}/ubuntu/trusty/#{name}/#{arch}/#{version}/stats/downloads/count.json"
23
+ json = @client.send(:get, url)
24
+ result = @client.send(:parsed_json_result, json)
25
+ result.succeeded
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Baps
2
+ VERSION = '0.1.1'
3
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: baps
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Elvin Efendi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-01-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fpm
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: packagecloud-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
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: docker-api
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5.0'
97
+ description: Define your build using provided DSL and baps will take care of building
98
+ the given software in a clean room environment, package it as deb and push to packagecloud.
99
+ email:
100
+ - elvin.efendiyev@gmail.com
101
+ executables:
102
+ - baps
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".ruby-version"
108
+ - Dockerfile
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - README.md
112
+ - Rakefile
113
+ - baps.gemspec
114
+ - bin/console
115
+ - circle.yml
116
+ - exe/baps
117
+ - lib/baps.rb
118
+ - lib/baps/build.rb
119
+ - lib/baps/command.rb
120
+ - lib/baps/deb.rb
121
+ - lib/baps/definition.rb
122
+ - lib/baps/executor.rb
123
+ - lib/baps/packagecloud_api.rb
124
+ - lib/baps/version.rb
125
+ homepage: ''
126
+ licenses: []
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.5.1
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: Build and Packaging System Edit.
148
+ test_files: []