foodcritic 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/CHANGELOG.md +691 -0
  2. data/LICENSE +21 -0
  3. data/README.md +28 -0
  4. data/chef_dsl_metadata/chef_0.10.10.json +22 -0
  5. data/chef_dsl_metadata/chef_10.12.0.json +22 -0
  6. data/chef_dsl_metadata/chef_10.14.0.json +22 -0
  7. data/chef_dsl_metadata/chef_10.14.2.json +22 -0
  8. data/chef_dsl_metadata/chef_10.14.4.json +22 -0
  9. data/chef_dsl_metadata/chef_10.16.0.json +22 -0
  10. data/chef_dsl_metadata/chef_10.16.2.json +22 -0
  11. data/chef_dsl_metadata/chef_10.16.4.json +22 -0
  12. data/chef_dsl_metadata/chef_10.16.6.json +22 -0
  13. data/chef_dsl_metadata/chef_10.18.0.json +22 -0
  14. data/chef_dsl_metadata/chef_10.18.2.json +22 -0
  15. data/chef_dsl_metadata/chef_10.20.0.json +22 -0
  16. data/chef_dsl_metadata/chef_10.22.0.json +22 -0
  17. data/chef_dsl_metadata/chef_10.24.0.json +22 -0
  18. data/chef_dsl_metadata/chef_10.24.4.json +8460 -0
  19. data/chef_dsl_metadata/chef_10.26.0.json +8460 -0
  20. data/chef_dsl_metadata/chef_11.0.0.json +22 -0
  21. data/chef_dsl_metadata/chef_11.2.0.json +22 -0
  22. data/chef_dsl_metadata/chef_11.4.0.json +22 -0
  23. data/chef_dsl_metadata/chef_11.4.2.json +8794 -0
  24. data/chef_dsl_metadata/chef_11.4.4.json +8794 -0
  25. data/features/002_check_string_interpolation.feature +45 -0
  26. data/features/003_check_for_chef_server.feature +56 -0
  27. data/features/004_check_service_resource_used.feature +53 -0
  28. data/features/005_check_for_resource_repetition.feature +64 -0
  29. data/features/006_check_file_mode.feature +35 -0
  30. data/features/007_check_for_undeclared_recipe_dependencies.feature +71 -0
  31. data/features/008_check_for_boilerplate_metadata.feature +25 -0
  32. data/features/009_check_for_unrecognised_resource_attributes.feature +77 -0
  33. data/features/010_check_search_syntax.feature +20 -0
  34. data/features/011_check_for_markdown_readme.feature +20 -0
  35. data/features/012_check_for_deprecated_readme_format.feature +20 -0
  36. data/features/013_check_for_hardcoded_tmpdir.feature +25 -0
  37. data/features/014_check_for_long_ruby_blocks.feature +30 -0
  38. data/features/015_check_for_definitions.feature +21 -0
  39. data/features/016_check_for_no_lwrp_default_action.feature +20 -0
  40. data/features/017_check_for_no_lwrp_notifications.feature +25 -0
  41. data/features/018_check_for_old_lwrp_notification_syntax.feature +25 -0
  42. data/features/019_check_for_consistent_node_access.feature +107 -0
  43. data/features/021_check_for_dodgy_lwrp_conditions.feature +28 -0
  44. data/features/022_check_for_dodgy_conditions_within_loop.feature +28 -0
  45. data/features/023_check_for_condition_around_resource.feature +52 -0
  46. data/features/024_check_for_missing_platforms.feature +43 -0
  47. data/features/025_check_for_deprecated_gem_install.feature +30 -0
  48. data/features/026_check_for_conditional_block_string.feature +20 -0
  49. data/features/027_check_for_internal_attribute_use.feature +22 -0
  50. data/features/028_check_for_incorrect_platform_method.feature +20 -0
  51. data/features/029_check_for_no_leading_cookbook_name.feature +18 -0
  52. data/features/030_check_for_debugger_breakpoints.feature +25 -0
  53. data/features/031_check_for_metadata_existence.feature +15 -0
  54. data/features/032_check_for_invalid_notification_timing.feature +22 -0
  55. data/features/033_check_for_missing_template.feature +75 -0
  56. data/features/034_check_for_unused_template_variables.feature +37 -0
  57. data/features/037_check_for_invalid_notification_action.feature +34 -0
  58. data/features/038_check_for_invalid_action.feature +51 -0
  59. data/features/039_check_for_key_access_to_node_methods.feature +33 -0
  60. data/features/040_check_raw_git_usage.feature +37 -0
  61. data/features/041_check_raw_download.feature +26 -0
  62. data/features/042_check_for_deprecated_require_recipe.feature +15 -0
  63. data/features/043_check_for_old_notification_style.feature +35 -0
  64. data/features/044_check_for_bare_attribute_keys.feature +43 -0
  65. data/features/045_check_for_cookbook_name_in_metadata.feature +20 -0
  66. data/features/046_check_for_assign_unless_nil_attributes.feature +21 -0
  67. data/features/build_framework_support.feature +99 -0
  68. data/features/checking_all_types_of_file.feature +40 -0
  69. data/features/choose_rules_to_apply.feature +49 -0
  70. data/features/command_line_help.feature +43 -0
  71. data/features/continuous_integration_support.feature +35 -0
  72. data/features/ignore_via_line_comments.feature +51 -0
  73. data/features/include_custom_rules.feature +29 -0
  74. data/features/individual_file.feature +12 -0
  75. data/features/limit_rules_to_specific_versions.feature +65 -0
  76. data/features/multiple_paths.feature +11 -0
  77. data/features/show_lines_matched.feature +20 -0
  78. data/features/sort_warnings.feature +10 -0
  79. data/features/specify_search_grammar.feature +25 -0
  80. data/features/step_definitions/cookbook_steps.rb +1791 -0
  81. data/features/support/command_helpers.rb +312 -0
  82. data/features/support/cookbook_helpers.rb +495 -0
  83. data/features/support/env.rb +11 -0
  84. data/lib/foodcritic.rb +0 -1
  85. data/lib/foodcritic/api.rb +3 -2
  86. data/lib/foodcritic/command_line.rb +4 -0
  87. data/lib/foodcritic/linter.rb +29 -6
  88. data/lib/foodcritic/output.rb +74 -26
  89. data/lib/foodcritic/rules.rb +6 -5
  90. data/lib/foodcritic/version.rb +1 -1
  91. data/man/foodcritic.1 +58 -0
  92. data/man/foodcritic.1.ronn +57 -0
  93. data/spec/foodcritic/api_spec.rb +1615 -0
  94. data/spec/foodcritic/chef_spec.rb +66 -0
  95. data/spec/foodcritic/command_line_spec.rb +51 -0
  96. data/spec/foodcritic/domain_spec.rb +24 -0
  97. data/spec/foodcritic/linter_spec.rb +91 -0
  98. data/spec/foodcritic/template_spec.rb +49 -0
  99. data/spec/regression/cookbooks.txt +135 -0
  100. data/spec/regression/expected-output.txt +443 -0
  101. data/spec/regression/regression_spec.rb +17 -0
  102. data/spec/regression_helpers.rb +37 -0
  103. data/spec/spec_helper.rb +10 -0
  104. metadata +87 -24
