bento-ya 0.0.1

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