bento-lpn 1.1.3
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.
- 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
|