cookstyle 8.6.10 → 9.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/config/chefstyle.yml +38 -0
  3. data/config/cookstyle.yml +181 -6
  4. data/lib/cookstyle/version.rb +5 -2
  5. data/lib/cookstyle.rb +1 -0
  6. data/lib/rubocop/chef/cookbook_helpers.rb +23 -0
  7. data/lib/rubocop/chef.rb +1 -1
  8. data/lib/rubocop/cop/chef/correctness/chef_application_fatal.rb +16 -4
  9. data/lib/rubocop/cop/chef/correctness/conditional_unified_mode_true.rb +91 -0
  10. data/lib/rubocop/cop/chef/correctness/empty_resource_guard.rb +18 -2
  11. data/lib/rubocop/cop/chef/correctness/execute_delete_file.rb +127 -0
  12. data/lib/rubocop/cop/chef/correctness/invalid_checksum.rb +79 -0
  13. data/lib/rubocop/cop/chef/correctness/invalid_platform_family_helper.rb +1 -1
  14. data/lib/rubocop/cop/chef/correctness/invalid_platform_family_values_in_case.rb +10 -0
  15. data/lib/rubocop/cop/chef/correctness/invalid_platform_values_in_case.rb +10 -0
  16. data/lib/rubocop/cop/chef/correctness/invalid_value_for_platform_helper.rb +1 -0
  17. data/lib/rubocop/cop/chef/correctness/openssl_password_helpers.rb +1 -1
  18. data/lib/rubocop/cop/chef/correctness/platform_version_string_comparison.rb +66 -0
  19. data/lib/rubocop/cop/chef/correctness/powershell_delete_file.rb +5 -5
  20. data/lib/rubocop/cop/chef/correctness/powershell_file_exists.rb +1 -1
  21. data/lib/rubocop/cop/chef/correctness/resource_with_none_action.rb +3 -3
  22. data/lib/rubocop/cop/chef/correctness/ruby_guard_without_block.rb +91 -0
  23. data/lib/rubocop/cop/chef/correctness/service_resource.rb +71 -20
  24. data/lib/rubocop/cop/chef/deprecation/chef_handler_recipe.rb +8 -1
  25. data/lib/rubocop/cop/chef/deprecation/chef_rest.rb +4 -1
  26. data/lib/rubocop/cop/chef/deprecation/chef_shellout.rb +2 -2
  27. data/lib/rubocop/cop/chef/deprecation/chef_sugar_helpers.rb +6 -0
  28. data/lib/rubocop/cop/chef/deprecation/chef_windows_platform_helper.rb +1 -1
  29. data/lib/rubocop/cop/chef/deprecation/chocolatey_package_uninstall_action.rb +3 -3
  30. data/lib/rubocop/cop/chef/deprecation/depends_partial_search.rb +4 -0
  31. data/lib/rubocop/cop/chef/deprecation/deprecated_mixins.rb +2 -2
  32. data/lib/rubocop/cop/chef/deprecation/hwrp_without_provides.rb +4 -4
  33. data/lib/rubocop/cop/chef/deprecation/hwrp_without_unified_mode_true.rb +2 -2
  34. data/lib/rubocop/cop/chef/deprecation/inherits_compat_resource.rb +3 -3
  35. data/lib/rubocop/cop/chef/deprecation/legacy_yum_cookbook_recipes.rb +9 -1
  36. data/lib/rubocop/cop/chef/deprecation/locale_lc_all_property.rb +5 -0
  37. data/lib/rubocop/cop/chef/deprecation/resource_uses_only_resource_name.rb +9 -1
  38. data/lib/rubocop/cop/chef/deprecation/resource_uses_provider_base_method.rb +4 -0
  39. data/lib/rubocop/cop/chef/deprecation/ruby_block_create_action.rb +42 -3
  40. data/lib/rubocop/cop/chef/deprecation/use_automatic_resource_name.rb +9 -0
  41. data/lib/rubocop/cop/chef/deprecation/use_yaml_dump.rb +5 -0
  42. data/lib/rubocop/cop/chef/deprecation/xml_ruby_recipe.rb +5 -1
  43. data/lib/rubocop/cop/chef/modernize/apt_default_recipe.rb +1 -1
  44. data/lib/rubocop/cop/chef/modernize/build_essential.rb +4 -1
  45. data/lib/rubocop/cop/chef/modernize/chef_14_resources.rb +8 -0
  46. data/lib/rubocop/cop/chef/modernize/chef_15_resources.rb +11 -0
  47. data/lib/rubocop/cop/chef/modernize/chef_gem_nokogiri.rb +4 -0
  48. data/lib/rubocop/cop/chef/modernize/conditional_using_test.rb +10 -3
  49. data/lib/rubocop/cop/chef/modernize/cron_d_file_or_template.rb +6 -4
  50. data/lib/rubocop/cop/chef/modernize/databag_helpers.rb +6 -3
  51. data/lib/rubocop/cop/chef/modernize/depends_chef_vault_cookbook.rb +4 -0
  52. data/lib/rubocop/cop/chef/modernize/depends_chocolatey_cookbooks.rb +7 -0
  53. data/lib/rubocop/cop/chef/modernize/depends_kernel_module_cookbook.rb +6 -0
  54. data/lib/rubocop/cop/chef/modernize/depends_locale_cookbook.rb +6 -0
  55. data/lib/rubocop/cop/chef/modernize/depends_openssl_cookbook.rb +9 -0
  56. data/lib/rubocop/cop/chef/modernize/depends_timezone_lwrp_cookbook.rb +4 -0
  57. data/lib/rubocop/cop/chef/modernize/depends_windows_firewall_cookbook.rb +8 -0
  58. data/lib/rubocop/cop/chef/modernize/depends_zypper_cookbook.rb +7 -0
  59. data/lib/rubocop/cop/chef/modernize/dsl_include_in_resource.rb +1 -1
  60. data/lib/rubocop/cop/chef/modernize/execute_apt_update.rb +3 -1
  61. data/lib/rubocop/cop/chef/modernize/execute_archive_extract.rb +121 -0
  62. data/lib/rubocop/cop/chef/modernize/execute_update_alternatives.rb +100 -0
  63. data/lib/rubocop/cop/chef/modernize/includes_mixin_shellout.rb +4 -0
  64. data/lib/rubocop/cop/chef/modernize/libarchive_file.rb +5 -3
  65. data/lib/rubocop/cop/chef/modernize/ohai_default_recipe.rb +6 -1
  66. data/lib/rubocop/cop/chef/modernize/powershell_download_file.rb +64 -0
  67. data/lib/rubocop/cop/chef/modernize/powershell_expand_archive.rb +8 -1
  68. data/lib/rubocop/cop/chef/modernize/powershell_guard_interpreter.rb +4 -4
  69. data/lib/rubocop/cop/chef/modernize/sc_windows_resource.rb +2 -2
  70. data/lib/rubocop/cop/chef/modernize/seven_zip_archive.rb +7 -1
  71. data/lib/rubocop/cop/chef/modernize/shellouts_to_chocolatey.rb +15 -4
  72. data/lib/rubocop/cop/chef/modernize/use_require_relative.rb +1 -1
  73. data/lib/rubocop/cop/chef/modernize/windows_default_recipe.rb +1 -1
  74. data/lib/rubocop/cop/chef/modernize/windows_zipfile.rb +8 -2
  75. data/lib/rubocop/cop/chef/modernize/zipfile_resource.rb +7 -1
  76. data/lib/rubocop/cop/chef/modernize/zypper_repo.rb +2 -2
  77. data/lib/rubocop/cop/chef/redundant/custom_resource_with_allowed_actions.rb +10 -0
  78. data/lib/rubocop/cop/chef/redundant/double_compile_time.rb +22 -8
  79. data/lib/rubocop/cop/chef/redundant/service_guard_on_stop_disable.rb +117 -0
  80. data/lib/rubocop/cop/chef/redundant/string_property_with_nil_default.rb +2 -2
  81. data/lib/rubocop/cop/chef/redundant/unnecessary_name_property.rb +1 -1
  82. data/lib/rubocop/cop/chef/security/insecure_remote_file_source.rb +82 -0
  83. data/lib/rubocop/cop/chef/security/ssh_private_key.rb +9 -0
  84. data/lib/rubocop/cop/chef/sharing/empty_property_description.rb +62 -0
  85. data/lib/rubocop/cop/chef/style/include_recipe_with_parentheses.rb +5 -2
  86. data/lib/rubocop/cop/chef/style/true_false_resource_properties.rb +1 -1
  87. data/lib/rubocop/monkey_patches/comment_config.rb +20 -0
  88. data/lib/rubocop/monkey_patches/directive_comment.rb +21 -3
  89. data/lib/rubocop/monkey_patches/registry_cop.rb +4 -6
  90. metadata +16 -4
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) 2016-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
4
+ # Author:: Tim Smith (<tsmith84@gmail.com>)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module RuboCop
19
+ module Cop
20
+ module Chef
21
+ module Correctness
22
+ # Setting `unified_mode true` conditionally leaves a resource running in unified mode on newer
23
+ # Chef Infra Client releases and in legacy mode on older ones. That's not a coherent thing to
24
+ # want: the resource has to be written, tested, and debugged against both execution models.
25
+ #
26
+ # Pick one. Set `unified_mode true` unconditionally and drop support for releases that predate
27
+ # it, or set `unified_mode false if respond_to?(:unified_mode)` to deliberately opt out of
28
+ # unified mode everywhere and keep writing the resource in the traditional style.
29
+ #
30
+ # Only `unified_mode true` is flagged. `unified_mode false` under a guard is the supported way
31
+ # to opt out and is left alone.
32
+ #
33
+ # @example
34
+ #
35
+ # # bad
36
+ # unified_mode true if respond_to?(:unified_mode)
37
+ #
38
+ # # good
39
+ # unified_mode true
40
+ #
41
+ # # good - deliberately opting out of unified mode
42
+ # unified_mode false if respond_to?(:unified_mode)
43
+ #
44
+ class ConditionalUnifiedModeTrue < Base
45
+ extend AutoCorrector
46
+ extend TargetChefVersion
47
+
48
+ minimum_target_chef_version '15.3'
49
+
50
+ MSG = 'Set `unified_mode true` unconditionally. Making it conditional gives you unified mode on newer Chef Infra Client releases and legacy mode on older ones, so the resource has to be tested and reasoned about both ways.'
51
+ RESTRICT_ON_SEND = [:unified_mode].freeze
52
+
53
+ def_node_matcher :unified_mode_true?, '(send nil? :unified_mode (true))'
54
+
55
+ def on_send(node)
56
+ return unless unified_mode_true?(node)
57
+
58
+ conditional = node.each_ancestor(:if).first
59
+ return unless conditional
60
+
61
+ add_offense(node, severity: :refactor) do |corrector|
62
+ # only unwrap the conditional when it wraps nothing but this property, otherwise
63
+ # we'd delete whatever else the branch was guarding. an elsif is never safe to unwrap:
64
+ # its node covers only the elsif itself, so replacing it drops that branch's condition
65
+ # and folds the property into the preceding branch
66
+ next if conditional.elsif?
67
+ next unless sole_branch_body?(conditional, node)
68
+
69
+ corrector.replace(conditional, node.source)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ # Is the property the entire body of the conditional? `if` puts the body in the first
76
+ # child after the condition and `unless` puts it in the second, so check both. A branch
77
+ # holding anything else is a `begin` node rather than the property itself, and an `if`
78
+ # with an `else` (or an `elsif` chain) leaves the other branch non-nil.
79
+ #
80
+ # @param [RuboCop::AST::IfNode] conditional
81
+ # @param [RuboCop::AST::SendNode] node the `unified_mode true` property
82
+ #
83
+ # @return [Boolean]
84
+ def sole_branch_body?(conditional, node)
85
+ conditional.children[1..2].compact == [node]
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -19,13 +19,18 @@ module RuboCop
19
19
  module Cop
