cookstyle 8.7.6 → 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 +2 -0
- data/config/cookstyle.yml +145 -6
- data/lib/cookstyle/version.rb +2 -2
- 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/directive_comment.rb +21 -3
- metadata +15 -4
|
@@ -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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
#
|
|
3
|
-
# Copyright:: 2016
|
|
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>)
|
|
4
5
|
#
|
|
5
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -18,36 +19,86 @@ module RuboCop
|
|
|
18
19
|
module Cop
|
|
19
20
|
module Chef
|
|
20
21
|
module Correctness
|
|
21
|
-
# Use
|
|
22
|
+
# Use the `service` resource to manage services instead of shelling out. The resource is
|
|
23
|
+
# idempotent, picks the right init system for the platform, and reports correctly on what it
|
|
24
|
+
# changed. A shelled out command runs on every converge whether or not anything needed to
|
|
25
|
+
# change, so the run reports the resource as updated every time.
|
|
22
26
|
#
|
|
23
|
-
# @example
|
|
27
|
+
# @example
|
|
24
28
|
#
|
|
25
29
|
# # bad
|
|
26
|
-
#
|
|
27
|
-
#
|
|
30
|
+
# execute 'restart apache' do
|
|
31
|
+
# command 'systemctl restart httpd'
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# execute '/etc/init.d/httpd start'
|
|
35
|
+
#
|
|
36
|
+
# # good
|
|
37
|
+
# service 'httpd' do
|
|
38
|
+
# action :restart
|
|
39
|
+
# end
|
|
28
40
|
#
|
|
29
41
|
class ServiceResource < Base
|
|
30
|
-
MSG = 'Use
|
|
31
|
-
RESTRICT_ON_SEND =
|
|
42
|
+
MSG = 'Use the service resource to manage services instead of shelling out to an init script, service, systemctl, or a similar tool'
|
|
43
|
+
RESTRICT_ON_SEND = %i(command code execute).freeze
|
|
44
|
+
|
|
45
|
+
# the state changing verbs. `status` is left out: a status check is usually a guard rather
|
|
46
|
+
# than something the service resource replaces
|
|
47
|
+
SERVICE_ACTIONS = %w(
|
|
48
|
+
start stop restart reload force-reload try-restart reload-or-restart
|
|
49
|
+
enable disable mask unmask
|
|
50
|
+
).freeze
|
|
51
|
+
|
|
52
|
+
# commands whose service action is captured and checked against SERVICE_ACTIONS
|
|
53
|
+
ACTION_COMMANDS = [
|
|
54
|
+
%r{\A/(?:etc/init\.d|etc/rc\.d|usr/local/etc/rc\.d)/\S+\s+(\S+)}, # /etc/init.d/httpd start
|
|
55
|
+
%r{\A(?:/usr/sbin/|/sbin/)?service\s+\S+\s+(\S+)}, # service httpd start
|
|
56
|
+
%r{\A(?:/usr/bin/|/bin/)?systemctl\s+(?:--\S+\s+)*(\S+)\s+\S+}, # systemctl start httpd
|
|
57
|
+
/\Ainvoke-rc\.d\s+\S+\s+(\S+)/, # invoke-rc.d httpd start
|
|
58
|
+
/\Ainitctl\s+(\S+)\s+\S+/, # initctl start httpd
|
|
59
|
+
/\Asvcadm\s+(\S+)\s+\S+/, # svcadm enable httpd
|
|
60
|
+
].freeze
|
|
61
|
+
|
|
62
|
+
# commands that manage a service whatever the rest of the line says
|
|
63
|
+
DIRECT_COMMANDS = [
|
|
64
|
+
/\A(?:start|stop|restart)\s+\S+\z/, # upstart: start httpd
|
|
65
|
+
/\Alaunchctl\s+(?:load|unload|start|stop|enable|disable|bootstrap|bootout)\s/,
|
|
66
|
+
/\Achkconfig\s+(?:--\S+\s+)*\S+\s+(?:on|off)\z/, # chkconfig httpd on
|
|
67
|
+
/\Aupdate-rc\.d\s/, # update-rc.d httpd defaults
|
|
68
|
+
/\A(?:sc|net)\s+(?:start|stop|config)\s+\S+/i, # windows: net start httpd
|
|
69
|
+
].freeze
|
|
70
|
+
|
|
71
|
+
# a command doing more than the one service action can't be swapped for the resource
|
|
72
|
+
SHELL_OPERATORS = ['&&', '||', ';', '|', '`', '$('].freeze
|
|
32
73
|
|
|
33
|
-
def_node_matcher :
|
|
34
|
-
|
|
35
|
-
PATTERN
|
|
74
|
+
def_node_matcher :command_property?, '(send nil? {:command :code} $str)'
|
|
75
|
+
def_node_matcher :execute_with_command_name?, '(send nil? :execute $str)'
|
|
36
76
|
|
|
37
77
|
def on_send(node)
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
command_property?(node) do |command|
|
|
79
|
+
add_offense(command, severity: :refactor) if manages_service?(command.value)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
execute_with_command_name?(node) do |command|
|
|
83
|
+
add_offense(node, severity: :refactor) if manages_service?(command.value)
|
|
42
84
|
end
|
|
43
85
|
end
|
|
44
86
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# @param [String] command the literal command string
|
|
90
|
+
#
|
|
91
|
+
# @return [Boolean]
|
|
92
|
+
def manages_service?(command)
|
|
93
|
+
cmd = command.strip
|
|
94
|
+
return false if SHELL_OPERATORS.any? { |operator| cmd.include?(operator) }
|
|
95
|
+
|
|
96
|
+
ACTION_COMMANDS.each do |pattern|
|
|
97
|
+
match = pattern.match(cmd)
|
|
98
|
+
return true if match && SERVICE_ACTIONS.include?(match[1].downcase)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
DIRECT_COMMANDS.any? { |pattern| cmd.match?(pattern) }
|
|
51
102
|
end
|
|
52
103
|
end
|
|
53
104
|
end
|
|
@@ -27,6 +27,13 @@ module RuboCop
|
|
|
27
27
|
# include_recipe 'chef_handler'
|
|
28
28
|
# include_recipe 'chef_handler::default'
|
|
29
29
|
#
|
|
30
|
+
# # good
|
|
31
|
+
# # the chef_handler resource ships in Chef Infra Client, so no recipe include is needed
|
|
32
|
+
# chef_handler 'MyHandler' do
|
|
33
|
+
# source '/etc/chef/handlers/my_handler.rb'
|
|
34
|
+
# action :enable
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
30
37
|
class ChefHandlerRecipe < Base
|
|
31
38
|
include RangeHelp
|
|
32
39
|
extend AutoCorrector
|
|
@@ -35,7 +42,7 @@ module RuboCop
|
|
|
35
42
|
RESTRICT_ON_SEND = [:include_recipe].freeze
|
|
36
43
|
|
|
37
44
|
def_node_matcher :chef_handler_recipe?, <<-PATTERN
|
|
38
|
-
(send nil? :include_recipe (str {"chef_handler" "chef_handler::default"}))
|
|
45
|
+
(send {nil? (send nil? :run_context)} :include_recipe (str {"chef_handler" "chef_handler::default"}))
|
|
39
46
|
PATTERN
|
|
40
47
|
|
|
41
48
|
def on_send(node)
|
|
@@ -27,6 +27,9 @@ module RuboCop
|
|
|
27
27
|
# require 'chef/rest'
|
|
28
28
|
# Chef::REST::RESTRequest.new(:GET, FOO, nil).call
|
|
29
29
|
#
|
|
30
|
+
# # good
|
|
31
|
+
# Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get('nodes')
|
|
32
|
+
#
|
|
30
33
|
class UsesChefRESTHelpers < Base
|
|
31
34
|
MSG = "Don't use the helpers in Chef::REST which were removed in Chef Infra Client 13"
|
|
32
35
|
RESTRICT_ON_SEND = [:require].freeze
|
|
@@ -36,7 +39,7 @@ module RuboCop
|
|
|
36
39
|
PATTERN
|
|
37
40
|
|
|
38
41
|
def_node_matcher :rest_const?, <<-PATTERN
|
|
39
|
-
(const (const nil? :Chef) :REST)
|
|
42
|
+
(const (const {nil? cbase} :Chef) :REST)
|
|
40
43
|
PATTERN
|
|
41
44
|
|
|
42
45
|
def on_send(node)
|
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
|
43
43
|
def_node_matcher :include_shellout?, <<-PATTERN
|
|
44
44
|
(send nil? :include
|
|
45
45
|
(const
|
|
46
|
-
(const nil? :Chef) :ShellOut))
|
|
46
|
+
(const {nil? cbase} :Chef) :ShellOut))
|
|
47
47
|
PATTERN
|
|
48
48
|
|
|
49
49
|
def_node_matcher :require_shellout?, <<-PATTERN
|
|
@@ -53,7 +53,7 @@ module RuboCop
|
|
|
53
53
|
def_node_matcher :shellout_new?, <<-PATTERN
|
|
54
54
|
(send
|
|
55
55
|
(const
|
|
56
|
-
(const nil? :Chef) :ShellOut) :new
|
|
56
|
+
(const {nil? cbase} :Chef) :ShellOut) :new
|
|
57
57
|
... )
|
|
58
58
|
PATTERN
|
|
59
59
|
|
|
@@ -41,6 +41,12 @@ module RuboCop
|
|
|
41
41
|
# nexentacore?
|
|
42
42
|
# opensolaris?
|
|
43
43
|
#
|
|
44
|
+
# # good
|
|
45
|
+
# # the equivalents now shipping in Chef Infra Client and in Ruby itself
|
|
46
|
+
# vagrant?
|
|
47
|
+
# node.recipe?('foo::bar')
|
|
48
|
+
# shell_out!('/opt/app/bin/app --version', out: File::NULL)
|
|
49
|
+
#
|
|
44
50
|
class ChefSugarHelpers < Base
|
|
45
51
|
MSG = 'Do not use legacy chef-sugar helper methods, which will not be moved into Chef Infra Client itself. For a complete set of chef-sugar helpers now shipping in Chef Infra Client itself see https://github.com/chef/chef/tree/main/chef-utils#getting-started'
|
|
46
52
|
RESTRICT_ON_SEND = [:vagrant_key?, :vagrant_domain?, :vagrant_user?, :require_chef_gem, :best_ip_for, :nexus?, :ios_xr?, :ruby_20?, :ruby_19?, :includes_recipe?, :wrlinux?, :dev_null, :nexentacore_platform?, :opensolaris_platform?, :nexentacore?, :opensolaris?].freeze
|