beaker 3.13.0 → 3.14.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.
@@ -204,7 +204,8 @@ module Beaker
204
204
  # 2. ARGV or provided arguments array
205
205
  # 3. the 'CONFIG' section of the hosts file
206
206
  # 4. options file values
207
- # 5. default or preset values are given the lowest priority
207
+ # 5. subcommand options, if executing beaker subcommands
208
+ # 6. default or preset values are given the lowest priority
208
209
  #
209
210
  # @param [Array] args ARGV or a provided arguments array
210
211
  # @raise [ArgumentError] Raises error on bad input
@@ -214,6 +215,12 @@ module Beaker
214
215
  cmd_line_options = @command_line_parser.parse(args)
215
216
  cmd_line_options[:command_line] = ([$0] + args).join(' ')
216
217
  @attribution = @attribution.merge(tag_sources(cmd_line_options, "flag"))
218
+
219
+ # Subcommands are the first to get merged into presets
220
+ subcommand_options = Beaker::Options::SubcommandOptionsParser.parse_subcommand_options(args)
221
+ @attribution = @attribution.merge(tag_sources(subcommand_options, 'subcommand'))
222
+ @options.merge!(subcommand_options)
223
+
217
224
  file_options = Beaker::Options::OptionsFileParser.parse_options_file(cmd_line_options[:options_file])
218
225
  @attribution = @attribution.merge(tag_sources(file_options, "options_file"))
219
226
 
@@ -0,0 +1,20 @@
1
+ module Beaker
2
+ module Options
3
+ #A set of functions to read options files
4
+ module SubcommandOptionsParser
5
+
6
+ # @return [OptionsHash, Hash] returns an empty OptionHash or loads subcommand options yaml
7
+ # from disk
8
+ def self.parse_subcommand_options(argv)
9
+ result = OptionsHash.new
10
+ if Beaker::Subcommands::SubcommandUtil.execute_subcommand?(argv[0])
11
+ return result if argv[0] == 'init'
12
+ if Beaker::Subcommands::SubcommandUtil::SUBCOMMAND_OPTIONS.exist?
13
+ result = YAML.load_file(Beaker::Subcommands::SubcommandUtil::SUBCOMMAND_OPTIONS)
14
+ end
15
+ end
16
+ result
17
+ end
18
+ end
19
+ end
20
+ end
@@ -108,7 +108,7 @@ module Beaker
108
108
 
109
109
  # Wait for the ssh connection to fail, returns true on connection failure and false otherwise
110
110
  # @param [Hash{Symbol=>String}] options Options hash to control method conditionals
111
- # @option options [Boolean] :pty Should we request a terminal when attempting
111
+ # @option options [Boolean] :pty Should we request a terminal when attempting
112
112
  # to send a command over this connection?
113
113
  # @option options [String] :stdin Any input to be sent along with the command
114
114
  # @param [IO] stdout_callback An IO stream to send connection stdout to, defaults to nil
@@ -6,9 +6,13 @@ module Beaker
6
6
  class Subcommand < Thor
7
7
  SubcommandUtil = Beaker::Subcommands::SubcommandUtil
8
8
 
9
+
9
10
  def initialize(*args)
10
11
  super
11
- @@config = SubcommandUtil.init_config()
12
+ FileUtils.mkdir_p(SubcommandUtil::CONFIG_DIR)
13
+ FileUtils.touch(SubcommandUtil::SUBCOMMAND_OPTIONS) unless SubcommandUtil::SUBCOMMAND_OPTIONS.exist?
14
+ FileUtils.touch(SubcommandUtil::SUBCOMMAND_STATE) unless SubcommandUtil::SUBCOMMAND_STATE.exist?
15
+ @cli = Beaker::CLI.new
12
16
  end
13
17
 
14
18
  # Options listed in this group 'Beaker run' are options that can be set on subcommands
@@ -49,55 +53,163 @@ module Beaker
49
53
  class_option :'exclude-tags', :type => :string, :group => 'Beaker run'
50
54
  class_option :'xml-time-order', :type => :boolean, :group => 'Beaker run'
51
55
 
52
- desc "init HYPERVISOR", "Initialises the beaker test environment configuration"
53
- option :help, :type => :boolean, :hide => true
56
+ desc "init BEAKER_RUN_OPTIONS", "Initializes the required configuration for Beaker subcommand execution"
54
57
  long_desc <<-LONGDESC