20
20
  module Chef
21
21
  module Correctness
22
- # Resource guards (not_if/only_if) should not be empty strings as empty strings will always evaluate to true.
23
- # This will cause confusion in your cookbooks as the guard will always pass.
22
+ # Resource guards (not_if/only_if) should not be empty. An empty string always evaluates to true
23
+ # and an empty block always evaluates to false, so in either case the guard stops doing the job
24
+ # it was written for and the resource silently runs, or fails to run, on every converge.
24
25
  #
25
26
  # Empty strings in Ruby are "truthy", which means:
26
27
  # - `only_if ''` will ALWAYS execute the resource (guard always passes)
27
28
  # - `not_if ''` will NEVER execute the resource (guard always blocks)
28
29
  #
30
+ # An empty block behaves the other way around, since a block with no body returns `nil`:
31
+ # - `only_if { }` will NEVER execute the resource
32
+ # - `not_if { }` will ALWAYS execute the resource
33
+ #
29
34
  # This behavior is usually unintended and can lead to resources running when they shouldn't
30
35
  # or never running when they should.
31
36
  #
@@ -72,6 +77,7 @@ module RuboCop
72
77
  #
73
78
  class EmptyResourceGuard < Base
74
79
  MSG = 'Resource guards (not_if/only_if) should not be empty strings as empty strings will always evaluate to true.'
