buildizer 0.0.1 → 0.0.5

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.
data/buildizer.gemspec CHANGED
@@ -18,8 +18,7 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.add_dependency "thor", ">= 0.19.1", "< 1.0"
20
20
  spec.add_dependency "net_status", ">= 0.0.1", "< 1.0"
21
- spec.add_dependency "mixlib-shellout", ">= 2.2.6", "< 3.0"
22
- spec.add_dependency "travis", "~> 1.8", ">= 1.8.2"
21
+ spec.add_dependency "shellfold", ">= 0.0.1", "< 1.0"
23
22
  spec.add_dependency "package_cloud", ">= 0.2", "< 1.0"
24
23
 
25
24
  spec.add_development_dependency "bundler", "~> 1.7"
@@ -27,4 +26,5 @@ Gem::Specification.new do |spec|
27
26
  spec.add_development_dependency "rspec", "~> 3.4", ">= 3.4.0"
28
27
  spec.add_development_dependency "pry", ">= 0.10.3", "< 1.0"
29
28
  spec.add_development_dependency 'pry-stack_explorer', '>= 0.4.9.2', '< 1.0'
29
+ spec.add_development_dependency "travis", "~> 1.8", ">= 1.8.2"
30
30
  end
@@ -0,0 +1,173 @@
1
+ module Buildizer
2
+ module Builder
3
+ class Base
4
+ attr_reader :packager
5
+ attr_reader :build_path
6
+ attr_reader :docker
7
+
8
+ def initialize(packager)
9
+ @packager = packager
10
+
11
+ @build_path = Pathname.new(ENV['PREFIX'] || packager.package_path.join('build')).expand_path
12
+ raise Error, message: "bad build prefix: '#{build_path}' is a file" if build_path.file?
13
+ build_path.mkdir rescue nil
14
+
15
+ @docker = Docker.new(self,
16
+ username: packager.docker_username,
17
+ password: packager.docker_password,
18
+ email: packager.docker_email,
19
+ server: packager.docker_server,
20
+ )
21
+ end
22
+
23
+ def build_type
24
+ raise
25
+ end
26
+
27
+ def target_klass
28
+ raise
29
+ end
30
+
31
+ def prepare_image_instructions(target)
32
+ end
33
+
34
+ def build_instructions(target)
35
+ end
36
+
37
+ def build_dep
38
+ end
39
+
40
+ def new_target(target_name)
41
+ os_name, os_version, target_package_name, target_package_version = target_name.split('-', 4)
42
+
43
+ image = docker.new_image(os_name, os_version)
44
+
45
+ params = merge_os_params(image.os_name)
46
+ params = merge_os_version_params(image.os_name, image.os_version, into: params)
47
+ params = merge_base_target_params(target_name, target_package_name, target_package_version,
48
+ into: params) if target_package_name
49
+ check_params! params
50
+
51
+ target_klass.new(self, image, name: target_name, **params).tap do |target|
52
+ image.target = target
53
+ end
54
+ end
55
+
56
+ def targets
57
+ @targets ||= packager.targets.map {|target_name| new_target(target_name)}
58
+ end
59
+
60
+ def initial_target_params
61
+ {}.tap do |params|
62
+ params[:package_name] = packager.package_name
63
+ params[:package_version] = packager.package_version
64
+ params[:package_cloud] = packager.package_cloud
65
+ params[:prepare] = packager.prepare
66
+ params[:build_dep] = packager.build_dep
67
+ params[:before_build] = packager.before_build
68
+ end
69
+ end
70
+
71
+ def merge_params(into: nil, params:, &blk)
72
+ into ||= initial_target_params
73
+ params ||= {}
74
+ yield into, params if block_given?
75
+ do_merge_params into, params
76
+ end
77
+
78
+ def do_merge_params(into, params)
79
+ {}.tap do |res|
80
+ res[:package_name] = into[:package_name] || params['package_name']
81
+ res[:package_version] = into[:package_version] || params['package_version']
82
+ res[:package_cloud] = into[:package_cloud]
83
+ res[:prepare] = into[:prepare] + Array(params['prepare'])
84
+ res[:build_dep] = into[:build_dep] | Array(params['build_dep']).to_set
85
+ res[:before_build] = into[:before_build] + Array(params['before_build'])
86
+ end
87
+ end
88
+
89
+ def merge_os_params(os_name, into: nil, &blk)
90
+ merge_params(into: into, params: packager.os_params(os_name), &blk)
91
+ end
92
+
93
+ def merge_os_version_params(os_name, os_version, into: nil, &blk)
94
+ merge_params(into: into,
95
+ params: packager.os_params([os_name, os_version].join('-')), &blk)
96
+ end
97
+
98
+ def merge_base_target_params(target, target_package_name, target_package_version,
99
+ into: nil, &blk)
100
+ merge_params(into: into,
101
+ params: {'package_name' => target_package_name,
102
+ 'package_version' => target_package_version}, &blk)
103
+ end
104
+
105
+ def check_params!(params)
106
+ [:package_name, :package_version, :package_cloud].each do |param|
107
+ raise(Error,
108
+ error: :input_error,
109
+ message: "#{param} is not defined") unless params[param] and not params[param].empty?
110
+ end
111
+ end
112
+
113
+ def prepare
114
+ return unless packager.enabled?
115
+
116
+ docker.login!
117
+
118
+ begin
119
+ packager.before_prepare.each {|cmd| packager.command! cmd, desc: "Before prepare command: #{cmd}"}
120
+ targets.each {|target| prepare_target_image(target)}
121
+ packager.after_prepare.each {|cmd| packager.command! cmd, desc: "After prepare command: #{cmd}"}
122
+ ensure
123
+ docker.logout!
124
+ end
125
+ end
126
+
127
+ def prepare_target_image(target)
128
+ docker.image_build_path(target.image).mkpath
129
+
130
+ (Array(prepare_image_instructions(target)) + target.prepare).each do |cmd|
131
+ target.image.instruction(:RUN, "bash -lec \"#{cmd}\"")
132
+ end
133
+ target.image.build_dep(Array(build_dep).to_set + target.build_dep)
134
+ docker.build_image! target.image
135
+ end
136
+
137
+ def build
138
+ return unless packager.enabled?
139
+ targets.each {|target| build_target(target)}
140
+ end
141
+
142
+ def build_target(target)
143
+ docker.image_runtime_build_path(target.image).mkpath
144
+
145
+ cmd = [
146
+ "cd /package",
147
+ *target.before_build,
148
+ *Array(build_instructions(target)),
149
+ ]
150
+
151
+ docker.run! target.image, cmd: cmd
152
+ end
153
+
154
+ def deploy
155
+ return unless packager.enabled?
156
+ targets.each {|target| deploy_target(target)}
157
+ end
158
+
159
+ def deploy_target(target)
160
+ cmd = Dir[docker.image_runtime_build_path(target.image)
161
+ .join("*.#{target.image.fpm_output_type}")]
162
+ .map {|p| Pathname.new(p)}
163
+ .map {|p| ["package_cloud yank #{target.package_cloud_path} #{p.basename}",
164
+ "package_cloud push #{target.package_cloud_path} #{p}",
165
+ p.basename]}
166
+ .each {|yank, push, package|
167
+ packager.command yank, desc: "Package cloud yank package '#{package}'"
168
+ packager.command! push, desc: "Package cloud push package '#{package}'"
169
+ }
170
+ end
171
+ end # Base
172
+ end # Builder
173
+ end # Buildizer
@@ -0,0 +1,129 @@
1
+ module Buildizer
2
+ module Builder
3
+ class Fpm < Base
4
+ FPM_SCRIPT_EVENTS = [:before, :after].map {|at|
5
+ [:install, :upgrade, :remove].map {|event|
6
+ "#{at}_#{event}"}}.flatten
7
+
8
+ def build_type
9
+ 'fpm'
10
+ end
11
+
12
+ def target_klass
13
+ Target::Fpm
14
+ end
15
+
16
+ def initial_target_params
17
+ super.tap do |params|
18
+ raise(Error,
19
+ error: :input_error,
20
+ message: [
21
+ "explicit definition of package_version in #{build_type} ",
22
+ "build type is forbidden ",
23
+ "(use TRAVIS_TAG, CI_BUILD_TAG env variables)",
24
+ ].join) if params[:package_version]
25
+ params[:package_version] = ENV['TRAVIS_TAG'] || ENV['CI_BUILD_TAG']
26
+ params[:fpm_script] = Array(packager.buildizer_conf['fpm_script'])
27
+ params[:fpm_config_files] = packager.buildizer_conf['fpm_config_files'].to_h
28
+ params[:fpm_files] = packager.buildizer_conf['fpm_files'].to_h
29
+ end
30
+ end
31
+
32
+ def cannot_redefine_package_params!(params, redefine_for: nil)
33
+ [:package_name, :package_version].each do |param|
34
+ raise(Error,
35
+ error: :input_error,
36
+ message: [
37
+ "cannot redefine #{param}",
38
+ redefine_for ? "for #{redefine_for}" : nil,
39
+ "in #{build_type} build_type",
40
+ ].compact.join(' ')
41
+ ) if params.key? param.to_s
42
+ end
43
+ end
44
+
45
+ def do_merge_params(into, params)
46
+ super.tap do |res|
47
+ res[:fpm_script] = into[:fpm_script] + Array(params['fpm_script'])
48
+ res[:fpm_config_files] = into[:fpm_config_files].merge params['fpm_config_files'].to_h
49
+ res[:fpm_files] = into[:fpm_files].merge params['fpm_files'].to_h
50
+ end
51
+ end
52
+
53
+ def merge_os_params(os_name, **kwargs, &blk)
54
+ super(os_name, **kwargs) do |into, params|
55
+ yield into, params if block_given?
56
+ cannot_redefine_package_params!(params, redefine_for: "os '#{os_name}'")
57
+ end
58
+ end
59
+
60
+ def merge_os_version_params(os_name, os_version, **kwargs, &blk)
61
+ super(os_name, os_version, **kwargs) do |into, params|
62
+ yield into, params if block_given?
63
+ cannot_redefine_package_params!(params,
64
+ redefine_for: "os version '#{os_name}-#{os_version}'")
65
+ end
66
+ end
67
+
68
+ def merge_base_target_params(target, target_package_name, target_package_version, **kwargs, &blk)
69
+ super(target, target_package_name, target_package_version, **kwargs) do |into, params|
70
+ yield into, params if block_given?
71
+ cannot_redefine_package_params!(params, redefine_for: "target '#{target}'")
72
+ end
73
+ end
74
+
75
+ def check_params!(params)
76
+ super
77
+ if [:fpm_files, :fpm_config_files].all? {|param| params[param].empty?}
78
+ raise Error, error: :input_error,
79
+ message: ["either of fpm_files or fpm_config_files ",
80
+ "required in #{build_type} build_type"].join
81
+ end
82
+ end
83
+
84
+ def build_instructions(target)
85
+ fpm_script = target.fpm_script.reduce({}) do |res, spec|
86
+ conditions = Array(spec['when'])
87
+ raise Error, message: ["no when conditions given ",
88
+ "for fpm_script of target ",
89
+ "'#{target.name}'"].join unless conditions.any?
90
+
91
+ cmd = Array(spec['do'])
92
+ next res unless cmd.any?
93
+ conditions.each do |_when|
94
+ raise(
95
+ Error,
96
+ message: "unknown fpm_script event #{_when.inspect}"
97
+ ) unless FPM_SCRIPT_EVENTS.include? _when
98
+ res[_when] ||= {fpm_option: "--#{_when.split('_').join('-')}",
99
+ file: docker.image_runtime_build_path(target.image)
100
+ .join("fpm_#{_when}.sh"),
101
+ container_file: Pathname.new('/package/build').join("fpm_#{_when}.sh"),
102
+ cmd: []}
103
+ res[_when][:cmd] += cmd
104
+ end
105
+ res
106
+ end
107
+
108
+ fpm_script.values.map do |desc|
109
+ desc[:file].write ["#!/bin/bash", *desc[:cmd], nil].join("\n")
110
+ desc[:file].chmod 0755
111
+ end
112
+
113
+ version, release = target.package_version.split('-')
114
+
115
+ ["fpm -s dir",
116
+ "--force",
117
+ "-p #{docker.container_build_path}",
118
+ "-t #{target.image.fpm_output_type}",
119
+ "-n #{target.package_name}",
120
+ "--version=#{version}",
121
+ "--iteration=#{release}",
122
+ *fpm_script.values.map {|desc| "#{desc[:fpm_option]}=#{desc[:container_file]}"},
123
+ *Array(target.image.fpm_extra_params),
124
+ *target.fpm_config_files.values.map {|p| "--config-files=#{p}"},
125
+ *target.fpm_files.merge(target.fpm_config_files).map {|p1, p2| "#{p1}=#{p2}"}].join(' ')
126
+ end
127
+ end # Fpm
128
+ end # Builder
129
+ end # Buildizer
@@ -0,0 +1,4 @@
1
+ module Buildizer
2
+ module Builder
3
+ end # Builder
4
+ end # Buildizer
@@ -0,0 +1,58 @@
1
+ module Buildizer
2
+ class Cli < ::Thor
3
+ class << self
4
+ def shared_options
5
+ (@shared_options || {}).each do |name, options|
6
+ method_option name, options
7
+ end
8
+ end
9
+
10
+ def construct_packager(options)
11
+ Packager.new(options: {'latest' => options['latest']}, debug: options['debug'])
12
+ end
13
+ end # << self
14
+
15
+ @shared_options = {
16
+ debug: {type: :boolean, default: false, desc: "turn on live logging for external commands"},
17
+ }
18
+
19
+ desc "init", "Initialize settings (.travis.yml, .buildizer.yml, git pre-commit hook)"
20
+ shared_options
21
+ method_option :latest,
22
+ type: :boolean,
23
+ desc: "use buildizer github master branch"
24
+ def init
25
+ self.class.construct_packager(options).init!
26
+ end
27
+
28
+ desc "update", "Regenerate .travis.yml"
29
+ shared_options
30
+ def update
31
+ self.class.construct_packager(options).update!
32
+ end
33
+
34
+ desc "deinit", "Deinitialize settings (.buildizer.yml, git pre-commit hook)"
35
+ shared_options
36
+ def deinit
37
+ self.class.construct_packager(options).deinit!
38
+ end
39
+
40
+ desc "prepare", "Prepare images for building packages"
41
+ shared_options
42
+ def prepare
43
+ self.class.construct_packager(options).prepare!
44
+ end
45
+
46
+ desc "build", "Build packages"
47
+ shared_options
48
+ def build
49
+ self.class.construct_packager(options).build!
50
+ end
51
+
52
+ desc "deploy", "Deploy packages"
53
+ shared_options
54
+ def deploy
55
+ self.class.construct_packager(options).deploy!
56
+ end
57
+ end # Cli
58
+ end # Buildizer
@@ -0,0 +1,95 @@
1
+ module Buildizer
2
+ class Docker
3
+ attr_reader :builder
4
+ attr_reader :username
5
+ attr_reader :password
6
+ attr_reader :email
7
+ attr_reader :server
8
+
9
+ def initialize(builder, username:, password:, email:, server: nil)
10
+ @builder = builder
11
+ @username = username
12
+ @password = password
13
+ @email = email
14
+ @server = server
15
+ end
16
+
17
+ def image_klass(os_name, os_version)
18
+ ({
19
+ 'ubuntu' => {
20
+ '12.04' => Image::Ubuntu1204,
21
+ '14.04' => Image::Ubuntu1404,
22
+ nil => Image::Ubuntu1404,
23
+ },
24
+ 'centos' => {
25
+ 'centos6' => Image::Centos6,
26
+ 'centos7' => Image::Centos7,
27
+ nil => Image::Centos7,
28
+ },
29
+ }[os_name] || {})[os_version]
30
+ end
31
+
32
+ def new_image(os_name, os_version, **kwargs)
33
+ klass = image_klass(os_name, os_version)
34
+ raise Error, message: "unknown os '#{[os_name, os_version].compact.join('-')}'" unless klass
35
+ klass.new(self, **kwargs)
36
+ end
37
+
38
+ def login!
39
+ docker_login = ["docker login --email=#{email} --username=#{username} --password=#{password}"]
40
+ docker_login << "--server=#{server}" if server
41
+ builder.packager.command! docker_login.join(' '), desc: "Docker login"
42
+ end
43
+
44
+ def logout!
45
+ builder.packager.command! 'docker logout', desc: "Docker logout"
46
+ end
47
+
48
+ def pull_image!(image)
49
+ builder.packager.command "docker pull #{image.base_image}", desc: "Docker pull #{image.base_image}"
50
+ builder.packager.command "docker pull #{image.name}", desc: "Docker pull #{image.name}"
51
+ end
52
+
53
+ def push_image!(image)
54
+ builder.packager.command! "docker push #{image.name}", desc: "Docker push #{image.name}"
55
+ end
56
+
57
+ def build_image!(image)
58
+ pull_image! image
59
+
60
+ image_build_path(image).join('Dockerfile').write [*image.instructions, nil].join("\n")
61
+ builder.packager.command! "docker build -t #{image.name} #{image_build_path(image)}", desc: "Docker build image #{image.name}"
62
+
63
+ push_image! image
64
+ end
65
+
66
+ def image_build_path(image)
67
+ builder.build_path.join(image.os_name).join(image.os_version)
68
+ end
69
+
70
+ def image_runtime_build_path(image)
71
+ image_build_path(image).join('build')
72
+ end
73
+
74
+ def container_package_path
75
+ Pathname.new('/package')
76
+ end
77
+
78
+ def container_build_path
79
+ container_package_path.join('build')
80
+ end
81
+
82
+ def run!(image, cmd:, env: {})
83
+ cmd = Array(cmd)
84
+
85
+ builder.packager.command! [
86
+ "docker run --rm",
87
+ *env.map {|k,v| "-e #{k}=#{v}"},
88
+ "-v #{builder.packager.package_path}:#{container_package_path}",
89
+ "-v #{image_runtime_build_path(image)}:#{container_build_path}",
90
+ image.name,
91
+ "'#{cmd.join('; ')}'"
92
+ ].join(' '), desc: "Run build in docker image #{image.name}"
93
+ end
94
+ end # Docker
95
+ end # Buildizer
@@ -0,0 +1,4 @@
1
+ module Buildizer
2
+ class Error < NetStatus::Exception
3
+ end # Error
4
+ end # Buildizer
@@ -0,0 +1,56 @@
1
+ module Buildizer
2
+ module Image
3
+ class Base
4
+ attr_reader :instructions
5
+ attr_reader :docker
6
+
7
+ attr_accessor :target
8
+
9
+ def initialize(docker, **kwargs)
10
+ @instructions = []
11
+ @docker = docker
12
+
13
+ instruction :FROM, base_image
14
+ end
15
+
16
+ def os_name
17
+ raise
18
+ end
19
+
20
+ def os_package_cloud_name
21
+ os_name
22
+ end
23
+
24
+ def os_package_cloud_version
25
+ os_version
26
+ end
27
+
28
+ def os_version
29
+ raise
30
+ end
31
+
32
+ def fpm_output_type
33
+ raise
34
+ end
35
+
36
+ def fpm_extra_params
37
+ end
38
+
39
+ def build_dep(build_dep)
40
+ raise
41
+ end
42
+
43
+ def base_image
44
+ "buildizer/#{os_name}:#{os_version}"
45
+ end
46
+
47
+ def name
48
+ target.docker_image
49
+ end
50
+
51
+ def instruction(instruction, cmd)
52
+ instructions << [instruction.to_s.upcase, cmd].join(' ')
53
+ end
54
+ end # Base
55
+ end # Image
56
+ end # Buildizer
@@ -0,0 +1,52 @@
1
+ module Buildizer
2
+ module Image
3
+ class Centos < Base
4
+ attr_reader :os_version
5
+
6
+ def initialize(docker, os_version, **kwargs)
7
+ @os_version = os_version
8
+ super(docker, **kwargs)
9
+ end
10
+
11
+ def os_name
12
+ 'centos'
13
+ end
14
+
15
+ def os_package_cloud_name
16
+ 'el'
17
+ end
18
+
19
+ def os_package_cloud_version
20
+ os_version.match(/\d+$/).to_s.to_i
21
+ end
22
+
23
+ def fpm_output_type
24
+ 'rpm'
25
+ end
26
+
27
+ def fpm_extra_params
28
+ Array(super).tap do |res|
29
+ res << '--rpm-use-file-permissions'
30
+ end
31
+ end
32
+
33
+ def build_dep(build_dep)
34
+ instruction :RUN, "yum-builddep -y #{build_dep.to_a.join(' ')}" if build_dep.any?
35
+ end
36
+
37
+ def add_repo(id:, name:, baseurl: nil, enabled: 1, gpgcheck: nil, gpgkey: nil, exclude: nil, includepkgs: nil, mirrorlist: nil)
38
+ repo = "[#{id}]\
39
+ \\nname=#{name}\
40
+ \\nenabled=#{enabled}\
41
+ #{baseurl ? "\\nbaseurl=#{baseurl}" : nil}\
42
+ #{mirrorlist ? "\\nmirrorlist=#{mirrorlist}" : nil}\
43
+ #{gpgcheck ? "\\ngpgcheck=#{gpgcheck}" : nil}\
44
+ #{gpgkey ? "\\ngpgkey=#{gpgkey}" : nil}\
45
+ #{exclude ? "\\nexclude=#{exclude}" : nil}\
46
+ #{includepkgs ? "\\nincludepkgs=#{includepkgs}" : nil}"
47
+
48
+ instruction :RUN, "bash -lec \"echo -e '#{repo}' >> /etc/yum.repos.d/CentOS-Extra-Buildizer.repo\""
49
+ end
50
+ end # Centos
51
+ end # Image
52
+ end # Buildizer
@@ -0,0 +1,9 @@
1
+ module Buildizer
2
+ module Image
3
+ class Centos6 < Centos
4
+ def initialize(docker, **kwargs)
5
+ super(docker, 'centos6', **kwargs)
6
+ end
7
+ end # Centos6
8
+ end # Image
9
+ end # Buildizer
@@ -0,0 +1,9 @@
1
+ module Buildizer
2
+ module Image
3
+ class Centos7 < Centos
4
+ def initialize(docker, **kwargs)
5
+ super(docker, 'centos7', **kwargs)
6
+ end
7
+ end # Centos7
8
+ end # Image
9
+ end # Buildizer
@@ -0,0 +1,39 @@
1
+ module Buildizer
2
+ module Image
3
+ class Ubuntu < Base
4
+ attr_reader :os_version
5
+
6
+ def initialize(docker, os_version, **kwargs)
7
+ @os_version = os_version
8
+ super(docker, **kwargs)
9
+ end
10
+
11
+ def os_name
12
+ 'ubuntu'
13
+ end
14
+
15
+ def os_codename
16
+ raise
17
+ end
18
+
19
+ def os_package_cloud_version
20
+ os_codename
21
+ end
22
+
23
+ def fpm_output_type
24
+ 'deb'
25
+ end
26
+
27
+ def fpm_extra_params
28
+ Array(super).tap do |res|
29
+ res << '--deb-use-file-permissions'
30
+ res << '--deb-no-default-config-files'
31
+ end
32
+ end
33
+
34
+ def build_dep(build_dep)
35
+ instruction :RUN, "apt-get build-dep -y #{build_dep.to_a.join(' ')}" if build_dep.any?
36
+ end
37
+ end # Ubuntu
38
+ end # Image
39
+ end # Buildizer
@@ -0,0 +1,13 @@
1
+ module Buildizer
2
+ module Image
3
+ class Ubuntu1204 < Ubuntu
4
+ def initialize(docker, **kwargs)
5
+ super(docker, '12.04', **kwargs)
6
+ end
7
+
8
+ def os_codename
9
+ 'precise'
10
+ end
11
+ end # Ubuntu1204
12
+ end # Image
13
+ end # Buildizer
@@ -0,0 +1,13 @@
1
+ module Buildizer
2
+ module Image
3
+ class Ubuntu1404 < Ubuntu
4
+ def initialize(docker, **kwargs)
5
+ super(docker, '14.04', **kwargs)
6
+ end
7
+
8
+ def os_codename
9
+ 'trusty'
10
+ end
11
+ end # Ubuntu1404
12
+ end # Image
13
+ end # Buildizer
@@ -0,0 +1,4 @@
1
+ module Buildizer
2
+ module Image
3
+ end # Image
4
+ end # Buildizer