bento-lpn 1.1.3

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