buildizer 0.0.1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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