55
-
56
- Initialises a test environment configuration for a specific hypervisor
57
-
58
- Supported hypervisors are: vagrant (default), vmpooler
59
-
58
+ Initializes the required .beaker configuration folder. This folder contains
59
+ a subcommand_options.yaml file that is user-facing; altering this file will
60
+ alter the options subcommand execution. Subsequent subcommand execution,
61
+ such as `provision`, will result in beaker making modifications to this file
62
+ as necessary.
60
63
  LONGDESC
61
- def init(hypervisor='vagrant')
64
+ option :help, :type => :boolean, :hide => true
65
+ def init()
62
66
  if options[:help]
63
67
  invoke :help, [], ["init"]
64
68
  return
65
69
  end
66
- SubcommandUtil.verify_init_args(hypervisor)
67
- SubcommandUtil.require_tasks()
68
- SubcommandUtil.init_hypervisor(hypervisor)
69
- say "Writing host config to .beaker/acceptance/config/default_#{hypervisor}_hosts.yaml"
70
- SubcommandUtil.store_config({:hypervisor => hypervisor})
71
- SubcommandUtil.delete_config([:provisioned])
70
+
71
+ @cli.parse_options
72
+
73
+ # delete unnecessary keys for saving the options
74
+ options_to_write = @cli.configured_options
75
+ # Remove keys we don't want to save
76
+ [:timestamp, :logger, :command_line, :beaker_version, :hosts_file].each do |key|
77
+ options_to_write.delete(key)
78
+ end
79
+
80
+ options_to_write = SubcommandUtil.sanitize_options_for_save(options_to_write)
81
+
82
+ @cli.logger.notify 'Writing configured options to disk'
83
+ File.open(SubcommandUtil::SUBCOMMAND_OPTIONS, 'w') do |f|
84
+ f.write(options_to_write.to_yaml)
85
+ end
86
+ @cli.logger.notify "Options written to #{SubcommandUtil::SUBCOMMAND_OPTIONS}"
87
+
88
+ state = YAML::Store.new(SubcommandUtil::SUBCOMMAND_STATE)
89
+ state.transaction do
90
+ state['provisioned'] = false
91
+ end
72
92
  end
73
93
 
74
- desc "provision", "Provisions the beaker test configuration"
75
- option :validate, :type => :boolean, :default => true
76
- option :configure, :type => :boolean, :default => true
77
- option :help, :type => :boolean, :hide => true
94
+ desc "provision", "Provisions the beaker systems under test(SUTs)"
78
95
  long_desc <<-LONGDESC
79
- Provisions a beaker configuration
96
+ Provisions hosts defined in your subcommand_options file. You can pass the --hosts
97
+ flag here to override any hosts provided there. Really, you can pass most any beaker
98
+ flag here to override.
80
99
  LONGDESC
100
+ option :help, :type => :boolean, :hide => true
81
101
  def provision()
82
102
  if options[:help]
83
103
  invoke :help, [], ["provision"]
84
104
  return
85
105
  end
86
106
 
87
- hypervisor = @@config.transaction { @@config[:hypervisor] }
107
+ state = YAML::Store.new(SubcommandUtil::SUBCOMMAND_STATE)
108
+ if state.transaction { state['provisioned']}
109
+ SubcommandUtil.error_with('Provisioned SUTs detected. Please destroy and reprovision.')
110
+ end
111
+
112
+ @cli.parse_options
113
+ @cli.provision
114
+
115
+ # Sanitize the hosts
116
+ cleaned_hosts = SubcommandUtil.sanitize_options_for_save(@cli.combined_instance_and_options_hosts)
117
+
118
+ # Update each host provisioned with a flag indicating that it no longer needs
119
+ # provisioning
120
+ cleaned_hosts.each do |host, host_hash|
121
+ host_hash['provision'] = false
122
+ end
123
+
124
+ # should we only update the options here with the new host? Or update the settings
125
+ # with whatever new flags may have been provided with provision?
126
+ options_storage = YAML::Store.new(SubcommandUtil::SUBCOMMAND_OPTIONS)
127
+ options_storage.transaction do
128
+ @cli.logger.notify 'updating HOSTS key in subcommand_options'
129
+ options_storage['HOSTS'] = cleaned_hosts
130
+ options_storage['hosts_preserved_yaml_file'] = @cli.options[:hosts_preserved_yaml_file]
131
+ end
132
+
133
+ @cli.preserve_hosts_file
134
+
135
+ state.transaction do
136
+ state['provisioned'] = true
137
+ end
138
+ end
139
+
140
+ desc 'exec FILE/BEAKER_SUITE', 'execute a directory, file, or beaker suite'
141
+ long_desc <<-LONG_DESC
142
+ Run a single file, directory, or beaker suite. If supplied a file or directory,
143
+ that resource will be run in the context of the `tests` suite; If supplied a beaker
144
+ suite, then just that suite will run. If no resource is supplied, then this command
145
+ executes the suites as they are defined in the configuration.
146
+ LONG_DESC
147
+ option :help, :type => :boolean, :hide => true
148
+ def exec(resource=nil)
149
+ if options[:help]
150
+ invoke :help, [], ["exec"]
151
+ return
152
+ end
153
+
154
+ @cli.parse_options
155
+ @cli.initialize_network_manager
88
156
 
