knife 17.3.13 → 17.4.47
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/Gemfile +2 -2
- data/knife.gemspec +3 -3
- data/lib/chef/knife/bootstrap/train_connector.rb +3 -3
- data/lib/chef/knife/bootstrap.rb +3 -0
- data/lib/chef/knife/client_create.rb +39 -0
- data/lib/chef/knife/cookbook_upload.rb +34 -13
- data/lib/chef/knife/core/bootstrap_context.rb +4 -4
- data/lib/chef/knife/core/windows_bootstrap_context.rb +4 -4
- data/lib/chef/knife/ssh.rb +29 -17
- data/lib/chef/knife/supermarket_unshare.rb +2 -1
- data/lib/chef/knife/version.rb +1 -1
- data/spec/data/knife/temp_dir/tmp.pem +0 -0
- data/spec/integration/cookbook_download_spec.rb +1 -1
- data/spec/unit/knife/bootstrap_spec.rb +14 -1
- data/spec/unit/knife/client_create_spec.rb +65 -2
- data/spec/unit/knife/cookbook_upload_spec.rb +98 -36
- data/spec/unit/knife/core/bootstrap_context_spec.rb +6 -6
- data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +5 -5
- data/spec/unit/knife/ssh_spec.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3db4cec0f56daadfe94c7f7be77b10589e0d09f33c81e035a09342d9d8c77e67
|
|
4
|
+
data.tar.gz: 4fa7ff636d2796bd7abd97ad690bc65c200c5a67e6b21166f146b6723004edcf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02af735db46c983591eadf91942088586e17a825acf3f5414ddced3791cbaf4d16791f3bd3f15296577b233c78978d7bdacea8b9de31f8b9df8648660f140294
|
|
7
|
+
data.tar.gz: e4c5c95a28c8ece4162514f1e1ae7611280aeb8da224ab4d376111aec5af2e0f780e05a6be1056814ce56f36be6cd87954cabbe0aaf78abd5b65660bfe9da919
|
data/Gemfile
CHANGED
|
@@ -17,10 +17,10 @@ group(:omnibus_package, :pry) do
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
group(:chefstyle) do
|
|
20
|
-
gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "
|
|
20
|
+
gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "main"
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
gem "ohai", git: "https://github.com/chef/ohai.git", branch: "
|
|
23
|
+
gem "ohai", git: "https://github.com/chef/ohai.git", branch: "main"
|
|
24
24
|
gem "chef", path: ".."
|
|
25
25
|
gem "chef-utils", path: File.expand_path("../chef-utils", __dir__) if File.exist?(File.expand_path("../chef-utils", __dir__))
|
|
26
26
|
gem "chef-config", path: File.expand_path("../chef-config", __dir__) if File.exist?(File.expand_path("../chef-config", __dir__))
|
data/knife.gemspec
CHANGED
|
@@ -30,8 +30,8 @@ Gem::Specification.new do |s|
|
|
|
30
30
|
s.add_dependency "net-ssh-multi", "~> 1.2", ">= 1.2.1"
|
|
31
31
|
s.add_dependency "ed25519", "~> 1.2" # ed25519 ssh key support
|
|
32
32
|
s.add_dependency "bcrypt_pbkdf", "~> 1.1" # ed25519 ssh key support
|
|
33
|
-
#
|
|
34
|
-
# s.add_dependency "x25519" # ed25519 KEX module
|
|
33
|
+
# disabling this until we get get it to compile on RHEL 7
|
|
34
|
+
# s.add_dependency "x25519", ">= 1.0.9" # ed25519 KEX module. 1.0.9+ required to resolve sigill failures
|
|
35
35
|
s.add_dependency "highline", ">= 1.6.9", "< 3" # Used in UI to present a list, no other usage.
|
|
36
36
|
|
|
37
37
|
s.add_dependency "tty-prompt", "~> 0.21" # knife ui.ask prompt
|
|
@@ -52,7 +52,7 @@ Gem::Specification.new do |s|
|
|
|
52
52
|
|
|
53
53
|
s.metadata = {
|
|
54
54
|
"bug_tracker_uri" => "https://github.com/chef/chef/issues",
|
|
55
|
-
"changelog_uri" => "https://github.com/chef/chef/blob/
|
|
55
|
+
"changelog_uri" => "https://github.com/chef/chef/blob/main/CHANGELOG.md",
|
|
56
56
|
"documentation_uri" => "https://docs.chef.io/",
|
|
57
57
|
"homepage_uri" => "https://www.chef.io",
|
|
58
58
|
"mailing_list_uri" => "https://discourse.chef.io/",
|
|
@@ -240,7 +240,7 @@ class Chef
|
|
|
240
240
|
|
|
241
241
|
# Now that everything is populated, fill in anything missing
|
|
242
242
|
# that may be found in user ssh config
|
|
243
|
-
opts.merge!(missing_opts_from_ssh_config(opts
|
|
243
|
+
opts.merge!(missing_opts_from_ssh_config(opts))
|
|
244
244
|
|
|
245
245
|
Train.target_config(opts)
|
|
246
246
|
end
|
|
@@ -297,12 +297,12 @@ class Chef
|
|
|
297
297
|
# in the configuration passed in.
|
|
298
298
|
# This is necessary because train will default these values
|
|
299
299
|
# itself - causing SSH config data to be ignored
|
|
300
|
-
def missing_opts_from_ssh_config(config
|
|
300
|
+
def missing_opts_from_ssh_config(config)
|
|
301
301
|
return {} unless config[:backend] == "ssh"
|
|
302
302
|
|
|
303
303
|
host_cfg = ssh_config_for_host(config[:host])
|
|
304
304
|
opts_out = {}
|
|
305
|
-
|
|
305
|
+
host_cfg.each do |key, _value|
|
|
306
306
|
if SSH_CONFIG_OVERRIDE_KEYS.include?(key) && !config.key?(key)
|
|
307
307
|
opts_out[key] = host_cfg[key]
|
|
308
308
|
end
|
data/lib/chef/knife/bootstrap.rb
CHANGED
|
@@ -20,6 +20,7 @@ require_relative "../knife"
|
|
|
20
20
|
require_relative "data_bag_secret_options"
|
|
21
21
|
require "chef-utils/dist" unless defined?(ChefUtils::Dist)
|
|
22
22
|
require "license_acceptance/cli_flags/mixlib_cli"
|
|
23
|
+
|
|
23
24
|
module LicenseAcceptance
|
|
24
25
|
autoload :Acceptor, "license_acceptance/acceptor"
|
|
25
26
|
end
|
|
@@ -705,6 +706,8 @@ class Chef
|
|
|
705
706
|
ui.warn("#{e.message} - trying with pty request")
|
|
706
707
|
conn_options[:pty] = true # ensure we can talk to systems with requiretty set true in sshd config
|
|
707
708
|
retry
|
|
709
|
+
elsif e.reason == :sudo_missing_terminal
|
|
710
|
+
ui.error "Sudo password is required for this operation. Please enter password using -P or --ssh-password option"
|
|
708
711
|
elsif config[:use_sudo_password] && (e.reason == :sudo_password_required || e.reason == :bad_sudo_password) && limit < 3
|
|
709
712
|
ui.warn("Failed to authenticate #{conn_options[:user]} to #{server_name} - #{e.message} \n sudo: #{limit} incorrect password attempt")
|
|
710
713
|
sudo_password = ui.ask("Enter sudo password for #{conn_options[:user]}@#{server_name}:", echo: false)
|
|
@@ -54,6 +54,10 @@ class Chef
|
|
|
54
54
|
@client_field ||= Chef::ApiClientV1.new
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
def file
|
|
58
|
+
config[:file]
|
|
59
|
+
end
|
|
60
|
+
|
|
57
61
|
def create_client(client)
|
|
58
62
|
# should not be using save :( bad behavior
|
|
59
63
|
Chef::ApiClientV1.from_hash(client).save
|
|
@@ -81,6 +85,8 @@ class Chef
|
|
|
81
85
|
client.public_key File.read(File.expand_path(config[:public_key]))
|
|
82
86
|
end
|
|
83
87
|
|
|
88
|
+
file_is_writable!
|
|
89
|
+
|
|
84
90
|
output = edit_hash(client)
|
|
85
91
|
final_client = create_client(output)
|
|
86
92
|
ui.info("Created #{final_client}")
|
|
@@ -96,6 +102,39 @@ class Chef
|
|
|
96
102
|
end
|
|
97
103
|
end
|
|
98
104
|
end
|
|
105
|
+
|
|
106
|
+
#
|
|
107
|
+
# This method is used to verify that the file and it's containing
|
|
108
|
+
# directory are writable. This ensures that you don't create the client
|
|
109
|
+
# and then lose the private key because you weren't able to write it to
|
|
110
|
+
# disk.
|
|
111
|
+
#
|
|
112
|
+
# @return [void]
|
|
113
|
+
#
|
|
114
|
+
def file_is_writable!
|
|
115
|
+
return unless file
|
|
116
|
+
|
|
117
|
+
dir = File.dirname(File.expand_path(file))
|
|
118
|
+
unless File.exist?(dir)
|
|
119
|
+
ui.fatal "Directory #{dir} does not exist. Please create and retry."
|
|
120
|
+
exit 1
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
unless File.directory?(dir)
|
|
124
|
+
ui.fatal "#{dir} exists, but is not a directory. Please update your file path (--file #{file}) or re-create #{dir} as a directory."
|
|
125
|
+
exit 1
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
unless File.writable?(dir)
|
|
129
|
+
ui.fatal "Directory #{dir} is not writable. Please check the permissions."
|
|
130
|
+
exit 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if File.exist?(file) && !File.writable?(file)
|
|
134
|
+
ui.fatal "File #{file} is not writable. Please check the permissions."
|
|
135
|
+
exit 1
|
|
136
|
+
end
|
|
137
|
+
end
|
|
99
138
|
end
|
|
100
139
|
end
|
|
101
140
|
end
|
|
@@ -71,6 +71,11 @@ class Chef
|
|
|
71
71
|
long: "--include-dependencies",
|
|
72
72
|
description: "Also upload cookbook dependencies."
|
|
73
73
|
|
|
74
|
+
option :check_dependencies,
|
|
75
|
+
boolean: true, long: "--[no-]check-dependencies",
|
|
76
|
+
description: "Whether or not cookbook dependencies are verified before uploading cookbook(s) to #{ChefUtils::Dist::Server::PRODUCT}. You shouldn't disable this unless you really know what you're doing.",
|
|
77
|
+
default: true
|
|
78
|
+
|
|
74
79
|
def run
|
|
75
80
|
# Sanity check before we load anything from the server
|
|
76
81
|
if ! config[:all] && @name_args.empty?
|
|
@@ -86,11 +91,6 @@ class Chef
|
|
|
86
91
|
upload_failures = 0
|
|
87
92
|
upload_ok = 0
|
|
88
93
|
|
|
89
|
-
# Get a list of cookbooks and their versions from the server
|
|
90
|
-
# to check for the existence of a cookbook's dependencies.
|
|
91
|
-
@server_side_cookbooks = Chef::CookbookVersion.list_all_versions
|
|
92
|
-
justify_width = @server_side_cookbooks.map(&:size).max.to_i + 2
|
|
93
|
-
|
|
94
94
|
cookbooks = []
|
|
95
95
|
cookbooks_to_upload.each do |cookbook_name, cookbook|
|
|
96
96
|
raise Chef::Exceptions::MetadataNotFound.new(cookbook.root_paths[0], cookbook_name) unless cookbook.has_metadata_file?
|
|
@@ -120,7 +120,7 @@ class Chef
|
|
|
120
120
|
if config[:all]
|
|
121
121
|
if cookbooks_for_upload.any?
|
|
122
122
|
begin
|
|
123
|
-
upload(cookbooks_for_upload
|
|
123
|
+
upload(cookbooks_for_upload)
|
|
124
124
|
rescue Chef::Exceptions::CookbookFrozen
|
|
125
125
|
ui.warn("Not updating version constraints for some cookbooks in the environment as the cookbook is frozen.")
|
|
126
126
|
ui.error("Uploading of some of the cookbooks must be failed. Remove cookbook whose version is frozen from your cookbooks repo OR use --force option.")
|
|
@@ -133,7 +133,7 @@ class Chef
|
|
|
133
133
|
else
|
|
134
134
|
tmp_cl.each do |cookbook_name, cookbook|
|
|
135
135
|
|
|
136
|
-
upload([cookbook]
|
|
136
|
+
upload([cookbook])
|
|
137
137
|
upload_ok += 1
|
|
138
138
|
rescue Exceptions::CookbookNotFoundInRepo => e
|
|
139
139
|
upload_failures += 1
|
|
@@ -165,6 +165,27 @@ class Chef
|
|
|
165
165
|
end
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
+
def server_side_cookbooks
|
|
169
|
+
@server_side_cookbooks ||= Chef::CookbookVersion.list_all_versions
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def justify_width
|
|
173
|
+
@justify_width ||= server_side_cookbooks.map(&:size).max.to_i + 2
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
#
|
|
177
|
+
# @param cookbook [Chef::CookbookVersion]
|
|
178
|
+
#
|
|
179
|
+
def left_justify_name(cookbook)
|
|
180
|
+
# We only want to lookup justify width value if we're already loading
|
|
181
|
+
# cookbooks to check dependencies exist in Chef Infra Server.
|
|
182
|
+
if config[:check_dependencies] == true
|
|
183
|
+
cookbook.name.to_s.ljust(justify_width + 10)
|
|
184
|
+
else
|
|
185
|
+
cookbook.name.to_s.ljust(24)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
168
189
|
def cookbooks_to_upload
|
|
169
190
|
@cookbooks_to_upload ||=
|
|
170
191
|
if config[:all]
|
|
@@ -220,11 +241,11 @@ class Chef
|
|
|
220
241
|
end
|
|
221
242
|
end
|
|
222
243
|
|
|
223
|
-
def upload(cookbooks
|
|
244
|
+
def upload(cookbooks)
|
|
224
245
|
cookbooks.each do |cb|
|
|
225
|
-
ui.info("Uploading #{cb
|
|
246
|
+
ui.info("Uploading #{left_justify_name(cb)} [#{cb.version}]")
|
|
226
247
|
check_for_broken_links!(cb)
|
|
227
|
-
check_for_dependencies!(cb)
|
|
248
|
+
check_for_dependencies!(cb) if config[:check_dependencies] == true
|
|
228
249
|
end
|
|
229
250
|
Chef::CookbookUploader.new(cookbooks, force: config[:force], concurrency: config[:concurrency]).upload_cookbooks
|
|
230
251
|
rescue Chef::Exceptions::CookbookFrozen => e
|
|
@@ -265,12 +286,12 @@ class Chef
|
|
|
265
286
|
end
|
|
266
287
|
|
|
267
288
|
def check_server_side_cookbooks(cookbook_name, version)
|
|
268
|
-
if
|
|
289
|
+
if server_side_cookbooks[cookbook_name].nil?
|
|
269
290
|
false
|
|
270
291
|
else
|
|
271
|
-
versions =
|
|
292
|
+
versions = server_side_cookbooks[cookbook_name]["versions"].collect { |versions| versions["version"] }
|
|
272
293
|
Log.debug "Versions of cookbook '#{cookbook_name}' returned by the server: #{versions.join(", ")}"
|
|
273
|
-
|
|
294
|
+
server_side_cookbooks[cookbook_name]["versions"].each do |versions_hash|
|
|
274
295
|
if Chef::VersionConstraint.new(version).include?(versions_hash["version"])
|
|
275
296
|
Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to cookbook version '#{versions_hash["version"]}' on the server"
|
|
276
297
|
return true
|
|
@@ -171,12 +171,12 @@ class Chef
|
|
|
171
171
|
client_rb << "fips true\n"
|
|
172
172
|
end
|
|
173
173
|
|
|
174
|
-
unless chef_config[:
|
|
175
|
-
client_rb << "file_cache_path \"#{chef_config[:
|
|
174
|
+
unless chef_config[:unix_bootstrap_file_cache_path].nil?
|
|
175
|
+
client_rb << "file_cache_path \"#{chef_config[:unix_bootstrap_file_cache_path]}\"\n"
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
unless chef_config[:
|
|
179
|
-
client_rb << "file_backup_path \"#{chef_config[:
|
|
178
|
+
unless chef_config[:unix_bootstrap_file_backup_path].nil?
|
|
179
|
+
client_rb << "file_backup_path \"#{chef_config[:unix_bootstrap_file_backup_path]}\"\n"
|
|
180
180
|
end
|
|
181
181
|
|
|
182
182
|
client_rb
|
|
@@ -71,8 +71,8 @@ class Chef
|
|
|
71
71
|
client_rb = <<~CONFIG
|
|
72
72
|
chef_server_url "#{chef_config[:chef_server_url]}"
|
|
73
73
|
validation_client_name "#{chef_config[:validation_client_name]}"
|
|
74
|
-
file_cache_path "#{ChefConfig::PathHelper.escapepath(
|
|
75
|
-
file_backup_path "#{ChefConfig::PathHelper.escapepath(
|
|
74
|
+
file_cache_path "#{ChefConfig::PathHelper.escapepath(chef_config[:windows_bootstrap_file_cache_path] || "")}"
|
|
75
|
+
file_backup_path "#{ChefConfig::PathHelper.escapepath(chef_config[:windows_bootstrap_file_backup_path] || "")}"
|
|
76
76
|
cache_options ({:path => "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\cache\\\\checksums", :skip_expires => true})
|
|
77
77
|
CONFIG
|
|
78
78
|
|
|
@@ -86,8 +86,8 @@ class Chef
|
|
|
86
86
|
client_rb << "# Using default node name (fqdn)\n"
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
if
|
|
90
|
-
client_rb << %Q{log_level :#{
|
|
89
|
+
if chef_config[:config_log_level]
|
|
90
|
+
client_rb << %Q{log_level :#{chef_config[:config_log_level]}\n}
|
|
91
91
|
else
|
|
92
92
|
client_rb << "log_level :auto\n"
|
|
93
93
|
end
|
data/lib/chef/knife/ssh.rb
CHANGED
|
@@ -134,6 +134,18 @@ class Chef
|
|
|
134
134
|
boolean: true,
|
|
135
135
|
default: false
|
|
136
136
|
|
|
137
|
+
option :pty,
|
|
138
|
+
long: "--[no-]pty",
|
|
139
|
+
description: "Request a PTY, enabled by default.",
|
|
140
|
+
boolean: true,
|
|
141
|
+
default: true
|
|
142
|
+
|
|
143
|
+
option :require_pty,
|
|
144
|
+
long: "--[no-]require-pty",
|
|
145
|
+
description: "Raise exception if a PTY cannot be acquired, disabled by default.",
|
|
146
|
+
boolean: true,
|
|
147
|
+
default: false
|
|
148
|
+
|
|
137
149
|
def session
|
|
138
150
|
ssh_error_handler = Proc.new do |server|
|
|
139
151
|
if config[:on_error]
|
|
@@ -353,26 +365,26 @@ class Chef
|
|
|
353
365
|
ui.msg(str)
|
|
354
366
|
end
|
|
355
367
|
|
|
356
|
-
|
|
368
|
+
# @param command [String] the command to run
|
|
369
|
+
# @param session_list [???] list of sessions, one per node
|
|
370
|
+
#
|
|
371
|
+
def ssh_command(command, session_list = session)
|
|
372
|
+
stderr = ""
|
|
357
373
|
exit_status = 0
|
|
358
|
-
subsession ||= session
|
|
359
374
|
command = fixup_sudo(command)
|
|
360
375
|
command.force_encoding("binary") if command.respond_to?(:force_encoding)
|
|
361
|
-
|
|
362
|
-
open_session(subsession, command)
|
|
363
|
-
rescue => e
|
|
364
|
-
open_session(subsession, command, true)
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
def open_session(subsession, command, pty = false)
|
|
369
|
-
stderr = ""
|
|
370
|
-
exit_status = 0
|
|
371
|
-
subsession.open_channel do |chan|
|
|
376
|
+
session_list.open_channel do |chan|
|
|
372
377
|
if config[:on_error] && exit_status != 0
|
|
373
378
|
chan.close
|
|
374
379
|
else
|
|
375
|
-
|
|
380
|
+
if config[:pty]
|
|
381
|
+
chan.request_pty do |ch, success|
|
|
382
|
+
unless success
|
|
383
|
+
ui.warn("Failed to obtain a PTY from #{ch.connection.host}")
|
|
384
|
+
raise ArgumentError, "Request for PTY failed" if config[:require_pty]
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
376
388
|
chan.exec command do |ch, success|
|
|
377
389
|
raise ArgumentError, "Cannot execute #{command}" unless success
|
|
378
390
|
|
|
@@ -383,13 +395,11 @@ class Chef
|
|
|
383
395
|
ichannel.send_data("#{get_password}\n")
|
|
384
396
|
end
|
|
385
397
|
end
|
|
386
|
-
|
|
387
398
|
ch.on_extended_data do |_, _type, data|
|
|
388
|
-
raise ArgumentError if data.eql?("sudo: no tty present and no askpass program specified\n")
|
|
399
|
+
raise ArgumentError, "No PTY present. If a PTY is required use --require-pty" if data.eql?("sudo: no tty present and no askpass program specified\n")
|
|
389
400
|
|
|
390
401
|
stderr += data
|
|
391
402
|
end
|
|
392
|
-
|
|
393
403
|
ch.on_request "exit-status" do |ichannel, data|
|
|
394
404
|
exit_status = [exit_status, data.read_long].max
|
|
395
405
|
end
|
|
@@ -398,6 +408,8 @@ class Chef
|
|
|
398
408
|
end
|
|
399
409
|
session.loop
|
|
400
410
|
exit_status
|
|
411
|
+
ensure
|
|
412
|
+
session_list.close
|
|
401
413
|
end
|
|
402
414
|
|
|
403
415
|
def get_password
|
|
@@ -50,7 +50,8 @@ class Chef
|
|
|
50
50
|
rescue Net::HTTPClientException => e
|
|
51
51
|
raise e unless /Forbidden/.match?(e.message)
|
|
52
52
|
|
|
53
|
-
ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it."
|
|
53
|
+
ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it & #{config[:supermarket_site]} must allow maintainers to unshare cookbooks."
|
|
54
|
+
ui.warn "The default supermarket #{default_config[:supermarket_site]} does not allow maintainers to unshare cookbooks."
|
|
54
55
|
exit 1
|
|
55
56
|
end
|
|
56
57
|
|
data/lib/chef/knife/version.rb
CHANGED
|
File without changes
|
|
@@ -1307,7 +1307,7 @@ describe Chef::Knife::Bootstrap do
|
|
|
1307
1307
|
context "when no identity file is specified" do
|
|
1308
1308
|
it "generates the expected configuration (no keys, keys_only false)" do
|
|
1309
1309
|
expect(knife.ssh_identity_opts).to eq( {
|
|
1310
|
-
key_files: [
|
|
1310
|
+
key_files: [],
|
|
1311
1311
|
keys_only: false,
|
|
1312
1312
|
})
|
|
1313
1313
|
end
|
|
@@ -2050,6 +2050,19 @@ describe Chef::Knife::Bootstrap do
|
|
|
2050
2050
|
expect { knife.do_connect({}) }.to raise_error(expected_error)
|
|
2051
2051
|
end
|
|
2052
2052
|
end
|
|
2053
|
+
|
|
2054
|
+
context "when a train sudo error is thrown for missing terminal" do
|
|
2055
|
+
let(:ui_error_msg) { "Sudo password is required for this operation. Please enter password using -P or --ssh-password option" }
|
|
2056
|
+
let(:expected_error) { Train::UserError.new(ui_error_msg, :sudo_missing_terminal) }
|
|
2057
|
+
before do
|
|
2058
|
+
allow(connection).to receive(:connect!).and_raise(expected_error)
|
|
2059
|
+
end
|
|
2060
|
+
it "outputs user friendly error message" do
|
|
2061
|
+
expect { knife.do_connect({}) }.not_to raise_error
|
|
2062
|
+
expect(stderr.string).to include(ui_error_msg)
|
|
2063
|
+
end
|
|
2064
|
+
end
|
|
2065
|
+
|
|
2053
2066
|
end
|
|
2054
2067
|
|
|
2055
2068
|
describe "validate_winrm_transport_opts!" do
|
|
@@ -54,6 +54,19 @@ describe Chef::Knife::ClientCreate do
|
|
|
54
54
|
Chef::Config[:node_name] = "webmonkey.example.com"
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
58
|
+
let(:file_path) { File.join(tmpdir, "client.pem") }
|
|
59
|
+
let(:dir_path) { File.dirname(file_path) }
|
|
60
|
+
|
|
61
|
+
before do
|
|
62
|
+
allow(File).to receive(:exist?).and_call_original
|
|
63
|
+
allow(File).to receive(:exist?).with(file_path).and_return(false)
|
|
64
|
+
allow(File).to receive(:exist?).with(dir_path).and_return(true)
|
|
65
|
+
allow(File).to receive(:directory?).with(dir_path).and_return(true)
|
|
66
|
+
allow(File).to receive(:writable?).with(file_path).and_return(true)
|
|
67
|
+
allow(File).to receive(:writable?).with(dir_path).and_return(true)
|
|
68
|
+
end
|
|
69
|
+
|
|
57
70
|
describe "run" do
|
|
58
71
|
context "when nothing is passed" do
|
|
59
72
|
# from spec/support/shared/unit/knife_shared.rb
|
|
@@ -118,16 +131,66 @@ describe Chef::Knife::ClientCreate do
|
|
|
118
131
|
|
|
119
132
|
describe "with -f or --file" do
|
|
120
133
|
before do
|
|
134
|
+
knife.config[:file] = file_path
|
|
121
135
|
client.private_key "woot"
|
|
122
136
|
end
|
|
123
137
|
|
|
124
138
|
it "should write the private key to a file" do
|
|
125
|
-
knife.config[:file] = "/tmp/monkeypants"
|
|
126
139
|
filehandle = double("Filehandle")
|
|
127
140
|
expect(filehandle).to receive(:print).with("woot")
|
|
128
|
-
expect(File).to receive(:open).with(
|
|
141
|
+
expect(File).to receive(:open).with(file_path, "w").and_yield(filehandle)
|
|
129
142
|
knife.run
|
|
130
143
|
end
|
|
144
|
+
|
|
145
|
+
context "when the directory does not exist" do
|
|
146
|
+
before { allow(File).to receive(:exist?).with(dir_path).and_return(false) }
|
|
147
|
+
|
|
148
|
+
it "writes a fatal message and exits 1" do
|
|
149
|
+
expect(knife.ui).to receive(:fatal).with("Directory #{dir_path} does not exist. Please create and retry.")
|
|
150
|
+
expect { knife.run }.to raise_error(SystemExit)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
context "when the directory is not writable" do
|
|
155
|
+
before { allow(File).to receive(:writable?).with(dir_path).and_return(false) }
|
|
156
|
+
|
|
157
|
+
it "writes a fatal message and exits 1" do
|
|
158
|
+
expect(knife.ui).to receive(:fatal).with("Directory #{dir_path} is not writable. Please check the permissions.")
|
|
159
|
+
expect { knife.run }.to raise_error(SystemExit)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
context "when the directory is a file" do
|
|
164
|
+
before { allow(File).to receive(:directory?).with(dir_path).and_return(false) }
|
|
165
|
+
|
|
166
|
+
it "writes a fatal message and exits 1" do
|
|
167
|
+
expect(knife.ui).to receive(:fatal).with("#{dir_path} exists, but is not a directory. Please update your file path (--file #{file_path}) or re-create #{dir_path} as a directory.")
|
|
168
|
+
expect { knife.run }.to raise_error(SystemExit)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context "when the file does not exist" do
|
|
173
|
+
before do
|
|
174
|
+
allow(File).to receive(:exist?).with(file_path).and_return(false)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "does not log a fatal message and does not raise exception" do
|
|
178
|
+
expect(knife.ui).not_to receive(:fatal)
|
|
179
|
+
expect { knife.run }.not_to raise_error
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
context "when the file exists and is not writable" do
|
|
184
|
+
before do
|
|
185
|
+
allow(File).to receive(:exist?).with(file_path).and_return(true)
|
|
186
|
+
allow(File).to receive(:writable?).with(file_path).and_return(false)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "writes a fatal message and exits 1" do
|
|
190
|
+
expect(knife.ui).to receive(:fatal).with("File #{file_path} is not writable. Please check the permissions.")
|
|
191
|
+
expect { knife.run }.to raise_error(SystemExit)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
131
194
|
end
|
|
132
195
|
|
|
133
196
|
describe "with -p or --public-key" do
|
|
@@ -60,11 +60,12 @@ describe Chef::Knife::CookbookUpload do
|
|
|
60
60
|
before(:each) do
|
|
61
61
|
allow(Chef::CookbookLoader).to receive(:new).and_return(cookbook_loader)
|
|
62
62
|
allow(Chef::CookbookLoader).to receive(:copy_to_tmp_dir_from_array).and_yield(cookbook_loader)
|
|
63
|
+
allow(Chef::CookbookVersion).to receive(:list).and_return({})
|
|
64
|
+
allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({})
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
describe "with --concurrency" do
|
|
66
68
|
it "should upload cookbooks with predefined concurrency" do
|
|
67
|
-
allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({})
|
|
68
69
|
knife.config[:concurrency] = 3
|
|
69
70
|
test_cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah")
|
|
70
71
|
allow(cookbook_loader).to receive(:each).and_yield("test_cookbook", test_cookbook)
|
|
@@ -79,7 +80,6 @@ describe Chef::Knife::CookbookUpload do
|
|
|
79
80
|
describe "run" do
|
|
80
81
|
before(:each) do
|
|
81
82
|
allow(Chef::CookbookUploader).to receive_messages(new: cookbook_uploader)
|
|
82
|
-
allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({})
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
it "should print usage and exit when a cookbook name is not provided" do
|
|
@@ -214,48 +214,110 @@ describe Chef::Knife::CookbookUpload do
|
|
|
214
214
|
end
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
context "when chef_dependencies config is disabled" do
|
|
218
|
+
before do
|
|
219
|
+
knife.config[:check_dependencies] = false
|
|
220
|
+
end
|
|
219
221
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
describe "when specifying a cookbook name with missing dependencies" do
|
|
223
|
+
let(:cookbook_dependency) { Chef::CookbookVersion.new("dependency", "/tmp/blah") }
|
|
224
|
+
|
|
225
|
+
before(:each) do
|
|
226
|
+
cookbook.metadata.depends("dependency")
|
|
227
|
+
allow(cookbook_loader).to receive(:[]) do |ckbk|
|
|
228
|
+
{ "test_cookbook" => cookbook,
|
|
229
|
+
"dependency" => cookbook_dependency }[ckbk]
|
|
230
|
+
end
|
|
231
|
+
allow(knife).to receive(:cookbook_names).and_return(%w{cookbook_dependency test_cookbook})
|
|
232
|
+
@stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
|
|
233
|
+
knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
|
|
225
234
|
end
|
|
226
|
-
allow(knife).to receive(:cookbook_names).and_return(%w{cookbook_dependency test_cookbook})
|
|
227
|
-
@stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
|
|
228
|
-
knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
|
|
229
|
-
end
|
|
230
235
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
236
|
+
it "should not fetch all cookbooks from Chef Infra Server" do
|
|
237
|
+
expect(Chef::CookbookVersion).not_to receive(:list_all_versions)
|
|
238
|
+
knife.run
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it "should upload the cookbook" do
|
|
242
|
+
expect(cookbook_loader).to receive(:[]).once.with("test_cookbook")
|
|
243
|
+
expect(cookbook_uploader).to receive(:upload_cookbooks)
|
|
244
|
+
knife.run
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it "should not output a message for a single missing dependency" do
|
|
248
|
+
knife.run
|
|
249
|
+
expect(@stderr.string).not_to include("Cookbook test_cookbook depends on cookbooks which are not currently")
|
|
250
|
+
expect(@stderr.string).not_to include("being uploaded and cannot be found on the server.")
|
|
251
|
+
expect(@stderr.string).not_to include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "should not output a message for a multiple missing dependencies which are concatenated" do
|
|
255
|
+
cookbook_dependency2 = Chef::CookbookVersion.new("dependency2")
|
|
256
|
+
cookbook.metadata.depends("dependency2")
|
|
257
|
+
allow(cookbook_loader).to receive(:[]) do |ckbk|
|
|
258
|
+
{ "test_cookbook" => cookbook,
|
|
259
|
+
"dependency" => cookbook_dependency,
|
|
260
|
+
"dependency2" => cookbook_dependency2 }[ckbk]
|
|
261
|
+
end
|
|
262
|
+
allow(knife).to receive(:cookbook_names).and_return(%w{dependency dependency2 test_cookbook})
|
|
263
|
+
knife.run
|
|
264
|
+
expect(@stderr.string).not_to include("Cookbook test_cookbook depends on cookbooks which are not currently")
|
|
265
|
+
expect(@stderr.string).not_to include("being uploaded and cannot be found on the server.")
|
|
266
|
+
expect(@stderr.string).not_to include("The missing cookbook(s) are:")
|
|
267
|
+
expect(@stderr.string).not_to include("'dependency' version '>= 0.0.0'")
|
|
268
|
+
expect(@stderr.string).not_to include("'dependency2' version '>= 0.0.0'")
|
|
269
|
+
end
|
|
235
270
|
end
|
|
271
|
+
end
|
|
236
272
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
|
|
241
|
-
expect(@stderr.string).to include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'")
|
|
273
|
+
context "when chef_dependencies config is enabled" do
|
|
274
|
+
before do
|
|
275
|
+
knife.config[:check_dependencies] = true
|
|
242
276
|
end
|
|
243
277
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
"
|
|
278
|
+
describe "when specifying a cookbook name with missing dependencies" do
|
|
279
|
+
let(:cookbook_dependency) { Chef::CookbookVersion.new("dependency", "/tmp/blah") }
|
|
280
|
+
|
|
281
|
+
before(:each) do
|
|
282
|
+
cookbook.metadata.depends("dependency")
|
|
283
|
+
allow(cookbook_loader).to receive(:[]) do |ckbk|
|
|
284
|
+
{ "test_cookbook" => cookbook,
|
|
285
|
+
"dependency" => cookbook_dependency }[ckbk]
|
|
286
|
+
end
|
|
287
|
+
allow(knife).to receive(:cookbook_names).and_return(%w{cookbook_dependency test_cookbook})
|
|
288
|
+
@stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
|
|
289
|
+
knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it "should exit and not upload the cookbook" do
|
|
293
|
+
expect(cookbook_loader).to receive(:[]).once.with("test_cookbook")
|
|
294
|
+
expect(cookbook_uploader).not_to receive(:upload_cookbooks)
|
|
295
|
+
expect { knife.run }.to raise_error(SystemExit)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it "should output a message for a single missing dependency" do
|
|
299
|
+
expect { knife.run }.to raise_error(SystemExit)
|
|
300
|
+
expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
|
|
301
|
+
expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
|
|
302
|
+
expect(@stderr.string).to include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it "should output a message for a multiple missing dependencies which are concatenated" do
|
|
306
|
+
cookbook_dependency2 = Chef::CookbookVersion.new("dependency2")
|
|
307
|
+
cookbook.metadata.depends("dependency2")
|
|
308
|
+
allow(cookbook_loader).to receive(:[]) do |ckbk|
|
|
309
|
+
{ "test_cookbook" => cookbook,
|
|
310
|
+
"dependency" => cookbook_dependency,
|
|
311
|
+
"dependency2" => cookbook_dependency2 }[ckbk]
|
|
312
|
+
end
|
|
313
|
+
allow(knife).to receive(:cookbook_names).and_return(%w{dependency dependency2 test_cookbook})
|
|
314
|
+
expect { knife.run }.to raise_error(SystemExit)
|
|
315
|
+
expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
|
|
316
|
+
expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
|
|
317
|
+
expect(@stderr.string).to include("The missing cookbook(s) are:")
|
|
318
|
+
expect(@stderr.string).to include("'dependency' version '>= 0.0.0'")
|
|
319
|
+
expect(@stderr.string).to include("'dependency2' version '>= 0.0.0'")
|
|
251
320
|
end
|
|
252
|
-
allow(knife).to receive(:cookbook_names).and_return(%w{dependency dependency2 test_cookbook})
|
|
253
|
-
expect { knife.run }.to raise_error(SystemExit)
|
|
254
|
-
expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
|
|
255
|
-
expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
|
|
256
|
-
expect(@stderr.string).to include("The missing cookbook(s) are:")
|
|
257
|
-
expect(@stderr.string).to include("'dependency' version '>= 0.0.0'")
|
|
258
|
-
expect(@stderr.string).to include("'dependency2' version '>= 0.0.0'")
|
|
259
321
|
end
|
|
260
322
|
end
|
|
261
323
|
|
|
@@ -80,16 +80,16 @@ describe Chef::Knife::Core::BootstrapContext do
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
describe "when
|
|
84
|
-
let(:chef_config) { {
|
|
85
|
-
it "sets
|
|
83
|
+
describe "when unix_bootstrap_file_cache_path is set" do
|
|
84
|
+
let(:chef_config) { { unix_bootstrap_file_cache_path: "/home/opscode/cache" } }
|
|
85
|
+
it "sets unix_bootstrap_file_cache_path in the generated config file" do
|
|
86
86
|
expect(bootstrap_context.config_content).to include("file_cache_path \"/home/opscode/cache\"")
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
describe "when
|
|
91
|
-
let(:chef_config) { {
|
|
92
|
-
it "sets
|
|
90
|
+
describe "when unix_bootstrap_file_backup_path is set" do
|
|
91
|
+
let(:chef_config) { { unix_bootstrap_file_backup_path: "/home/opscode/backup" } }
|
|
92
|
+
it "sets unix_bootstrap_file_backup_path in the generated config file" do
|
|
93
93
|
expect(bootstrap_context.config_content).to include("file_backup_path \"/home/opscode/backup\"")
|
|
94
94
|
end
|
|
95
95
|
end
|
|
@@ -154,8 +154,8 @@ describe Chef::Knife::Core::WindowsBootstrapContext do
|
|
|
154
154
|
config_log_location: STDOUT,
|
|
155
155
|
chef_server_url: "http://chef.example.com:4444",
|
|
156
156
|
validation_client_name: "chef-validator-testing",
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
windows_bootstrap_file_cache_path: "c:/chef/cache",
|
|
158
|
+
windows_bootstrap_file_backup_path: "c:/chef/backup",
|
|
159
159
|
cache_options: ({ path: "c:/chef/cache/checksums", skip_expires: true })
|
|
160
160
|
)
|
|
161
161
|
)
|
|
@@ -165,11 +165,11 @@ describe Chef::Knife::Core::WindowsBootstrapContext do
|
|
|
165
165
|
expected = <<~EXPECTED
|
|
166
166
|
echo.chef_server_url "http://chef.example.com:4444"
|
|
167
167
|
echo.validation_client_name "chef-validator-testing"
|
|
168
|
-
echo.file_cache_path "
|
|
169
|
-
echo.file_backup_path "
|
|
168
|
+
echo.file_cache_path "c:/chef/cache"
|
|
169
|
+
echo.file_backup_path "c:/chef/backup"
|
|
170
170
|
echo.cache_options ^({:path =^> "C:\\\\chef\\\\cache\\\\checksums", :skip_expires =^> true}^)
|
|
171
171
|
echo.# Using default node name ^(fqdn^)
|
|
172
|
-
echo.log_level
|
|
172
|
+
echo.log_level :info
|
|
173
173
|
echo.log_location STDOUT
|
|
174
174
|
EXPECTED
|
|
175
175
|
expect(bootstrap_context.config_content).to eq expected
|
data/spec/unit/knife/ssh_spec.rb
CHANGED
|
@@ -289,7 +289,7 @@ describe Chef::Knife::Ssh do
|
|
|
289
289
|
let(:execution_channel2) { double(:execution_channel, on_data: nil, on_extended_data: nil) }
|
|
290
290
|
let(:session_channel2) { double(:session_channel, request_pty: nil) }
|
|
291
291
|
|
|
292
|
-
let(:session) { double(:session, loop: nil) }
|
|
292
|
+
let(:session) { double(:session, loop: nil, close: nil) }
|
|
293
293
|
|
|
294
294
|
let(:command) { "false" }
|
|
295
295
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: knife
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 17.
|
|
4
|
+
version: 17.4.47
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adam Jacob
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-09-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: chef-config
|
|
@@ -751,6 +751,7 @@ files:
|
|
|
751
751
|
- spec/data/kitchen/openldap/recipes/woot.rb
|
|
752
752
|
- spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb
|
|
753
753
|
- spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb
|
|
754
|
+
- spec/data/knife/temp_dir/tmp.pem
|
|
754
755
|
- spec/data/knife_subcommand/test_explicit_category.rb
|
|
755
756
|
- spec/data/knife_subcommand/test_name_mapping.rb
|
|
756
757
|
- spec/data/knife_subcommand/test_yourself.rb
|
|
@@ -1129,7 +1130,7 @@ licenses:
|
|
|
1129
1130
|
- Apache-2.0
|
|
1130
1131
|
metadata:
|
|
1131
1132
|
bug_tracker_uri: https://github.com/chef/chef/issues
|
|
1132
|
-
changelog_uri: https://github.com/chef/chef/blob/
|
|
1133
|
+
changelog_uri: https://github.com/chef/chef/blob/main/CHANGELOG.md
|
|
1133
1134
|
documentation_uri: https://docs.chef.io/
|
|
1134
1135
|
homepage_uri: https://www.chef.io
|
|
1135
1136
|
mailing_list_uri: https://discourse.chef.io/
|
|
@@ -1149,7 +1150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1149
1150
|
- !ruby/object:Gem::Version
|
|
1150
1151
|
version: '0'
|
|
1151
1152
|
requirements: []
|
|
1152
|
-
rubygems_version: 3.2.
|
|
1153
|
+
rubygems_version: 3.2.22
|
|
1153
1154
|
signing_key:
|
|
1154
1155
|
specification_version: 4
|
|
1155
1156
|
summary: The knife CLI for Chef Infra.
|