beaker-puppet 0.1.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 (52) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +25 -0
  3. data/.simplecov +9 -0
  4. data/Gemfile +25 -0
  5. data/HISTORY.md +8 -0
  6. data/LICENSE +202 -0
  7. data/README.md +55 -0
  8. data/Rakefile +299 -0
  9. data/acceptance/config/acceptance-options.rb +6 -0
  10. data/acceptance/config/gem/acceptance-options.rb +9 -0
  11. data/acceptance/config/git/acceptance-options.rb +9 -0
  12. data/acceptance/config/nodes/vagrant-ubuntu-1404.yml +8 -0
  13. data/acceptance/config/pkg/acceptance-options.rb +8 -0
  14. data/acceptance/lib/beaker/acceptance/install_utils.rb +58 -0
  15. data/acceptance/pre_suite/gem/install.rb +8 -0
  16. data/acceptance/pre_suite/git/install.rb +97 -0
  17. data/acceptance/pre_suite/pkg/install.rb +9 -0
  18. data/acceptance/tests/README.md +3 -0
  19. data/acceptance/tests/backwards_compatible.rb +19 -0
  20. data/acceptance/tests/install_smoke_test.rb +21 -0
  21. data/acceptance/tests/stub_host.rb +47 -0
  22. data/acceptance/tests/web_helpers_test.rb +54 -0
  23. data/acceptance/tests/with_puppet_running_on.rb +26 -0
  24. data/beaker-puppet.gemspec +38 -0
  25. data/bin/beaker-puppet +32 -0
  26. data/lib/beaker-puppet.rb +46 -0
  27. data/lib/beaker-puppet/helpers/facter_helpers.rb +57 -0
  28. data/lib/beaker-puppet/helpers/puppet_helpers.rb +865 -0
  29. data/lib/beaker-puppet/helpers/tk_helpers.rb +89 -0
  30. data/lib/beaker-puppet/install_utils/aio_defaults.rb +93 -0
  31. data/lib/beaker-puppet/install_utils/ezbake_utils.rb +256 -0
  32. data/lib/beaker-puppet/install_utils/foss_defaults.rb +211 -0
  33. data/lib/beaker-puppet/install_utils/foss_utils.rb +1309 -0
  34. data/lib/beaker-puppet/install_utils/module_utils.rb +244 -0
  35. data/lib/beaker-puppet/install_utils/puppet_utils.rb +157 -0
  36. data/lib/beaker-puppet/version.rb +3 -0
  37. data/lib/beaker-puppet/wrappers.rb +93 -0
  38. data/lib/beaker/dsl/helpers/facter_helpers.rb +1 -0
  39. data/lib/beaker/dsl/helpers/puppet_helpers.rb +1 -0
  40. data/lib/beaker/dsl/helpers/tk_helpers.rb +1 -0
  41. data/lib/beaker/dsl/install_utils/aio_defaults.rb +1 -0
  42. data/lib/beaker/dsl/install_utils/ezbake_utils.rb +1 -0
  43. data/lib/beaker/dsl/install_utils/foss_defaults.rb +1 -0
  44. data/lib/beaker/dsl/install_utils/foss_utils.rb +1 -0
  45. data/lib/beaker/dsl/install_utils/module_utils.rb +1 -0
  46. data/lib/beaker/dsl/install_utils/puppet_utils.rb +1 -0
  47. data/spec/beaker-puppet/helpers/facter_helpers_spec.rb +64 -0
  48. data/spec/beaker-puppet/helpers/puppet_helpers_spec.rb +1287 -0
  49. data/spec/beaker-puppet/helpers/tk_helpers_spec.rb +86 -0
  50. data/spec/helpers.rb +109 -0
  51. data/spec/spec_helper.rb +23 -0
  52. metadata +249 -0
