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.
- checksums.yaml +4 -4
- data/config/chefstyle.yml +38 -0
- data/config/cookstyle.yml +181 -6
- data/lib/cookstyle/version.rb +5 -2
- data/lib/cookstyle.rb +1 -0
- data/lib/rubocop/chef/cookbook_helpers.rb +23 -0
- data/lib/rubocop/chef.rb +1 -1
- data/lib/rubocop/cop/chef/correctness/chef_application_fatal.rb +16 -4
- data/lib/rubocop/cop/chef/correctness/conditional_unified_mode_true.rb +91 -0
- data/lib/rubocop/cop/chef/correctness/empty_resource_guard.rb +18 -2
- data/lib/rubocop/cop/chef/correctness/execute_delete_file.rb +127 -0
- data/lib/rubocop/cop/chef/correctness/invalid_checksum.rb +79 -0
- data/lib/rubocop/cop/chef/correctness/invalid_platform_family_helper.rb +1 -1
- data/lib/rubocop/cop/chef/correctness/invalid_platform_family_values_in_case.rb +10 -0
- data/lib/rubocop/cop/chef/correctness/invalid_platform_values_in_case.rb +10 -0
- data/lib/rubocop/cop/chef/correctness/invalid_value_for_platform_helper.rb +1 -0
- data/lib/rubocop/cop/chef/correctness/openssl_password_helpers.rb +1 -1
- data/lib/rubocop/cop/chef/correctness/platform_version_string_comparison.rb +66 -0
- data/lib/rubocop/cop/chef/correctness/powershell_delete_file.rb +5 -5
- data/lib/rubocop/cop/chef/correctness/powershell_file_exists.rb +1 -1
- data/lib/rubocop/cop/chef/correctness/resource_with_none_action.rb +3 -3
- data/lib/rubocop/cop/chef/correctness/ruby_guard_without_block.rb +91 -0
- data/lib/rubocop/cop/chef/correctness/service_resource.rb +71 -20
- data/lib/rubocop/cop/chef/deprecation/chef_handler_recipe.rb +8 -1
- data/lib/rubocop/cop/chef/deprecation/chef_rest.rb +4 -1
- data/lib/rubocop/cop/chef/deprecation/chef_shellout.rb +2 -2
- data/lib/rubocop/cop/chef/deprecation/chef_sugar_helpers.rb +6 -0
- data/lib/rubocop/cop/chef/deprecation/chef_windows_platform_helper.rb +1 -1
- data/lib/rubocop/cop/chef/deprecation/chocolatey_package_uninstall_action.rb +3 -3
- data/lib/rubocop/cop/chef/deprecation/depends_partial_search.rb +4 -0
- data/lib/rubocop/cop/chef/deprecation/deprecated_mixins.rb +2 -2
- data/lib/rubocop/cop/chef/deprecation/hwrp_without_provides.rb +4 -4
- data/lib/rubocop/cop/chef/deprecation/hwrp_without_unified_mode_true.rb +2 -2
- data/lib/rubocop/cop/chef/deprecation/inherits_compat_resource.rb +3 -3
- data/lib/rubocop/cop/chef/deprecation/legacy_yum_cookbook_recipes.rb +9 -1
- data/lib/rubocop/cop/chef/deprecation/locale_lc_all_property.rb +5 -0
- data/lib/rubocop/cop/chef/deprecation/resource_uses_only_resource_name.rb +9 -1
- data/lib/rubocop/cop/chef/deprecation/resource_uses_provider_base_method.rb +4 -0
- data/lib/rubocop/cop/chef/deprecation/ruby_block_create_action.rb +42 -3
- data/lib/rubocop/cop/chef/deprecation/use_automatic_resource_name.rb +9 -0
- data/lib/rubocop/cop/chef/deprecation/use_yaml_dump.rb +5 -0
- data/lib/rubocop/cop/chef/deprecation/xml_ruby_recipe.rb +5 -1
- data/lib/rubocop/cop/chef/modernize/apt_default_recipe.rb +1 -1
- data/lib/rubocop/cop/chef/modernize/build_essential.rb +4 -1
- data/lib/rubocop/cop/chef/modernize/chef_14_resources.rb +8 -0
- data/lib/rubocop/cop/chef/modernize/chef_15_resources.rb +11 -0
- data/lib/rubocop/cop/chef/modernize/chef_gem_nokogiri.rb +4 -0
- data/lib/rubocop/cop/chef/modernize/conditional_using_test.rb +10 -3
- data/lib/rubocop/cop/chef/modernize/cron_d_file_or_template.rb +6 -4
- data/lib/rubocop/cop/chef/modernize/databag_helpers.rb +6 -3
- data/lib/rubocop/cop/chef/modernize/depends_chef_vault_cookbook.rb +4 -0
- data/lib/rubocop/cop/chef/modernize/depends_chocolatey_cookbooks.rb +7 -0
- data/lib/rubocop/cop/chef/modernize/depends_kernel_module_cookbook.rb +6 -0
- data/lib/rubocop/cop/chef/modernize/depends_locale_cookbook.rb +6 -0
- data/lib/rubocop/cop/chef/modernize/depends_openssl_cookbook.rb +9 -0
- data/lib/rubocop/cop/chef/modernize/depends_timezone_lwrp_cookbook.rb +4 -0
- data/lib/rubocop/cop/chef/modernize/depends_windows_firewall_cookbook.rb +8 -0
- data/lib/rubocop/cop/chef/modernize/depends_zypper_cookbook.rb +7 -0
- data/lib/rubocop/cop/chef/modernize/dsl_include_in_resource.rb +1 -1
- data/lib/rubocop/cop/chef/modernize/execute_apt_update.rb +3 -1
- data/lib/rubocop/cop/chef/modernize/execute_archive_extract.rb +121 -0
- data/lib/rubocop/cop/chef/modernize/execute_update_alternatives.rb +100 -0
- data/lib/rubocop/cop/chef/modernize/includes_mixin_shellout.rb +4 -0
- data/lib/rubocop/cop/chef/modernize/libarchive_file.rb +5 -3
- data/lib/rubocop/cop/chef/modernize/ohai_default_recipe.rb +6 -1
- data/lib/rubocop/cop/chef/modernize/powershell_download_file.rb +64 -0
- data/lib/rubocop/cop/chef/modernize/powershell_expand_archive.rb +8 -1
- data/lib/rubocop/cop/chef/modernize/powershell_guard_interpreter.rb +4 -4
- data/lib/rubocop/cop/chef/modernize/sc_windows_resource.rb +2 -2
- data/lib/rubocop/cop/chef/modernize/seven_zip_archive.rb +7 -1
- data/lib/rubocop/cop/chef/modernize/shellouts_to_chocolatey.rb +15 -4
- data/lib/rubocop/cop/chef/modernize/use_require_relative.rb +1 -1
- data/lib/rubocop/cop/chef/modernize/windows_default_recipe.rb +1 -1
- data/lib/rubocop/cop/chef/modernize/windows_zipfile.rb +8 -2
- data/lib/rubocop/cop/chef/modernize/zipfile_resource.rb +7 -1
- data/lib/rubocop/cop/chef/modernize/zypper_repo.rb +2 -2
- data/lib/rubocop/cop/chef/redundant/custom_resource_with_allowed_actions.rb +10 -0
- data/lib/rubocop/cop/chef/redundant/double_compile_time.rb +22 -8
- data/lib/rubocop/cop/chef/redundant/service_guard_on_stop_disable.rb +117 -0
- data/lib/rubocop/cop/chef/redundant/string_property_with_nil_default.rb +2 -2
- data/lib/rubocop/cop/chef/redundant/unnecessary_name_property.rb +1 -1
- data/lib/rubocop/cop/chef/security/insecure_remote_file_source.rb +82 -0
- data/lib/rubocop/cop/chef/security/ssh_private_key.rb +9 -0
- data/lib/rubocop/cop/chef/sharing/empty_property_description.rb +62 -0
- data/lib/rubocop/cop/chef/style/include_recipe_with_parentheses.rb +5 -2
- data/lib/rubocop/cop/chef/style/true_false_resource_properties.rb +1 -1
- data/lib/rubocop/monkey_patches/comment_config.rb +20 -0
- data/lib/rubocop/monkey_patches/directive_comment.rb +21 -3
- data/lib/rubocop/monkey_patches/registry_cop.rb +4 -6
- 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
|
|
23
|
-
#
|
|
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,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
|
|
@@ -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
|
|
29
|
+
# only_if { ::File.exist?('C:\Windows\foo\bar.txt') }
|
|
30
30
|
# end
|
|
31
31
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
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
|
|
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
|
|
45
|
-
next unless action.
|
|
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,
|
|
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
|