@@ -0,0 +1,312 @@
1
+ module FoodCritic
2
+
3
+ # Helpers for asserting that the correct warnings are displayed.
4
+ #
5
+ # Unless the environment variable FC_FORK_PROCESS is set to 'true' then the features will be run in the same process.
6
+ module CommandHelpers
7
+
8
+ # The warning codes and messages displayed to the end user.
9
+ WARNINGS = {
10
+ 'FC002' => 'Avoid string interpolation where not required',
11
+ 'FC003' => 'Check whether you are running with chef server before using server-specific features',
12
+ 'FC004' => 'Use a service resource to start and stop services',
13
+ 'FC005' => 'Avoid repetition of resource declarations',
14
+ 'FC006' => 'Mode should be quoted or fully specified when setting file permissions',
15
+ 'FC007' => 'Ensure recipe dependencies are reflected in cookbook metadata',
16
+ 'FC008' => 'Generated cookbook metadata needs updating',
17
+ 'FC009' => 'Resource attribute not recognised',
18
+ 'FC010' => 'Invalid search syntax',
19
+ 'FC011' => 'Missing README in markdown format',
20
+ 'FC012' => 'Use Markdown for README rather than RDoc',
21
+ 'FC013' => 'Use file_cache_path rather than hard-coding tmp paths',
22
+ 'FC014' => 'Consider extracting long ruby_block to library',
23
+ 'FC015' => 'Consider converting definition to a LWRP',
24
+ 'FC016' => 'LWRP does not declare a default action',
25
+ 'FC017' => 'LWRP does not notify when updated',
26
+ 'FC018' => 'LWRP uses deprecated notification syntax',
27
+ 'FC019' => 'Access node attributes in a consistent manner',
28
+ 'FC021' => 'Resource condition in provider may not behave as expected',
29
+ 'FC022' => 'Resource condition within loop may not behave as expected',
30
+ 'FC023' => 'Prefer conditional attributes',
31
+ 'FC024' => 'Consider adding platform equivalents',
32
+ 'FC025' => 'Prefer chef_gem to compile-time gem install',
33
+ 'FC026' => 'Conditional execution block attribute contains only string',
34
+ 'FC027' => 'Resource sets internal attribute',
35
+ 'FC028' => 'Incorrect #platform? usage',
36
+ 'FC029' => 'No leading cookbook name in recipe metadata',
37
+ 'FC030' => 'Cookbook contains debugger breakpoints',
38
+ 'FC031' => 'Cookbook without metadata file',
39
+ 'FC032' => 'Invalid notification timing',
40
+ 'FC033' => 'Missing template',
41
+ 'FC034' => 'Unused template variables',
42
+ 'FC037' => 'Invalid notification action',
43
+ 'FC038' => 'Invalid resource action',
44
+ 'FC039' => 'Node method cannot be accessed with key',
45
+ 'FC040' => 'Execute resource used to run git commands',
46
+ 'FC041' => 'Execute resource used to run curl or wget commands',
47
+ 'FC042' => 'Prefer include_recipe to require_recipe',
48
+ 'FC043' => 'Prefer new notification syntax',
49
+ 'FC044' => 'Avoid bare attribute keys',
50
+ 'FC045' => 'Consider setting cookbook name in metadata',
51
+ 'FC046' => 'Attribute assignment uses assign unless nil',
52
+ 'FCTEST001' => 'Test Rule'
53
+ }
54
+
55
+ # If the cucumber features should run foodcritic in the same process or spawn a separate process.
56
+ def self.running_in_process?
57
+ ! (ENV.has_key?('FC_FORK_PROCESS') and ENV['FC_FORK_PROCESS'] == true.to_s)
58
+ end
59
+
60
+ # Capture an error expected when calling a command.
61
+ def capture_error
62
+ begin
63
+ yield
64
+ @error = all_output unless last_exit_status == 0
65
+ rescue => @error
66
+ end
67
+ end
68
+
69
+ # Return the last error captured
70
+ #
71
+ # @return [String] The last error captured
72
+ def last_error
73
+ @error.respond_to?(:message) ? @error.message : @error
74
+ end
75
+
76
+ # Expect a line of context
77
+ #
78
+ # @param [Number] line_no The line number
79
+ # @param [String] text The text of the matching line
80
+ def expect_line_shown(line_no, text)
81
+ expect_output %r{^ +#{Regexp.escape(line_no.to_s)}\|#{Regexp.escape(text)}$}
82
+ end
83
+
84
+ # Expect a warning to be included in the command output.
85
+ #
86
+ # @param [String] code The warning code to check for.
87
+ # @param [Hash] options The warning options.
88
+ # @option options [Integer] :line The line number the warning should appear on - nil for any line.
89
+ # @option options [Boolean] :expect_warning If false then assert that a warning is NOT present
90
+ # @option options [String] :file The path to the file the warning should be raised against
91
+ # @option options [Symbol] :file_type Alternative to specifying file name. One of: :attributes, :definition,
92
+ # :metadata, :provider, :resource
93
+ def expect_warning(code, options={})
94
+ if options.has_key?(:file_type)
95
+ options[:file] = {:attributes => 'attributes/default.rb', :definition => 'definitions/apache_site.rb',
96
+ :metadata => 'metadata.rb', :provider => 'providers/site.rb',
97
+ :resource => 'resources/site.rb', :libraries => 'libraries/lib.rb'}[options[:file_type]]
98
+ end
99
+ options = {:line => 1, :expect_warning => true, :file => 'recipes/default.rb'}.merge!(options)
100
+ if options[:warning_only]
101
+ warning = "#{code}: #{WARNINGS[code]}"
102
+ else
103
+ warning = "#{code}: #{WARNINGS[code]}: cookbooks/example/#{options[:file]}:#{options[:line]}#{"\n" if ! options[:line].nil?}"
104
+ end
105
+ options[:expect_warning] ? expect_output(warning) : expect_no_output(warning)
106
+ end
107
+
108
+ # Expect a warning not to be included in the command output.
109
+ #
110
+ # @see CommandHelpers#expect_warning
111
+ def expect_no_warning(code, options={:expect_warning => false})
112
+ expect_warning(code, options)
113
+ end
114
+
115
+ # Expect a command line option / switch to be included in the usage.
116
+ #
117
+ # @param [String] short_switch The short version of the switch
118
+ # @param [String] long_switch The long descriptive version of the switch
119
+ # @param [String] description The description of the switch
120
+ def expect_usage_option(short_switch, long_switch, description)
121
+ expected_switch = "-#{Regexp.escape(short_switch)}, --#{Regexp.escape(long_switch)}[ ]+#{Regexp.escape(description)}"
122
+ expect_output(Regexp.new(expected_switch))
123
+ end
124
+
125
+ # Assert that the usage message is displayed.
126
+ #
127
+ # @param [Boolean] is_exit_zero The exit code to check for.
128
+ def usage_displayed(is_exit_zero)
129
+ expect_output 'foodcritic [cookbook_paths]'
130
+ expect_usage_option('c', 'chef-version VERSION', 'Only check against rules valid for this version of Chef.')
131
+ expect_usage_option('f', 'epic-fail TAGS',
132
+ "Fail the build if any of the specified tags are matched ('any' -> fail on any match).")
133
+ expect_usage_option('t', 'tags TAGS', 'Only check against rules with the specified tags.')
134
+ expect_usage_option('C', '[no-]context', 'Show lines matched against rather than the default summary.')
135
+ expect_usage_option('I', 'include PATH', 'Additional rule file path(s) to load.')
136
+ expect_usage_option('S', 'search-grammar PATH', 'Specify grammar to use when validating search syntax.')
137
+ expect_usage_option('V', 'version', 'Display the foodcritic version.')
138
+ if is_exit_zero
139
+ assert_no_error_occurred
140
+ else
141
+ assert_error_occurred
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ # Helpers used when features are executed in-process.
148
+ module InProcessHelpers
149
+
150
+ # Assert that the output contains the specified warning.
151
+ #
152
+ # @param [String] output The warning to check for.
153
+ def expect_output(output)
154
+ if output.respond_to?(:~)
155
+ @review.should match(output)
156
+ else
157
+ @review.should include(output)
158
+ end
159
+ end
160
+
161
+ # Assert that the output does not contain the specified warning.
162
+ #
163
+ # @param [String] output The output to check for.
164
+ def expect_no_output(output)
165
+ if output.respond_to?(:~)
166
+ @review.should_not match(output)
167
+ else
168
+ @review.should_not include(output)
169
+ end
170
+ end
171
+
172
+ # Assert that an error occurred following a lint check.
173
+ def assert_error_occurred
174
+ @status.should_not == 0
175
+ end
176
+
177
+ # Assert that no error occurred following a lint check.
178
+ def assert_no_error_occurred
179
+ @status.should == 0
180
+ end
181
+
182
+ # Run a lint check with the provided command line arguments.
183
+ #
184
+ # @param [Array] cmd_args The command line arguments.
185
+ def run_lint(cmd_args)
186
+ in_current_dir do
187
+ show_context = cmd_args.include?('-C')
188
+ review, @status = FoodCritic::Linter.check(CommandLine.new(cmd_args))
189
+ @review =
190
+ if review.nil? || (review.respond_to?(:warnings) && review.warnings.empty?)
191
+ ''
192
+ elsif show_context
193
+ ContextOutput.new.output(review)
194
+ else
195
+ "#{review.to_s}\n"
196
+ end
197
+ end
198
+ end
199
+
200
+ end
201
+
202
+ # For use with steps that use bundler and rake. These will always be run
203
+ # via Aruba.
204
+ module BuildHelpers
205
+
206
+ # Assert the build outcome
207
+ #
208
+ # @param [Boolean] success True if the build should succeed
209
+ # @param [Array] warnings The warnings expected
210
+ def assert_build_result(success, warnings)
211
+ success ? assert_no_error_occurred : assert_error_occurred
212
+ warnings.each do |code|
213
+ expect_warning(code, :warning_only => true)
214
+ end
215
+ end
216
+
217
+ # Assert that warnings have not been raised against the test code which
218
+ # should have been excluded from linting.
219
+ def assert_no_test_warnings
220
+ all_output.split("\n").grep(/FC[0-9]+:/).map do |warn|
221
+ File.basename(File.dirname(warn.split(':').take(3).last.strip))
222
+ end.should_not include 'test'
223
+ end
224
+
225
+ # The available tasks for this build
226
+ #
227
+ # @return [Array] Task name and description
228
+ def build_tasks
229
+ all_output.split("\n").map do |task|
230
+ next unless task.start_with? 'rake'
231
+ task.split("#").map{|t| t.strip.sub(/^rake /, '')}
232
+ end.compact
233
+ end
234
+
235
+ # List the defined Rake tasks
236
+ def list_available_build_tasks
237
+ cd 'cookbooks/example'
238
+ unset_bundler_env_vars
239
+ run_simple 'bundle exec rake -T'
240
+ end
241
+
242
+ # Run a build for a Rakefile that uses the lint rake task
243
+ def run_build
244
+ cd 'cookbooks/example'
245
+ run_simple "bundle exec rake", false
246
+ end
247
+
248
+ # We want to avoid traversing vendored gems because of the unnecessary
249
+ # performance hit and because gems may contain deeply-nested code which
250
+ # will blow the stack on parsing.
251
+ def vendor_gems
252
+ cd 'cookbooks/example'
253
+ unset_bundler_env_vars
254
+ run_simple 'bundle install --path vendor/bundle'
255
+ cd '../..'
256
+ end
257
+
258
+ end
259
+
260
+ # Helpers used when features are executed out of process.
261
+ module ArubaHelpers
262
+
263
+ include BuildHelpers
264
+
265
+ # Assert that the output contains the specified warning.
266
+ #
267
+ # @param [String] output The output to check for.
268
+ def expect_output(output)
269
+ if output.respond_to?(:~)
270
+ assert_matching_output(output.to_s, all_output)
271
+ else
272
+ assert_partial_output(output, all_output)
273
+ end
274
+ end
275
+
276
+ # Assert that the output does not contain the specified warning.
277
+ #
278
+ # @param [String] output The output to check for.
279
+ def expect_no_output(output)
280
+ if output.respond_to?(:~)
281
+ assert_matching_output('^((?!#{output}).)*$', all_output)
282
+ else
283
+ assert_no_partial_output(output, all_output)
284
+ end
285
+ end
286
+
287
+ # Assert that an error occurred following a lint check.
288
+ def assert_error_occurred
289
+ assert_not_exit_status 0
290
+ end
291
+
292
+ # Assert that no error occurred following a lint check.
293
+ def assert_no_error_occurred
294
+ assert_exit_status(0)
295
+ end
296
+
297
+ # Run a lint check with the provided command line arguments.
298
+ #
299
+ # @param [Array] cmd_args The command line arguments.
300
+ def run_lint(cmd_args)
301
+ run_simple(unescape("foodcritic #{cmd_args.join(' ')}"), false)
302
+ end
303
+ end
304
+
305
+ end
306
+
307
+ World(FoodCritic::CommandHelpers)
308
+ if FoodCritic::CommandHelpers.running_in_process?
309
+ World(FoodCritic::InProcessHelpers)
310
+ else
311
+ World(FoodCritic::ArubaHelpers)
312
+ end
@@ -0,0 +1,495 @@
1
+ module FoodCritic
2
+
3
+ # Helper cookbook methods for use in your steps.
4
+ module CookbookHelpers
5
+
6
+ # Create an attributes file that references attributes with symbols
7
+ #
8
+ # @param [String] type The type of node attribute to write
9
+ def attributes_with_symbols(type)
10
+ write_attributes %Q{#{type}[:apache][:dir] = "/etc/apache2"}
11
+ end
12
+
13
+ # Create a Gemfile for a cookbook
14
+ def buildable_gemfile
15
+ write_file 'cookbooks/example/Gemfile', %q{
16
+ source :rubygems
17
+ gem 'rake'
18
+ gem 'foodcritic', :path => '../../../..'
19
+ }
20
+ end
21
+
22
+ # Create a cookbook that declares dependencies on external recipes.
23
+ #
24
+ # @param [Symbol] declaration_type The type of declaration - :brace or :bracket
25
+ def cookbook_declares_dependencies(declaration_type)
26
+ write_recipe %q{
27
+ include_recipe "foo::default"
28
+ include_recipe "bar::default"
29
+ include_recipe "baz::default"
30
+ }
31
+ if declaration_type == :brace
32
+ write_metadata %q{
33
+ %w{foo bar baz}.each{|cookbook| depends cookbook}
34
+ }
35
+ else
36
+ write_metadata %q{
37
+ %w{foo bar baz}.each do |cb|
38
+ depends cb
39
+ end
40
+ }
41
+ end
42
+ end
43
+
44
+ # Create a cookbook that will match the specified rules.
45
+ #
46
+ # @param [Array] codes The codes to match. Only FC002, FC003 and FC004 are supported.
47
+ def cookbook_that_matches_rules(codes)
48
+ recipe = ''
49
+ codes.each do |code|
50
+ if code == 'FC002'
51
+ recipe += %q{
52
+ directory "#{node['base_dir']}" do
53
+ action :create
54
+ end
55
+ }
56
+ elsif code == 'FC003'
57
+ recipe += %Q{nodes = search(:node, "hostname:[* TO *]")\n}
58
+ elsif code == 'FC004'
59
+ recipe += %q{
60
+ execute "stop-jetty" do
61
+ command "/etc/init.d/jetty6 stop"
62
+ action :run
63
+ end
64
+ }
65
+ elsif code == 'FC006'
66
+ recipe += %q{
67
+ directory "/var/lib/foo" do
68
+ mode 644
69
+ action :create
70
+ end
71
+ }
72
+ end
73
+ end
74
+ write_recipe(recipe)
75
+ write_file('cookbooks/example/recipes/server.rb', '')
76
+ write_readme('Hello World') # Don't trigger FC011
77
+ write_metadata('name "example"') # Don't trigger FC031
78
+ end
79
+
80
+ # Create a cookbook with a LRWP
81
+ #
82
+ # @param [Hash] lwrp The options to use for the created LWRP
83
+ # @option lwrp [Symbol] :default_action One of :no_default_action, :ruby_default_action, :dsl_default_action
84
+ # @option lwrp [Symbol] :notifies One of :does_not_notify, :does_notify, :does_notify_without_parens, :deprecated_syntax, :class_variable
85
+ def cookbook_with_lwrp(lwrp)
86
+ lwrp = {:default_action => false, :notifies => :does_not_notify}.merge!(lwrp)
87
+ ruby_default_action = %q{
88
+ def initialize(*args)
89
+ super
90
+ @action = :create
91
+ end
92
+ }.strip
93
+ write_resource("site", %Q{
94
+ actions :create
95
+ attribute :name, :kind_of => String, :name_attribute => true
96
+ #{ruby_default_action if lwrp[:default_action] == :ruby_default_action}
97
+ #{'default_action :create' if lwrp[:default_action] == :dsl_default_action}
98
+ })
99
+ notifications = {:does_notify => 'new_resource.updated_by_last_action(true)',
100
+ :does_notify_without_parens => 'new_resource.updated_by_last_action true',
101
+ :deprecated_syntax => 'new_resource.updated = true',
102
+ :class_variable => '@updated = true'}
103
+ write_provider("site", %Q{
104
+ action :create do
105
+ log "Here is where I would create a site"
106
+ #{notifications[lwrp[:notifies]]}
107
+ end
108
+ })
109
+ end
110
+
111
+ # Create an cookbook with the maintainer specified in the metadata
112
+ #
113
+ # @param [String] name The maintainer name
114
+ # @param [String] email The maintainer email address
115
+ def cookbook_with_maintainer(name, email)
116
+ write_recipe %q{
117
+ #
118
+ # Cookbook Name:: example
119
+ # Recipe:: default
120
+ #
121
+ # Copyright 2011, YOUR_COMPANY_NAME
122
+ #
123
+ # All rights reserved - Do Not Redistribute
124
+ #
125
+ }
126
+
127
+ fields = {}
128
+ fields['maintainer'] = name unless name.nil?
129
+ fields['maintainer_email'] = email unless email.nil?
130
+ write_metadata %Q{
131
+ #{fields.map{|field,value| %Q{#{field}\t"#{value}"}}.join("\n")}
132
+ license "All rights reserved"
133
+ description "Installs/Configures example"
134
+ long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
135
+ version "0.0.1"
136
+ }
137
+ end
138
+
139
+ # Create a placeholder minitest spec that would be linted due to its path
140
+ # unless an exclusion is specified.
141
+ def minitest_spec_attributes
142
+ write_file 'cookbooks/example/test/attributes/default_spec.rb', %q{
143
+ describe 'Example::Attributes::Default' do
144
+ end
145
+ }
146
+ end
147
+
148
+ # Create a Rakefile that uses the linter rake task
149
+ #
150
+ # @param [Symbol] task Type of task
151
+ # @param [Hash] options Task options
152
+ # @option options [String] :name Task name
153
+ # @option options [String] :files Files to process
154
+ # @option options [String] :options The options to set on the rake task
155
+ def rakefile(task, options)
156
+ rakefile_content = 'task :default => []'
157
+ task_def = case task
158
+ when :no_block then 'FoodCritic::Rake::LintTask.new'
159
+ else %Q{
160
+ FoodCritic::Rake::LintTask.new do |t|
161
+ #{"t.name = '#{options[:name]}'" if options[:name]}
162
+ #{"t.files = #{options[:files]}" if options[:files]}
163
+ #{"t.options = #{options[:options]}" if options[:options]}
164
+ end
165
+ }
166
+ end
167
+ if task_def
168
+ rakefile_content = %Q{
169
+ require 'foodcritic'
170
+ task :default => [:#{options[:name] ? options[:name] : 'foodcritic'}]
171
+ #{task_def}
172
+ }
173
+ end
174
+ write_file 'cookbooks/example/Rakefile', rakefile_content
175
+ end
176
+
177
+ # Create a recipe that downloads a file
178
+ #
179
+ # @param [Symbol] path_type The type of path, one of: :tmp_dir, :chef_file_cache_dir, :home_dir
180
+ def recipe_downloads_file(path_type)
181
+ download_path = {:tmp_dir => '/tmp/large-file.tar.gz',
182
+ :tmp_dir_expr => '/tmp/#{file}',
183
+ :home_dir => '/home/ernie/large-file.tar.gz',
184
+ :chef_file_cache_dir => '#{Chef::Config[:file_cache_path]}/large-file.tar.gz'}[path_type]
185
+ write_recipe %Q{
186
+ remote_file "#{download_path}" do
187
+ source "http://www.example.org/large-file.tar.gz"
188
+ end
189
+ }
190
+ end
191
+
192
+ # Install a gem using the specified approach.
193
+ #
194
+ # @param [Symbol] type The type of approach, one of :simple, :compile_time,
195
+ # :compile_time_from_array, :compile_time_from_word_list
196
+ # @param [Symbol] action Either :install or :upgrade
197
+ def recipe_installs_gem(type, action = :install)
198
+ case type
199
+ when :simple
200
+ write_recipe %Q{
201
+ gem_package "bluepill" do
202
+ action :#{action}
203
+ end
204
+ }.strip
205
+ when :compile_time
206
+ write_recipe %Q{
207
+ r = gem_package "mysql" do
208
+ action :nothing
209
+ end
210
+
211
+ r.run_action(:#{action})
212
+ Gem.clear_paths
213
+ }.strip
214
+ when :compile_time_from_array
215
+ write_recipe %Q{
216
+ ['foo', 'bar', 'baz'].each do |pkg|
217
+ r = gem_package pkg do
218
+ action :nothing
219
+ end
220
+ r.run_action(:#{action})
221
+ end
222
+ }.strip
223
+ when :compile_time_from_word_list
224
+ write_recipe %Q{
225
+ %w{foo bar baz}.each do |pkg|
226
+ r = gem_package pkg do
227
+ action :nothing
228
+ end
229
+ r.run_action(:#{action})
230
+ end
231
+ }.strip
232
+ else
233
+ fail "Unrecognised type: #{type}"
234
+ end
235
+ end
236
+
237
+ # Create a recipe that declares a resource with the specified file mode.
238
+ #
239
+ # @param [String] type The type of resource (file, template)
240
+ # @param [String] mode The file mode as a string
241
+ # @param [String] comment Comment that may specify to exclude a match
242
+ def recipe_resource_with_mode(type, mode, comment='')
243
+ source_att = type == 'template' ? 'source "foo.erb"' : ''
244
+ write_recipe %Q{
245
+ #{type} "/tmp/something" do #{comment}
246
+ #{source_att}
247
+ owner "root"
248
+ group "root"
249
+ mode #{mode}
250
+ action :create
251
+ end
252
+ }
253
+ end
254
+
255
+ # Create a recipe that controls a service using the specified method.
256
+ #
257
+ # @param [Symbol] method How to start the service, one of: :init_d, :invoke_rc_d, :upstart, :service, :service_full_path.
258
+ # @param [Boolean] do_sleep Whether to prefix the service cmd with a bash sleep
259
+ # @param [Symbol] action The action to take (start, stop, reload, restart)
260
+ def recipe_controls_service(method = :service, do_sleep = false, action = :start)
261
+ cmds = {:init_d => "/etc/init.d/foo #{action}", :invoke_rc_d => "invoke-rc.d foo #{action}", :upstart => "#{action} foo",
262
+ :service => "service foo #{action}", :service_full_path => "/sbin/service foo #{action}"}
263
+ write_recipe %Q{
264
+ execute "#{action}-foo-service" do
265
+ command "#{do_sleep ? 'sleep 5; ' : ''}#{cmds[method]}"
266
+ action :run
267
+ end
268
+ }
269
+ end
270
+
271
+ # Create a recipe with an external dependency on another cookbook.
272
+ #
273
+ # @param [Hash] dep The options to use for dependency
274
+ # @option dep [Boolean] :is_declared True if this dependency has been declared in the cookbook metadata
275
+ # @option dep [Boolean] :is_scoped True if the include_recipe references a specific recipe or the cookbook
276
+ def recipe_with_dependency(dep)
277
+ dep = {:is_scoped => true, :is_declared => true}.merge!(dep)
278
+ write_recipe %Q{
279
+ include_recipe 'foo#{dep[:is_scoped] ? '::default' : ''}'
280
+ }
281
+ write_metadata %Q{
282
+ version "1.9.0"
283
+ depends "#{dep[:is_declared] ? 'foo' : 'dogs'}"
284
+ }
285
+ end
286
+
287
+ # Create a recipe with a directory resource
288
+ #
289
+ # @param [Symbol] path_expr_type The type of path expression, one of: :compound_symbols, :interpolated_string,
290
+ # :interpolated_symbol, :interpolated_symbol_and_literal, :literal_and_interpolated_symbol, :string_literal.
291
+ def recipe_with_dir_path(path_expr_type)
292
+ path = {:compound_symbols => '#{node[:base_dir]}#{node[:sub_dir]}', :interpolated_string => %q{#{node['base_dir']}},
293
+ :interpolated_symbol => '#{node[:base_dir]}', :interpolated_symbol_and_literal => '#{node[:base_dir]}/sub_dir',
294
+ :literal_and_interpolated_symbol => 'base_dir/#{node[:sub_dir]}', :string_literal => '/var/lib/foo' }[path_expr_type]
295
+ write_recipe %Q{
296
+ directory "#{path}" do
297
+ owner "root"
298
+ group "root"
299
+ mode "0755"
300
+ action :create
301
+ end
302
+ }
303
+ end
304
+
305
+ # Create a recipe with the specified resource type and attribute names.
306
+ #
307
+ # @param [String] type The type of resource
308
+ # @param [Array] attribute_names The attributes to declare on this resource
309
+ def recipe_with_resource(type, attribute_names)
310
+ write_recipe %Q{
311
+ #{type} "resource-name" do
312
+ #{attribute_names.join(" 'foo'\n")} 'bar'
313
+ end
314
+ }
315
+ end
316
+
317
+ # Create a recipe with a ruby_block resource.
318
+ #
319
+ # @param [Symbol] length A :short or :long block, or :both
320
+ def recipe_with_ruby_block(length)
321
+ recipe = ''
322
+ if length == :short || length == :both
323
+ recipe << %q{
324
+ ruby_block "subexpressions" do
325
+ block do
326
+ rc = Chef::Util::FileEdit.new("/foo/bar.conf")
327
+ rc.search_file_replace_line(/^search/, "search #{node["foo"]["bar"]} compute-1.internal")
328
+ rc.search_file_replace_line(/^domain/, "domain #{node["foo"]["bar"]}")
329
+ rc.write_file
330
+ end
331
+ action :create
332
+ end
333
+ }
334
+ end
335
+ if length == :long || length == :both
336
+ recipe << %q{
337
+ ruby_block "too_long" do
338
+ block do
339
+ begin
340
+ do_something('with argument')
341
+ do_something_else('with another argument')
342
+ foo = Foo.new('bar')
343
+ foo.activate_turbo_boost
344
+ foo.each do |thing|
345
+ case thing
346
+ when "fee"
347
+ puts 'Fee'
348
+ when "fi"
349
+ puts 'Fi'
350
+ when "fo"
351
+ puts 'Fo'
352
+ else
353
+ puts "Fum"
354
+ end
355
+ end
356
+ rescue Some::Exception
357
+ Chef::Log.warn "Problem activating the turbo boost"
358
+ end
359
+ end
360
+ action :create
361
+ end
362
+ }
363
+ end
364
+ write_recipe(recipe)
365
+ end
366
+
367
+ # Create a recipe that performs a search of the specified type.
368
+ #
369
+ # @param [Symbol] type The type of search. One of: :invalid_syntax, :valid_syntax, :with_subexpression.
370
+ def recipe_with_search(type)
371
+ search = {:invalid_syntax => 'run_list:recipe[foo::bar]', :valid_syntax => 'run_list:recipe\[foo\:\:bar\]',
372
+ :with_subexpression => %q{roles:#{node['foo']['role']}}}[type]
373
+ write_recipe %Q{
374
+ search(:node, "#{search}") do |matching_node|
375
+ puts matching_node.to_s
376
+ end
377
+ }
378
+ end
379
+
380
+ # Create a rule with the specified Chef version constraints
381
+ #
382
+ # @param [String] from_version The from version
383
+ # @param [String] to_version The to version
384
+ def rule_with_version_constraint(from_version, to_version)
385
+ constraint = if from_version && to_version
386
+ %Q{
387
+ applies_to do |version|
388
+ version >= gem_version("#{from_version}") && version <= gem_version("#{to_version}")
389
+ end
390
+ }
391
+ elsif from_version
392
+ %Q{
393
+ applies_to do |version|
394
+ version >= gem_version("#{from_version}")
395
+ end
396
+ }
397
+ elsif to_version
398
+ %Q{
399
+ applies_to do |version|
400
+ version <= gem_version("#{to_version}")
401
+ end
402
+ }
403
+ end
404
+ write_rule %Q{
405
+ rule "FCTEST001", "Test Rule" do
406
+ #{constraint}
407
+ recipe do |ast, filename|
408
+ [file_match(filename)]
409
+ end
410
+ end
411
+ }
412
+ end
413
+
414
+ # Return the provided string or nil if 'unspecified'
415
+ #
416
+ # @param [String] str The string
417
+ # @return [String] The string or nil if 'unspecified'
418
+ def nil_if_unspecified(str)
419
+ str == 'unspecified' ? nil : str
420
+ end
421
+
422
+ # Create a README with the provided content.
423
+ #
424
+ # @param [String] content The recipe content.
425
+ # @param [String] cookbook_name Optional name of the cookbook.
426
+ def write_readme(content, cookbook_name = 'example')
427
+ write_file "cookbooks/#{cookbook_name}/README.md", content.strip
428
+ end
429
+
430
+ # Create a recipe with the provided content.
431
+ #
432
+ # @param [String] content The recipe content.
433
+ # @param [String] cookbook_name Optional name of the cookbook.
434
+ def write_recipe(content, cookbook_name = 'example')
435
+ write_file "cookbooks/#{cookbook_name}/recipes/default.rb", content.strip
436
+ end
437
+
438
+ # Create a rule with the provided content.
439
+ #
440
+ # @param [String] content The rule content.
441
+ def write_rule(content)
442
+ write_file "rules/test.rb", content.strip
443
+ end
444
+
445
+ # Create attributes with the provided content.
446
+ #
447
+ # @param [String] content The attributes content.
448
+ def write_attributes(content)
449
+ write_file 'cookbooks/example/attributes/default.rb', content.strip
450
+ end
451
+
452
+ # Create a definition with the provided content.
453
+ #
454
+ # @param [String] name The definition name.
455
+ # @param [String] content The definition content.
456
+ def write_definition(name, content)
457
+ write_file "cookbooks/example/definitions/#{name}.rb", content.strip
458
+ end
459
+
460
+ # Create a library with the provided content.
461
+ #
462
+ # @param [String] name The library name.
463
+ # @param [String] content The library content.
464
+ def write_library(name, content)
465
+ write_file "cookbooks/example/libraries/#{name}.rb", content.strip
466
+ end
467
+
468
+ # Create metdata with the provided content.
469
+ #
470
+ # @param [String] content The metadata content.
471
+ def write_metadata(content)
472
+ write_file 'cookbooks/example/metadata.rb', content.strip
473
+ end
474
+
475
+ # Create a resource with the provided content.
476
+ #
477
+ # @param [String] name The resource name.
478
+ # @param [String] content The resource content.
479
+ def write_resource(name, content)
480
+ write_file "cookbooks/example/resources/#{name}.rb", content.strip
481
+ end
482
+
483
+ # Create a provider with the provided content.
484
+ #
485
+ # @param [String] name The resource name.
486
+ # @param [String] content The resource content.
487
+ def write_provider(name, content)
488
+ write_file "cookbooks/example/providers/#{name}.rb", content.strip
489
+ end
490
+
491
+ end
492
+
493
+ end
494
+
495
+ World(FoodCritic::CookbookHelpers)