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,112 @@
1
+ require 'buildkit'
2
+
3
+ class BuildRemoteRunner
4
+ include Common
5
+
6
+ attr_reader :bento_version, :dry_run, :platforms, :queue, :token, :s3_endpoint, :s3_bucket
7
+
8
+ def initialize(opts)
9
+ @dry_run = opts.dry_run
10
+ @bento_version = opts.override_version
11
+ @platforms = opts.platforms
12
+ @org = ENV['BUILDKITE_ORG']
13
+ @queue = ENV['BUILDKITE_QUEUE']
14
+ @token = ENV['BUILDKITE_TOKEN']
15
+ @s3_endpoint ||= ENV['BENTO_S3_ENDPOINT'] # 'https://s3.amazonaws.com''
16
+ @s3_bucket ||= ENV['BENTO_S3_BUCKET'] # 'opscode-vm-bento''
17
+ end
18
+
19
+ def start
20
+ msg_pre = dry_run ? "DRY RUN: " : ""
21
+
22
+ banner("#{msg_pre}Scheduling builds for Bento Version: #{bento_version}")
23
+ time = Benchmark.measure do
24
+ builds['public'].each do |platform, versions|
25
+ versions.each do |version, archs|
26
+ archs.each do |arch|
27
+ builds['providers'].each do |provider|
28
+ build(platform, version, arch, provider, bento_version, dry_run)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ banner("#{msg_pre}Scheduling finished in #{duration(time.real)}.")
35
+ end
36
+
37
+ private
38
+
39
+ def bk_uri
40
+ "v1/organizations/#{org}"
41
+ end
42
+
43
+ def build(platform, version, arch, provider, bento_version, dry_run)
44
+ plat = platform.include?('omnios') ? "#{platform}-#{version}" : "#{platform}-#{version}-#{arch}"
45
+ atlas_name = /(.*)64/.match(arch) ? plat.chomp("-#{arch}") : plat
46
+ no_shared = builds['no_shared_folder'].include?("#{platform}-#{version}-#{arch}-#{provider}") || builds['no_shared_folder'].include?("#{platform}-#{version}-#{arch}-all")
47
+ test_shared = no_shared ? "0" : "1"
48
+ unless builds['broken'].include?("#{plat}-#{provider}") || builds['broken'].include?("#{plat}-all")
49
+ build = {
50
+ "commit"=> "HEAD",
51
+ "branch"=> "master",
52
+ "message"=> "#{plat}-#{provider}",
53
+ "env"=> {
54
+ "ATLAS_ORG" => atlas_org,
55
+ "ATLAS_TOKEN" => atlas_token,
56
+ "ATLAS_NAME" => atlas_name,
57
+ "AWS_ACCESS_KEY_ID" => ENV['AWS_ACCESS_KEY_ID'],
58
+ "AWS_SECRET_ACCESS_KEY" => ENV['AWS_SECRET_ACCESS_KEY'],
59
+ "AWS_REGION" => 'us-east-1',
60
+ "BENTO_TEST_SHARED_FOLDER" => test_shared,
61
+ "BENTO_UPLOAD" => bento_upload,
62
+ "BENTO_VERSION" => bento_version,
63
+ "BENTO_S3_ENDPOINT" => s3_endpoint,
64
+ "BENTO_S3_BUCKET" => s3_bucket,
65
+ "BENTO_PROVIDERS" => provider,
66
+ "PLATFORM" => plat,
67
+ }
68
+ }
69
+ puts " - #{plat}-#{provider}"
70
+ client.post("/#{bk_uri}/projects/bento-#{provider.chomp('-iso')}/builds", build) unless dry_run
71
+ end
72
+ end
73
+
74
+ def client
75
+ Buildkit.new(token: token)
76
+ end
77
+
78
+ def create_project
79
+ project = {
80
+ "name"=> project,
81
+ "repository"=>"git@github.com:chef/bento.git",
82
+ "env"=>{"BUILDKITE_BIN_PATH" => "/usr/local/opt/buildkite-agent/bin"},
83
+ "steps"=>
84
+ [{"type"=>"script",
85
+ "name"=>"build",
86
+ "command"=>"build.sh",
87
+ "agent_query_rules"=>["queue=#{queue}"]
88
+ }
89
+ ]
90
+ }
91
+
92
+ puts "Creating Project #{project['name']}"
93
+ client.post("/#{bk_uri}/projects", project)
94
+ end
95
+
96
+ def delete_project
97
+ banner("Deleting Project #{project['name']}")
98
+ client.delete("/#{bk_uri}/projects/#{project}")
99
+ end
100
+
101
+ def get_builders(provider)
102
+ organization = client.organization(org)
103
+ agents = organization.rels[:agents].get.data
104
+
105
+ banner("Available Builders for provider: #{provider}")
106
+ agents.each do |agent|
107
+ if agent[:meta_data].include? "#{provider.chomp('-iso')}=true"
108
+ puts " - #{agent[:name]}"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,70 @@
1
+ require 'bento/common'
2
+
3
+ class BuildMetadata
4
+ include Common
5
+
6
+ def initialize(template, build_timestamp, override_version)
7
+ @template = template
8
+ @build_timestamp = build_timestamp
9
+ @override_version = override_version
10
+ end
11
+
12
+ def read
13
+ {
14
+ name: name,
15
+ version: version,
16
+ build_timestamp: build_timestamp,
17
+ git_revision: git_revision,
18
+ #git_status: git_clean? ? "clean" : "dirty"
19
+ box_basename: box_basename,
20
+ template: template_vars.fetch("template", UNKNOWN),
21
+ cpus: cpus.to_s,
22
+ memory: memory.to_s,
23
+ }
24
+ end
25
+
26
+ private
27
+
28
+ UNKNOWN = "__unknown__".freeze
29
+
30
+ attr_reader :template, :build_timestamp, :override_version
31
+
32
+ def box_basename
33
+ "#{name.gsub("/", "__")}-#{version}"
34
+ end
35
+
36
+ def git_revision
37
+ sha = %x{git rev-parse HEAD}.strip
38
+ end
39
+
40
+ def git_clean?
41
+ %x{git status --porcelain}.strip.empty?
42
+ end
43
+
44
+ def merged_vars
45
+ @merged_vars ||= begin
46
+ if File.exist?("#{template}.variables.json")
47
+ template_vars.merge(JSON.load(IO.read("#{template}.variables.json")))
48
+ else
49
+ template_vars
50
+ end
51
+ end
52
+ end
53
+
54
+ def name
55
+ merged_vars.fetch("name", template)
56
+ end
57
+
58
+ def template_vars
59
+ @template_vars ||= JSON.load(IO.read("#{template}.json")).fetch("variables")
60
+ end
61
+
62
+ def version
63
+ if override_version
64
+ override_version
65
+ else
66
+ merged_vars.fetch("version", "#{UNKNOWN}.TIMESTAMP").
67
+ rpartition(".").first.concat(".#{build_timestamp}")
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,228 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ require 'bento/common'
5
+ require 'bento/build'
6
+ require 'bento/build_remote'
7
+ require 'bento/delete'
8
+ require 'bento/normalize'
9
+ require 'bento/release'
10
+ require 'bento/revoke'
11
+ require 'bento/test'
12
+ require 'bento/upload'
13
+
14
+
15
+ class Options
16
+
17
+ NAME = File.basename($0).freeze
18
+
19
+ def self.parse(args)
20
+ options = OpenStruct.new
21
+ options.templates = calculate_templates("*.json")
22
+
23
+ global = OptionParser.new do |opts|
24
+ opts.banner = "Usage: #{NAME} [SUBCOMMAND [options]]"
25
+ opts.separator ""
26
+ opts.separator <<-COMMANDS.gsub(/^ {8}/, "")
27
+ build : build one or more templates
28
+ build_remote : build one or more templates via buildkite
29
+ help : prints this help message
30
+ list : list all templates in project
31
+ normalize : normalize one or more templates
32
+ test : test one or more builds with kitchen
33
+ upload : upload one or more builds to Atlas and S3
34
+ release : release a version of a box on Atlas
35
+ revoke : revoke a version of a box on Atlas
36
+ delete : delete a version of a box from Atlas
37
+ COMMANDS
38
+ end
39
+
40
+ platforms_argv_proc = proc { |options|
41
+ options.platforms = builds['public'] unless args.empty?
42
+ }
43
+
44
+ templates_argv_proc = proc { |options|
45
+ options.templates = calculate_templates(args) unless args.empty?
46
+
47
+ options.templates.each do |t|
48
+ if !File.exists?("#{t}.json")
49
+ $stderr.puts "File #{t}.json does not exist for template '#{t}'"
50
+ exit(1)
51
+ end
52
+ end
53
+ }
54
+
55
+ box_version_argv_proc = proc { |options|
56
+ options.box = ARGV[0]
57
+ options.version = ARGV[1]
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 { |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("-d", "--[no-]debug", "Run packer with debug output") do |opt|
78
+ options.debug = opt
79
+ end
80
+
81
+ opts.on("-o BUILDS", "--only BUILDS", "Only build some Packer builds") do |opt|
82
+ options.builds = opt
83
+ end
84
+
85
+ opts.on("-e BUILDS", "--except BUILDS", "Build all Packer builds except these") do |opt|
86
+ options.except = opt
87
+ end
88
+
89
+ opts.on("-m MIRROR", "--mirror MIRROR", "Look for isos at MIRROR") do |opt|
90
+ options.mirror = opt
91
+ end
92
+
93
+ opts.on("-H", "--headless", "Run providers as headless") do |opt|
94
+ options.headless = opt
95
+ end
96
+
97
+ opts.on("-v VERSION", "--version VERSION", "Override the version set in the template") do |opt|
98
+ options.override_version = opt
99
+ end
100
+ },
101
+ argv: templates_argv_proc
102
+ },
103
+ build_remote: {
104
+ class: BuildRemoteRunner,
105
+ parser: OptionParser.new { |opts|
106
+ opts.banner = "Usage: #{NAME} build_remote [options] [PLATFORM ...]"
107
+
108
+ opts.on("-v VERSION", "--version VERSION", "Override the version set in the template") do |opt|
109
+ options.override_version = opt
110
+ end
111
+
112
+ opts.on("--dry-run", "Show me what you got") do |opt|
113
+ options.dry_run = opt
114
+ end
115
+ },
116
+ argv: platforms_argv_proc
117
+ },
118
+ list: {
119
+ class: ListRunner,
120
+ parser: OptionParser.new { |opts|
121
+ opts.banner = "Usage: #{NAME} list [TEMPLATE ...]"
122
+ },
123
+ argv: templates_argv_proc
124
+ },
125
+ normalize: {
126
+ class: NormalizeRunner,
127
+ parser: OptionParser.new { |opts|
128
+ opts.banner = "Usage: #{NAME} normalize TEMPLATE[ TEMPLATE ...]"
129
+
130
+ opts.on("-d", "--[no-]debug", "Run packer with debug output") do |opt|
131
+ options.debug = opt
132
+ end
133
+ },
134
+ argv: templates_argv_proc
135
+ },
136
+ test: {
137
+ class: TestRunner,
138
+ parser: OptionParser.new { |opts|
139
+ opts.banner = "Usage: #{NAME} test [options]"
140
+
141
+ opts.on("-f", "--shared-folder", "Enable shared folder") do |opt|
142
+ options.shared_folder = opt
143
+ end
144
+
145
+ opts.on("-p", "--provisioner PROVISIONER", "Use a specfic provisioner") do |opt|
146
+ options.provisioner = opt
147
+ end
148
+ },
149
+ argv: Proc.new {}
150
+ },
151
+ upload: {
152
+ class: UploadRunner,
153
+ parser: OptionParser.new { |opts|
154
+ opts.banner = "Usage: #{NAME} upload"
155
+ },
156
+ argv: box_version_argv_proc
157
+ },
158
+ release: {
159
+ class: ReleaseRunner,
160
+ parser: OptionParser.new { |opts|
161
+ opts.banner = "Usage: #{NAME} release BOX VERSION"
162
+ },
163
+ argv: box_version_argv_proc
164
+ },
165
+ revoke: {
166
+ class: RevokeRunner,
167
+ parser: OptionParser.new { |opts|
168
+ opts.banner = "Usage: #{NAME} revoke BOX VERSION"
169
+ },
170
+ argv: box_version_argv_proc
171
+ },
172
+ delete: {
173
+ class: DeleteRunner,
174
+ parser: OptionParser.new { |opts|
175
+ opts.banner = "Usage: #{NAME} delete BOX VERSION"
176
+ },
177
+ argv: box_version_argv_proc
178
+ }
179
+ }
180
+
181
+ global.order!
182
+ command = args.empty? ? :help : ARGV.shift.to_sym
183
+ subcommand.fetch(command).fetch(:parser).order!
184
+ subcommand.fetch(command).fetch(:argv).call(options)
185
+
186
+ options.command = command
187
+ options.klass = subcommand.fetch(command).fetch(:class)
188
+
189
+ options
190
+ end
191
+
192
+ def self.calculate_templates(globs)
193
+ Array(globs).
194
+ map { |glob| result = Dir.glob(glob); result.empty? ? glob : result }.
195
+ flatten.
196
+ sort.
197
+ delete_if { |file| file =~ /\.variables\./ }.
198
+ map { |template| template.sub(/\.json$/, '') }
199
+ end
200
+ end
201
+
202
+ class ListRunner
203
+
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
+
219
+ attr_reader :options
220
+
221
+ def initialize(options)
222
+ @options = options
223
+ end
224
+
225
+ def start
226
+ options.klass.new(options).start
227
+ end
228
+ end
@@ -0,0 +1,160 @@
1
+ require 'benchmark'
2
+ require 'fileutils'
3
+ require 'json'
4
+ require 'tempfile'
5
+ require 'yaml'
6
+
7
+ MEGABYTE = 1024.0 * 1024.0
8
+
9
+ module Common
10
+
11
+ def banner(msg)
12
+ puts "==> #{msg}"
13
+ end
14
+
15
+ def info(msg)
16
+ puts " #{msg}"
17
+ end
18
+
19
+ def warn(msg)
20
+ puts ">>> #{msg}"
21
+ end
22
+
23
+ def duration(total)
24
+ total = 0 if total.nil?
25
+ minutes = (total / 60).to_i
26
+ seconds = (total - (minutes * 60))
27
+ format("%dm%.2fs", minutes, seconds)
28
+ end
29
+
30
+ def box_metadata(metadata_file)
31
+ metadata = Hash.new
32
+ file = File.read(metadata_file)
33
+ json = JSON.parse(file)
34
+
35
+ # metadata needed for upload: boxname, version, provider, box filename
36
+ metadata['name'] = json['name']
37
+ metadata['version'] = json['version']
38
+ metadata['box_basename'] = json['box_basename']
39
+ metadata['tool_versions'] = json['box_basename']
40
+ metadata['providers'] = Hash.new
41
+ json['providers'].each do |provider|
42
+ metadata['providers'][provider['name']] = provider.reject { |k, _| k == 'name' }
43
+ end
44
+ metadata
45
+ end
46
+
47
+ def metadata_files
48
+ @metadata_files ||= compute_metadata_files
49
+ end
50
+
51
+ def compute_metadata_files
52
+ `ls builds/*.json`.split("\n")
53
+ end
54
+
55
+ def atlas_api
56
+ @atlas_api ||= 'https://atlas.hashicorp.com/api/v1'
57
+ end
58
+
59
+ def atlas_org
60
+ @atlas_org ||= ENV['ATLAS_ORG']
61
+ end
62
+
63
+ def atlas_token
64
+ @atlas_token ||= ENV['ATLAS_TOKEN']
65
+ end
66
+
67
+ def bento_upload
68
+ "1"
69
+ end
70
+
71
+ def bento_version
72
+ @bento_version ||= ENV['BENTO_VERSION']
73
+ end
74
+
75
+ def cpus
76
+ 1
77
+ end
78
+
79
+ def memory
80
+ 1024
81
+ end
82
+
83
+ def builds
84
+ YAML.load(File.read("builds.yml"))
85
+ end
86
+
87
+ def private_box?(boxname)
88
+ proprietary_os_list = %w(macosx sles solaris windows)
89
+ proprietary_os_list.any? { |p| boxname.include?(p) }
90
+ end
91
+
92
+ def tool_versions
93
+ tool_versions = Hash.new
94
+ path = File.join('/Applications/VMware\ Fusion.app/Contents/Library')
95
+ fusion_cmd = File.join(path, "vmware-vmx -v")
96
+ ver_hash = {
97
+ packer: "packer --version",
98
+ vagrant: "vagrant --version",
99
+ parallels: "prlctl --version",
100
+ virtualbox: "VBoxManage --version",
101
+ vmware_fusion: fusion_cmd
102
+ }
103
+ ver_hash.each do |tool, command|
104
+ cmd = Mixlib::ShellOut.new(command)
105
+ cmd.run_command
106
+ case tool
107
+ when /parallels/
108
+ ver = cmd.stdout.split(' ')[2]
109
+ # hash = cmd.stdout.split(' ')[3]
110
+ # ver = "#{semver} #{hash}"
111
+ when /fusion/
112
+ ver = cmd.stderr.split(' ')[5]
113
+ # hash = cmd.stderr.split(' ')[6].gsub(/^build-/,'')
114
+ # ver = "#{semver} \(#{hash}\)"
115
+ when /vagrant/
116
+ semver = cmd.stdout.split(' ')[1]
117
+ ver = "#{semver}"
118
+ when /virtualbox/
119
+ ver = cmd.stdout.split('r')[0]
120
+ # hash = cmd.stdout.split('r')[1].gsub(/\n/,'')
121
+ # ver = "#{semver} \(#{hash}\)"
122
+ else
123
+ ver = cmd.stdout.split("\n")[0]
124
+ end
125
+ tool_versions[tool] = ver
126
+ end
127
+ tool_versions
128
+ end
129
+ end
130
+
131
+ # module PackerExec
132
+
133
+ # def for_packer_run_with(template)
134
+ # Tempfile.open("#{template}-metadata.json") do |md_file|
135
+ # Tempfile.open("#{template}-metadata-var-file") do |var_file|
136
+ # write_box_metadata(template, md_file)
137
+ # yield md_file, var_file
138
+ # end
139
+ # end
140
+ # end
141
+
142
+ # def write_box_metadata(template, io)
143
+ # md = BuildMetadata.new(template, build_timestamp, override_version).read
144
+ # io.write(JSON.pretty_generate(md))
145
+ # io.close
146
+ # end
147
+
148
+ # # def write_var_file(template, md_file, io)
149
+ # # md = BuildMetadata.new(template, build_timestamp, override_version).read
150
+
151
+ # # io.write(JSON.pretty_generate({
152
+ # # box_basename: md[:box_basename],
153
+ # # build_timestamp: md[:build_timestamp],
154
+ # # git_revision: md[:git_revision],
155
+ # # metadata: md_file.path,
156
+ # # version: md[:version],
157
+ # # }))
158
+ # # io.close
159
+ # # end
160
+ # end