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.
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
@@ -0,0 +1,2 @@
1
+ require "bento/common"
2
+ require "bento/version"
@@ -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