knife 17.2.38 → 17.4.46
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/knife.gemspec +2 -2
- 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 +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bce1fdf8a026140f71d734c579f29ac19eeb68da7965ebc5426cdad7c00eb51
|
4
|
+
data.tar.gz: b10eafb114347dec04f3e1f0aaf0c1a9d9806ba2f76cfad8b57c1065e72bb0b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a5df15d3687dddd23e0469fb170fd2ea6325ee186378f98a334f7a1d42757195d2dc541ff9b370b624f949a0ab06de11d607b2029559788f0a1e406b0d8ac8d
|
7
|
+
data.tar.gz: 24414b3f673ae91d6c837c3b3c8740cf60746dc337c770b752fa1baf65f1773510ea9a8c72ee19e2979d70688ca1810a502afbe43092c321ae0cc1dceb8a8e69
|
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,7 +30,7 @@ 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
|
-
s.add_dependency "x25519" # ed25519 KEX module
|
33
|
+
s.add_dependency "x25519", ">= 1.0.9" # ed25519 KEX module. 1.0.9+ required to resolve sigill failures
|
34
34
|
s.add_dependency "highline", ">= 1.6.9", "< 3" # Used in UI to present a list, no other usage.
|
35
35
|
|
36
36
|
s.add_dependency "tty-prompt", "~> 0.21" # knife ui.ask prompt
|
@@ -51,7 +51,7 @@ Gem::Specification.new do |s|
|
|
51
51
|
|
52
52
|
s.metadata = {
|
53
53
|
"bug_tracker_uri" => "https://github.com/chef/chef/issues",
|
54
|
-
"changelog_uri" => "https://github.com/chef/chef/blob/
|
54
|
+
"changelog_uri" => "https://github.com/chef/chef/blob/main/CHANGELOG.md",
|
55
55
|
"documentation_uri" => "https://docs.chef.io/",
|
56
56
|
"homepage_uri" => "https://www.chef.io",
|
57
57
|
"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.46
|
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
|
@@ -262,14 +262,14 @@ dependencies:
|
|
262
262
|
requirements:
|
263
263
|
- - ">="
|
264
264
|
- !ruby/object:Gem::Version
|
265
|
-
version:
|
265
|
+
version: 1.0.9
|
266
266
|
type: :runtime
|
267
267
|
prerelease: false
|
268
268
|
version_requirements: !ruby/object:Gem::Requirement
|
269
269
|
requirements:
|
270
270
|
- - ">="
|
271
271
|
- !ruby/object:Gem::Version
|
272
|
-
version:
|
272
|
+
version: 1.0.9
|
273
273
|
- !ruby/object:Gem::Dependency
|
274
274
|
name: highline
|
275
275
|
requirement: !ruby/object:Gem::Requirement
|
@@ -765,6 +765,7 @@ files:
|
|
765
765
|
- spec/data/kitchen/openldap/recipes/woot.rb
|
766
766
|
- spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb
|
767
767
|
- spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb
|
768
|
+
- spec/data/knife/temp_dir/tmp.pem
|
768
769
|
- spec/data/knife_subcommand/test_explicit_category.rb
|
769
770
|
- spec/data/knife_subcommand/test_name_mapping.rb
|
770
771
|
- spec/data/knife_subcommand/test_yourself.rb
|
@@ -1143,7 +1144,7 @@ licenses:
|
|
1143
1144
|
- Apache-2.0
|
1144
1145
|
metadata:
|
1145
1146
|
bug_tracker_uri: https://github.com/chef/chef/issues
|
1146
|
-
changelog_uri: https://github.com/chef/chef/blob/
|
1147
|
+
changelog_uri: https://github.com/chef/chef/blob/main/CHANGELOG.md
|
1147
1148
|
documentation_uri: https://docs.chef.io/
|
1148
1149
|
homepage_uri: https://www.chef.io
|
1149
1150
|
mailing_list_uri: https://discourse.chef.io/
|
@@ -1163,7 +1164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1163
1164
|
- !ruby/object:Gem::Version
|
1164
1165
|
version: '0'
|
1165
1166
|
requirements: []
|
1166
|
-
rubygems_version: 3.2.
|
1167
|
+
rubygems_version: 3.2.22
|
1167
1168
|
signing_key:
|
1168
1169
|
specification_version: 4
|
1169
1170
|
summary: The knife CLI for Chef Infra.
|