puppet_rake_tasks 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rubocop.yml +15 -0
- data/.yardopts +1 -0
- data/Gemfile +17 -0
- data/LICENSE +674 -0
- data/README.md +57 -0
- data/Rakefile +16 -0
- data/lib/puppet_rake_tasks/depchecker/filter.rb +38 -0
- data/lib/puppet_rake_tasks/depchecker/helpers.rb +70 -0
- data/lib/puppet_rake_tasks/depchecker/ignores.rb +107 -0
- data/lib/puppet_rake_tasks/depchecker/incidents.rb +27 -0
- data/lib/puppet_rake_tasks/depchecker/report.rb +79 -0
- data/lib/puppet_rake_tasks/depchecker/resolver.rb +69 -0
- data/lib/puppet_rake_tasks/depchecker.rb +47 -0
- data/lib/puppet_rake_tasks/version.rb +5 -0
- data/lib/puppet_rake_tasks.rb +6 -0
- data/puppet_rake_tasks.gemspec +28 -0
- metadata +90 -0
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# PuppetModuleTask
|
2
|
+
|
3
|
+
TODO: Delete this and describe your gem
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'puppet_rake_tasks'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install puppet_rake_tasks
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Simple usage:
|
24
|
+
|
25
|
+
1. Add the ruby gem: `gem 'puppet_rake_tasks'`
|
26
|
+
2. Require the depchecker in your `Rakefile`: `require 'puppet_rake_tasks/depchecker'`
|
27
|
+
3. Create the task: `PuppetRakeTasks::DepChecker::Task.new`
|
28
|
+
4. Profit: `rake exec depcheck
|
29
|
+
|
30
|
+
### Advanced usage:
|
31
|
+
|
32
|
+
Optionally, you can also configure the task:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'puppet_rake_tasks/depchecker'
|
36
|
+
PuppetRakeTasks::DepChecker::Task.new do |checker|
|
37
|
+
checker.fail_on_error = true
|
38
|
+
checker.modulepath = [
|
39
|
+
'site',
|
40
|
+
'modules'
|
41
|
+
]
|
42
|
+
checker.ignore /.*/, { name: 'foo/bar', reason: :missing }
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
## Development
|
47
|
+
|
48
|
+
After checking out the repo, run `bundler install` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bundle console` for an interactive prompt that will allow you to experiment.
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/vStone/puppet_rake_tasks).
|
53
|
+
|
54
|
+
## License
|
55
|
+
|
56
|
+
The gem is available as open source under the terms of the [GPL-3 License](http://opensource.org/licenses/GPL-3.0).
|
57
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'yard'
|
5
|
+
require 'yard/rake/yardoc_task'
|
6
|
+
|
7
|
+
desc 'Run rubocop'
|
8
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
9
|
+
task.formatters = %w(fuubar offenses)
|
10
|
+
task.fail_on_error = true
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new(:spec)
|
14
|
+
YARD::Rake::YardocTask.new(:docs)
|
15
|
+
|
16
|
+
task default: [:rubocop, :spec]
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module PuppetRakeTasks
|
2
|
+
module DepChecker
|
3
|
+
class Resolver
|
4
|
+
# Apply ignore rules to incidents and return the filtered results
|
5
|
+
module Filter
|
6
|
+
# @return [Hash] list with filtered incidents (cached).
|
7
|
+
def filtered
|
8
|
+
@filtered ||= filter_incidents
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# This method calls ignores_matches_incident for each issue and removes it
|
14
|
+
# if the result of the call is true. The ignores_matches_incident will loop
|
15
|
+
# all configured ignores (global/wildcards or specific for the module)
|
16
|
+
# @param issues [Array] An array with all issues related to the module to check.
|
17
|
+
# @param modulename [String] simple module name to filter incidents for.
|
18
|
+
# @return [Array] with filtered issues.
|
19
|
+
def filter_module_incidents(modulename, issues)
|
20
|
+
## Reject all incidents that match an ignore rule
|
21
|
+
issues.reject do |incident|
|
22
|
+
ignores_matches_incident(modulename, incident)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Loop all incidents and apply the ignore filters for each module.
|
27
|
+
# Modules without no incidents (emtpy array) are also removed from the result.
|
28
|
+
# @return [Hash] filtered incidents.
|
29
|
+
def filter_incidents
|
30
|
+
intermediate = incidents.update(incidents) do |modulename, issues|
|
31
|
+
filter_module_incidents(modulename, issues)
|
32
|
+
end
|
33
|
+
intermediate.reject { |_m, i| i.empty? }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module PuppetRakeTasks
|
2
|
+
module DepChecker
|
3
|
+
# General helpers for various methods.
|
4
|
+
module Helpers
|
5
|
+
class << self
|
6
|
+
# Normalize paths split by `File::PATH_SEPARATOR` by making sure it always
|
7
|
+
# returns an array of paths.
|
8
|
+
# @param path [String,Array] A string or an array with paths.
|
9
|
+
# @return [Array] The normalized paths
|
10
|
+
def normalize_path(path)
|
11
|
+
if path.is_a?(String)
|
12
|
+
path.split(File::PATH_SEPARATOR)
|
13
|
+
else
|
14
|
+
[path].flatten
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Compare values by converting symbols to strings or by using
|
19
|
+
# regexes to match if the ignore_value is one.
|
20
|
+
# @param ignore_value [String,Regex,Symbol] Value to compare
|
21
|
+
# @param incident_value [String, Symbol] Value to compare against.
|
22
|
+
# @return [Boolean] true or false depending on the equality-ish-ness.
|
23
|
+
def compare_values(ignore_value, incident_value)
|
24
|
+
if ignore_value.is_a?(Regexp)
|
25
|
+
!ignore_value.match(incident_value.to_s).nil?
|
26
|
+
else
|
27
|
+
incident_value = incident_value.is_a?(Symbol) ? incident_value.to_s : incident_value
|
28
|
+
ignore_value = ignore_value.is_a?(Symbol) ? ignore_value.to_s : ignore_value
|
29
|
+
incident_value == ignore_value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Swats hash keys and turns them into symbols. See example for what swatting is.
|
34
|
+
#
|
35
|
+
# @example Swatting a hash.
|
36
|
+
# swat_hash({
|
37
|
+
# 'foo' => {
|
38
|
+
# 'bar' => 'rab',
|
39
|
+
# 'foo' => {
|
40
|
+
# 'super' => 'nested'
|
41
|
+
# },
|
42
|
+
# },
|
43
|
+
# 'extra' => 'value'
|
44
|
+
# }, '_') #=>
|
45
|
+
# {
|
46
|
+
# :foo_bar => 'rab',
|
47
|
+
# :foo_foo_super => 'nested',
|
48
|
+
# :extra => 'value'
|
49
|
+
# }
|
50
|
+
#
|
51
|
+
# @param hash [Hash] hash to swat.
|
52
|
+
# @param prefix [String] prefix to add to the keys. This is normally only filled in by recursive calls.
|
53
|
+
# @param glue [String] String to use to join the keys.
|
54
|
+
# @return [Hash<Symbol, Any>] Hash without nesting.
|
55
|
+
def swat_hash(hash, glue = '.', prefix = nil)
|
56
|
+
prefix = prefix.nil? ? '' : "#{prefix}#{glue}"
|
57
|
+
intermediate = {}
|
58
|
+
hash.each do |k, v|
|
59
|
+
if v.is_a?(Hash)
|
60
|
+
intermediate.merge!(swat_hash(v, glue, "#{prefix}#{k}"))
|
61
|
+
else
|
62
|
+
intermediate["#{prefix}#{k}".to_sym] = v
|
63
|
+
end
|
64
|
+
end
|
65
|
+
intermediate
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require_relative 'helpers'
|
2
|
+
|
3
|
+
module PuppetRakeTasks
|
4
|
+
module DepChecker
|
5
|
+
class Resolver
|
6
|
+
# Deal with management of the ignore rules you can set.
|
7
|
+
# You can either set all rules in one go using {#ignores=} or add one by one
|
8
|
+
# using the {#ignore} method.
|
9
|
+
#
|
10
|
+
# ## Reference:
|
11
|
+
# When referring to the 'simple name' of a puppet module, use the name without `author/` in front.
|
12
|
+
# You can not have a module with the same name installed in your tree anyhow.
|
13
|
+
#
|
14
|
+
# @example Adding a rule for a single module.
|
15
|
+
# # Ignores the stdlib version mismatch on `your_module`.
|
16
|
+
# @depchecker.ignore 'your_module', { name: 'puppetlabs/stdlib', reason: :version_mismatch }
|
17
|
+
#
|
18
|
+
# @example Adding a rule for a single module with regexp values.
|
19
|
+
# # Ignores all modules from author `custom` that are missing.
|
20
|
+
# @depchecker.ignore 'your_module', { name: %r{^custom/.*}, reason: :missing }
|
21
|
+
#
|
22
|
+
# @example Adding a rule for all modules using a regexp.
|
23
|
+
# # ignores a missing module for all modules that might declare it as a dependency.
|
24
|
+
# @depchecker.ignore %r{.*}, { name: 'foobar/module', reason: :missing }
|
25
|
+
#
|
26
|
+
# @example Loading ignores from a (yaml) file
|
27
|
+
# @depchecker.ignores = YAML.load_file('.ignores.yaml')
|
28
|
+
# # .ignores.yaml
|
29
|
+
# # ---
|
30
|
+
# # !ruby/regexp /.*/:
|
31
|
+
# # - :name: always/missing
|
32
|
+
# # :reason: :missing
|
33
|
+
# # foobar:
|
34
|
+
# # - :name: !ruby/regexp /.*/
|
35
|
+
# # :reason: :version_mismatch
|
36
|
+
#
|
37
|
+
module Ignores
|
38
|
+
extend Helpers
|
39
|
+
|
40
|
+
# Configure all ignore rules at once by setting a single hash.
|
41
|
+
# This clears the cached ignore rules for modules.
|
42
|
+
# @param ignores [Hash] Hash with ignore rules.
|
43
|
+
def ignores=(ignores)
|
44
|
+
@ignores = ignores
|
45
|
+
@ignores_for_module = {}
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param for_module [String,Regexp] module name or regex this rule applies to.
|
49
|
+
# @param expr [Hash] ignore expression.
|
50
|
+
def ignore(for_module, expr)
|
51
|
+
# reset cached ignores matching modules
|
52
|
+
(@ignores_for_module ||= {})[for_module] = nil
|
53
|
+
|
54
|
+
# initialize if not exists
|
55
|
+
(@ignores ||= {})[for_module] ||= []
|
56
|
+
@ignores[for_module] << expr
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns all configured ignore rules.
|
60
|
+
# @return [Hash] ignore rules.
|
61
|
+
def ignores
|
62
|
+
@ignores ||= {}
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns ignore rules that might apply to the modulename and caches the result.
|
66
|
+
# @param modulename [String] module name to get ignore rules for in its simple form.
|
67
|
+
def ignores_for_module(modulename)
|
68
|
+
if (@ignores_for_module ||= {})[modulename].nil?
|
69
|
+
@ignores_for_module[modulename] ||= collect_ignores_for_module(modulename)
|
70
|
+
end
|
71
|
+
@ignores_for_module[modulename]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Loop over ignores for a modulename and check if it matches the incident.
|
75
|
+
# @param modulename [String] puppet module name.
|
76
|
+
# @param incident [Hash] The module incident to check.
|
77
|
+
def ignores_matches_incident(modulename, incident)
|
78
|
+
loop_ignores = ignores_for_module(modulename)
|
79
|
+
# Look for a match, return true immediately, otherwise, false
|
80
|
+
loop_ignores.each do |ign|
|
81
|
+
this_ignore = true
|
82
|
+
ign.keys.each do |k|
|
83
|
+
compare = Helpers.compare_values(ign[k], incident[k])
|
84
|
+
this_ignore = false unless compare
|
85
|
+
end
|
86
|
+
return true if this_ignore
|
87
|
+
end
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# @param modulename [String] puppet module name. Must be in the 'simple' form.
|
94
|
+
# @return [Array] array with all ignores that apply to a certain puppet module.
|
95
|
+
def collect_ignores_for_module(modulename)
|
96
|
+
tmp_ignores = []
|
97
|
+
ignores.each do |k, i|
|
98
|
+
if (k.is_a?(String) && k == modulename) || (k.is_a?(Regexp) && modulename =~ k)
|
99
|
+
tmp_ignores += i
|
100
|
+
end
|
101
|
+
end
|
102
|
+
tmp_ignores
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module PuppetRakeTasks
|
2
|
+
module DepChecker
|
3
|
+
class Resolver
|
4
|
+
# Collect all incidents
|
5
|
+
module Incidents
|
6
|
+
# Returns all (cached) detected incidents in modules.
|
7
|
+
# @return [Hash] all incidents.
|
8
|
+
def incidents
|
9
|
+
@incidents ||= initialize_incidents
|
10
|
+
end
|
11
|
+
|
12
|
+
# Detect all incidents for the current env.
|
13
|
+
# @return [Hash] incidents.
|
14
|
+
def initialize_incidents
|
15
|
+
tmp_incidents = {}
|
16
|
+
modules.by_name.each do |name, mod|
|
17
|
+
mod.unmet_dependencies.each do |incident|
|
18
|
+
tmp_incidents[name] ||= []
|
19
|
+
tmp_incidents[name] << incident
|
20
|
+
end
|
21
|
+
end
|
22
|
+
tmp_incidents
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module PuppetRakeTasks
|
4
|
+
module DepChecker
|
5
|
+
class Resolver
|
6
|
+
# Prints stuff to `stderr` and optionally raises an error
|
7
|
+
module Report
|
8
|
+
extend Helpers
|
9
|
+
|
10
|
+
# Make sure we don't throw a too generic error.
|
11
|
+
class DependencyError < StandardError
|
12
|
+
attr_reader :filtered
|
13
|
+
def initialize(message, filtered)
|
14
|
+
@filtered = filtered
|
15
|
+
super(message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_writer :fail_on_error
|
20
|
+
attr_writer :format
|
21
|
+
attr_writer :output
|
22
|
+
|
23
|
+
def output
|
24
|
+
@output ||= $stderr
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the format configured (or the default format)
|
28
|
+
# @return [String] string fed into sprintf.
|
29
|
+
def format
|
30
|
+
@format ||= 'ERROR: module %<module_name>s: %<reason>s dependency %<name>s. '\
|
31
|
+
"Wants: '%<version_constraint>s', got: '%<mod_details.installed_version>s'\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Boolean indicating wether or not we will fail (raise error) when there are incidents.
|
35
|
+
# @return [Boolean]
|
36
|
+
def fail_on_error
|
37
|
+
@fail_on_error ||= false
|
38
|
+
end
|
39
|
+
|
40
|
+
# Gets all filtered incidents for all modules and reports them module by module.
|
41
|
+
# @raise [DependencyError] if there are any issues detected and `fail_on_error` is enabled (`true`)
|
42
|
+
def report
|
43
|
+
filtered.each do |module_name, module_incidents|
|
44
|
+
format_module_incidents(module_name, module_incidents)
|
45
|
+
end
|
46
|
+
raise DependencyError.new('Module errors found', filtered) unless filtered.empty? || !fail_on_error
|
47
|
+
end
|
48
|
+
|
49
|
+
# Adds extra information to the incident hash for reporting purposes.
|
50
|
+
# @param incident [Hash] Incident to enrich.
|
51
|
+
# @return [Hash] copy of the incident with extra data and a default_proc assigned.
|
52
|
+
def enrich_incident(incident)
|
53
|
+
incident = Helpers.swat_hash(incident)
|
54
|
+
# For recent enough ruby versions, this default proc will just show missing
|
55
|
+
# keys in stead of failing on them (when using sprintf).
|
56
|
+
incident.default_proc = proc { |hash, key| hash[key] = "%<#{key}>s" }
|
57
|
+
incident
|
58
|
+
end
|
59
|
+
|
60
|
+
# Format all incidents for a certain module.
|
61
|
+
# @param module_name [String] name of the module the incidents are detected for.
|
62
|
+
# @param module_incidents [Array<Hash>] All incidents.
|
63
|
+
def format_module_incidents(module_name, module_incidents)
|
64
|
+
module_incidents.each do |incident|
|
65
|
+
# Add the module_name to the incident
|
66
|
+
info_hash = incident.merge(module_name: module_name)
|
67
|
+
format_incident(info_hash)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Format a single incident.
|
72
|
+
# @param info_hash [Hash] hash with all information for the incident.
|
73
|
+
def format_incident(info_hash)
|
74
|
+
output.puts(Kernel.format(format, enrich_incident(info_hash)))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'puppet'
|
2
|
+
require 'puppet/module_tool'
|
3
|
+
|
4
|
+
require_relative 'helpers'
|
5
|
+
require_relative 'ignores'
|
6
|
+
require_relative 'incidents'
|
7
|
+
require_relative 'filter'
|
8
|
+
require_relative 'report'
|
9
|
+
|
10
|
+
module PuppetRakeTasks
|
11
|
+
module DepChecker
|
12
|
+
# Class to detect all puppet module dependency issues.
|
13
|
+
# It uses puppet module tool internals to detect all issues.
|
14
|
+
class Resolver
|
15
|
+
include Helpers
|
16
|
+
include Ignores
|
17
|
+
include Incidents
|
18
|
+
include Filter
|
19
|
+
include Report
|
20
|
+
|
21
|
+
def initialize(module_path = '.')
|
22
|
+
@modulepath = Helpers.normalize_path(module_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the puppet module tool environment.
|
26
|
+
# @see http://www.rubydoc.info/gems/puppet/Puppet/ModuleTool#environment_from_options-class_method
|
27
|
+
def env
|
28
|
+
@env ||= Puppet::ModuleTool.environment_from_options(modulepath: modulepath_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Collect the installed modules using Puppet::ModuleTool.
|
32
|
+
# @see http://www.rubydoc.info/gems/puppet/Puppet/ModuleTool/InstalledModules
|
33
|
+
def modules
|
34
|
+
@modules ||= Puppet::ModuleTool::InstalledModules.new(env)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Clear all cached data (env, modules, incidents)
|
38
|
+
def reset_caches
|
39
|
+
@env = nil
|
40
|
+
@modules = nil
|
41
|
+
@incidents = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the configured module paths.
|
45
|
+
# @return [Array<String>] configured module paths as an array.
|
46
|
+
def modulepath
|
47
|
+
@modulepath ||= ['.']
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return modulepath as a string
|
51
|
+
# @return [String] module paths joined with `File::PATH_SEPARATOR`
|
52
|
+
def modulepath_s
|
53
|
+
@modulepath.flatten.join(File::PATH_SEPARATOR)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set the modulepath and normalize using {Helpers#normalize_path}
|
57
|
+
# When the module path changes, cached values are {#reset_caches cleared}
|
58
|
+
# @param [Array<String>,String] path Module paths
|
59
|
+
# @return [Array<String>] configured and normalized module paths.
|
60
|
+
def modulepath=(path)
|
61
|
+
unless path == @modulepath
|
62
|
+
# reset the env and loaded modules when changing the modulepath
|
63
|
+
reset_caches
|
64
|
+
end
|
65
|
+
@modulepath = Helpers.normalize_path(path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|