beaker 2.7.1 → 2.8.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.
Files changed (53) hide show
  1. checksums.yaml +8 -8
  2. data/HISTORY.md +121 -2
  3. data/lib/beaker/dsl.rb +2 -2
  4. data/lib/beaker/dsl/helpers.rb +13 -1429
  5. data/lib/beaker/dsl/helpers/facter_helpers.rb +48 -0
  6. data/lib/beaker/dsl/helpers/hiera_helpers.rb +71 -0
  7. data/lib/beaker/dsl/helpers/host_helpers.rb +506 -0
  8. data/lib/beaker/dsl/helpers/puppet_helpers.rb +698 -0
  9. data/lib/beaker/dsl/helpers/tk_helpers.rb +101 -0
  10. data/lib/beaker/dsl/helpers/web_helpers.rb +115 -0
  11. data/lib/beaker/dsl/install_utils.rb +8 -1570
  12. data/lib/beaker/dsl/install_utils/ezbake_utils.rb +256 -0
  13. data/lib/beaker/dsl/install_utils/module_utils.rb +237 -0
  14. data/lib/beaker/dsl/install_utils/pe_utils.rb +518 -0
  15. data/lib/beaker/dsl/install_utils/puppet_utils.rb +722 -0
  16. data/lib/beaker/dsl/outcomes.rb +0 -4
  17. data/lib/beaker/dsl/roles.rb +0 -3
  18. data/lib/beaker/dsl/structure.rb +127 -4
  19. data/lib/beaker/dsl/wrappers.rb +0 -4
  20. data/lib/beaker/host.rb +23 -0
  21. data/lib/beaker/host/unix/pkg.rb +4 -4
  22. data/lib/beaker/host_prebuilt_steps.rb +11 -5
  23. data/lib/beaker/hypervisor/vagrant.rb +1 -0
  24. data/lib/beaker/hypervisor/vmpooler.rb +38 -0
  25. data/lib/beaker/logger.rb +10 -4
  26. data/lib/beaker/network_manager.rb +5 -4
  27. data/lib/beaker/options/command_line_parser.rb +7 -0
  28. data/lib/beaker/shared.rb +2 -1
  29. data/lib/beaker/shared/semvar.rb +41 -0
  30. data/lib/beaker/test_suite.rb +20 -6
  31. data/lib/beaker/version.rb +1 -1
  32. data/spec/beaker/dsl/helpers/facter_helpers_spec.rb +59 -0
  33. data/spec/beaker/dsl/helpers/hiera_helpers_spec.rb +96 -0
  34. data/spec/beaker/dsl/helpers/host_helpers_spec.rb +413 -0
  35. data/spec/beaker/dsl/{helpers_spec.rb → helpers/puppet_helpers_spec.rb} +2 -611
  36. data/spec/beaker/dsl/helpers/tk_helpers_spec.rb +83 -0
  37. data/spec/beaker/dsl/helpers/web_helpers_spec.rb +60 -0
  38. data/spec/beaker/dsl/install_utils/module_utils_spec.rb +241 -0
  39. data/spec/beaker/dsl/install_utils/pe_utils_spec.rb +475 -0
  40. data/spec/beaker/dsl/install_utils/puppet_utils_spec.rb +523 -0
  41. data/spec/beaker/dsl/structure_spec.rb +108 -0
  42. data/spec/beaker/host_prebuilt_steps_spec.rb +44 -0
  43. data/spec/beaker/host_spec.rb +41 -0
  44. data/spec/beaker/hypervisor/vagrant_spec.rb +2 -1
  45. data/spec/beaker/logger_spec.rb +9 -2
  46. data/spec/beaker/network_manager_spec.rb +7 -1
  47. data/spec/beaker/options/command_line_parser_spec.rb +3 -2
  48. data/spec/beaker/shared/semvar_spec.rb +36 -0
  49. data/spec/beaker/test_suite_spec.rb +48 -0
  50. data/spec/mocks.rb +10 -0
  51. metadata +23 -5
  52. data/lib/beaker/dsl/ezbake_utils.rb +0 -259
  53. data/spec/beaker/dsl/install_utils_spec.rb +0 -1242
@@ -0,0 +1,256 @@
1
+ require 'fileutils'
2
+
3
+ module Beaker
4
+ module DSL
5
+ module InstallUtils
6
+ # This module contains methods to assist in installing projects from source
7
+ # that use ezbake for packaging.
8
+ #
9
+ module EZBakeUtils
10
+
11
+ # @!group Public DSL Methods
12
+
13
+ # Installs leiningen project with given name and version on remote host.
14
+ #
15
+ # @param [Host] host A single remote host on which to install the
16
+ # specified leiningen project.
17
+ def install_from_ezbake host
18
+ ezbake_validate_support host
19
+ ezbake_tools_available?
20
+ install_ezbake_tarball_on_host host
21
+ ezbake_installsh host, "service"
22
+ end
23
+
24
+ # Installs termini with given name and version on remote host.
25
+ #
26
+ # @param [Host] host A single remote host on which to install the
27
+ # specified leiningen project.
28
+ def install_termini_from_ezbake host
29
+ ezbake_validate_support host
30
+ ezbake_tools_available?
31
+ install_ezbake_tarball_on_host host
32
+ ezbake_installsh host, "termini"
33
+ end
34
+
35
+ # Install a development version of ezbake into the local m2 repository
36
+ #
37
+ # This can be useful if you want to work on a development branch of
38
+ # ezbake that hasn't been released yet. Ensure your project dependencies
39
+ # in your development branch include a reference to the -SNAPSHOT
40
+ # version of the project for it to successfully pickup a pre-shipped
41
+ # version of ezbake.
42
+ #
43
+ # @param url [String] git url
44
+ # @param branch [String] git branch
45
+ def ezbake_dev_build url = "git@github.com:puppetlabs/ezbake.git",
46
+ branch = "master"
47
+ ezbake_dir = 'tmp/ezbake'
48
+ conditionally_clone url, ezbake_dir, branch
49
+ lp = ezbake_lein_prefix
50
+
51
+ Dir.chdir(ezbake_dir) do
52
+ ezbake_local_cmd "#{lp} install",
53
+ :throw_on_failure => true
54
+ end
55
+ end
56
+
57
+ # @!endgroup
58
+
59
+ class << self
60
+ attr_accessor :config
61
+ end
62
+
63
+ # @!group Private helpers
64
+
65
+ # Test for support in one place
66
+ #
67
+ # @param [Host] host host to check for support
68
+ # @raise [RuntimeError] if OS is not supported
69
+ # @api private
70
+ def ezbake_validate_support host
71
+ variant, version, _, _ = host['platform'].to_array
72
+ unless variant =~ /^(fedora|el|centos|debian|ubuntu)$/
73
+ raise RuntimeError,
74
+ "No support for #{variant} within ezbake_utils ..."
75
+ end
76
+ end
77
+
78
+ # Build, copy & unpack tarball on remote host
79
+ #
80
+ # @param [Host] host installation destination
81
+ # @api private
82
+ def install_ezbake_tarball_on_host host
83
+ if not ezbake_config
84
+ ezbake_stage
85
+ end
86
+
87
+ # Skip installation if the remote directory exists
88
+ result = on host, "test -d #{ezbake_install_dir}", :acceptable_exit_codes => [0, 1]
89
+ return if result.exit_code == 0
90
+
91
+ ezbake_staging_dir = File.join('target', 'staging')
92
+ Dir.chdir(ezbake_staging_dir) do
93
+ ezbake_local_cmd 'rake package:tar'
94
+ end
95
+
96
+ local_tarball = ezbake_staging_dir + "/pkg/" + ezbake_install_name + ".tar.gz"
97
+ remote_tarball = ezbake_install_dir + ".tar.gz"
98
+ scp_to host, local_tarball, remote_tarball
99
+
100
+ # untar tarball on host
101
+ on host, "tar -xzf " + remote_tarball
102
+
103
+ # Check to ensure directory exists
104
+ on host, "test -d #{ezbake_install_dir}"
105
+ end
106
+
107
+ LOCAL_COMMANDS_REQUIRED = [
108
+ ['leiningen', 'lein --version', nil],
109
+ ['lein-pprint', 'lein with-profile ci pprint :version',
110
+ 'Must have lein-pprint installed under the :ci profile.'],
111
+ ['java', 'java -version', nil],
112
+ ['git', 'git --version', nil],
113
+ ['rake', 'rake --version', nil],
114
+ ]
115
+
116
+ # Checks given host for the tools necessary to perform
117
+ # install_from_ezbake.
118
+ #
119
+ # @raise [RuntimeError] if tool is not found
120
+ # @api private
121
+ def ezbake_tools_available?
122
+ LOCAL_COMMANDS_REQUIRED.each do |software_name, command, additional_error_message|
123
+ if not system command
124
+ error_message = "Must have #{software_name} installed on development system.\n"
125
+ if additional_error_message
126
+ error_message += additional_error_message
127
+ end
128
+ raise RuntimeError, error_message
129
+ end
130
+ end
131
+ end
132
+
133
+ # Return the ezbake config.
134
+ #
135
+ # @return [Hash] configuration for ezbake, usually from ezbake.rb
136
+ # @api private
137
+ def ezbake_config
138
+ EZBakeUtils.config
139
+ end
140
+
141
+ # Returns a leiningen prefix with local m2 repo capability
142
+ #
143
+ # @return [String] lein prefix command that uses a local build
144
+ # m2 repository.
145
+ # @api private
146
+ def ezbake_lein_prefix
147
+ # Get the absolute path to the local repo
148
+ m2_repo = File.join(Dir.pwd, 'tmp', 'm2-local')
149
+
150
+ 'lein update-in : assoc :local-repo "\"' + m2_repo + '\"" --'
151
+ end
152
+
153
+ # Prepares a staging directory for the specified project.
154
+ #
155
+ # @api private
156
+ def ezbake_stage
157
+ # Install the PuppetDB jar into the local repository
158
+ ezbake_local_cmd "#{ezbake_lein_prefix} install",
159
+ :throw_on_failure => true
160
+
161
+ # Run ezbake stage
162
+ ezbake_local_cmd "#{ezbake_lein_prefix} with-profile ezbake ezbake stage",
163
+ :throw_on_failure => true
164
+
165
+ # Boostrap packaging, and grab configuration info from project
166
+ staging_dir = File.join('target','staging')
167
+ Dir.chdir(staging_dir) do
168
+ ezbake_local_cmd 'rake package:bootstrap'
169
+
170
+ load 'ezbake.rb'
171
+ ezbake = EZBake::Config
172
+ ezbake[:package_version] = `echo -n $(rake pl:print_build_param[ref] | tail -n 1)`
173
+ EZBakeUtils.config = ezbake
174
+ end
175
+ end
176
+
177
+ # Executes a local command using system, logging the prepared command
178
+ #
179
+ # @param [String] cmd command to execute
180
+ # @param [Hash] opts options
181
+ # @option opts [bool] :throw_on_failure If true, throws an
182
+ # exception if the exit code is non-zero. Defaults to false.
183
+ # @return [bool] true if exit == 0 false if otherwise
184
+ # @raise [RuntimeError] if :throw_on_failure is true and
185
+ # command fails
186
+ # @api private
187
+ def ezbake_local_cmd cmd, opts={}
188
+ opts = {
189
+ :throw_on_failure => false,
190
+ }.merge(opts)
191
+
192
+ logger.notify "localhost $ #{cmd}"
193
+ result = system cmd
194
+ if opts[:throw_on_failure] && result == false
195
+ raise RuntimeError, "Command failure #{cmd}"
196
+ end
197
+ result
198
+ end
199
+
200
+ # Retrieve the tarball installation name. This is the name of
201
+ # the tarball without the .tar.gz extension, and the name of the
202
+ # path where it will unpack to.
203
+ #
204
+ # @return [String] name of the tarball and directory
205
+ # @api private
206
+ def ezbake_install_name
207
+ ezbake = ezbake_config
208
+ project_package_version = ezbake[:package_version]
209
+ project_name = ezbake[:project]
210
+ "%s-%s" % [ project_name, project_package_version ]
211
+ end
212
+
213
+ # Returns the full path to the installed software on the remote host.
214
+ #
215
+ # This only returns the path, it doesn't work out if its installed or
216
+ # not.
217
+ #
218
+ # @return [String] path to the installation dir
219
+ # @api private
220
+ def ezbake_install_dir
221
+ "/root/#{ezbake_install_name}"
222
+ end
223
+
224
+ # A helper that wraps the execution of install.sh in the proper
225
+ # ezbake installation directory.
226
+ #
227
+ # @param [Host] host Host to run install.sh on
228
+ # @param [String] task Task to execute with install.sh
229
+ # @api private
230
+ def ezbake_installsh host, task=""
231
+ on host, "cd #{ezbake_install_dir}; bash install.sh #{task}"
232
+ end
233
+
234
+ # Only clone from given git URI if there is no existing git clone at the
235
+ # given local_path location.
236
+ #
237
+ # @param [String] upstream_uri git URI
238
+ # @param [String] local_path path to conditionally install to
239
+ # @param [String] branch to checkout
240
+ # @api private
241
+ def conditionally_clone upstream_uri, local_path, branch="origin/HEAD"
242
+ if ezbake_local_cmd "git --work-tree=#{local_path} --git-dir=#{local_path}/.git status"
243
+ ezbake_local_cmd "git --work-tree=#{local_path} --git-dir=#{local_path}/.git fetch origin"
244
+ ezbake_local_cmd "git --work-tree=#{local_path} --git-dir=#{local_path}/.git checkout #{branch}"
245
+ else
246
+ parent_dir = File.dirname(local_path)
247
+ FileUtils.mkdir_p(parent_dir)
248
+ ezbake_local_cmd "git clone #{upstream_uri} #{local_path}"
249
+ ezbake_local_cmd "git --work-tree=#{local_path} --git-dir=#{local_path}/.git checkout #{branch}"
250
+ end
251
+ end
252
+
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,237 @@
1
+ module Beaker
2
+ module DSL
3
+ module InstallUtils
4
+ #
5
+ # This module contains methods to help install puppet modules
6
+ #
7
+ # To mix this is into a class you need the following:
8
+ # * a method *hosts* that yields any hosts implementing
9
+ # {Beaker::Host}'s interface to act upon.
10
+ # * a method *options* that provides an options hash, see {Beaker::Options::OptionsHash}
11
+ # * the module {Beaker::DSL::Roles} that provides access to the various hosts implementing
12
+ # {Beaker::Host}'s interface to act upon
13
+ # * the module {Beaker::DSL::Wrappers} the provides convenience methods for {Beaker::DSL::Command} creation
14
+ module ModuleUtils
15
+
16
+ # The directories in the module directory that will not be scp-ed to the test system when using
17
+ # `copy_module_to`
18
+ PUPPET_MODULE_INSTALL_IGNORE = ['.bundle', '.git', '.idea', '.vagrant', '.vendor', 'vendor', 'acceptance',
19
+ 'bundle', 'spec', 'tests', 'log']
20
+
21
+ # Install the desired module on all hosts using either the PMT or a
22
+ # staging forge
23
+ #
24
+ # @see install_dev_puppet_module
25
+ def install_dev_puppet_module_on( host, opts )
26
+ if options[:forge_host]
27
+ with_forge_stubbed_on( host ) do
28
+ install_puppet_module_via_pmt_on( host, opts )
29
+ end
30
+ else
31
+ copy_module_to( host, opts )
32
+ end
33
+ end
34
+ alias :puppet_module_install_on :install_dev_puppet_module_on
35
+
36
+ # Install the desired module on all hosts using either the PMT or a
37
+ # staging forge
38
+ #
39
+ # Passes options through to either `install_puppet_module_via_pmt_on`
40
+ # or `copy_module_to`
41
+ #
42
+ # @param opts [Hash]
43
+ #
44
+ # @example Installing a module from the local directory
45
+ # install_dev_puppet_module( :source => './', :module_name => 'concat' )
46
+ #
47
+ # @example Installing a module from a staging forge
48
+ # options[:forge_host] = 'my-forge-api.example.com'
49
+ # install_dev_puppet_module( :source => './', :module_name => 'concat' )
50
+ #
51
+ # @see install_puppet_module_via_pmt
52
+ # @see copy_module_to
53
+ def install_dev_puppet_module( opts )
54
+ block_on( hosts ) {|h| install_dev_puppet_module_on( h, opts ) }
55
+ end
56
+ alias :puppet_module_install :install_dev_puppet_module
57
+
58
+ # Install the desired module with the PMT on a given host
59
+ #
60
+ # @param opts [Hash]
61
+ # @option opts [String] :module_name The short name of the module to be installed
62
+ # @option opts [String] :version The version of the module to be installed
63
+ def install_puppet_module_via_pmt_on( host, opts = {} )
64
+ block_on host do |h|
65
+ version_info = opts[:version] ? "-v #{opts[:version]}" : ""
66
+ if opts[:source]
67
+ author_name, module_name = parse_for_modulename( opts[:source] )
68
+ modname = "#{author_name}-#{module_name}"
69
+ else
70
+ modname = opts[:module_name]
71
+ end
72
+
73
+ puppet_opts = {}
74
+ if host[:default_module_install_opts].respond_to? :merge
75
+ puppet_opts = host[:default_module_install_opts].merge( puppet_opts )
76
+ end
77
+
78
+ on h, puppet("module install #{modname} #{version_info}", puppet_opts)
79
+ end
80
+ end
81
+
82
+ # Install the desired module with the PMT on all known hosts
83
+ # @see #install_puppet_module_via_pmt_on
84
+ def install_puppet_module_via_pmt( opts = {} )
85
+ install_puppet_module_via_pmt_on(hosts, opts)
86
+ end
87
+
88
+ # Install local module for acceptance testing
89
+ # should be used as a presuite to ensure local module is copied to the hosts you want, particularly masters
90
+ # @param [Host, Array<Host>, String, Symbol] one_or_more_hosts
91
+ # One or more hosts to act upon,
92
+ # or a role (String or Symbol) that identifies one or more hosts.
93
+ # @option opts [String] :source ('./')
94
+ # The current directory where the module sits, otherwise will try
95
+ # and walk the tree to figure out
96
+ # @option opts [String] :module_name (nil)
97
+ # Name which the module should be installed under, please do not include author,
98
+ # if none is provided it will attempt to parse the metadata.json and then the Module file to determine
99
+ # the name of the module
100
+ # @option opts [String] :target_module_path (host['distmoduledir']/modules)
101
+ # Location where the module should be installed, will default
102
+ # to host['distmoduledir']/modules
103
+ # @option opts [Array] :ignore_list
104
+ # @option opts [String] :protocol
105
+ # Name of the underlying transfer method. Valid options are 'scp' or 'rsync'.
106
+ # @raise [ArgumentError] if not host is provided or module_name is not provided and can not be found in Modulefile
107
+ #
108
+ def copy_module_to(one_or_more_hosts, opts = {})
109
+ block_on one_or_more_hosts do |host|
110
+ opts = {:source => './',
111
+ :target_module_path => host['distmoduledir'],
112
+ :ignore_list => PUPPET_MODULE_INSTALL_IGNORE}.merge(opts)
113
+ ignore_list = build_ignore_list(opts)
114
+ target_module_dir = on( host, "echo #{opts[:target_module_path]}" ).stdout.chomp
115
+ source_path = File.expand_path( opts[:source] )
116
+ source_dir = File.dirname(source_path)
117
+ source_name = File.basename(source_path)
118
+ if opts.has_key?(:module_name)
119
+ module_name = opts[:module_name]
120
+ else
121
+ _, module_name = parse_for_modulename( source_path )
122
+ end
123
+
124
+ opts[:protocol] ||= 'scp'
125
+ case opts[:protocol]
126
+ when 'scp'
127
+ #move to the host
128
+ scp_to host, source_path, target_module_dir, {:ignore => ignore_list}
129
+ #rename to the selected module name, if not correct
130
+ cur_path = File.join(target_module_dir, source_name)
131
+ cur_path = File.join(target_module_dir, source_name)
132
+ new_path = File.join(target_module_dir, module_name)
133
+ if cur_path != new_path
134
+ # NOTE: this will need to be updated to handle powershell only windows SUTs
135
+ on host, "mv #{cur_path} #{new_path}"
136
+ end
137
+ when 'rsync'
138
+ rsync_to host, source, File.join(target_module_dir, module_name), {:ignore => ignore_list}
139
+ else
140
+ logger.debug "Unsupported transfer protocol, returning nil"
141
+ nil
142
+ end
143
+ end
144
+ end
145
+ alias :copy_root_module_to :copy_module_to
146
+
147
+ #Recursive method for finding the module root
148
+ # Assumes that a Modulefile exists
149
+ # @param [String] possible_module_directory
150
+ # will look for Modulefile and if none found go up one level and try again until root is reached
151
+ #
152
+ # @return [String,nil]
153
+ def parse_for_moduleroot(possible_module_directory)
154
+ if File.exists?("#{possible_module_directory}/Modulefile") || File.exists?("#{possible_module_directory}/metadata.json")
155
+ possible_module_directory
156
+ elsif possible_module_directory === '/'
157
+ logger.error "At root, can't parse for another directory"
158
+ nil
159
+ else
160
+ logger.debug "No Modulefile or metadata.json found at #{possible_module_directory}, moving up"
161
+ parse_for_moduleroot File.expand_path(File.join(possible_module_directory,'..'))
162
+ end
163
+ end
164
+
165
+ #Parse root directory of a module for module name
166
+ # Searches for metadata.json and then if none found, Modulefile and parses for the Name attribute
167
+ # @param [String] root_module_dir
168
+ # @return [String] module name
169
+ def parse_for_modulename(root_module_dir)
170
+ author_name, module_name = nil, nil
171
+ if File.exists?("#{root_module_dir}/metadata.json")
172
+ logger.debug "Attempting to parse Modulename from metadata.json"
173
+ module_json = JSON.parse(File.read "#{root_module_dir}/metadata.json")
174
+ if(module_json.has_key?('name'))
175
+ author_name, module_name = get_module_name(module_json['name'])
176
+ end
177
+ end
178
+ if !module_name && File.exists?("#{root_module_dir}/Modulefile")
179
+ logger.debug "Attempting to parse Modulename from Modulefile"
180
+ if /^name\s+'?(\w+-\w+)'?\s*$/i.match(File.read("#{root_module_dir}/Modulefile"))
181
+ author_name, module_name = get_module_name(Regexp.last_match[1])
182
+ end
183
+ end
184
+ if !module_name && !author_name
185
+ logger.debug "Unable to determine name, returning null"
186
+ end
187
+ return author_name, module_name
188
+ end
189
+
190
+ #Parse modulename from the pattern 'Auther-ModuleName'
191
+ #
192
+ # @param [String] author_module_name <Author>-<ModuleName> pattern
193
+ #
194
+ # @return [String,nil]
195
+ #
196
+ def get_module_name(author_module_name)
197
+ split_name = split_author_modulename(author_module_name)
198
+ if split_name
199
+ return split_name[:author], split_name[:module]
200
+ end
201
+ end
202
+
203
+ #Split the Author-Name into a hash
204
+ # @param [String] author_module_attr
205
+ #
206
+ # @return [Hash<Symbol,String>,nil] :author and :module symbols will be returned
207
+ #
208
+ def split_author_modulename(author_module_attr)
209
+ result = /(\w+)-(\w+)/.match(author_module_attr)
210
+ if result
211
+ {:author => result[1], :module => result[2]}
212
+ else
213
+ nil
214
+ end
215
+ end
216
+
217
+ # Build an array list of files/directories to ignore when pushing to remote host
218
+ # Automatically adds '..' and '.' to array. If not opts of :ignore list is provided
219
+ # it will use the static variable PUPPET_MODULE_INSTALL_IGNORE
220
+ #
221
+ # @param opts [Hash]
222
+ # @option opts [Array] :ignore_list A list of files/directories to ignore
223
+ def build_ignore_list(opts = {})
224
+ ignore_list = opts[:ignore_list] || PUPPET_MODULE_INSTALL_IGNORE
225
+ if !ignore_list.kind_of?(Array) || ignore_list.nil?
226
+ raise ArgumentError "Ignore list must be an Array"
227
+ end
228
+ ignore_list << '.' unless ignore_list.include? '.'
229
+ ignore_list << '..' unless ignore_list.include? '..'
230
+ ignore_list
231
+ end
232
+
233
+ end
234
+ end
235
+
236
+ end
237
+ end