beaker-puppet 0.1.0

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