80
+ EMPTY_BLOCK_MSG = 'Resource guards (not_if/only_if) should not be empty blocks as an empty block returns nil, which always evaluates to false.'
75
81
  RESTRICT_ON_SEND = [:not_if, :only_if].freeze
76
82
 
77
83
  def_node_matcher :empty_string_guard?, <<-PATTERN
@@ -82,6 +88,12 @@ module RuboCop
82
88
  (block (send nil? {:not_if :only_if}) (args) (str #empty_string?))
83
89
  PATTERN
84
90
 
91
+ # A block with no body at all. Such a block returns nil rather than the empty string matched
92
+ # above, so the guard fails rather than passes.
93
+ def_node_matcher :empty_block_guard?, <<-PATTERN
94
+ (block (send nil? {:not_if :only_if}) (args) nil?)
95
+ PATTERN
96
+
85
97
  def empty_string?(str)
86
98
  str.empty?
87
99
  end
@@ -96,6 +108,10 @@ module RuboCop
96
108
  empty_string_block_guard?(node) do
97
109
  add_offense(node, severity: :refactor)
98
110
  end
111
+
112
+ empty_block_guard?(node) do
113
+ add_offense(node, message: EMPTY_BLOCK_MSG, severity: :refactor)
114
+ end
99
115
  end
100
116
  end
101
117
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) 2016-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
4
+ # Author:: Tim Smith (<tsmith84@gmail.com>)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module RuboCop
19
+ module Cop
20
+ module Chef
21
+ module Correctness
22
+ # Use the `file` or `directory` resources built into Chef Infra Client with the `:delete` action
23
+ # to remove files and directories instead of shelling out to `rm`. The resources are idempotent,
24
+ # report correctly on what they changed, and work without a shell.
25
+ #
26
+ # The two resources are not interchangeable and the one you pick has to match what is on disk.
27
+ # `directory` asserts the path really is a directory before deleting, so it raises
28
+ # `Cannot delete directory[...]` on a file even with `recursive true`, and `file` calls
29
+ # `File.delete`, so it raises `Errno::EISDIR` on a directory. A `directory` delete also needs
30
+ # `recursive true` for anything that isn't empty, or it raises `Errno::ENOTEMPTY`.
31
+ #
32
+ # Only a single `rm` of one path is flagged. A command containing a glob, a shell operator, or
33
+ # anything beyond the one deletion is left alone, since the `file` and `directory` resources
34
+ # can't express those.
35
+ #
36
+ # @example
37
+ #
38
+ # # bad -- rm without -r, which the shell will not let you point at a directory
39
+ # execute 'rm /etc/foo.conf'
40
+ #
41
+ # # good
42
+ # file '/etc/foo.conf' do
43
+ # action :delete
44
+ # end
45
+ #
46
+ # # bad -- a recursive rm of a directory
47
+ # execute 'delete the thing' do
48
+ # command 'rm -rf /opt/thing'
49
+ # end
50
+ #
51
+ # # good
52
+ # directory '/opt/thing' do
53
+ # recursive true
54
+ # action :delete
55
+ # end
56
+ #
57
+ class ExecuteDeleteFile < Base
58
+ include RuboCop::Chef::CookbookHelpers
59
+
60
+ # The two resources are not interchangeable, so the message has to name the right one.
61
+ # `directory` asserts the path really is a directory before deleting, and `file` calls
62
+ # File.delete, so pointing at the wrong one fails the converge rather than doing nothing.
63
+ FILE_MSG = 'Use the `file` resource with the :delete action to remove a file instead of shelling out to rm'
64
+ DIRECTORY_MSG = 'Use the `directory` resource with `recursive true` and the :delete action to remove a directory instead of shelling out to rm. Use the `file` resource instead if the path is a file.'
65
+
66
+ RESTRICT_ON_SEND = [:execute].freeze
67
+
68
+ # a lone rm of a single path: optional flags, one target, nothing else on the line.
69
+ # the target excludes the shell metacharacters used for substitution and expansion, since
70
+ # a path the shell computes at runtime isn't something the file/directory resources can take
71
+ DELETE_COMMAND = %r{\A\s*(?:/bin/|/usr/bin/)?rm\s+(?<flags>(?:-[a-zA-Z]+\s+)*)(?<path>[^\s;&|<>$`(){}]+)\s*\z}.freeze
72
+
73
+ # the shell resources whose script lives in a code property
74
+ SCRIPT_RESOURCES = %i(bash sh csh ksh zsh).freeze
75
+
76
+ def_node_matcher :execute_with_command_name?, '(send nil? :execute (str $_))'
77
+
78
+ def on_send(node)
79
+ execute_with_command_name?(node) do |command|
80
+ message = message_for(command)
81
+ add_offense(node, message: message, severity: :refactor) if message
82
+ end
83
+ end
84
+
85
+ def on_block(node)
86
+ match_property_in_resource?(:execute, 'command', node) do |property|
87
+ report_property(node, property)
88
+ end
89
+
90
+ match_property_in_resource?(SCRIPT_RESOURCES, 'code', node) do |property|
91
+ report_property(node, property)
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def report_property(node, property)
98
+ command = method_arg_ast_to_string(property)
99
+ return unless command
100
+
101
+ message = message_for(command)
102
+ add_offense(node, message: message, severity: :refactor) if message
103
+ end
104
+
105
+ # Does the command do nothing but delete one named path, and if so which resource replaces
106
+ # it? Globs are excluded because the file and directory resources take a single path rather
107
+ # than a pattern.
108
+ #
109
+ # Without -r the shell refuses to remove a directory, so a bare `rm` provably targets a
110
+ # file and `file` is the right answer. With -r the author is removing a tree, so
111
+ # `directory` is, though the path could still be a file that -rf was used on out of habit.
112
+ #
113
+ # @param [String] command
114
+ #
115
+ # @return [String, nil] the message to report, or nil when this isn't a simple delete
116
+ def message_for(command)
117
+ match = DELETE_COMMAND.match(command)
118
+ return if match.nil?
119
+ return if match[:path].match?(/[*?\[]/)
120
+
121
+ match[:flags].match?(/-[a-zA-Z]*r/i) ? DIRECTORY_MSG : FILE_MSG
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) 2016-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
4
+ # Author:: Tim Smith (<tsmith84@gmail.com>)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module RuboCop
19
+ module Cop
20
+ module Chef
21
+ module Correctness
22
+ # The `checksum` property of the file downloading resources is a SHA-256 digest. An MD5 or
23
+ # SHA-1 digest can never match, so the resource fails the run with a checksum mismatch rather
24
+ # than installing anything.
25
+ #
26
+ # Only digests that are unambiguously the wrong algorithm are flagged: 32 character (MD5) and
27
+ # 40 character (SHA-1) hex strings. Anything else is left alone.
28
+ #
29
+ # @example
30
+ #
31
+ # # bad
32
+ # remote_file '/tmp/foo.tar.gz' do
33
+ # source 'https://example.com/foo.tar.gz'
34
+ # checksum 'd41d8cd98f00b204e9800998ecf8427e'
35
+ # end
36
+ #
37
+ # # good
38
+ # remote_file '/tmp/foo.tar.gz' do
39
+ # source 'https://example.com/foo.tar.gz'
40
+ # checksum 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
41
+ # end
42
+ #
43
+ class InvalidChecksum < Base
44
+ include RuboCop::Chef::CookbookHelpers
45
+
46
+ MSG = 'The checksum property takes a SHA-256 digest. An MD5 or SHA-1 digest can never match, so the run fails with a checksum mismatch.'
47
+
48
+ # the file downloading resources that validate a SHA-256 checksum
49
+ RESOURCES = %i(remote_file windows_package msu_package dmg_package).freeze
50
+
51
+ # hex digest lengths of the algorithms people reach for by mistake, with the article that
52
+ # reads correctly in front of each name
53
+ WRONG_ALGORITHM_LENGTHS = { 32 => 'an MD5', 40 => 'a SHA-1' }.freeze
54
+
55
+ def on_block(node)
56
+ match_property_in_resource?(RESOURCES, 'checksum', node) do |checksum|
57
+ value = checksum.arguments.first
58
+ next unless value&.str_type?
59
+
60
+ algorithm = WRONG_ALGORITHM_LENGTHS[value.value.length]
61
+ next unless algorithm && hex?(value.value)
62
+
63
+ add_offense(checksum, message: "#{MSG} This looks like #{algorithm} digest.", severity: :refactor)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # @param [String] string
70
+ #
71
+ # @return [Boolean]
72
+ def hex?(string)
73
+ string.match?(/\A\h+\z/)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # platform_family?('redhat')
28
28
  # platform_family?('sles')
29
29
  #
30
- # # bad
30
+ # # good
31
31
  # platform_family?('rhel')
32
32
  # platform_family?('suse')
33
33
  #
@@ -27,6 +27,16 @@ module RuboCop
27
27
  # case node['platform_family']
28
28
  # when 'redhat'
29
29
  # puts "I'm on a RHEL-like system"
30
+ # when 'debian'
31
+ # puts "I'm on a Debian-like system"
32
+ # end
33
+ #
34
+ # # good
35
+ # case node['platform_family']
36
+ # when 'rhel'
37
+ # puts "I'm on a RHEL-like system"
38
+ # when 'debian'
39
+ # puts "I'm on a Debian-like system"
30
40
  # end
31
41
  #
32
42
  class InvalidPlatformFamilyInCase < Base
@@ -27,6 +27,16 @@ module RuboCop
27
27
  # case node['platform']
28
28
  # when 'rhel'
29
29
  # puts "I'm on a Red Hat system!"
30
+ # when 'ubuntu'
31
+ # puts "I'm on Ubuntu!"
32
+ # end
33
+ #
34
+ # # good
35
+ # case node['platform']
36
+ # when 'redhat'
37
+ # puts "I'm on a Red Hat system!"
38
+ # when 'ubuntu'
39
+ # puts "I'm on Ubuntu!"
30
40
  # end
31
41
  #
32
42
  class InvalidPlatformInCase < Base
@@ -28,6 +28,7 @@ module RuboCop
28
28
  # %w(rhel mac_os_x_server) => { 'default' => 'foo' },
29
29
  # %w(sles) => { 'default' => 'bar' }
30
30
  # )
31
+ #
31
32
  # # good
32
33
  # value_for_platform(
33
34
  # %w(redhat mac_os_x) => { 'default' => 'foo' },
@@ -31,7 +31,7 @@ module RuboCop
31
31
  def_node_matcher :openssl_helper?, <<~PATTERN
32
32
  (const
33
33
  (const
34
- (const nil? :Opscode) :OpenSSL) :Password)
34
+ (const {nil? cbase} :Opscode) :OpenSSL) :Password)
35
35
  PATTERN
36
36
 
37
37
  def on_const(node)
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) 2016-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
4
+ # Author:: Tim Smith (<tsmith84@gmail.com>)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module RuboCop
19
+ module Cop
20
+ module Chef
21
+ module Correctness
22
+ # Ohai reports version attributes as strings, so comparing one with `<`, `>`, `<=`, or `>=`
23
+ # compares them character by character rather than as versions. That gives the wrong answer
24
+ # whenever the version numbers have different digit counts:
25
+ #
26
+ # '7.9' >= '10' # => true, because '7' sorts after '1'
27
+ # '9' > '10' # => true
28
+ #
29
+ # Compare the major version as an integer with `node['platform_version'].to_i`, or compare
30
+ # full versions with `Gem::Version`.
31
+ #
32
+ # @example
33
+ #
34
+ # # bad
35
+ # node['platform_version'] >= '10'
36
+ # node['platform_version'] < '7.4'
37
+ #
38
+ # # good
39
+ # node['platform_version'].to_i >= 10
40
+ # Gem::Version.new(node['platform_version']) >= Gem::Version.new('7.4')
41
+ #
42
+ # # good - equality is a plain string match, which is fine
43
+ # node['platform_version'] == '10'
44
+ #
45
+ class PlatformVersionStringComparison < Base
46
+ MSG = 'Ohai version attributes are strings, so comparing them with an ordering operator compares them character by character. Use .to_i for a major version comparison, or Gem::Version to compare full versions.'
47
+ RESTRICT_ON_SEND = %i(< > <= >=).freeze
48
+
49
+ # the flat Ohai attributes that hold a dotted version string
50
+ def_node_matcher :version_attribute_comparison?, <<-PATTERN
51
+ (send
52
+ (send (send nil? :node) :[] (str {"platform_version" "os_version"}))
53
+ {:< :> :<= :>=}
54
+ (str _))
55
+ PATTERN
56
+
57
+ def on_send(node)
58
+ version_attribute_comparison?(node) do
59
+ add_offense(node, severity: :refactor)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -26,13 +26,13 @@ module RuboCop
26
26
  # # bad
27
27
  # powershell_script 'Cleanup old files' do
28
28
  # code 'Remove-Item C:\Windows\foo\bar.txt'
29
- # only_if { ::File.exist?('C:\\Windows\\foo\\bar.txt') }
29
+ # only_if { ::File.exist?('C:\Windows\foo\bar.txt') }
30
30
  # end
31
31
  #
32
- # ### correct
33
- # file 'C:\Windows\foo\bar.txt' do
34
- # action :delete
35
- # end
32
+ # # good
33
+ # file 'C:\Windows\foo\bar.txt' do
34
+ # action :delete
35
+ # end
36
36
  #
37
37
  class PowershellScriptDeleteFile < Base
38
38
  include RuboCop::Chef::CookbookHelpers
@@ -24,7 +24,7 @@ module RuboCop
24
24
  # @example
25
25
  #
26
26
  # # bad
27
- # powershell_out('Test-Path "C:\\Program Files\\LAPS\\CSE\\AdmPwd.dll"').stdout.strip == 'True'
27
+ # powershell_out('Test-Path "C:\Program Files\LAPS\CSE\AdmPwd.dll"').stdout.strip == 'True'
28
28
  #
29
29
  # # good
30
30
  # ::File.exist?('C:\Program Files\LAPS\CSE\AdmPwd.dll')
@@ -41,10 +41,10 @@ module RuboCop
41
41
 
42
42
  def on_block(node)
43
43
  match_property_in_resource?(nil, 'action', node) do |action_node|
44
- action_node.arguments.each do |action|
45
- next unless action.source == ':none'
44
+ each_action_symbol(action_node) do |action, prefix|
45
+ next unless action.value == :none
46
46
  add_offense(action, severity: :refactor) do |corrector|
47
- corrector.replace(action, ':nothing')
47
+ corrector.replace(action, "#{prefix}nothing")
48
48
  end
49
49
  end
50
50
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) 2016-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
4
+ # Author:: Tim Smith (<tsmith84@gmail.com>)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module RuboCop
19
+ module Cop
20
+ module Chef
21
+ module Correctness
22
+ # A `not_if`/`only_if` guard takes either a string, which is run as a shell command, or a block,
23
+ # which is run as Ruby. Passing a Ruby expression directly gives the guard the expression's
24
+ # *result* rather than the expression, because it is evaluated while the recipe is compiled.
25
+ #
26
+ # A guard that receives `true` or `false` raises at converge time:
27
+ #
28
+ # ArgumentError: Invalid only_if/not_if command, expected a string: true (TrueClass)
29
+ #
30
+ # Worse, an expression that happens to return a string is accepted and then run as a shell
31
+ # command, so the guard quietly tests something entirely different to what was intended.
32
+ #
33
+ # Only expressions that clearly produce a boolean are flagged, so a shell command built in Ruby
34
+ # and held in a variable is left alone.
35
+ #
36
+ # @example
37
+ #
38
+ # # bad
39
+ # not_if ::File.exist?('/etc/foo')
40
+ # only_if node['foo']['version'] == '1.0'
41
+ #
42
+ # # good
43
+ # not_if { ::File.exist?('/etc/foo') }
44
+ # only_if { node['foo']['version'] == '1.0' }
45
+ #
46
+ # # good - a string guard runs as a shell command
47
+ # not_if 'test -f /etc/foo'
48
+ #
49
+ class RubyGuardWithoutBlock < Base
50
+ extend AutoCorrector
51
+
52
+ MSG = 'A Ruby expression used as a resource guard has to be wrapped in a block. Passing it directly hands the guard the expression result, which raises at converge time or silently runs a shell command.'
53
+ RESTRICT_ON_SEND = [:not_if, :only_if].freeze
54
+
55
+ # comparison and negation operators always produce a boolean
56
+ BOOLEAN_OPERATORS = %i(== != < > <= >= =~ !~ ! equal? eql?).freeze
57
+
58
+ def_node_matcher :guard_with_argument, '(send nil? {:not_if :only_if} $_)'
59
+
60
+ def on_send(node)
61
+ guard_with_argument(node) do |argument|
62
+ next unless boolean_expression?(argument)
63
+
64
+ add_offense(node, severity: :refactor) do |corrector|
65
+ corrector.replace(node, "#{node.method_name} { #{argument.source} }")
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # Can we tell that this expression produces a boolean rather than a shell command? Anything
73
+ # we can't be sure about, such as a bare method call or a local variable, is left alone: it
74
+ # may well hold a perfectly good command string.
75
+ #
76
+ # @param [RuboCop::AST::Node] node
77
+ #
78
+ # @return [Boolean]
79
+ def boolean_expression?(node)
80
+ return true if node.boolean_type? || node.defined_type?
81
+ return true if node.operator_keyword? # && and ||
82
+ return node.children.one? && boolean_expression?(node.children.first) if node.begin_type?
83
+ return false unless node.send_type?
84
+
85
+ node.method_name.to_s.end_with?('?') || BOOLEAN_OPERATORS.include?(node.method_name)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end