@@ -0,0 +1,244 @@
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', '.svn', 'junit', 'pkg', 'example']
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
+
114
+ ignore_list = build_ignore_list(opts)
115
+ target_module_dir = on( host, "echo #{opts[:target_module_path]}" ).stdout.chomp
116
+ source_path = File.expand_path( opts[:source] )
117
+ source_dir = File.dirname(source_path)
118
+ source_name = File.basename(source_path)
119
+ if opts.has_key?(:module_name)
120
+ module_name = opts[:module_name]
121
+ else
122
+ _, module_name = parse_for_modulename( source_path )
123
+ end
124
+
125
+ target_path = File.join(target_module_dir, module_name)
126
+ if host.is_powershell? #make sure our slashes are correct
127
+ target_path = target_path.gsub(/\//,'\\')
128
+ end
129
+
130
+ opts[:protocol] ||= 'scp'
131
+ case opts[:protocol]
132
+ when 'scp'
133
+ #move to the host
134
+ logger.debug "Using scp to transfer #{source_path} to #{target_path}"
135
+ scp_to host, source_path, target_module_dir, {:ignore => ignore_list}
136
+
137
+ #rename to the selected module name, if not correct
138
+ cur_path = File.join(target_module_dir, source_name)
139
+ if host.is_powershell? #make sure our slashes are correct
140
+ cur_path = cur_path.gsub(/\//,'\\')
141
+ end
142
+ host.mv cur_path, target_path unless cur_path == target_path
143
+ when 'rsync'
144
+ logger.debug "Using rsync to transfer #{source_path} to #{target_path}"
145
+ rsync_to host, source_path, target_path, {:ignore => ignore_list}
146
+ else
147
+ logger.debug "Unsupported transfer protocol, returning nil"
148
+ nil
149
+ end
150
+ end
151
+ end
152
+ alias :copy_root_module_to :copy_module_to
153
+
154
+ #Recursive method for finding the module root
155
+ # Assumes that a Modulefile exists
156
+ # @param [String] possible_module_directory
157
+ # will look for Modulefile and if none found go up one level and try again until root is reached
158
+ #
159
+ # @return [String,nil]
160
+ def parse_for_moduleroot(possible_module_directory)
161
+ if File.exists?("#{possible_module_directory}/Modulefile") || File.exists?("#{possible_module_directory}/metadata.json")
162
+ possible_module_directory
163
+ elsif possible_module_directory === '/'
164
+ logger.error "At root, can't parse for another directory"
165
+ nil
166
+ else
167
+ logger.debug "No Modulefile or metadata.json found at #{possible_module_directory}, moving up"
168
+ parse_for_moduleroot File.expand_path(File.join(possible_module_directory,'..'))
169
+ end
170
+ end
171
+
172
+ #Parse root directory of a module for module name
173
+ # Searches for metadata.json and then if none found, Modulefile and parses for the Name attribute
174
+ # @param [String] root_module_dir
175
+ # @return [String] module name
176
+ def parse_for_modulename(root_module_dir)
177
+ author_name, module_name = nil, nil
178
+ if File.exists?("#{root_module_dir}/metadata.json")
179
+ logger.debug "Attempting to parse Modulename from metadata.json"
180
+ module_json = JSON.parse(File.read "#{root_module_dir}/metadata.json")
181
+ if(module_json.has_key?('name'))
182
+ author_name, module_name = get_module_name(module_json['name'])
183
+ end
184
+ end
185
+ if !module_name && File.exists?("#{root_module_dir}/Modulefile")
186
+ logger.debug "Attempting to parse Modulename from Modulefile"
187
+ if /^name\s+'?(\w+-\w+)'?\s*$/i.match(File.read("#{root_module_dir}/Modulefile"))
188
+ author_name, module_name = get_module_name(Regexp.last_match[1])
189
+ end
190
+ end
191
+ if !module_name && !author_name
192
+ logger.debug "Unable to determine name, returning null"
193
+ end
194
+ return author_name, module_name
195
+ end
196
+
197
+ #Parse modulename from the pattern 'Auther-ModuleName'
198
+ #
199
+ # @param [String] author_module_name <Author>-<ModuleName> pattern
200
+ #
201
+ # @return [String,nil]
202
+ #
203
+ def get_module_name(author_module_name)
204
+ split_name = split_author_modulename(author_module_name)
205
+ if split_name
206
+ return split_name[:author], split_name[:module]
207
+ end
208
+ end
209
+
210
+ #Split the Author-Name into a hash
211
+ # @param [String] author_module_attr
212
+ #
213
+ # @return [Hash<Symbol,String>,nil] :author and :module symbols will be returned
214
+ #
215
+ def split_author_modulename(author_module_attr)
216
+ result = /(\w+)-(\w+)/.match(author_module_attr)
217
+ if result
218
+ {:author => result[1], :module => result[2]}
219
+ else
220
+ nil
221
+ end
222
+ end
223
+
224
+ # Build an array list of files/directories to ignore when pushing to remote host
225
+ # Automatically adds '..' and '.' to array. If not opts of :ignore list is provided
226
+ # it will use the static variable PUPPET_MODULE_INSTALL_IGNORE
227
+ #
228
+ # @param opts [Hash]
229
+ # @option opts [Array] :ignore_list A list of files/directories to ignore
230
+ def build_ignore_list(opts = {})
231
+ ignore_list = opts[:ignore_list] || PUPPET_MODULE_INSTALL_IGNORE
232
+ if !ignore_list.kind_of?(Array) || ignore_list.nil?
233
+ raise ArgumentError "Ignore list must be an Array"
234
+ end
235
+ ignore_list << '.' unless ignore_list.include? '.'
236
+ ignore_list << '..' unless ignore_list.include? '..'
237
+ ignore_list
238
+ end
239
+
240
+ end
241
+ end
242
+
243
+ end
244
+ end
@@ -0,0 +1,157 @@
1
+ module Beaker
2
+ module DSL
3
+ module InstallUtils
4
+ #
5
+ # This module contains methods useful for both foss and pe installs
6
+ #
7
+ module PuppetUtils
8
+
9
+ #Given a type return an understood host type
10
+ #@param [String] type The host type to be normalized
11
+ #@return [String] The normalized type
12
+ #
13
+ #@example
14
+ # normalize_type('pe-aio')
15
+ # 'pe'
16
+ #@example
17
+ # normalize_type('git')
18
+ # 'foss'
19
+ #@example
20
+ # normalize_type('foss-internal')
21
+ # 'foss'
22
+ def normalize_type type
23
+ case type
24
+ when /(\A|-)(git)|(foss)(\Z|-)/
25
+ 'foss'
26
+ when /(\A|-)pe(\Z|-)/
27
+ 'pe'
28
+ when /(\A|-)aio(\Z|-)/
29
+ 'aio'
30
+ else
31
+ nil
32
+ end
33
+ end
34
+
35
+ #Given a host construct a PATH that includes puppetbindir, facterbindir and hierabindir
36
+ # @param [Host] host A single host to construct pathing for
37
+ def construct_puppet_path(host)
38
+ path = (%w(puppetbindir facterbindir hierabindir)).compact.reject(&:empty?)
39
+ #get the PATH defaults
40
+ path.map! { |val| host[val] }
41
+ path = path.compact.reject(&:empty?)
42
+ #run the paths through echo to see if they have any subcommands that need processing
43
+ path.map! { |val| echo_on(host, val) }
44
+
45
+ separator = host['pathseparator']
46
+ if not host.is_powershell?
47
+ separator = ':'
48
+ end
49
+ path.join(separator)
50
+ end
51
+
52
+ #Append puppetbindir, facterbindir and hierabindir to the PATH for each host
53
+ # @param [Host, Array<Host>, String, Symbol] hosts One or more hosts to act upon,
54
+ # or a role (String or Symbol) that identifies one or more hosts.
55
+ def add_puppet_paths_on(hosts)
56
+ block_on hosts do | host |
57
+ puppet_path = construct_puppet_path(host)
58
+ host.add_env_var('PATH', puppet_path)
59
+ host.add_env_var('PATH', 'PATH') # don't destroy the path!
60
+ end
61
+ end
62
+
63
+ #Remove puppetbindir, facterbindir and hierabindir to the PATH for each host
64
+ #
65
+ # @param [Host, Array<Host>, String, Symbol] hosts One or more hosts to act upon,
66
+ # or a role (String or Symbol) that identifies one or more hosts.
67
+ def remove_puppet_paths_on(hosts)
68
+ block_on hosts do | host |
69
+ puppet_path = construct_puppet_path(host)
70
+ host.delete_env_var('PATH', puppet_path)
71
+ host.add_env_var('PATH', 'PATH') # don't destroy the path!
72
+ end
73
+ end
74
+
75
+ #Configure the provided hosts to be of the provided type (one of foss, aio, pe), if the host
76
+ #is already associated with a type then remove the previous settings for that type
77
+ # @param [Host, Array<Host>, String, Symbol] hosts One or more hosts to act upon,
78
+ # or a role (String or Symbol) that identifies one or more hosts.
79
+ # @param [String] type One of 'aio', 'pe' or 'foss'
80
+ def configure_defaults_on( hosts, type )
81
+ block_on hosts do |host|
82
+
83
+ # check to see if the host already has a type associated with it
84
+ remove_defaults_on(host)
85
+
86
+ add_method = "add_#{type}_defaults_on"
87
+ if self.respond_to?(add_method, host)
88
+ self.send(add_method, host)
89
+ else
90
+ raise "cannot add defaults of type #{type} for host #{host.name} (#{add_method} not present)"
91
+ end
92
+ # add pathing env
93
+ add_puppet_paths_on(host)
94
+ end
95
+ end
96
+
97
+ # Configure the provided hosts to be of their host[:type], it host[type] == nil do nothing
98
+ def configure_type_defaults_on( hosts )
99
+ block_on hosts do |host|
100
+ has_defaults = false
101
+ if host[:type]
102
+ host_type = host[:type]
103
+ # clean up the naming conventions here (some teams use foss-package, git-whatever, we need
104
+ # to correctly handle that
105
+ # don't worry about aio, that happens in the aio_version? check
106
+ host_type = normalize_type(host_type)
107
+ if host_type and host_type !~ /aio/
108
+ add_method = "add_#{host_type}_defaults_on"
109
+ if self.respond_to?(add_method, host)
110
+ self.send(add_method, host)
111
+ else
112
+ raise "cannot add defaults of type #{host_type} for host #{host.name} (#{add_method} not present)"
113
+ end
114
+ has_defaults = true
115
+ end
116
+ end
117
+ if aio_version?(host)
118
+ add_aio_defaults_on(host)
119
+ has_defaults = true
120
+ end
121
+ # add pathing env
122
+ if has_defaults
123
+ add_puppet_paths_on(host)
124
+ end
125
+ end
126
+ end
127
+ alias_method :configure_foss_defaults_on, :configure_type_defaults_on
128
+ alias_method :configure_pe_defaults_on, :configure_type_defaults_on
129
+
130
+ #If the host is associated with a type remove all defaults and environment associated with that type.
131
+ # @param [Host, Array<Host>, String, Symbol] hosts One or more hosts to act upon,
132
+ # or a role (String or Symbol) that identifies one or more hosts.
133
+ def remove_defaults_on( hosts )
134
+ block_on hosts do |host|
135
+ if host['type']
136
+ # clean up the naming conventions here (some teams use foss-package, git-whatever, we need
137
+ # to correctly handle that
138
+ # don't worry about aio, that happens in the aio_version? check
139
+ host_type = normalize_type(host['type'])
140
+ remove_puppet_paths_on(hosts)
141
+ remove_method = "remove_#{host_type}_defaults_on"
142
+ if self.respond_to?(remove_method, host)
143
+ self.send(remove_method, host)
144
+ else
145
+ raise "cannot remove defaults of type #{host_type} associated with host #{host.name} (#{remove_method} not present)"
146
+ end
147
+ if aio_version?(host)
148
+ remove_aio_defaults_on(host)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,3 @@
1
+ module BeakerPuppet
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,93 @@
1
+ module Beaker
2
+ module DSL
3
+ module Wrappers
4
+
5
+ # This is hairy and because of legacy code it will take a bit more
6
+ # work to disentangle all of the things that are being passed into
7
+ # this catchall param.
8
+ #
9
+ def facter(*args)
10
+ options = args.last.is_a?(Hash) ? args.pop : {}
11
+ options['ENV'] ||= {}
12
+ options[:cmdexe] = true
13
+ Command.new('facter', args, options )
14
+ end
15
+
16
+ # This is hairy and because of legacy code it will take a bit more
17
+ # work to disentangle all of the things that are being passed into
18
+ # this catchall param.
19
+ #
20
+ def cfacter(*args)
21
+ options = args.last.is_a?(Hash) ? args.pop : {}
22
+ options['ENV'] ||= {}
23
+ options[:cmdexe] = true
24
+ Command.new('cfacter', args, options )
25
+ end
26
+
27
+ # This is hairy and because of legacy code it will take a bit more
28
+ # work to disentangle all of the things that are being passed into
29
+ # this catchall param.
30
+ #
31
+ def hiera(*args)
32
+ options = args.last.is_a?(Hash) ? args.pop : {}
33
+ options['ENV'] ||= {}
34
+ options[:cmdexe] = true
35
+ Command.new('hiera', args, options )
36
+ end
37
+
38
+ # This is hairy and because of legacy code it will take a bit more
39
+ # work to disentangle all of the things that are being passed into
40
+ # this catchall param.
41
+ #
42
+ def puppet(*args)
43
+ options = args.last.is_a?(Hash) ? args.pop : {}
44
+ options['ENV'] ||= {}
45
+ options[:cmdexe] = true
46
+ # we assume that an invocation with `puppet()` will have it's first argument
47
+ # a face or sub command
48
+ cmd = "puppet #{args.shift}"
49
+ Command.new( cmd, args, options )
50
+ end
51
+
52
+ # @!visibility private
53
+ def puppet_resource(*args)
54
+ puppet( 'resource', *args )
55
+ end
56
+
57
+ # @!visibility private
58
+ def puppet_doc(*args)
59
+ puppet( 'doc', *args )
60
+ end
61
+
62
+ # @!visibility private
63
+ def puppet_kick(*args)
64
+ puppet( 'kick', *args )
65
+ end
66
+
67
+ # @!visibility private
68
+ def puppet_cert(*args)
69
+ puppet( 'cert', *args )
70
+ end
71
+
72
+ # @!visibility private
73
+ def puppet_apply(*args)
74
+ puppet( 'apply', *args )
75
+ end
76
+
77
+ # @!visibility private
78
+ def puppet_master(*args)
79
+ puppet( 'master', *args )
80
+ end
81
+
82
+ # @!visibility private
83
+ def puppet_agent(*args)
84
+ puppet( 'agent', *args )
85
+ end
86
+
87
+ # @!visibility private
88
+ def puppet_filebucket(*args)
89
+ puppet( 'filebucket', *args )
90
+ end
91
+ end
92
+ end
93
+ end