89
- unless hypervisor
90
- SubcommandUtil.error_with("Please initialise a configuration")
157
+ if !resource
158
+ @cli.execute!
159
+ return
91
160
  end
92
161
 
93
- provisioned = @@config.transaction { @@config[:provisioned] }
162
+ beaker_suites = [:pre_suite, :tests, :post_suite, :pre_cleanup]
94
163
 
95
- if !provisioned or options[:force]
96
- SubcommandUtil.provision(hypervisor, options)
97
- SubcommandUtil.store_config({:provisioned => true})
164
+ if Pathname(resource).exist?
165
+ # If we determine the resource is a valid file resource, then we empty
166
+ # all the suites and run that file resource in the tests suite. In the
167
+ # future, when we have the ability to have custom suites, we should change
168
+ # this to run in a custom suite. You know, in the future.
169
+ beaker_suites.each do |suite|
170
+ @cli.options[suite] = []
171
+ end
172
+ if Pathname(resource).directory?
173
+ @cli.options[:tests] = Dir.glob("#{Pathname(resource).expand_path}/*.rb")
174
+ else
175
+ @cli.options[:tests] = [Pathname(resource).expand_path.to_s]
176
+ end
177
+ elsif resource.match(/pre-suite|tests|post-suite|pre-cleanup/)
178
+ # The regex match here is loose so that users can supply multiple suites,
179
+ # such as `beaker exec pre-suite,tests`.
180
+ beaker_suites.each do |suite|
181
+ @cli.options[suite] = [] unless resource.gsub(/-/, '_').match(suite.to_s)
182
+ end
98
183
  else
99
- say "Hosts have already been provisioned"
184
+ raise ArgumentError, "Unable to parse #{resource} with beaker exec"
100
185
  end
186
+ @cli.execute!
187
+ end
188
+
189
+ desc "destroy", "Destroys the provisioned VMs"
190
+ long_desc <<-LONG_DESC
191
+ Destroys the currently provisioned VMs
192
+ LONG_DESC
193
+ option :help, :type => :boolean, :hide => true
194
+ def destroy()
195
+ if options[:help]
196
+ invoke :help, [], ["destroy"]
197
+ return
198
+ end
199
+
200
+ state = YAML::Store.new(SubcommandUtil::SUBCOMMAND_STATE)
201
+ unless state.transaction { state['provisioned']}
202
+ SubcommandUtil.error_with('Please provision an environment')
203
+ end
204
+
205
+ @cli.parse_options
206
+ @cli.options[:provision] = false
207
+ @cli.initialize_network_manager
208
+ @cli.network_manager.cleanup
209
+
210
+ state.transaction {
211
+ state.delete('provisioned')
212
+ }
101
213
  end
102
214
  end
103
215
  end
@@ -1,3 +1,4 @@
1
+ require 'json'
1
2
  require 'rake'
2
3
  require 'stringio'
3
4
  require 'yaml/store'
@@ -7,87 +8,27 @@ module Beaker
7
8
  module Subcommands
8
9
  # Methods used in execution of Subcommands
9
10
  # - should we execute a subcommand?
10
- # - reset ARGV
11
- # - execute Beaker
12
- # - update a rakefile to require beaker quickstart tasks
13
- # - initialise a rake application
14
- # - execute a rake task
15
- # - execute the vagrant quickstart task
16
- # - execute the vmpooler quickstart task
11
+ # - sanitize options for saving as json
17
12
  # - exit with a specific message
