foodcritic 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/CHANGELOG.md +83 -0
- data/chef_dsl_metadata/chef_0.10.0.json +2 -3
- data/chef_dsl_metadata/chef_0.10.10.json +2 -3
- data/chef_dsl_metadata/chef_0.10.2.json +2 -3
- data/chef_dsl_metadata/chef_0.10.4.json +2 -3
- data/chef_dsl_metadata/chef_0.10.6.json +2 -3
- data/chef_dsl_metadata/chef_0.10.8.json +2 -3
- data/chef_dsl_metadata/chef_0.8.14.json +2 -3
- data/chef_dsl_metadata/chef_0.8.16.json +2 -3
- data/chef_dsl_metadata/chef_0.9.0.json +2 -3
- data/chef_dsl_metadata/chef_0.9.10.json +2 -3
- data/chef_dsl_metadata/chef_0.9.12.json +2 -3
- data/chef_dsl_metadata/chef_0.9.14.json +2 -3
- data/chef_dsl_metadata/chef_0.9.16.json +2 -3
- data/chef_dsl_metadata/chef_0.9.18.json +2 -3
- data/chef_dsl_metadata/chef_0.9.2.json +2 -3
- data/chef_dsl_metadata/chef_0.9.4.json +2 -3
- data/chef_dsl_metadata/chef_0.9.6.json +2 -3
- data/chef_dsl_metadata/chef_0.9.8.json +2 -3
- data/chef_dsl_metadata/chef_10.12.0.json +2 -3
- data/chef_dsl_metadata/chef_10.14.0.json +2 -3
- data/chef_dsl_metadata/chef_10.14.2.json +2 -3
- data/chef_dsl_metadata/chef_10.14.4.json +2 -3
- data/chef_dsl_metadata/chef_10.16.0.json +2 -3
- data/chef_dsl_metadata/chef_10.16.2.json +2 -3
- data/chef_dsl_metadata/chef_10.16.4.json +2 -3
- data/chef_dsl_metadata/chef_10.16.6.json +2 -3
- data/chef_dsl_metadata/chef_10.18.0.json +2 -3
- data/chef_dsl_metadata/chef_10.18.2.json +2 -3
- data/chef_dsl_metadata/chef_10.20.0.json +2 -3
- data/chef_dsl_metadata/chef_10.22.0.json +2 -3
- data/chef_dsl_metadata/chef_10.24.0.json +2 -3
- data/chef_dsl_metadata/chef_10.24.4.json +2 -3
- data/chef_dsl_metadata/chef_10.26.0.json +2 -3
- data/chef_dsl_metadata/chef_11.0.0.json +2 -3
- data/chef_dsl_metadata/chef_11.2.0.json +2 -3
- data/chef_dsl_metadata/chef_11.4.0.json +2 -3
- data/chef_dsl_metadata/chef_11.4.2.json +2 -3
- data/chef_dsl_metadata/chef_11.4.4.json +2 -3
- data/chef_dsl_metadata/chef_11.6.0.json +9734 -0
- data/features/007_check_for_undeclared_recipe_dependencies.feature +18 -34
- data/features/017_check_for_no_lwrp_notifications.feature +25 -0
- data/features/019_check_for_consistent_node_access.feature +1 -0
- data/features/033_check_for_missing_template.feature +20 -64
- data/features/034_check_for_unused_template_variables.feature +44 -0
- data/features/047_check_for_attribute_assignment_without_precedence.feature +47 -0
- data/features/048_check_for_shellout.feature +34 -0
- data/features/049_check_for_role_name_mismatch_with_file_name.feature +31 -0
- data/features/050_check_for_invalid_name.feature +33 -0
- data/features/051_check_for_template_partial_loops.feature +21 -0
- data/features/command_line_help.feature +15 -0
- data/features/ignore_via_line_comments.feature +18 -0
- data/features/individual_file.feature +17 -1
- data/features/multiple_paths.feature +26 -2
- data/features/step_definitions/cookbook_steps.rb +328 -9
- data/features/support/command_helpers.rb +71 -10
- data/features/support/cookbook_helpers.rb +88 -6
- data/lib/foodcritic/api.rb +89 -20
- data/lib/foodcritic/command_line.rb +64 -18
- data/lib/foodcritic/domain.rb +26 -7
- data/lib/foodcritic/dsl.rb +3 -0
- data/lib/foodcritic/linter.rb +93 -61
- data/lib/foodcritic/rake_task.rb +3 -2
- data/lib/foodcritic/rules.rb +105 -14
- data/lib/foodcritic/template.rb +34 -1
- data/lib/foodcritic/version.rb +1 -1
- data/man/foodcritic.1 +13 -1
- data/man/foodcritic.1.ronn +9 -0
- data/spec/foodcritic/api_spec.rb +210 -1
- data/spec/foodcritic/command_line_spec.rb +13 -0
- data/spec/foodcritic/domain_spec.rb +40 -5
- data/spec/foodcritic/linter_spec.rb +19 -22
- data/spec/foodcritic/template_spec.rb +8 -4
- data/spec/regression/expected-output.txt +139 -60
- metadata +31 -26
@@ -9,40 +9,59 @@ module FoodCritic
|
|
9
9
|
def initialize(args)
|
10
10
|
@args = args
|
11
11
|
@original_args = args.dup
|
12
|
-
@options = {
|
12
|
+
@options = {
|
13
|
+
:fail_tags => [],
|
14
|
+
:tags => [],
|
15
|
+
:include_rules => [],
|
16
|
+
:cookbook_paths => [],
|
17
|
+
:role_paths => [],
|
18
|
+
:environment_paths => []
|
19
|
+
}
|
13
20
|
@parser = OptionParser.new do |opts|
|
14
21
|
opts.banner = 'foodcritic [cookbook_paths]'
|
15
22
|
opts.on("-t", "--tags TAGS",
|
16
23
|
"Only check against rules with the specified tags.") do |t|
|
17
|
-
options[:tags] << t
|
24
|
+
@options[:tags] << t
|
18
25
|
end
|
19
26
|
opts.on("-f", "--epic-fail TAGS",
|
20
27
|
"Fail the build if any of the specified tags are matched ('any' -> fail on any match).") do |t|
|
21
|
-
options[:fail_tags] << t
|
28
|
+
@options[:fail_tags] << t
|
22
29
|
end
|
23
30
|
opts.on("-c", "--chef-version VERSION",
|
24
31
|
"Only check against rules valid for this version of Chef.") do |c|
|
25
|
-
options[:chef_version] = c
|
32
|
+
@options[:chef_version] = c
|
33
|
+
end
|
34
|
+
opts.on("-B", "--cookbook-path PATH",
|
35
|
+
"Cookbook path(s) to check.") do |b|
|
36
|
+
@options[:cookbook_paths] << b
|
26
37
|
end
|
27
38
|
opts.on("-C", "--[no-]context",
|
28
39
|
"Show lines matched against rather than the default summary.") do |c|
|
29
|
-
options[:context] = c
|
40
|
+
@options[:context] = c
|
41
|
+
end
|
42
|
+
opts.on("-E", "--environment-path PATH",
|
43
|
+
"Environment path(s) to check.") do |e|
|
44
|
+
@options[:environment_paths] << e
|
30
45
|
end
|
31
46
|
opts.on("-I", "--include PATH",
|
32
47
|
"Additional rule file path(s) to load.") do |i|
|
33
|
-
options[:include_rules] << i
|
48
|
+
@options[:include_rules] << i
|
34
49
|
end
|
35
50
|
opts.on("-G", "--search-gems",
|
36
51
|
"Search rubygems for rule files with the path foodcritic/rules/**/*.rb") do |g|
|
37
|
-
options[:search_gems] = true
|
52
|
+
@options[:search_gems] = true
|
53
|
+
end
|
54
|
+
opts.on("-R", "--role-path PATH",
|
55
|
+
"Role path(s) to check.") do |r|
|
56
|
+
@options[:role_paths] << r
|
38
57
|
end
|
39
58
|
opts.on("-S", "--search-grammar PATH",
|
40
59
|
"Specify grammar to use when validating search syntax.") do |s|
|
41
|
-
options[:search_grammar] = s
|
60
|
+
@options[:search_grammar] = s
|
42
61
|
end
|
43
62
|
opts.on("-V", "--version",
|
44
63
|
"Display the foodcritic version.") do |v|
|
45
|
-
options[:version] = true
|
64
|
+
@options[:version] = true
|
46
65
|
end
|
47
66
|
end
|
48
67
|
# -v is not implemented but OptionParser gives the Foodcritic's version
|
@@ -86,18 +105,13 @@ module FoodCritic
|
|
86
105
|
"foodcritic #{FoodCritic::VERSION}"
|
87
106
|
end
|
88
107
|
|
89
|
-
# If the
|
108
|
+
# If the paths provided are valid
|
90
109
|
#
|
91
110
|
# @return [Boolean] True if the paths exist.
|
92
111
|
def valid_paths?
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# The cookbook paths
|
97
|
-
#
|
98
|
-
# @return [Array<String>] Path(s) to the cookbook(s) being checked.
|
99
|
-
def cookbook_paths
|
100
|
-
@args
|
112
|
+
paths = options[:cookbook_paths] + options[:role_paths] +
|
113
|
+
options[:environment_paths]
|
114
|
+
paths.any? && paths.all?{|path| File.exists?(path) }
|
101
115
|
end
|
102
116
|
|
103
117
|
# Is the search grammar specified valid?
|
@@ -112,6 +126,27 @@ module FoodCritic
|
|
112
126
|
search.parser?
|
113
127
|
end
|
114
128
|
|
129
|
+
# The cookbook paths to check
|
130
|
+
#
|
131
|
+
# @return [Array<String>] Path(s) to the cookbook(s) being checked.
|
132
|
+
def cookbook_paths
|
133
|
+
@args + Array(@options[:cookbook_paths])
|
134
|
+
end
|
135
|
+
|
136
|
+
# The role paths to check
|
137
|
+
#
|
138
|
+
# @return [Array<String>] Path(s) to the role directories being checked.
|
139
|
+
def role_paths
|
140
|
+
Array(@options[:role_paths])
|
141
|
+
end
|
142
|
+
|
143
|
+
# The environment paths to check
|
144
|
+
#
|
145
|
+
# @return [Array<String>] Path(s) to the environment directories being checked.
|
146
|
+
def environment_paths
|
147
|
+
Array(@options[:environment_paths])
|
148
|
+
end
|
149
|
+
|
115
150
|
# If matches should be shown with context rather than the default summary
|
116
151
|
# display.
|
117
152
|
#
|
@@ -124,6 +159,17 @@ module FoodCritic
|
|
124
159
|
#
|
125
160
|
# @return [Hash] The parsed command-line options.
|
126
161
|
def options
|
162
|
+
original_options.merge({
|
163
|
+
:cookbook_paths => cookbook_paths,
|
164
|
+
:role_paths => role_paths,
|
165
|
+
:environment_paths => environment_paths,
|
166
|
+
})
|
167
|
+
end
|
168
|
+
|
169
|
+
# The original command-line options
|
170
|
+
#
|
171
|
+
# @return [Hash] The original command-line options.
|
172
|
+
def original_options
|
127
173
|
@options
|
128
174
|
end
|
129
175
|
|
data/lib/foodcritic/domain.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
|
+
require 'gherkin/tag_expression'
|
2
|
+
|
1
3
|
module FoodCritic
|
2
4
|
|
3
5
|
# A warning of a possible issue
|
4
6
|
class Warning
|
5
|
-
attr_reader :rule, :match
|
7
|
+
attr_reader :rule, :match, :is_failed
|
6
8
|
|
7
9
|
# Create a new warning.
|
8
10
|
#
|
9
11
|
# Warning.new(rule, :filename => 'foo/recipes.default.rb',
|
10
12
|
# :line => 5, :column=> 40)
|
11
13
|
#
|
12
|
-
def initialize(rule, match={})
|
14
|
+
def initialize(rule, match={}, options={})
|
13
15
|
@rule, @match = rule, match
|
16
|
+
@is_failed = options[:fail_tags].empty? ? false : rule.matches_tags?(options[:fail_tags])
|
17
|
+
end
|
18
|
+
|
19
|
+
# If this warning has failed or not.
|
20
|
+
def failed?
|
21
|
+
@is_failed
|
14
22
|
end
|
15
23
|
end
|
16
24
|
|
@@ -19,15 +27,19 @@ module FoodCritic
|
|
19
27
|
|
20
28
|
attr_reader :cookbook_paths, :warnings
|
21
29
|
|
22
|
-
def initialize(cookbook_paths, warnings
|
30
|
+
def initialize(cookbook_paths, warnings)
|
23
31
|
@cookbook_paths = Array(cookbook_paths)
|
24
32
|
@warnings = warnings
|
25
|
-
@is_failed = is_failed
|
26
33
|
end
|
27
34
|
|
28
|
-
# If this review
|
35
|
+
# If any of the warnings in this review have failed or not.
|
29
36
|
def failed?
|
30
|
-
|
37
|
+
warnings.any? { |w| w.failed? }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an array of warnings that are marked as failed.
|
41
|
+
def failures
|
42
|
+
warnings.select { |w| w.failed? }
|
31
43
|
end
|
32
44
|
|
33
45
|
# Returns a string representation of this review. This representation is
|
@@ -48,7 +60,7 @@ module FoodCritic
|
|
48
60
|
# A rule to be matched against.
|
49
61
|
class Rule
|
50
62
|
attr_accessor :code, :name, :applies_to, :cookbook, :attributes, :recipe,
|
51
|
-
:provider, :resource, :metadata, :library, :template
|
63
|
+
:provider, :resource, :metadata, :library, :template, :role, :environment
|
52
64
|
|
53
65
|
attr_writer :tags
|
54
66
|
|
@@ -64,6 +76,13 @@ module FoodCritic
|
|
64
76
|
['any'] + @tags
|
65
77
|
end
|
66
78
|
|
79
|
+
# Checks the rule's tags to see if they match a Gherkin (Cucumber) expression
|
80
|
+
def matches_tags?(tag_expr)
|
81
|
+
Gherkin::TagExpression.new(tag_expr).evaluate(tags.map do |t|
|
82
|
+
Gherkin::Formatter::Model::Tag.new(t, 1)
|
83
|
+
end)
|
84
|
+
end
|
85
|
+
|
67
86
|
# Returns a string representation of this rule.
|
68
87
|
def to_s
|
69
88
|
"#{@code}: #{@name}"
|
data/lib/foodcritic/dsl.rb
CHANGED
data/lib/foodcritic/linter.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'ripper'
|
3
3
|
require 'rubygems'
|
4
|
-
require 'gherkin/tag_expression'
|
5
4
|
require 'set'
|
6
5
|
|
7
6
|
module FoodCritic
|
@@ -25,8 +24,7 @@ module FoodCritic
|
|
25
24
|
if ! cmd_line.valid_grammar?
|
26
25
|
[cmd_line.help, 4]
|
27
26
|
elsif cmd_line.valid_paths?
|
28
|
-
review = FoodCritic::Linter.new.check(cmd_line.
|
29
|
-
cmd_line.options)
|
27
|
+
review = FoodCritic::Linter.new.check(cmd_line.options)
|
30
28
|
[review, review.failed? ? 3 : 0]
|
31
29
|
else
|
32
30
|
[cmd_line.help, 2]
|
@@ -38,55 +36,86 @@ module FoodCritic
|
|
38
36
|
#
|
39
37
|
# The `options` are a hash where the valid keys are:
|
40
38
|
#
|
39
|
+
# * `:cookbook_paths` - Cookbook paths to lint
|
40
|
+
# * `:role_paths` - Role paths to lint
|
41
41
|
# * `:include_rules` - Paths to additional rules to apply
|
42
42
|
# * `:search_gems - If true then search for custom rules in installed gems.
|
43
43
|
# * `:tags` - The tags to filter rules based on
|
44
44
|
# * `:fail_tags` - The tags to fail the build on
|
45
45
|
# * `:exclude_paths` - Paths to exclude from linting
|
46
46
|
#
|
47
|
-
def check(
|
47
|
+
def check(options = {})
|
48
48
|
|
49
|
-
cookbook_paths = sanity_check_cookbook_paths(cookbook_paths)
|
50
49
|
options = setup_defaults(options)
|
51
50
|
@options = options
|
52
51
|
@chef_version = options[:chef_version] || DEFAULT_CHEF_VERSION
|
53
52
|
|
54
53
|
warnings = []; last_dir = nil; matched_rule_tags = Set.new
|
55
|
-
|
56
54
|
load_rules
|
55
|
+
paths = specified_paths!(options)
|
57
56
|
|
58
57
|
# Loop through each file to be processed and apply the rules
|
59
|
-
files_to_process(
|
60
|
-
ast = read_ast(file)
|
61
|
-
relevant_tags = options[:tags].any? ? options[:tags] : cookbook_tags(file)
|
62
|
-
active_rules(relevant_tags).each do |rule|
|
63
|
-
rule_matches = matches(rule.recipe, ast, file)
|
58
|
+
files_to_process(paths).each do |p|
|
64
59
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
60
|
+
relevant_tags = if options[:tags].any?
|
61
|
+
options[:tags]
|
62
|
+
else
|
63
|
+
cookbook_tags(p[:filename])
|
64
|
+
end
|
69
65
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
66
|
+
active_rules(relevant_tags).each do |rule|
|
67
|
+
|
68
|
+
state = {
|
69
|
+
:path_type => p[:path_type],
|
70
|
+
:file => p[:filename],
|
71
|
+
:ast => read_ast(p[:filename]),
|
72
|
+
:rule => rule,
|
73
|
+
:last_dir => last_dir
|
74
|
+
}
|
75
|
+
|
76
|
+
matches = if p[:path_type] == :cookbook
|
77
|
+
cookbook_matches(state)
|
78
|
+
else
|
79
|
+
other_matches(state)
|
75
80
|
end
|
76
81
|
|
77
|
-
|
82
|
+
matches = remove_ignored(matches, state[:rule], state[:file])
|
78
83
|
|
79
84
|
# Convert the matches into warnings
|
80
|
-
|
81
|
-
warnings << Warning.new(rule,
|
82
|
-
|
85
|
+
matches.each do |match|
|
86
|
+
warnings << Warning.new(state[:rule],
|
87
|
+
{:filename => state[:file]}.merge(match), options)
|
88
|
+
matched_rule_tags << state[:rule].tags
|
83
89
|
end
|
84
90
|
end
|
85
|
-
last_dir = cookbook_dir(
|
91
|
+
last_dir = cookbook_dir(p[:filename])
|
92
|
+
end
|
93
|
+
|
94
|
+
Review.new(paths, warnings)
|
95
|
+
end
|
96
|
+
|
97
|
+
def cookbook_matches(state)
|
98
|
+
cbk_matches = matches(state[:rule].recipe, state[:ast], state[:file])
|
99
|
+
|
100
|
+
if dsl_method_for_file(state[:file])
|
101
|
+
cbk_matches += matches(state[:rule].send(
|
102
|
+
dsl_method_for_file(state[:file])), state[:ast], state[:file])
|
103
|
+
end
|
104
|
+
|
105
|
+
per_cookbook_rules(state[:last_dir], state[:file]) do
|
106
|
+
if File.basename(state[:file]) == 'metadata.rb'
|
107
|
+
cbk_matches += matches(
|
108
|
+
state[:rule].metadata, state[:ast], state[:file])
|
109
|
+
end
|
110
|
+
cbk_matches += matches(
|
111
|
+
state[:rule].cookbook, cookbook_dir(state[:file]))
|
86
112
|
end
|
87
113
|
|
88
|
-
|
89
|
-
|
114
|
+
cbk_matches
|
115
|
+
end
|
116
|
+
|
117
|
+
def other_matches(state)
|
118
|
+
matches(state[:rule].send(state[:path_type]), state[:ast], state[:file])
|
90
119
|
end
|
91
120
|
|
92
121
|
# Load the rules from the (fairly unnecessary) DSL.
|
@@ -120,7 +149,7 @@ module FoodCritic
|
|
120
149
|
def ignore_line_match?(line, rule)
|
121
150
|
ignores = line.to_s[/\s+#\s*(.*)/, 1]
|
122
151
|
if ignores and ignores.include?('~')
|
123
|
-
!
|
152
|
+
! rule.matches_tags?(ignores.split(/[ ,]/))
|
124
153
|
else
|
125
154
|
false
|
126
155
|
end
|
@@ -147,7 +176,7 @@ module FoodCritic
|
|
147
176
|
|
148
177
|
def active_rules(tags)
|
149
178
|
@rules.select do |rule|
|
150
|
-
|
179
|
+
rule.matches_tags?(tags) and
|
151
180
|
applies_to_version?(rule, chef_version)
|
152
181
|
end
|
153
182
|
end
|
@@ -178,19 +207,29 @@ module FoodCritic
|
|
178
207
|
|
179
208
|
# Return the files within a cookbook tree that we are interested in trying
|
180
209
|
# to match rules against.
|
181
|
-
def files_to_process(
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
210
|
+
def files_to_process(paths)
|
211
|
+
paths.reject{|type, _| type == :exclude}.map do |path_type, dirs|
|
212
|
+
dirs.map do |dir|
|
213
|
+
exclusions = []
|
214
|
+
unless paths[:exclude].empty?
|
215
|
+
exclusions = Dir.glob(paths[:exclude].map{|p| File.join(dir, p)})
|
216
|
+
end
|
217
|
+
|
218
|
+
if File.directory?(dir)
|
219
|
+
glob = if path_type == :cookbook
|
220
|
+
'{metadata.rb,{attributes,definitions,libraries,providers,recipes,resources}/*.rb,templates/*/*.erb}'
|
221
|
+
else
|
222
|
+
'*.rb'
|
223
|
+
end
|
224
|
+
(Dir.glob(File.join(dir, glob)) +
|
225
|
+
Dir.glob(File.join(dir, "*/#{glob}")) - exclusions)
|
226
|
+
else
|
227
|
+
dir unless exclusions.include?(dir)
|
228
|
+
end
|
229
|
+
end.compact.flatten.map do |filename|
|
230
|
+
{:filename => filename, :path_type => path_type}
|
191
231
|
end
|
192
|
-
end
|
193
|
-
files
|
232
|
+
end.flatten
|
194
233
|
end
|
195
234
|
|
196
235
|
# Invoke the DSL method with the provided parameters.
|
@@ -211,34 +250,27 @@ module FoodCritic
|
|
211
250
|
end.flatten
|
212
251
|
end
|
213
252
|
|
214
|
-
# We use the Gherkin (Cucumber) syntax to specify tags.
|
215
|
-
def matching_tags?(tag_expr, tags)
|
216
|
-
Gherkin::TagExpression.new(tag_expr).evaluate(tags.map do |t|
|
217
|
-
Gherkin::Formatter::Model::Tag.new(t, 1)
|
218
|
-
end)
|
219
|
-
end
|
220
|
-
|
221
253
|
def per_cookbook_rules(last_dir, file)
|
222
254
|
yield if last_dir != cookbook_dir(file)
|
223
255
|
end
|
224
256
|
|
225
|
-
def
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
257
|
+
def specified_paths!(options)
|
258
|
+
paths = Hash[options.map do |key, value|
|
259
|
+
[key, Array(value)] if key.to_s.end_with?('paths')
|
260
|
+
end.compact]
|
261
|
+
|
262
|
+
unless paths.find{|k, v| k != :exclude_paths and ! v.empty?}
|
263
|
+
raise ArgumentError, "A cookbook path or role path must be specified"
|
230
264
|
end
|
231
|
-
cookbook_paths
|
232
|
-
end
|
233
265
|
|
234
|
-
|
235
|
-
|
236
|
-
|
266
|
+
Hash[paths.map do |key, value|
|
267
|
+
[key.to_s.sub(/_paths$/, '').to_sym, value]
|
268
|
+
end]
|
237
269
|
end
|
238
270
|
|
239
|
-
def
|
240
|
-
|
241
|
-
|
271
|
+
def setup_defaults(options)
|
272
|
+
{:tags => [], :fail_tags => [], :include_rules => [], :exclude_paths => [],
|
273
|
+
:cookbook_paths => [], :role_paths => []}.merge(options)
|
242
274
|
end
|
243
275
|
|
244
276
|
end
|