racker 0.1.6 → 0.2.0

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.
@@ -2,12 +2,15 @@
2
2
  require 'optparse'
3
3
  require 'racker/processor'
4
4
  require 'racker/version'
5
- require 'log4r'
6
5
 
7
6
  module Racker
8
7
  # The CLI is a class responsible for handling the command line interface
9
8
  # logic.
10
9
  class CLI
10
+ include Racker::LogSupport
11
+
12
+ STDOUT_TOKEN = '-'
13
+
11
14
  attr_reader :options
12
15
 
13
16
  def initialize(argv)
@@ -15,74 +18,60 @@ module Racker
15
18
  end
16
19
 
17
20
  def execute!
18
- # Get the global logger
19
- log = Log4r::Logger['racker']
20
-
21
21
  # Parse our arguments
22
22
  option_parser.parse!(@argv)
23
23
 
24
24
  # Set the logging level specified by the command line
25
- log.level = get_log4r_level(options[:log_level])
26
- log.info("Log level set to: #{options[:log_level]}")
25
+ Racker::LogSupport.level = options[:log_level]
27
26
 
28
27
  # Display the options if a minimum of 1 template and an output file is not provided
29
28
  if @argv.length < 2
30
- puts option_parser
29
+ puts option_parser
31
30
  Kernel.exit!(1)
32
31
  end
33
32
 
34
- # Set the output file to the last arg
35
- options[:output] = @argv.pop
36
- log.debug("Output file set to: #{options[:output]}")
33
+ # Set the output file to the last arg. A single dash can be supplied to
34
+ # indicate that the compiled template should be written to STDOUT. Output
35
+ # to STDOUT assumes the quiet option.
36
+ options[:output] = output = @argv.pop
37
+ logger.debug("Output file set to: #{output}")
38
+
39
+ # Output to STDOUT assumes quiet mode
40
+ @options[:quiet] = true if output == STDOUT_TOKEN
37
41
 
38
42
  # Set the input files to the remaining args
39
43
  options[:templates] = @argv
40
44
 
41
45
  # Run through Racker
42
- log.debug('Executing the Racker Processor...')
43
- Processor.new(options).execute!
44
- log.debug('Processing complete.')
46
+ logger.debug('Executing the Racker Processor...')
47
+ template = Processor.new(options).execute!
48
+
49
+ write(output, template)
45
50
 
46
51
  # Thats all folks!
52
+ logger.debug('Processing complete.')
47
53
  puts "Processing complete!" unless options[:quiet]
48
54
  puts "Packer file generated: #{options[:output]}" unless options[:quiet]
49
55
 
50
56
  return 0
51
57
  end
52
-
53
- private
54
58
 
55
- def get_log4r_level(level)
56
- case level
57
- when /fatal/
58
- Log4r::FATAL
59
- when /error/
60
- Log4r::ERROR
61
- when /warn/
62
- Log4r::WARN
63
- when /info/
64
- Log4r::INFO
65
- when /debug/
66
- Log4r::DEBUG
67
- else
68
- Log4r::INFO
69
- end
70
- end
59
+ private
71
60
 
72
61
  def options
73
62
  @options ||= {
74
63
  log_level: :warn,
75
- knockout: '~~',
64
+ knockout: '~~',
76
65
  output: '',
77
66
  templates: [],
78
- quiet: false,
67
+ quiet: false,
79
68
  }
80
69
  end
81
70
 
82
71
  def option_parser
83
72
  @option_parser ||= OptionParser.new do |opts|
84
73
  opts.banner = "Usage: #{opts.program_name} [options] [TEMPLATE1, TEMPLATE2, ...] OUTPUT"
85
-
74
+
86
75
  opts.on('-l', '--log-level [LEVEL]', [:fatal, :error, :warn, :info, :debug], 'Set log level') do |v|
87
76
  options[:log_level] = v
88
77
  end
@@ -106,5 +95,41 @@ module Racker
106
95
  end
107
96
  end
108
97
  end
98
+
99
+ private
100
+
101
+ def write(output_path, template)
102
+ if output_path == STDOUT_TOKEN
103
+ write_to_stdout(template)
104
+ else
105
+ write_to_file(template, output_path)
106
+ end
107
+ true
108
+ end
109
+
110
+ def write_to_file(template, path)
111
+ path = File.expand_path(path)
112
+ output_dir = File.dirname(path)
113
+
114
+ # Create output directory if it does not exist
115
+ unless File.directory?(output_dir)
116
+ logger.info(%Q[Creating output directory "#{output_dir}"])
117
+ FileUtils.mkdir_p(output_dir)
118
+ end
119
+
120
+ File.open(path, 'w') { |file| write_to_stream(template, file, path) }
121
+ end
122
+
123
+ def write_to_stdout(template)
124
+ write_to_stream(template, $stdout, :STDOUT)
125
+ end
126
+
127
+ def write_to_stream(template, stream, stream_name)
128
+ logger.info("Writing packer template to #{stream_name}")
129
+ stream.write(template)
130
+ stream.flush if stream.respond_to?(:flush)
131
+ logger.info("Writing packer template to #{stream_name} complete.")
132
+ end
133
+
109
134
  end
110
135
  end
@@ -0,0 +1,47 @@
1
+ require 'log4r'
2
+
3
+ module Racker
4
+ module LogSupport
5
+ unless Log4r::Logger['racker']
6
+ # Create the initial logger
7
+ logger = Log4r::Logger.new('racker')
8
+
9
+ # Set the output to STDOUT
10
+ logger.outputters = Log4r::Outputter.stdout
11
+
12
+ # We set the initial log level to ERROR
13
+ logger.level = Log4r::ERROR
14
+ end
15
+
16
+ def self.level=(level)
17
+ log_level = log4r_level_for(level)
18
+ logger.level = log_level
19
+ logger.info("Log level set to: #{log_level}")
20
+ end
21
+
22
+ def self.log4r_level_for(level)
23
+ case level
24
+ when /fatal/
25
+ Log4r::FATAL
26
+ when /error/
27
+ Log4r::ERROR
28
+ when /warn/
29
+ Log4r::WARN
30
+ when /info/
31
+ Log4r::INFO
32
+ when /debug/
33
+ Log4r::DEBUG
34
+ else
35
+ Log4r::INFO
36
+ end
37
+ end
38
+
39
+ def self.logger
40
+ Log4r::Logger['racker']
41
+ end
42
+
43
+ def logger
44
+ Racker::LogSupport.logger
45
+ end
46
+ end
47
+ end
@@ -8,6 +8,7 @@ require 'pp'
8
8
  module Racker
9
9
  # This class handles command line options.
10
10
  class Processor
11
+ include Racker::LogSupport
11
12
 
12
13
  CONFIGURE_MUTEX = Mutex.new
13
14
 
@@ -16,30 +17,20 @@ module Racker
16
17
  end
17
18
 
18
19
  def execute!
19
- # Get the global logger
20
- log = Log4r::Logger['racker']
21
-
22
20
  # Verify that the templates exist
23
21
  @options[:templates].each do |template|
24
22
  raise "File does not exist! (#{template})" unless ::File.exists?(template)
25
23
  end
26
24
 
27
- # Check that the output directory exists
28
- output_dir = File.dirname(File.expand_path(@options[:output]))
29
-
30
- # If the output directory doesnt exist
31
- log.info('Creating the output directory if it does not exist...')
32
- FileUtils.mkdir_p output_dir unless File.exists? output_dir
33
-
34
25
  # Load the templates
35
26
  templates = []
36
27
 
37
28
  # Load the template procs
38
- log.info('Loading racker templates...')
29
+ logger.info('Loading racker templates...')
39
30
  template_procs = load(@options[:templates])
40
31
 
41
32
  # Load the actual templates
42
- log.info('Processing racker templates...')
33
+ logger.info('Processing racker templates...')
43
34
  template_procs.each do |version,proc|
44
35
  # Create the new template
45
36
  template = Racker::Template.new
@@ -50,27 +41,23 @@ module Racker
50
41
  # Store the template
51
42
  templates << template
52
43
  end
53
- log.info('Racker template processing complete.')
44
+ logger.info('Racker template processing complete.')
54
45
 
55
46
  # Get the first template and merge each subsequent one on the latest
56
- log.info('Merging racker templates...')
47
+ logger.info('Merging racker templates...')
57
48
  current_template = templates.shift
58
49
 
59
50
  # Overlay the templates
60
51
  templates.each do |template|
61
52
  current_template = current_template.deep_merge!(template, {:knockout_prefix => @options[:knockout]})
62
53
  end
63
-
54
+
64
55
  # Compact the residual template to remove nils
65
- log.info('Compacting racker template...')
56
+ logger.info('Compacting racker template...')
66
57
  compact_template = current_template.compact(:recurse => true)
67
58
 
68
- # Write the compact template out to file
69
- File.open(@options[:output], 'w') do |file|
70
- log.info('Writing packer template...')
71
- file.write(JSON.pretty_generate(compact_template.to_packer))
72
- log.info('Writing packer template complete.')
73
- end
59
+ # Pretty-print the JSON template
60
+ JSON.pretty_generate(compact_template.to_packer)
74
61
  end
75
62
 
76
63
  def load(templates)
@@ -98,4 +85,4 @@ module Racker
98
85
  end
99
86
  end
100
87
  end
101
- end
88
+ end
@@ -62,7 +62,7 @@ module DeepMergeModified
62
62
  # dest = {:x => [{:z => 2}]}
63
63
  # dest.deep_merge!(source, {:merge_hash_arrays => true})
64
64
  # Results: {:x => [{:y => 1, :z => 2}]}
65
- #
65
+ #
66
66
  # There are many tests for this library - and you can learn more about the features
67
67
  # and usages of deep_merge! by just browsing the test examples
68
68
  def self.deep_merge!(source, dest, options = {})
@@ -7,6 +7,7 @@ require 'racker/builders/docker'
7
7
  require 'racker/builders/google'
8
8
  require 'racker/builders/null'
9
9
  require 'racker/builders/openstack'
10
+ require 'racker/builders/parallels'
10
11
  require 'racker/builders/qemu'
11
12
  require 'racker/builders/virtualbox'
12
13
  require 'racker/builders/vmware'
@@ -14,22 +15,21 @@ require 'racker/builders/vmware'
14
15
  module Racker
15
16
  # This class handles the bulk of the legwork working with Racker templates
16
17
  class Template < Smash
18
+ include Racker::LogSupport
19
+
17
20
  # This formats the template into packer format hash
18
21
  def to_packer
19
- # Get the global logger
20
- log = Log4r::Logger['racker']
21
-
22
22
  # Create the new smash
23
23
  packer = Smash.new
24
24
 
25
25
  # Variables
26
26
  packer['variables'] = self['variables'].dup unless self['variables'].nil? || self['variables'].empty?
27
-
27
+
28
28
  # Builders
29
29
  packer['builders'] = [] unless self['builders'].nil? || self['builders'].empty?
30
- log.info("Processing builders...")
30
+ logger.info("Processing builders...")
31
31
  self['builders'].each do |name,config|
32
- log.info("Processing builder: #{name} with type: #{config['type']}")
32
+ logger.info("Processing builder: #{name} with type: #{config['type']}")
33
33
 
34
34
  # Get the builder for this config
35
35
  builder = get_builder(config['type'])
@@ -40,19 +40,19 @@ module Racker
40
40
 
41
41
  # Provisioners
42
42
  packer['provisioners'] = [] unless self['provisioners'].nil? || self['provisioners'].empty?
43
- log.info("Processing provisioners...")
43
+ logger.info("Processing provisioners...")
44
44
  self['provisioners'].sort.map do |index, provisioners|
45
45
  provisioners.each do |name,config|
46
- log.debug("Processing provisioner: #{name}")
46
+ logger.debug("Processing provisioner: #{name}")
47
47
  packer['provisioners'] << config.dup
48
48
  end
49
49
  end
50
50
 
51
51
  # Post-Processors
52
52
  packer['post-processors'] = [] unless self['postprocessors'].nil? || self['postprocessors'].empty?
53
- log.info("Processing post-processors...")
53
+ logger.info("Processing post-processors...")
54
54
  self['postprocessors'].each do |name,config|
55
- log.debug("Processing post-processor: #{name}")
55
+ logger.debug("Processing post-processor: #{name}")
56
56
  packer['post-processors'] << config.dup unless config.nil?
57
57
  end
58
58
 
@@ -4,8 +4,8 @@ module Racker
4
4
  # This defines the version of the gem
5
5
  module Version
6
6
  MAJOR = 0
