-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add packer/bin/bento for building templates.
This is a first-step addition of a small binary wrapper around Packer to help inject custom variables, inject metadata and re-normalize the templates using `packer fix`. In its present implementation, no external RubyGems are required in an attempt to maximize portability between platforms.
- Loading branch information
Showing
2 changed files
with
227 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
bin/ | ||
/bin/ | ||
.bundle/ | ||
iso | ||
*.box | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |