beaker 2.7.1 → 2.8.0

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