Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add packer/bin/bento for building templates. #357

Merged
merged 2 commits into from
May 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
bin/
.bundle/
iso
*.box
Expand All @@ -7,3 +6,4 @@ packer_cache
packer.log
.DS_Store
/packer-*/
*.variables.json
226 changes: 226 additions & 0 deletions bin/bento
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#!/usr/bin/env ruby
# -*- encoding: utf-8 -*-

Signal.trap("INT") { exit 1 }

$stdout.sync = true
$stderr.sync = true

require 'optparse'
require 'ostruct'
require 'benchmark'

class Options

NAME = File.basename($0).freeze

def self.parse(args)
options = OpenStruct.new
options.templates = calculate_templates("*.json")

ENV['PACKER_CACHE_DIR'] = "packer_cache"

global = OptionParser.new do |opts|
opts.banner = "Usage: #{NAME} [SUBCOMMAND [options]]"
opts.separator ""
opts.separator <<-COMMANDS.gsub(/^ {8}/, "")
build : build one or more templates
fix : fix one or more templates
help : prints this help message
list : list all templates in project
COMMANDS
end

templates_argv_proc = proc { |options|
options.templates = calculate_templates(args) unless args.empty?

options.templates.each do |t|
if !File.exists?("#{t}.json")
$stderr.puts "File #{t}.json does not exist for template '#{t}'"
exit(1)
end
end
}

subcommand = {
help: {
parser: OptionParser.new {},
argv: proc { |options|
puts global
exit(0)
}
},
build: {
class: BuildRunner,
parser: OptionParser.new { |opts|
opts.banner = "Usage: #{NAME} build [options] TEMPLATE[ TEMPLATE ...]"

opts.on("-n", "--[no-]dry-run", "Dry run (what would happen)") do |opt|
options.dry_run = opt
end

opts.on("-d", "--[no-]debug", "Run packer with debug output") do |opt|
options.debug = opt
end

opts.on("-o BUILDS", "--only BUILDS", "Only build some Packer builds") do |opt|
options.builds = opt
end
},
argv: templates_argv_proc
},
fix: {
class: FixRunner,
parser: OptionParser.new { |opts|
opts.banner = "Usage: #{NAME} fix TEMPLATE[ TEMPLATE ...]"
},
argv: templates_argv_proc
},
list: {
class: ListRunner,
parser: OptionParser.new { |opts|
opts.banner = "Usage: #{NAME} list [TEMPLATE ...]"
},
argv: templates_argv_proc
}
}

global.order!
command = args.empty? ? :help : ARGV.shift.to_sym
subcommand.fetch(command).fetch(:parser).order!
subcommand.fetch(command).fetch(:argv).call(options)

options.command = command
options.klass = subcommand.fetch(command).fetch(:class)

options
end

def self.calculate_templates(globs)
Array(globs).
map { |glob| result = Dir.glob(glob); result.empty? ? glob : result }.
flatten.
sort.
delete_if { |file| file =~ /\.variables\./ }.
map { |template| template.sub(/\.json$/, '') }
end
end

module Common

def banner(msg)
puts "==> #{msg}"
end

def duration(total)
total = 0 if total.nil?
minutes = (total / 60).to_i
seconds = (total - (minutes * 60))
format("%dm%.2fs", minutes, seconds)
end
end

class BuildRunner

include Common

attr_reader :templates, :dry_run, :debug, :builds

def initialize(opts)
@templates = opts.templates
@dry_run = opts.dry_run
@debug = opts.debug
@builds = opts.builds
end

def start
banner("Starting build for templates: #{templates}")
time = Benchmark.measure do
templates.each { |template| packer(template) }
end
banner("Build finished in #{duration(time.real)}.")
end

def packer(template)
cmd = packer_cmd(template)
banner("[#{template}] Running: '#{cmd.join(' ')}'")
time = Benchmark.measure do
system(*cmd) or raise "[#{template}] Error building, exited #{$?}"
end
banner("[#{template}] Finished in #{duration(time.real)}.")
end

def packer_cmd(template)
vars = "#{template}.variables.json"
cmd = %W[packer build #{template}.json]
cmd.insert(2, "-var-file=#{vars}") if File.exist?(vars)
cmd.insert(2, "-only=#{builds}") if builds
cmd.insert(2, "-debug") if debug
cmd.insert(0, "echo") if dry_run
cmd
end

def git_sha
%x{git rev-parse --short HEAD}.strip
end
end

class FixRunner

include Common

attr_reader :templates

def initialize(opts)
@templates = opts.templates
end

def start
banner("Fixing for templates: #{templates}")
time = Benchmark.measure do
templates.each { |template| fix(template) }
end
banner("Fixing finished in #{duration(time.real)}.")
end

def fix(template)
output = %x{packer fix #{template}.json}
raise "[#{template}] Error fixing, exited #{$?}" if $?.exitstatus != 0
File.open("#{template}.json", "wb") { |file| file.write(output) }
end
end

class ListRunner

include Common

attr_reader :templates

def initialize(opts)
@templates = opts.templates
end

def start
templates.each { |template| puts template }
end
end

class Runner

attr_reader :options

def initialize(options)
@options = options
end

def start
options.klass.new(options).start
end
end

begin
Runner.new(Options.parse(ARGV)).start
rescue => ex
$stderr.puts ">>> #{ex.message}"
exit(($? && $?.exitstatus) || 99)
end