beaker 3.13.0 → 3.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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