18
- # - execute the quick start task for the specified hypervisor
19
13
  # - capture stdout and stderr
20
14
  module SubcommandUtil
21
- BEAKER_REQUIRE = "require 'beaker/tasks/quick_start'"
22
- HYPERVISORS = ["vagrant", "vmpooler"]
23
15
  CONFIG_DIR = ".beaker"
24
- CONFIG_KEYS = [:hypervisor, :provisioned]
16
+ SUBCOMMAND_OPTIONS = Pathname("#{CONFIG_DIR}/subcommand_options.yaml")
17
+ SUBCOMMAND_STATE = Pathname("#{CONFIG_DIR}/.subcommand_state.yaml")
18
+ PERSISTED_HOSTS = Pathname("#{CONFIG_DIR}/.hosts.yaml")
19
+ PERSISTED_HYPERVISORS = Pathname("#{CONFIG_DIR}/.hypervisors.yaml")
25
20
 
26
- # Check if the first argument to the beaker execution is a subcommand
27
- # @return [Boolean] true if argv[0] is "help" or a method defined in the Subcommands class, false otherwise
28
21
  def self.execute_subcommand?(arg0)
29
22
  return false if arg0.nil?
30
23
  (Beaker::Subcommand.instance_methods(false) << :help).include? arg0.to_sym
31
24
  end
32
25
 
33
- # Reset ARGV to contain the arguments determined by a specific subcommand
34
- # @param [Array<String>] args the arguments determined by a specific subcommand
35
- def self.reset_argv(args)
36
- ARGV.clear
37
- args.each do |arg|
38
- ARGV << arg
39
- end
40
- end
41
-
42
- # Update ARGV and call Beaker
43
- # @param [Array<String>] args the arguments determined by a specific subcommand
44
- def self.execute_beaker(*args)
45
- reset_argv(args)
46
- Beaker::CLI.new.execute!
47
- end
48
-
49
- # Determines what Rakefile to use
50
- # @return [String] the name of the rakefile to use
51
- def self.determine_rake_file()
52
- rake_app.find_rakefile_location() ? rake_app.find_rakefile_location()[0] : "Rakefile"
53
- end
54
-
55
- # Check for the presence of a Rakefile containing the require of the
56
- # quick start tasks
57
- def self.require_tasks()
58
- rake_file = determine_rake_file()
59
- FileUtils.touch(rake_file)
60
- unless File.readlines(rake_file).grep(/#{BEAKER_REQUIRE}/).any?
61
- File.open(rake_file, "a+") { |f| f.puts(BEAKER_REQUIRE) }
62
- end
63
- end
64
-
65
- # Initialises a rake application
66
- # @return [Object] a rake application
67
- def self.rake_app()
68
- unless @rake_app
69
- ARGV.clear
70
- @rake_app = Rake.application
71
- @rake_app.init
72
- end
73
- @rake_app
74
- end
75
-
76
- # Clear ARGV and execute a Rake task
77
- # @param [String] task the rake task to execute
78
- def self.execute_rake_task(task)
79
- rake_app.load_rakefile()
80
- with_captured_output { rake_app.invoke_task(task) }
81
- end
82
-
83
- # Execute the quick start task for vagrant
84
- def self.init_vagrant()
85
- execute_rake_task("beaker_quickstart:gen_hosts[vagrant]")
86
- end
87
-
88
- # Execute the quick start task for vmpooler
89
- def self.init_vmpooler()
90
- execute_rake_task("beaker_quickstart:gen_hosts[vmpooler]")
26
+ def self.sanitize_options_for_save(options)
27
+ # God help us, the YAML library won't stop adding tags to objects, so this
28
+ # hack is a way to force the options into the basic object types so that
29
+ # an eventual YAML.dump or .to_yaml call doesn't add tags.
30
+ # Relevant stackoverflow: http://stackoverflow.com/questions/18178098/how-do-i-have-ruby-yaml-dump-a-hash-subclass-as-a-simple-hash
31
+ JSON.parse(options.to_json)
91
32
  end
92
33
 
93
34
  # Print a message to the console and exit with specified exit code, defaults to 1
@@ -100,25 +41,6 @@ module Beaker
100
41
  exit(exit_code)
101
42
  end
102
43
 
103
- # Call the quick start task for the specified hypervisor
104
- # @param [String] hypervisor the hypervisor we want to query
105
- def self.init_hypervisor(hypervisor)
106
- case hypervisor
107
- when "vagrant"
108
- init_vagrant
109
- when "vmpooler"
110
- init_vmpooler
111
- end
112
- end
113
-
114
- # Verify that a valid hypervisor has been specified
115
- # @param [String] hypervisor the hypervisor we want to validate
116
- def self.verify_init_args(hypervisor)
117
- unless HYPERVISORS.include?(hypervisor)
118
- error_with("Invalid hypervisor. Currently supported hypervisors are: #{HYPERVISORS.join(', ')}")
119
- end
120
- end
121
-
122
44
  # Execute a task but capture stdout and stderr to a buffer
123
45
  def self.with_captured_output
124
46
  begin
@@ -133,41 +55,6 @@ module Beaker
133
55
  end
134
56
  end
135
57
 
136
- # Initialise the beaker config
137
- def self.init_config()
138
- FileUtils.mkdir_p CONFIG_DIR
139
- @@store = YAML::Store.new("#{CONFIG_DIR}/config")
140
- end
141
-
142
- # Store values from a hash into the beaker config
143
- # @param [Hash{Symbol=>String}] config values we want to store
144
- def self.store_config(config)
145
- @@store.transaction do
146
- CONFIG_KEYS.each do |key|
147
- @@store[key] = config[key] unless config[key].nil?
148
- end
149
- end
150
- end
151
-
152
- # Delete keys from the beaker configi
153
- # @param [Array<Object>] keys the keys we want to delete from the config
154
- def self.delete_config(keys)
155
- @@store.transaction {
156
- keys.each do |key|
157
- @@store.delete(key)
158
- end
159
- }
160
- end
161
-
162
- # Reset args and provision nodes
163
- # @param [String] hypervisor the hypervisor to use
164
- # @param [Array<Object>] options the options to use when provisioning
165
- def self.provision(hypervisor, options)
166
- reset_argv(["--hosts", "#{CONFIG_DIR}/acceptance/config/default_#{hypervisor}_hosts.yaml", "--validate", options[:validate], "--configure", options[:configure]])
167
- beaker = Beaker::CLI.new.parse_options
168
- beaker.provision
169
- beaker.preserve_hosts_file
170
- end
171
58
  end
172
59
  end
173
60
  end
@@ -1,5 +1,5 @@
1
1
  module Beaker
2
2
  module Version
3
- STRING = '3.13.0'
3
+ STRING = '3.14.0'
4
4
  end
5
5
  end
@@ -50,6 +50,36 @@ module Beaker
50
50
  Beaker::CLI.new.parse_options
51
51
  }
