bento-lpn 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/.travis.yml +19 -0
- data/CHANGELOG.md +137 -0
- data/Gemfile +12 -0
- data/LICENSE +201 -0
- data/README.md +66 -0
- data/Rakefile +22 -0
- data/bento-lpn.gemspec +26 -0
- data/bin/bento +17 -0
- data/lib/bento.rb +2 -0
- data/lib/bento/build.rb +95 -0
- data/lib/bento/buildmetadata.rb +87 -0
- data/lib/bento/cli.rb +227 -0
- data/lib/bento/common.rb +105 -0
- data/lib/bento/delete.rb +22 -0
- data/lib/bento/normalize.rb +82 -0
- data/lib/bento/packerexec.rb +31 -0
- data/lib/bento/providermetadata.rb +84 -0
- data/lib/bento/release.rb +22 -0
- data/lib/bento/revoke.rb +22 -0
- data/lib/bento/test.rb +60 -0
- data/lib/bento/upload.rb +47 -0
- data/lib/bento/version.rb +3 -0
- data/templates/bootstrap.sh.erb +1 -0
- data/templates/kitchen.yml.erb +29 -0
- metadata +98 -0
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new do |t|
|
7
|
+
t.pattern = "spec/**/*_spec.rb"
|
8
|
+
end
|
9
|
+
rescue LoadError
|
10
|
+
desc "rspec is not installed, this task is disabled"
|
11
|
+
task :spec do
|
12
|
+
abort "rspec is not installed. `(sudo) gem install rspec` to run unit tests"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :spec
|
17
|
+
|
18
|
+
require "chefstyle"
|
19
|
+
require "rubocop/rake_task"
|
20
|
+
RuboCop::RakeTask.new(:style) do |task|
|
21
|
+
task.options += ["--display-cop-names", "--no-color"]
|
22
|
+
end
|
data/bento-lpn.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
3
|
+
require "bento/version"
|
4
|
+
require "English"
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "bento-lpn"
|
8
|
+
gem.version = Bento::VERSION
|
9
|
+
gem.license = "Apache-2.0"
|
10
|
+
gem.authors = ["Stephan Linz"]
|
11
|
+
gem.email = ["linz@li-pro.net"]
|
12
|
+
gem.description = "bento-lpn (fork of bento-ya) builds bento boxes"
|
13
|
+
gem.summary = "A RubyGem for managing chef/bento builds"
|
14
|
+
gem.homepage = "https://github.com/rexut/bento-lpn"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
|
+
gem.bindir = "bin"
|
18
|
+
gem.executables = %w{bento}
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
|
22
|
+
gem.required_ruby_version = ">= 2.3.1"
|
23
|
+
|
24
|
+
gem.add_dependency "mixlib-shellout", ">= 2.3.2"
|
25
|
+
gem.add_dependency "vagrant_cloud", "~> 1.0"
|
26
|
+
end
|
data/bin/bento
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: utf-8 -*-
|
3
|
+
|
4
|
+
Signal.trap("INT") { exit 1 }
|
5
|
+
|
6
|
+
$stdout.sync = true
|
7
|
+
$stderr.sync = true
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), %w{.. lib})
|
10
|
+
require "bento/cli"
|
11
|
+
|
12
|
+
begin
|
13
|
+
Runner.new(Options.parse(ARGV)).start
|
14
|
+
rescue => ex
|
15
|
+
$stderr.puts ">>> #{ex.message}"
|
16
|
+
exit(($? && $?.exitstatus) || 99)
|
17
|
+
end
|
data/lib/bento.rb
ADDED
data/lib/bento/build.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
require "bento/buildmetadata"
|
3
|
+
require "bento/providermetadata"
|
4
|
+
require "bento/packerexec"
|
5
|
+
|
6
|
+
class BuildRunner
|
7
|
+
include Common
|
8
|
+
include PackerExec
|
9
|
+
|
10
|
+
attr_reader :template_files, :config, :dry_run, :debug, :only, :except, :mirror, :headed, :single,
|
11
|
+
:override_version, :build_timestamp, :cpus, :mem
|
12
|
+
|
13
|
+
def initialize(opts)
|
14
|
+
@template_files = opts.template_files
|
15
|
+
@config = opts.config ||= false
|
16
|
+
@dry_run = opts.dry_run
|
17
|
+
@debug = opts.debug
|
18
|
+
@only = opts.only ||= "parallels-iso,virtualbox-iso,vmware-iso"
|
19
|
+
@except = opts.except
|
20
|
+
@mirror = opts.mirror
|
21
|
+
@headed = opts.headed ||= false
|
22
|
+
@single = opts.single ||= false
|
23
|
+
@override_version = opts.override_version
|
24
|
+
@build_timestamp = Time.now.gmtime.strftime("%Y%m%d%H%M%S")
|
25
|
+
@cpus = opts.cpus
|
26
|
+
@mem = opts.mem
|
27
|
+
end
|
28
|
+
|
29
|
+
def start
|
30
|
+
templates = config ? build_list : template_files
|
31
|
+
banner("Starting build for templates:")
|
32
|
+
templates.each { |t| puts "- #{t}" }
|
33
|
+
time = Benchmark.measure do
|
34
|
+
templates.each { |template| build(template) }
|
35
|
+
end
|
36
|
+
banner("Build finished in #{duration(time.real)}.")
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def build(file)
|
42
|
+
dir, template = file.split("/")[0], file.split("/")[1]
|
43
|
+
Dir.chdir dir
|
44
|
+
for_packer_run_with(template) do |md_file, var_file|
|
45
|
+
cmd = packer_build_cmd(template, md_file.path)
|
46
|
+
banner("[#{template}] Building: '#{cmd.join(' ')}'")
|
47
|
+
time = Benchmark.measure do
|
48
|
+
system(*cmd) || raise("[#{template}] Error building, exited #{$?}")
|
49
|
+
end
|
50
|
+
write_final_metadata(template, time.real.ceil)
|
51
|
+
banner("[#{template}] Finished building in #{duration(time.real)}.")
|
52
|
+
end
|
53
|
+
Dir.chdir("..")
|
54
|
+
end
|
55
|
+
|
56
|
+
def packer_build_cmd(template, var_file)
|
57
|
+
vars = "#{template}.variables.json"
|
58
|
+
cmd = %W{packer build -var-file=#{var_file} #{template}.json}
|
59
|
+
cmd.insert(2, "-var-file=#{vars}") if File.exist?(vars)
|
60
|
+
cmd.insert(2, "-only=#{only}")
|
61
|
+
cmd.insert(2, "-except=#{except}") if except
|
62
|
+
# Build the command line in the correct order and without spaces as future input for the splat operator.
|
63
|
+
cmd.insert(2, "cpus=#{cpus}") if cpus
|
64
|
+
cmd.insert(2, "-var") if cpus
|
65
|
+
cmd.insert(2, "memory=#{mem}") if mem
|
66
|
+
cmd.insert(2, "-var") if mem
|
67
|
+
cmd.insert(2, "mirror=#{mirror}") if mirror
|
68
|
+
cmd.insert(2, "-var") if mirror
|
69
|
+
cmd.insert(2, "headless=true") unless headed
|
70
|
+
cmd.insert(2, "-var") unless headed
|
71
|
+
cmd.insert(2, "-parallel=false") if single
|
72
|
+
cmd.insert(2, "-debug") if debug
|
73
|
+
cmd.insert(0, "echo") if dry_run
|
74
|
+
cmd
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_final_metadata(template, buildtime)
|
78
|
+
md = BuildMetadata.new(template, build_timestamp, override_version).read
|
79
|
+
path = File.join("../builds")
|
80
|
+
filename = File.join(path, "#{md[:box_basename]}.metadata.json")
|
81
|
+
md[:providers] = ProviderMetadata.new(path, md[:box_basename]).read
|
82
|
+
md[:providers].each do |p|
|
83
|
+
p[:build_time] = buildtime
|
84
|
+
p[:build_cpus] = cpus unless cpus.nil?
|
85
|
+
p[:build_mem] = mem unless mem.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
if dry_run
|
89
|
+
banner("(Dry run) Metadata file contents would be something similar to:")
|
90
|
+
puts JSON.pretty_generate(md)
|
91
|
+
else
|
92
|
+
File.open(filename, "wb") { |file| file.write(JSON.pretty_generate(md)) }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
require "mixlib/shellout"
|
3
|
+
|
4
|
+
class BuildMetadata
|
5
|
+
include Common
|
6
|
+
|
7
|
+
def initialize(template, build_timestamp, override_version)
|
8
|
+
@template = template
|
9
|
+
@build_timestamp = build_timestamp
|
10
|
+
@override_version = override_version
|
11
|
+
end
|
12
|
+
|
13
|
+
def read
|
14
|
+
{
|
15
|
+
name: name,
|
16
|
+
version: version,
|
17
|
+
build_timestamp: build_timestamp,
|
18
|
+
git_revision: git_revision,
|
19
|
+
git_status: git_clean? ? "clean" : "dirty",
|
20
|
+
box_basename: box_basename,
|
21
|
+
template: template_vars.fetch("template", UNKNOWN),
|
22
|
+
packer: packer_ver,
|
23
|
+
vagrant: vagrant_ver,
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
UNKNOWN = "__unknown__".freeze
|
30
|
+
|
31
|
+
attr_reader :template, :build_timestamp, :override_version
|
32
|
+
|
33
|
+
def box_basename
|
34
|
+
"#{name.gsub("/", "__")}-#{version}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def git_revision
|
38
|
+
sha = `git rev-parse HEAD`.strip
|
39
|
+
end
|
40
|
+
|
41
|
+
def git_clean?
|
42
|
+
`git status --porcelain`.strip.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def merged_vars
|
46
|
+
@merged_vars ||= begin
|
47
|
+
if File.exist?("#{template}.variables.json")
|
48
|
+
template_vars.merge(JSON.load(IO.read("#{template}.variables.json")))
|
49
|
+
else
|
50
|
+
template_vars
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def name
|
56
|
+
merged_vars.fetch("name", template)
|
57
|
+
end
|
58
|
+
|
59
|
+
def template_vars
|
60
|
+
@template_vars ||= JSON.load(IO.read("#{template}.json")).fetch("variables")
|
61
|
+
end
|
62
|
+
|
63
|
+
def version
|
64
|
+
if override_version
|
65
|
+
override_version
|
66
|
+
else
|
67
|
+
merged_vars.fetch("version", "#{UNKNOWN}.TIMESTAMP").
|
68
|
+
rpartition(".").first.concat("#{build_timestamp}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def packer_ver
|
73
|
+
cmd = Mixlib::ShellOut.new("packer --version")
|
74
|
+
cmd.run_command
|
75
|
+
cmd.stdout.split("\n")[0]
|
76
|
+
end
|
77
|
+
|
78
|
+
def vagrant_ver
|
79
|
+
if ENV["TRAVIS"]
|
80
|
+
"travis"
|
81
|
+
else
|
82
|
+
cmd = Mixlib::ShellOut.new("vagrant --version")
|
83
|
+
cmd.run_command
|
84
|
+
cmd.stdout.split(" ")[1]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/bento/cli.rb
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
require "bento/common"
|
5
|
+
require "bento/build"
|
6
|
+
require "bento/delete"
|
7
|
+
require "bento/normalize"
|
8
|
+
require "bento/release"
|
9
|
+
require "bento/revoke"
|
10
|
+
require "bento/test"
|
11
|
+
require "bento/upload"
|
12
|
+
|
13
|
+
class Options
|
14
|
+
NAME = File.basename($0).freeze
|
15
|
+
|
16
|
+
def self.parse(args)
|
17
|
+
options = OpenStruct.new
|
18
|
+
options.template_files = calculate_templates("**/*.json")
|
19
|
+
|
20
|
+
global = OptionParser.new do |opts|
|
21
|
+
opts.banner = "Usage: #{NAME} [SUBCOMMAND [options]]"
|
22
|
+
opts.separator ""
|
23
|
+
opts.separator <<-COMMANDS.gsub(/^ {8}/, "")
|
24
|
+
build : build one or more templates
|
25
|
+
help : prints this help message
|
26
|
+
list : list all templates in project
|
27
|
+
normalize : normalize one or more templates
|
28
|
+
test : test one or more builds with kitchen
|
29
|
+
upload : upload one or more builds to Atlas and S3
|
30
|
+
release : release a version of a box on Atlas
|
31
|
+
revoke : revoke a version of a box on Atlas
|
32
|
+
delete : delete a version of a box from Atlas
|
33
|
+
COMMANDS
|
34
|
+
end
|
35
|
+
|
36
|
+
platforms_argv_proc = proc { |options|
|
37
|
+
options.platforms = builds["public"] unless args.empty?
|
38
|
+
}
|
39
|
+
|
40
|
+
templates_argv_proc = proc { |options|
|
41
|
+
options.template_files = calculate_templates(args) unless args.empty?
|
42
|
+
|
43
|
+
options.template_files.each do |t|
|
44
|
+
if !File.exists?("#{t}.json")
|
45
|
+
$stderr.puts "File #{t}.json does not exist for template '#{t}'"
|
46
|
+
exit(1)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
}
|
50
|
+
|
51
|
+
box_version_argv_proc = proc { |options|
|
52
|
+
options.box = ARGV[0]
|
53
|
+
options.version = ARGV[1]
|
54
|
+
}
|
55
|
+
|
56
|
+
md_json_argv_proc = proc { |options|
|
57
|
+
options.md_json = ARGV[0]
|
58
|
+
}
|
59
|
+
|
60
|
+
subcommand = {
|
61
|
+
help: {
|
62
|
+
parser: OptionParser.new {},
|
63
|
+
argv: proc { |options|
|
64
|
+
puts global
|
65
|
+
exit(0)
|
66
|
+
},
|
67
|
+
},
|
68
|
+
build: {
|
69
|
+
class: BuildRunner,
|
70
|
+
parser: OptionParser.new do |opts|
|
71
|
+
opts.banner = "Usage: #{NAME} build [options] TEMPLATE[ TEMPLATE ...]"
|
72
|
+
|
73
|
+
opts.on("-n", "--dry-run", "Dry run (what would happen)") do |opt|
|
74
|
+
options.dry_run = opt
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.on("-c BUILD_YML", "--config BUILD_YML", "Use a configuration file") do |opt|
|
78
|
+
options.config = opt
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on("-d", "--[no-]debug", "Run packer with debug output") do |opt|
|
82
|
+
options.debug = opt
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on("-o BUILDS", "--only BUILDS", "Only build some Packer builds") do |opt|
|
86
|
+
options.only = opt
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on("-e BUILDS", "--except BUILDS", "Build all Packer builds except these") do |opt|
|
90
|
+
options.except = opt
|
91
|
+
end
|
92
|
+
|
93
|
+
opts.on("-m MIRROR", "--mirror MIRROR", "Look for isos at MIRROR") do |opt|
|
94
|
+
options.mirror = opt
|
95
|
+
end
|
96
|
+
|
97
|
+
opts.on("-C cpus", "--cpus CPUS", "# of CPUs per provider") do |opt|
|
98
|
+
options.cpus = opt
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.on("-M MEMORY", "--memory MEMORY", "Memory (MB) per provider") do |opt|
|
102
|
+
options.mem = opt
|
103
|
+
end
|
104
|
+
|
105
|
+
opts.on("-H", "--headed", "Display provider UI windows") do |opt|
|
106
|
+
options.headed = opt
|
107
|
+
end
|
108
|
+
|
109
|
+
opts.on("-S", "--single", "Disable parallelization of Packer builds") do |opt|
|
110
|
+
options.single = opt
|
111
|
+
end
|
112
|
+
|
113
|
+
opts.on("-v VERSION", "--version VERSION", "Override the version set in the template") do |opt|
|
114
|
+
options.override_version = opt
|
115
|
+
end
|
116
|
+
end,
|
117
|
+
argv: templates_argv_proc,
|
118
|
+
},
|
119
|
+
list: {
|
120
|
+
class: ListRunner,
|
121
|
+
parser: OptionParser.new do |opts|
|
122
|
+
opts.banner = "Usage: #{NAME} list [TEMPLATE ...]"
|
123
|
+
end,
|
124
|
+
argv: templates_argv_proc,
|
125
|
+
},
|
126
|
+
normalize: {
|
127
|
+
class: NormalizeRunner,
|
128
|
+
parser: OptionParser.new do |opts|
|
129
|
+
opts.banner = "Usage: #{NAME} normalize TEMPLATE[ TEMPLATE ...]"
|
130
|
+
|
131
|
+
opts.on("-d", "--[no-]debug", "Run packer with debug output") do |opt|
|
132
|
+
options.debug = opt
|
133
|
+
end
|
134
|
+
end,
|
135
|
+
argv: templates_argv_proc,
|
136
|
+
},
|
137
|
+
test: {
|
138
|
+
class: TestRunner,
|
139
|
+
parser: OptionParser.new do |opts|
|
140
|
+
opts.banner = "Usage: #{NAME} test [options]"
|
141
|
+
|
142
|
+
opts.on("--no-shared-folder", "Disable shared folder testing") do |opt|
|
143
|
+
options.no_shared = opt
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on("-p", "--provisioner PROVISIONER", "Use a specfic provisioner") do |opt|
|
147
|
+
options.provisioner = opt
|
148
|
+
end
|
149
|
+
end,
|
150
|
+
argv: Proc.new {},
|
151
|
+
},
|
152
|
+
upload: {
|
153
|
+
class: UploadRunner,
|
154
|
+
parser: OptionParser.new do |opts|
|
155
|
+
opts.banner = "Usage: #{NAME} upload"
|
156
|
+
end,
|
157
|
+
argv: md_json_argv_proc,
|
158
|
+
},
|
159
|
+
release: {
|
160
|
+
class: ReleaseRunner,
|
161
|
+
parser: OptionParser.new do |opts|
|
162
|
+
opts.banner = "Usage: #{NAME} release BOX VERSION"
|
163
|
+
end,
|
164
|
+
argv: box_version_argv_proc,
|
165
|
+
},
|
166
|
+
revoke: {
|
167
|
+
class: RevokeRunner,
|
168
|
+
parser: OptionParser.new do |opts|
|
169
|
+
opts.banner = "Usage: #{NAME} revoke BOX VERSION"
|
170
|
+
end,
|
171
|
+
argv: box_version_argv_proc,
|
172
|
+
},
|
173
|
+
delete: {
|
174
|
+
class: DeleteRunner,
|
175
|
+
parser: OptionParser.new do |opts|
|
176
|
+
opts.banner = "Usage: #{NAME} delete BOX VERSION"
|
177
|
+
end,
|
178
|
+
argv: box_version_argv_proc,
|
179
|
+
},
|
180
|
+
}
|
181
|
+
|
182
|
+
global.order!
|
183
|
+
command = args.empty? ? :help : ARGV.shift.to_sym
|
184
|
+
subcommand.fetch(command).fetch(:parser).order!
|
185
|
+
subcommand.fetch(command).fetch(:argv).call(options)
|
186
|
+
|
187
|
+
options.command = command
|
188
|
+
options.klass = subcommand.fetch(command).fetch(:class)
|
189
|
+
|
190
|
+
options
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.calculate_templates(globs)
|
194
|
+
Array(globs).
|
195
|
+
map { |glob| result = Dir.glob(glob); result.empty? ? glob : result }.
|
196
|
+
flatten.
|
197
|
+
sort.
|
198
|
+
delete_if { |file| file =~ /\.(variables||metadata)\.json/ }.
|
199
|
+
map { |template| template.sub(/\.json$/, "") }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class ListRunner
|
204
|
+
include Common
|
205
|
+
|
206
|
+
attr_reader :templates
|
207
|
+
|
208
|
+
def initialize(opts)
|
209
|
+
@templates = opts.templates
|
210
|
+
end
|
211
|
+
|
212
|
+
def start
|
213
|
+
templates.each { |template| puts template }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class Runner
|
218
|
+
attr_reader :options
|
219
|
+
|
220
|
+
def initialize(options)
|
221
|
+
@options = options
|
222
|
+
end
|
223
|
+
|
224
|
+
def start
|
225
|
+
options.klass.new(options).start
|
226
|
+
end
|
227
|
+
end
|