7
- MINOR = 1
8
- PATCH = 6
7
+ MINOR = 2
8
+ PATCH = 0
9
9
  BUILD = nil
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
@@ -0,0 +1,6 @@
1
+ Racker::Processor.register_template do |t|
2
+ t.variables = {
3
+ :iso_url => 'priority.img',
4
+ :password => '~~',
5
+ }
6
+ end
@@ -0,0 +1,7 @@
1
+ Racker::Processor.register_template do |t|
2
+ t.variables = {
3
+ :iso_url => 'os.img',
4
+ :password => 'password',
5
+ :nil => nil,
6
+ }
7
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe :output_to_file do
4
+ before(:all) do
5
+ @output_path = "/tmp/#{SecureRandom.uuid}/this_directory_should_not_exist/template.json"
6
+ @instance = Racker::CLI.new(['-q', fixture_path('low_priority_template.rb'), @output_path])
7
+ end
8
+
9
+ context 'when successful' do
10
+ it 'writes the computed template to the given path' do
11
+ output_dir = File.dirname(@output_path)
12
+ FileUtils.rm_rf(output_dir) if Dir.exists?(output_dir)
13
+
14
+ @instance.execute!
15
+ expect(File.exists?(@output_path)).to eq(true)
16
+
17
+ result = JSON.parse(File.read(@output_path))
18
+ expect(result).to eq(parsed_low_priority_template)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe :output_to_stdout do
4
+ before(:all) do
5
+ @instance = Racker::CLI.new([fixture_path('low_priority_template.rb'), '-'])
6
+ end
7
+
8
+ context 'when successful' do
9
+ it 'writes the computed template to $stdout' do
10
+ pretty_output = JSON.pretty_generate(parsed_low_priority_template)
11
+ expect(@instance).to receive(:puts).never
12
+ expect($stdout).to receive(:write).with(pretty_output)
13
+ @instance.execute!
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'racker'
2
+
3
+ class RSpec::Core::ExampleGroup
4
+ FIXTURE_DIR = File.expand_path('../fixtures', __FILE__)
5
+ PARSED_LOW_PRIORITY_TEMPLATE = {
6
+ 'variables' => {
7
+ 'iso_url' => 'os.img',
8
+ 'password' => 'password',
9
+ },
10
+ }.freeze
11
+
12
+ def fixture_path(filename)
13
+ File.join(FIXTURE_DIR, filename.to_s)
14
+ end
15
+
16
+ def parsed_low_priority_template
17
+ PARSED_LOW_PRIORITY_TEMPLATE
18
+ end
19
+ end
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Racker::CLI do
4
+
5
+ context '#execute!' do
6
+ it 'exits with a status of 1 if fewer than 2 arguments were received' do
7
+ allow(Kernel).to receive(:exit!)
8
+
9
+ instance = Racker::CLI.new(['template.rb'])
10
+ allow(instance).to receive(:puts)
11
+
12
+ # This next call is going to break somewhere because we stubbed the exit!
13
+ # call, so catch any error that occurs.
14
+ instance.execute! rescue nil
15
+
16
+ # set expextation on puts to silence output
17
+ expect(instance).to have_received(:puts)
18
+ expect(Kernel).to have_received(:exit!).with(1)
19
+ end
20
+
21
+ context 'with valid options' do
22
+ before(:each) do
23
+ @immutable_argv = ['template.rb', 'environment.rb', 'template.json'].freeze
24
+ @argv = @immutable_argv.dup
25
+ @instance = Racker::CLI.new(@argv)
26
+ @options = @instance.send(:options)
27
+ @options[:quiet] = true
28
+ # Prevent fake file from being written
29
+ allow(File).to receive(:open)
30
+ end
31
+
32
+ it 'uses the last argument for the value of the output option' do
33
+ allow_any_instance_of(Racker::Processor).to receive(:execute!)
34
+ @instance.execute!
35
+ expect(@options[:output]).to eq(@immutable_argv.last)
36
+ end
37
+
38
+ it 'uses all arguments except the last for the value of the templates option' do
39
+ allow_any_instance_of(Racker::Processor).to receive(:execute!)
40
+ @instance.execute!
41
+ expect(@options[:templates]).to eq(@immutable_argv[0..-2])
42
+ end
43
+
44
+ it 'initializes and executes a new Racker::Processor with the given options' do
45
+ processor_instance = Racker::Processor.new(@options)
46
+ expect(Racker::Processor).to receive(:new) { processor_instance }.with(@options)
47
+ expect(processor_instance).to receive(:execute!)
48
+ @instance.execute!
49
+ end
50
+
51
+ it 'outputs no message when quieted' do
52
+ @options[:quiet] = true
53
+ allow_any_instance_of(Racker::Processor).to receive(:execute!)
54
+ expect(@instance).not_to receive(:puts)
55
+ @instance.execute!
56
+ end
57
+
58
+ it 'outputs a message upon success when not quieted' do
59
+ @options[:quiet] = false
60
+ allow_any_instance_of(Racker::Processor).to receive(:execute!)
61
+ expect(@instance).to receive(:puts).at_least(1)
62
+ @instance.execute!
63
+ end
64
+
65
+ it 'returns 0 on success' do
66
+ allow_any_instance_of(Racker::Processor).to receive(:execute!)
67
+ expect(@instance.execute!).to eq(0)
68
+ end
69
+ end
70
+ end
71
+
72
+ context '#initialize' do
73
+ it 'sets the @argv instance variable to the provided argument' do
74
+ instance = described_class.new(argv = [])
75
+ expect(instance.instance_variable_get(:@argv).object_id).to eq(argv.object_id)
76
+ end
77
+ end
78
+
79
+ context '#option_parser' do
80
+ before(:each) { @instance = described_class.new([]) }
81
+
82
+ it 'returns a new default OptionParser if none exists' do
83
+ expect(@instance.instance_variable_get(:@option_parser)).to eq(nil)
84
+ expect(@instance.send(:option_parser)).to be_an(OptionParser)
85
+ end
86
+
87
+ it 'returns the same OptionParser on subsequent calls' do
88
+ first_option_parser = @instance.send(:option_parser)
89
+ second_option_parser = @instance.send(:option_parser)
90
+ expect(second_option_parser).to be(first_option_parser)
91
+ end
92
+ end
93
+
94
+ context '#options' do
95
+ before(:each) { @instance = described_class.new([]) }
96
+
97
+ it 'returns a Hash of default options if none exists' do
98
+ expect(@instance.instance_variable_get(:@options)).to eq(nil)
99
+ options = @instance.send(:options)
100
+ expect(options).to eq({
101
+ log_level: :warn,
102
+ knockout: '~~',
103
+ output: '',
104
+ templates: [],
105
+ quiet: false,
106
+ })
107
+ end
108
+
109
+ it 'returns the same Hash on subsequent calls' do
110
+ first_options = @instance.send(:options)
111
+ second_options = @instance.send(:options)
112
+ expect(second_options).to be(first_options)
113
+ end
114
+ end
115
+
116
+ context 'option parser' do
117
+ before(:all) { @instance = described_class.new([]) }
118
+ before(:each) do
119
+ @instance.instance_variable_set(:@option_parser, nil)
120
+ @instance.instance_variable_set(:@options, nil)
121
+ @parser = @instance.send(:option_parser)
122
+ @options = @instance.send(:options)
123
+ end
124
+
125
+ context 'log_level' do
126
+ %w[-l --log-level].each do |format|
127
+ it "is triggered by the #{format} arg" do
128
+ @options.delete(:log_level)
129
+ @parser.parse!(%W[#{format} fatal])
130
+ expect(@options[:log_level]).to be(:fatal)
131
+ end
132
+ end
133
+
134
+ %w[debug error fatal info warn].each do |log_level|
135
+ it "supports a log level of #{log_level}" do
136
+ @options.delete(:log_level)
137
+ @parser.parse!(%W[-l #{log_level}])
138
+ expect(@options[:log_level]).to be(log_level.to_sym)
139
+ end
140
+ end
141
+
142
+ it 'defaults invalid log levels to nil' do
143
+ @options.delete(:log_level)
144
+ @parser.parse!(%W[-l foo])
145
+ expect(@options[:log_level]).to be(nil)
146
+ end
147
+ end
148
+
149
+ context 'knockout' do
150
+ %w[-k --knockout].each do |format|
151
+ it "is triggered by the #{format} arg" do
152
+ @options.delete(:knockout)
153
+ @parser.parse!(%W[#{format} xxx])
154
+ expect(@options[:knockout]).to eq('xxx')
155
+ end
156
+ end
157
+ end
158
+
159
+ context 'quiet' do
160
+ %w[-q --quiet].each do |format|
161
+ it "is triggered by the #{format} arg" do
162
+ @options.delete(:quiet)
163
+ @parser.parse!([format])
164
+ expect(@options[:quiet]).to eq(true)
165
+ end
166
+ end
167
+ end
168
+
169
+ context 'help' do
170
+ %w[-h --help].each do |format|
171
+ it "is triggered by the #{format} arg" do
172
+ expect(@instance).to receive(:puts)
173
+ expect(Kernel).to receive(:exit!)
174
+ @parser.parse!([format])
175
+ end
176
+ end
177
+
178
+ it 'outputs help then exits with a status of 0' do
179
+ expect(@instance).to receive(:puts)
180
+ expect(Kernel).to receive(:exit!).with(0)
181
+ @parser.parse!(['--help'])
182
+ end
183
+ end
184
+
185
+ context 'version' do
186
+ %w[-v --version].each do |format|
187
+ it "is triggered by the #{format} arg" do
188
+ expect(@instance).to receive(:puts)
189
+ expect(Kernel).to receive(:exit!)
190
+ @parser.parse!([format])
191
+ end
192
+ end
193
+
194
+ it 'outputs the version then exits with a status of 0' do
195
+ expect(@instance).to receive(:puts)
196
+ expect(Kernel).to receive(:exit!).with(0)
197
+ @parser.parse!(['--version'])
198
+ end
199
+ end
200
+ end
201
+ end