bento-ya 0.0.1

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.
@@ -0,0 +1,38 @@
1
+ require 'bento/common'
2
+ require 'bento/httpstuff'
3
+
4
+ class DeleteRunner
5
+ include Common
6
+ include HttpStuff
7
+
8
+ attr_reader :boxname, :version
9
+
10
+ def initialize(opts)
11
+ @boxname = opts.box
12
+ @version = opts.version
13
+ end
14
+
15
+ def start
16
+ banner("Starting Delete...")
17
+ time = Benchmark.measure do
18
+ delete_version(boxname, version)
19
+ end
20
+ banner("Delete finished in #{duration(time.real)}.")
21
+ end
22
+
23
+ private
24
+
25
+ def delete_version(boxname, version)
26
+ banner("Deleting version #{version} of box #{boxname}")
27
+ req = request('delete', "#{atlas_api}/box/#{atlas_org}/#{boxname}/version/#{version}", { 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' })
28
+
29
+ case req.code
30
+ when '200'
31
+ banner("Version #{version} of box #{boxname} has been successfully deleted")
32
+ when '404'
33
+ warn("No box exists for this version")
34
+ else
35
+ warn("Something went wrong #{req.code}")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,73 @@
1
+ require 'net/http'
2
+
3
+ module HttpStuff
4
+
5
+ def class_for_request(verb)
6
+ Net::HTTP.const_get(verb.to_s.capitalize)
7
+ end
8
+
9
+ def build_uri(verb, path, params = {})
10
+ if %w(delete, get).include?(verb)
11
+ path = [path, to_query_string(params)].compact.join('?')
12
+ end
13
+
14
+ # Parse the URI
15
+ uri = URI.parse(path)
16
+
17
+ # Don't merge absolute URLs
18
+ uri = URI.parse(File.join(endpoint, path)) unless uri.absolute?
19
+
20
+ # Return the URI object
21
+ uri
22
+ end
23
+
24
+ def to_query_string(hash)
25
+ hash.map do |key, value|
26
+ "#{CGI.escape(key)}=#{CGI.escape(value)}"
27
+ end.join('&')[/.+/]
28
+ end
29
+
30
+ def request(verb, url, data = {}, headers = {})
31
+ uri = build_uri(verb, url, data)
32
+
33
+ # Build the request.
34
+ request = class_for_request(verb).new(uri.request_uri)
35
+ if %w(patch post put delete).include?(verb)
36
+ if data.respond_to?(:read)
37
+ request.content_length = data.size
38
+ request.body_stream = data
39
+ elsif data.is_a?(Hash)
40
+ request.form_data = data
41
+ else
42
+ request.body = data
43
+ end
44
+ end
45
+
46
+ # Add headers
47
+ headers.each do |key, value|
48
+ request.add_field(key, value)
49
+ end
50
+
51
+ connection = Net::HTTP.new(uri.host, uri.port)
52
+
53
+ if uri.scheme == 'https'
54
+ require 'net/https' unless defined?(Net::HTTPS)
55
+
56
+ # Turn on SSL
57
+ connection.use_ssl = true
58
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
59
+ end
60
+
61
+ connection.start do |http|
62
+ response = http.request(request)
63
+
64
+ case response
65
+ when Net::HTTPRedirection
66
+ redirect = URI.parse(response['location'])
67
+ request(verb, redirect, data, headers)
68
+ else
69
+ response
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,81 @@
1
+ require 'bento/common'
2
+ require 'mixlib/shellout'
3
+
4
+ class NormalizeRunner
5
+
6
+ include Common
7
+ include PackerExec
8
+
9
+ attr_reader :templates, :build_timestamp, :debug, :override_version
10
+
11
+ def initialize(opts)
12
+ @templates = opts.templates
13
+ @debug = opts.debug
14
+ @modified = []
15
+ @build_timestamp = Time.now.gmtime.strftime("%Y%m%d%H%M%S")
16
+ end
17
+
18
+ def start
19
+ banner("Normalizing for templates: #{templates}")
20
+ time = Benchmark.measure do
21
+ templates.each do |template|
22
+ validate(template)
23
+ fix(template)
24
+ end
25
+ end
26
+ if !@modified.empty?
27
+ info("")
28
+ info("The following templates were modified:")
29
+ @modified.sort.each { |template| info(" * #{template}")}
30
+ end
31
+ banner("Normalizing finished in #{duration(time.real)}.")
32
+ end
33
+
34
+ private
35
+
36
+ def checksum(file)
37
+ Digest::MD5.file(file).hexdigest
38
+ end
39
+
40
+ def fix(template)
41
+ file = "#{template}.json"
42
+
43
+ banner("[#{template}] Fixing")
44
+ original_checksum = checksum(file)
45
+ output = %x{packer fix #{file}}
46
+ raise "[#{template}] Error fixing, exited #{$?}" if $?.exitstatus != 0
47
+ # preserve ampersands in shell commands,
48
+ # see: https://github.com/mitchellh/packer/issues/784
49
+ output.gsub!("\\u0026", "&")
50
+ File.open(file, "wb") { |dest| dest.write(output) }
51
+ fixed_checksum = checksum(file)
52
+
53
+ if original_checksum == fixed_checksum
54
+ puts("No changes made.")
55
+ else
56
+ warn("Template #{template} has been modified.")
57
+ @modified << template
58
+ end
59
+ end
60
+
61
+ def packer_validate_cmd(template, var_file)
62
+ vars = "#{template}.variables.json"
63
+ cmd = %W[packer validate -var-file=#{var_file} #{template}.json]
64
+ cmd.insert(2, "-var-file=#{vars}") if File.exist?(vars)
65
+ cmd
66
+ end
67
+
68
+ def validate(template)
69
+ for_packer_run_with(template) do |md_file, var_file|
70
+ cmd = packer_validate_cmd(template, var_file.path)
71
+ banner("[#{template}] Validating: '#{cmd.join(' ')}'")
72
+ if debug
73
+ banner("[#{template}] DEBUG: var_file(#{var_file.path}) is:")
74
+ puts IO.read(var_file.path)
75
+ banner("[#{template}] DEBUG: md_file(#{md_file.path}) is:")
76
+ puts IO.read(md_file.path)
77
+ end
78
+ system(*cmd) or raise "[#{template}] Error validating, exited #{$?}"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+
2
+ module PackerExec
3
+
4
+ def for_packer_run_with(template)
5
+ Tempfile.open("#{template}-metadata.json") do |md_file|
6
+ Tempfile.open("#{template}-metadata-var-file") do |var_file|
7
+ write_box_metadata(template, md_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,41 @@
1
+ require 'digest'
2
+
3
+ class ProviderMetadata
4
+
5
+ def initialize(path, box_basename)
6
+ @base = File.join(path, box_basename)
7
+ end
8
+
9
+ def read
10
+ Dir.glob("#{base}.*.box").map do |file|
11
+ {
12
+ name: provider_from_file(file),
13
+ file: "#{File.basename(file)}",
14
+ checksum_type: "sha256",
15
+ checksum: shasum(file),
16
+ size: "#{size_in_mb(file)} MB",
17
+ }
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :base
24
+
25
+ def provider_from_file(file)
26
+ case provider = file.sub(/^.*\.([^.]+)\.box$/, '\1')
27
+ when /vmware/i then "vmware_desktop"
28
+ else provider
29
+ end
30
+ end
31
+
32
+ def shasum(file)
33
+ Digest::SHA256.file(file).hexdigest
34
+ end
35
+
36
+ def size_in_mb(file)
37
+ size = File.size(file)
38
+ size_mb = size / MEGABYTE
39
+ size_mb.ceil.to_s
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ require 'bento/common'
2
+ require 'mixlib/shellout'
3
+
4
+ class ReleaseRunner
5
+ include Common
6
+ include HttpStuff
7
+
8
+ attr_reader :boxname, :version
9
+
10
+ def initialize(opts)
11
+ @boxname = opts.box
12
+ @version = opts.version
13
+ end
14
+
15
+ def start
16
+ banner("Starting Release...")
17
+ time = Benchmark.measure do
18
+ release_version(boxname, version)
19
+ end
20
+ banner("Release finished in #{duration(time.real)}.")
21
+ end
22
+
23
+ private
24
+
25
+ def release_version(boxname, version)
26
+ case status(boxname, version)
27
+ when 'unreleased'
28
+ banner("Releasing version #{version} of box #{boxname}")
29
+ req = request('put', "#{atlas_api}/box/#{atlas_org}/#{boxname}/version/#{version}/release", { 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' })
30
+ if req.code == '200'
31
+ banner("Version #{version} of box #{boxname} has been successfully released")
32
+ else
33
+ warn("Something went wrong #{req.code}")
34
+ end
35
+ when 'active'
36
+ banner("Version #{version} of box #{boxname} has already been released - nothing to do")
37
+ else
38
+ warn("Unexpected status retrieved from Atlas")
39
+ end
40
+ end
41
+
42
+ def status(boxname, version)
43
+ req = request('get', "#{atlas_api}/box/#{atlas_org}/#{boxname}/version/#{version}", { 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' })
44
+ status = JSON.parse(req.body)['status']
45
+ end
46
+ end
@@ -0,0 +1,34 @@
1
+ require 'bento/common'
2
+ require 'mixlib/shellout'
3
+
4
+ class RevokeRunner
5
+ include Common
6
+ include HttpStuff
7
+
8
+ attr_reader :boxname, :version
9
+
10
+ def initialize(opts)
11
+ @boxname = opts.box
12
+ @version = opts.version
13
+ end
14
+
15
+ def start
16
+ banner("Starting Revoke...")
17
+ time = Benchmark.measure do
18
+ revoke_version(boxname, version)
19
+ end
20
+ banner("Revoke finished in #{duration(time.real)}.")
21
+ end
22
+
23
+ private
24
+
25
+ def revoke_version(boxname, version)
26
+ banner("Revoking version #{version} of box #{boxname}")
27
+ req = request('put', "#{atlas_api}/box/#{atlas_org}/#{boxname}/version/#{version}/revoke", { 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' })
28
+ if req.code == '200'
29
+ banner("Version #{version} of box #{boxname} has been successfully revoked")
30
+ else
31
+ banner("Something went wrong #{req.code}")
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,71 @@
1
+ require 'bento/common'
2
+ require 'kitchen'
3
+
4
+ class TestRunner
5
+ include Common
6
+
7
+ attr_reader :shared_folder, :boxname, :provider, :box_url, :share_disabled, :provisioner
8
+
9
+ def initialize(opts)
10
+ @debug = opts.debug
11
+ @shared_folder = opts.shared_folder
12
+ @provisioner = opts.provisioner.nil? ? "shell" : opts.provisioner
13
+ end
14
+
15
+ def start
16
+ banner("Starting testing...")
17
+ time = Benchmark.measure do
18
+ metadata_files.each do |metadata_file|
19
+ m = box_metadata(metadata_file)
20
+ destroy_all_bento
21
+ test_box(m['name'], m['providers'])
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(boxname, providers)
44
+ providers.each do |provider, provider_data|
45
+
46
+ if provider == 'vmware_desktop'
47
+ case RUBY_PLATFORM
48
+ when /darwin/
49
+ provider = 'vmware_fusion'
50
+ when /linux/
51
+ provider = 'vmware_workstation'
52
+ end
53
+ end
54
+
55
+ @boxname = boxname
56
+ @provider = provider
57
+ @share_disabled = shared_folder ? false : true
58
+ @box_url = "file://#{ENV['PWD']}/builds/#{provider_data['file']}"
59
+
60
+ kitchen_cfg = ERB.new(File.read('.kitchen.yml.erb'), nil, '-').result(binding)
61
+ File.open(".kitchen.#{provider}.yml", "w") { |f| f.puts kitchen_cfg }
62
+
63
+ Kitchen.logger = Kitchen.default_file_logger
64
+ @loader = Kitchen::Loader::YAML.new(project_config: "./.kitchen.#{provider}.yml")
65
+ config = Kitchen::Config.new(loader: @loader)
66
+ config.instances.each do |instance|
67
+ instance.test(:always)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,118 @@
1
+ require 'aws-sdk'
2
+ require 'bento/common'
3
+
4
+ class UploadRunner
5
+ include Common
6
+ include HttpStuff
7
+
8
+ attr_reader :templates
9
+
10
+ def initialize(opts)
11
+ @templates = opts.templates
12
+ end
13
+
14
+ def start
15
+ banner("Starting uploads...")
16
+ time = Benchmark.measure do
17
+ metadata_files.each do |file|
18
+ md = box_metadata(file)
19
+ create_box(md['name'])
20
+ create_box_version(md['name'], md['version'], file)
21
+ create_providers(md['name'], md['version'], md['providers'].keys)
22
+ upload_to_atlas(md['name'], md['version'], md['providers'])
23
+ #upload_to_s3(md['name'], md['version'], md['providers'])
24
+ end
25
+ end
26
+ banner("Atlas uploads finished in #{duration(time.real)}.")
27
+ end
28
+
29
+ private
30
+
31
+ def create_box(boxname)
32
+ req = request('get', "#{atlas_api}/box/#{atlas_org}/#{boxname}", { 'box[username]' => atlas_org, 'access_token' => atlas_token } )
33
+ if req.code.eql?('404')
34
+ if private_box?(boxname)
35
+ banner("Creating the private box #{boxname} in Atlas.")
36
+ req = request('post', "#{atlas_api}/boxes", { 'box[name]' => boxname, 'box[username]' => atlas_org, 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' } )
37
+ else
38
+ banner("Creating the box #{boxname} in Atlas.")
39
+ req = request('post', "#{atlas_api}/boxes", { 'box[name]' => boxname, 'box[username]' => atlas_org, 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' } )
40
+ make_public(boxname)
41
+ end
42
+ else
43
+ banner("The box #{boxname} exists in Atlas, continuing...")
44
+ end
45
+ end
46
+
47
+ def make_public(boxname)
48
+ banner("Making #{boxname} public")
49
+ req = request('put', "#{atlas_api}/box/#{atlas_org}/#{boxname}", { 'box[is_private]' => false, 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' } )
50
+ banner("#{boxname} successfully made public") if req.code == '200'
51
+ end
52
+
53
+ def create_box_version(boxname, version, md_json)
54
+ payload = {
55
+ 'version[version]' => version,
56
+ 'access_token' => atlas_token,
57
+ 'version[description]' => File.read(md_json)
58
+ }
59
+ req = request('post', "#{atlas_api}/box/#{atlas_org}/#{boxname}/versions", payload, { 'Content-Type' => 'application/json' } )
60
+
61
+ banner("Created box version #{boxname} #{version}.") if req.code == '200'
62
+ banner("Box version #{boxname} #{version} already exists, continuing.") if req.code == '422'
63
+ end
64
+
65
+ def create_providers(boxname, version, provider_names)
66
+ provider_names.each do |provider|
67
+ banner("Creating provider #{provider} for #{boxname} #{version}")
68
+ req = request('post', "#{atlas_api}/box/#{atlas_org}/#{boxname}/version/#{version}/providers", { 'provider[name]' => provider, 'access_token' => atlas_token }, { 'Content-Type' => 'application/json' } )
69
+ banner("Created #{provider} for #{boxname} #{version}") if req.code == '200'
70
+ banner("Provider #{provider} for #{boxname} #{version} already exists, continuing.") if req.code == '422'
71
+ end
72
+ end
73
+
74
+ def upload_to_atlas(boxname, version, providers)
75
+ providers.each do |provider, provider_data|
76
+ boxfile = provider_data['file']
77
+ req = request('get', "#{atlas_api}/box/#{atlas_org}/#{boxname}/version/#{version}/provider/#{provider}/upload?access_token=#{atlas_token}")
78
+ upload_path = JSON.parse(req.body)['upload_path']
79
+ token = JSON.parse(req.body)['token']
80
+
81
+ banner("Atlas: Uploading #{boxfile}")
82
+ info("Name: #{boxname}")
83
+ info("Version: #{version}")
84
+ info("Provider: #{provider}")
85
+ info("Upload Path: #{upload_path}")
86
+ upload_request = request('put', upload_path, File.open("builds/#{boxfile}"))
87
+
88
+ req = request('get', "#{atlas_api}/box/#{atlas_org}/#{boxname}/version/#{version}/provider/#{provider}?access_token=#{atlas_token}")
89
+ hosted_token = JSON.parse(req.body)['hosted_token']
90
+
91
+ if token == hosted_token
92
+ banner("Successful upload of box #{boxfile}")
93
+ else
94
+ banner("Failed upload due to non-matching tokens of box #{boxfile} to atlas box: #{boxname}, version: #{version}, provider: #{provider}")
95
+ warn("Code: #{req.code}")
96
+ warn("Body: #{req.body}")
97
+ end
98
+ end
99
+
100
+ def upload_to_s3(boxname, version, providers)
101
+ providers.each do |provider, provider_data|
102
+ boxfile = provider_data['file']
103
+ provider = 'vmware' if provider == 'vmware_desktop'
104
+ box_path = "vagrant/#{provider}/opscode_#{boxname}_chef-provisionerless.box"
105
+ credentials = Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
106
+
107
+ s3 = Aws::S3::Resource.new(credentials: credentials, endpoint: s3_endpoint)
108
+ banner("S3: Uploading #{boxfile}")
109
+ info("Name: #{boxname}")
110
+ info("Version: #{version}")
111
+ info("Provider: #{provider}")
112
+ s3_object = s3.bucket(s3_bucket).object(box_path)
113
+ s3_object.upload_file("builds/#{boxfile}", acl:'public-read')
114
+ banner("Upload Path: #{s3_object.public_url}")
115
+ end
116
+ end
117
+ end
118
+ end