52
52
 
53
+ context '#configured_options' do
54
+ it 'returns a list of options that were not presets' do
55
+ attribution = cli.instance_variable_get(:@attribution)
56
+ attribution.each do |attribute, setter|
57
+ if setter == 'preset'
58
+ expect(cli.configured_options[attribute]).to be_nil
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ context '#combined_instance_and_options_hosts' do
65
+ let (:options_host) { {'HOSTS' => {'ubuntu' => {:options_attribute => 'options'}} }}
66
+ let (:instance_host ) {
67
+ [Beaker::Host.create('ubuntu', {:platform => 'host'}, {} )]
68
+ }
69
+ before do
70
+ cli.instance_variable_set(:@options, options_host)
71
+ cli.instance_variable_set(:@hosts, instance_host)
72
+ end
73
+ it 'combines the options and instance host objects' do
74
+ merged_host = cli.combined_instance_and_options_hosts
75
+ expect(merged_host).to have_key('ubuntu')
76
+ expect(merged_host['ubuntu']).to have_key(:options_attribute)
77
+ expect(merged_host['ubuntu']).to have_key(:platform)
78
+ expect(merged_host['ubuntu'][:options_attribute]).to eq('options')
79
+ expect(merged_host['ubuntu'][:platform]).to eq('host')
80
+ end
81
+ end
82
+
53
83
  context 'execute!' do
54
84
  before :each do
55
85
  stub_const("Beaker::Logger", double().as_null_object )
@@ -503,6 +533,7 @@ module Beaker
503
533
  expect( answer.start_with?(command_correct) ).to be_truthy
504
534
  end
505
535
  end
536
+
506
537
  end
507
538
  end
508
539
  end