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/lib/bento/common.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
require "fileutils"
|
3
|
+
require "json"
|
4
|
+
require "tempfile"
|
5
|
+
require "yaml"
|
6
|
+
require "vagrant_cloud"
|
7
|
+
|
8
|
+
MEGABYTE = 1024.0 * 1024.0
|
9
|
+
|
10
|
+
module Common
|
11
|
+
def vc_account
|
12
|
+
VagrantCloud::Account.new(ENV["VAGRANT_CLOUD_ORG"], ENV["VAGRANT_CLOUD_TOKEN"])
|
13
|
+
end
|
14
|
+
|
15
|
+
def banner(msg)
|
16
|
+
puts "==> #{msg}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def info(msg)
|
20
|
+
puts " #{msg}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def warn(msg)
|
24
|
+
puts ">>> #{msg}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def duration(total)
|
28
|
+
total = 0 if total.nil?
|
29
|
+
minutes = (total / 60).to_i
|
30
|
+
seconds = (total - (minutes * 60))
|
31
|
+
format("%dm%.2fs", minutes, seconds)
|
32
|
+
end
|
33
|
+
|
34
|
+
def box_metadata(metadata_file)
|
35
|
+
metadata = Hash.new
|
36
|
+
file = File.read(metadata_file)
|
37
|
+
json = JSON.parse(file)
|
38
|
+
|
39
|
+
# metadata needed for upload: boxname, version, provider, box filename
|
40
|
+
metadata["name"] = json["name"]
|
41
|
+
metadata["version"] = json["version"]
|
42
|
+
metadata["box_basename"] = json["box_basename"]
|
43
|
+
metadata["tools"] = json["tools"]
|
44
|
+
metadata["providers"] = Hash.new
|
45
|
+
json["providers"].each do |provider|
|
46
|
+
metadata["providers"][provider["name"]] = provider.reject { |k, _| k == "name" }
|
47
|
+
end
|
48
|
+
metadata
|
49
|
+
end
|
50
|
+
|
51
|
+
def metadata_files
|
52
|
+
@metadata_files ||= compute_metadata_files
|
53
|
+
end
|
54
|
+
|
55
|
+
def compute_metadata_files
|
56
|
+
`ls builds/*.json`.split("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def builds_yml
|
60
|
+
YAML.load(File.read("builds.yml"))
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_list
|
64
|
+
bit32 = []
|
65
|
+
bit64 = []
|
66
|
+
builds_yml["public"].each do |platform, versions|
|
67
|
+
versions.each do |version, archs|
|
68
|
+
archs.each do |arch|
|
69
|
+
folder = case platform
|
70
|
+
when "opensuse-leap"
|
71
|
+
"opensuse"
|
72
|
+
when "oracle"
|
73
|
+
"oraclelinux"
|
74
|
+
else
|
75
|
+
platform
|
76
|
+
end
|
77
|
+
case arch
|
78
|
+
when "i386"
|
79
|
+
bit32 << "#{folder}/#{platform}-#{version}-#{arch}"
|
80
|
+
else
|
81
|
+
bit64 << "#{folder}/#{platform}-#{version}-#{arch}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
bit64 + bit32
|
87
|
+
end
|
88
|
+
|
89
|
+
def private_box?(boxname)
|
90
|
+
proprietary_os_list = %w{macos windows sles solaris rhel}
|
91
|
+
proprietary_os_list.any? { |p| boxname.include?(p) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def os_x?
|
95
|
+
!!(RUBY_PLATFORM =~ /darwin/)
|
96
|
+
end
|
97
|
+
|
98
|
+
def unix?
|
99
|
+
!windows?
|
100
|
+
end
|
101
|
+
|
102
|
+
def windows?
|
103
|
+
!!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
|
104
|
+
end
|
105
|
+
end
|
data/lib/bento/delete.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
|
3
|
+
class DeleteRunner
|
4
|
+
include Common
|
5
|
+
|
6
|
+
attr_reader :box, :version
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@box = opts.box
|
10
|
+
@version = opts.version
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
banner("Starting Delete...")
|
15
|
+
time = Benchmark.measure do
|
16
|
+
box = vc_account.get_box(box)
|
17
|
+
version = box.get_version(version)
|
18
|
+
version.delete
|
19
|
+
end
|
20
|
+
banner("Delete finished in #{duration(time.real)}.")
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
|
3
|
+
class NormalizeRunner
|
4
|
+
include Common
|
5
|
+
include PackerExec
|
6
|
+
|
7
|
+
attr_reader :templates, :build_timestamp, :debug, :override_version
|
8
|
+
|
9
|
+
def initialize(opts)
|
10
|
+
@templates = opts.template_files
|
11
|
+
@debug = opts.debug
|
12
|
+
@modified = []
|
13
|
+
@build_timestamp = Time.now.gmtime.strftime("%Y%m%d%H%M%S")
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
banner("Normalizing for templates:")
|
18
|
+
templates.each { |t| puts "- #{t}" }
|
19
|
+
time = Benchmark.measure do
|
20
|
+
templates.each do |file|
|
21
|
+
dir, template = file.split("/")[0], file.split("/")[1]
|
22
|
+
Dir.chdir dir
|
23
|
+
validate(template)
|
24
|
+
Dir.chdir("..")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
if !@modified.empty?
|
28
|
+
info("")
|
29
|
+
info("The following templates were modified:")
|
30
|
+
@modified.sort.each { |template| info(" * #{template}") }
|
31
|
+
end
|
32
|
+
banner("Normalizing finished in #{duration(time.real)}.")
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def checksum(file)
|
38
|
+
Digest::MD5.file(file).hexdigest
|
39
|
+
end
|
40
|
+
|
41
|
+
def fix(template)
|
42
|
+
file = "#{template}.json"
|
43
|
+
|
44
|
+
banner("[#{template}] Fixing")
|
45
|
+
original_checksum = checksum(file)
|
46
|
+
output = `packer fix #{file}`
|
47
|
+
raise "[#{template}] Error fixing, exited #{$?}" if $?.exitstatus != 0
|
48
|
+
# preserve ampersands in shell commands,
|
49
|
+
# see: https://github.com/mitchellh/packer/issues/784
|
50
|
+
output.gsub!("\\u0026", "&")
|
51
|
+
File.open(file, "wb") { |dest| dest.write(output) }
|
52
|
+
fixed_checksum = checksum(file)
|
53
|
+
|
54
|
+
if original_checksum == fixed_checksum
|
55
|
+
puts("No changes made.")
|
56
|
+
else
|
57
|
+
warn("Template #{template} has been modified.")
|
58
|
+
@modified << template
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def packer_validate_cmd(template, var_file)
|
63
|
+
vars = "#{template}.variables.json"
|
64
|
+
cmd = %W{packer validate -var-file=#{var_file} #{template}.json}
|
65
|
+
cmd.insert(2, "-var-file=#{vars}") if File.exist?(vars)
|
66
|
+
cmd
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate(template)
|
70
|
+
for_packer_run_with(template) do |md_file, var_file|
|
71
|
+
cmd = packer_validate_cmd(template, var_file.path)
|
72
|
+
banner("[#{template}] Validating: '#{cmd.join(' ')}'")
|
73
|
+
if debug
|
74
|
+
banner("[#{template}] DEBUG: var_file(#{var_file.path}) is:")
|
75
|
+
puts IO.read(var_file.path)
|
76
|
+
banner("[#{template}] DEBUG: md_file(#{md_file.path}) is:")
|
77
|
+
puts IO.read(md_file.path)
|
78
|
+
end
|
79
|
+
system(*cmd) || raise( "[#{template}] Error validating, exited #{$?}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module PackerExec
|
3
|
+
def for_packer_run_with(template)
|
4
|
+
Tempfile.open("#{template}-metadata.json") do |md_file|
|
5
|
+
Tempfile.open("#{template}-metadata-var-file") do |var_file|
|
6
|
+
write_box_metadata(template, md_file)
|
7
|
+
write_var_file(template, md_file, var_file)
|
8
|
+
yield md_file, var_file
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def write_box_metadata(template, io)
|
14
|
+
md = BuildMetadata.new(template, build_timestamp, override_version).read
|
15
|
+
io.write(JSON.pretty_generate(md))
|
16
|
+
io.close
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_var_file(template, md_file, io)
|
20
|
+
md = BuildMetadata.new(template, build_timestamp, override_version).read
|
21
|
+
|
22
|
+
io.write(JSON.pretty_generate({
|
23
|
+
box_basename: md[:box_basename],
|
24
|
+
build_timestamp: md[:build_timestamp],
|
25
|
+
git_revision: md[:git_revision],
|
26
|
+
metadata: md_file.path,
|
27
|
+
version: md[:version],
|
28
|
+
}))
|
29
|
+
io.close
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "digest"
|
2
|
+
require "bento/common"
|
3
|
+
|
4
|
+
class ProviderMetadata
|
5
|
+
include Common
|
6
|
+
|
7
|
+
def initialize(path, box_basename)
|
8
|
+
@base = File.join(path, box_basename)
|
9
|
+
end
|
10
|
+
|
11
|
+
def read
|
12
|
+
Dir.glob("#{base}.*.box").map do |file|
|
13
|
+
{
|
14
|
+
name: provider_from_file(file),
|
15
|
+
version: version(provider_from_file(file)),
|
16
|
+
file: "#{File.basename(file)}",
|
17
|
+
checksum_type: "sha256",
|
18
|
+
checksum: shasum(file),
|
19
|
+
size: "#{size_in_mb(file)} MB",
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :base
|
27
|
+
|
28
|
+
def provider_from_file(file)
|
29
|
+
provider = file.sub(/^.*\.([^.]+)\.box$/, '\1')
|
30
|
+
if provider == "vmware"
|
31
|
+
"vmware_desktop"
|
32
|
+
else
|
33
|
+
provider
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def shasum(file)
|
38
|
+
Digest::SHA256.file(file).hexdigest
|
39
|
+
end
|
40
|
+
|
41
|
+
def size_in_mb(file)
|
42
|
+
size = File.size(file)
|
43
|
+
size_mb = size / MEGABYTE
|
44
|
+
size_mb.ceil.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def version(provider)
|
48
|
+
case provider
|
49
|
+
when /vmware/
|
50
|
+
ver_vmware
|
51
|
+
when /virtualbox/
|
52
|
+
ver_vbox
|
53
|
+
when /parallels/
|
54
|
+
ver_parallels
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def ver_vmware
|
59
|
+
if os_x?
|
60
|
+
path = File.join('/Applications/VMware\ Fusion.app/Contents/Library')
|
61
|
+
fusion_cmd = File.join(path, "vmware-vmx -v")
|
62
|
+
cmd = Mixlib::ShellOut.new(fusion_cmd)
|
63
|
+
cmd.run_command
|
64
|
+
cmd.stderr.split(" ")[5]
|
65
|
+
else
|
66
|
+
cmd = Mixlib::ShellOut.new("vmware --version")
|
67
|
+
cmd.run_command
|
68
|
+
cmd.stdout.split(" ")[2]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def ver_parallels
|
73
|
+
raise "Platform is not macOS / OS X, exiting..." unless os_x?
|
74
|
+
cmd = Mixlib::ShellOut.new("prlctl --version")
|
75
|
+
cmd.run_command
|
76
|
+
cmd.stdout.split(" ")[2]
|
77
|
+
end
|
78
|
+
|
79
|
+
def ver_vbox
|
80
|
+
cmd = Mixlib::ShellOut.new("VBoxManage --version")
|
81
|
+
cmd.run_command
|
82
|
+
cmd.stdout.split("r")[0]
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
|
3
|
+
class ReleaseRunner
|
4
|
+
include Common
|
5
|
+
|
6
|
+
attr_reader :box, :version
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@box = opts.box
|
10
|
+
@version = opts.version
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
banner("Releasing #{box}/#{version}...")
|
15
|
+
time = Benchmark.measure do
|
16
|
+
b = vc_account.get_box(box)
|
17
|
+
v = b.get_version(version)
|
18
|
+
v.release
|
19
|
+
end
|
20
|
+
banner("Release finished in #{duration(time.real)}.")
|
21
|
+
end
|
22
|
+
end
|
data/lib/bento/revoke.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
|
3
|
+
class RevokeRunner
|
4
|
+
include Common
|
5
|
+
|
6
|
+
attr_reader :box, :version
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@box = opts.box
|
10
|
+
@version = opts.version
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
banner("Revoking #{box}/#{version}...")
|
15
|
+
time = Benchmark.measure do
|
16
|
+
box = vc_account.get_box(box)
|
17
|
+
version = box.get_version(version)
|
18
|
+
version.revoke
|
19
|
+
end
|
20
|
+
banner("Revoke finished in #{duration(time.real)}.")
|
21
|
+
end
|
22
|
+
end
|
data/lib/bento/test.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
require "mixlib/shellout"
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
class TestRunner
|
6
|
+
include Common
|
7
|
+
|
8
|
+
attr_reader :shared_folder, :boxname, :provider, :box_url, :no_shared, :provisioner
|
9
|
+
|
10
|
+
def initialize(opts)
|
11
|
+
@debug = opts.debug
|
12
|
+
@no_shared = opts.no_shared
|
13
|
+
@provisioner = opts.provisioner.nil? ? "shell" : opts.provisioner
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
banner("Starting testing...")
|
18
|
+
time = Benchmark.measure do
|
19
|
+
metadata_files.each do |metadata_file|
|
20
|
+
destroy_all_bento
|
21
|
+
test_box(metadata_file)
|
22
|
+
destroy_all_bento
|
23
|
+
end
|
24
|
+
end
|
25
|
+
banner("Testing finished in #{duration(time.real)}.")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def destroy_all_bento
|
31
|
+
cmd = Mixlib::ShellOut.new("vagrant box list | grep 'bento-'")
|
32
|
+
cmd.run_command
|
33
|
+
boxes = cmd.stdout.split("\n")
|
34
|
+
|
35
|
+
boxes.each do |box|
|
36
|
+
b = box.split(" ")
|
37
|
+
rm_cmd = Mixlib::ShellOut.new("vagrant box remove --force #{b[0]} --provider #{b[1].to_s.gsub(/(,|\()/, '')}")
|
38
|
+
banner("Removing #{b[0]} for provider #{b[1].to_s.gsub(/(,|\()/, '')}")
|
39
|
+
rm_cmd.run_command
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_box(md_json)
|
44
|
+
md = box_metadata(md_json)
|
45
|
+
@boxname = md["name"]
|
46
|
+
@providers = md["providers"]
|
47
|
+
@share_disabled = no_shared || /(bsd|opensuse)/.match(boxname) ? true : false
|
48
|
+
|
49
|
+
dir = "#{File.expand_path("../../", File.dirname(__FILE__))}/templates"
|
50
|
+
%w{.kitchen.yml bootstrap.sh}.each do |file|
|
51
|
+
t = file =~ /kitchen/ ? "kitchen.yml.erb" : "#{file}.erb"
|
52
|
+
erb = ERB.new(File.read(dir + "/#{t}"), nil, "-").result(binding)
|
53
|
+
File.open(file, "w") { |f| f.puts erb }
|
54
|
+
end
|
55
|
+
|
56
|
+
test = Mixlib::ShellOut.new("kitchen test", :timeout => 900, live_stream: STDOUT)
|
57
|
+
test.run_command
|
58
|
+
test.error!
|
59
|
+
end
|
60
|
+
end
|
data/lib/bento/upload.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "bento/common"
|
2
|
+
|
3
|
+
class UploadRunner
|
4
|
+
include Common
|
5
|
+
|
6
|
+
attr_reader :md_json
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@md_json = opts.md_json
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
banner("Starting uploads...")
|
14
|
+
time = Benchmark.measure do
|
15
|
+
files = md_json ? [md_json] : metadata_files
|
16
|
+
files.each do |md_file|
|
17
|
+
upload(md_file)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
banner("Uploads finished in #{duration(time.real)}.")
|
21
|
+
end
|
22
|
+
|
23
|
+
def upload(md_file)
|
24
|
+
md = box_metadata(md_file)
|
25
|
+
box_desc = "a bento box for #{md['name']}"
|
26
|
+
box = vc_account.ensure_box(md["name"], box_desc, private_box?(md["name"]))
|
27
|
+
box_ver = box.ensure_version(md["version"], File.read(md_file))
|
28
|
+
|
29
|
+
if builds_yml["slugs"].values.include?(box.name)
|
30
|
+
slug_desc = "a bento box for #{builds_yml['slugs'].key(box.name)}"
|
31
|
+
slug = vc_account.ensure_box(builds_yml["slugs"].key(box.name), slug_desc, false)
|
32
|
+
slug_ver = slug.ensure_version(md["version"], File.read(md_file))
|
33
|
+
end
|
34
|
+
|
35
|
+
md["providers"].each do |k, v|
|
36
|
+
provider = box_ver.ensure_provider(k, nil)
|
37
|
+
banner("Uploading #{box.name}/#{box_ver.version}/#{provider.name}...")
|
38
|
+
provider.upload_file("builds/#{v['file']}")
|
39
|
+
banner("#{provider.download_url}")
|
40
|
+
next unless builds_yml["slugs"].values.include?(box.name)
|
41
|
+
slug_provider = slug_ver.ensure_provider(k, nil)
|
42
|
+
banner("Uploading #{slug.name}/#{slug_ver.version}/#{slug_provider.name}...")
|
43
|
+
slug_provider.upload_file("builds/#{v['file']}")
|
44
|
+
banner("#{slug_provider.download_url}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|