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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c59d8d9f4d2b9c07749f19f302a444f81c8e4f18c1a9d975664339f491ab6a10
4
- data.tar.gz: b66856e635b5af7e45ba80af0d2f9f6e13736ba8000dd82d1953864f20be9640
3
+ metadata.gz: 5bce1fdf8a026140f71d734c579f29ac19eeb68da7965ebc5426cdad7c00eb51
4
+ data.tar.gz: b10eafb114347dec04f3e1f0aaf0c1a9d9806ba2f76cfad8b57c1065e72bb0b9
5
5
  SHA512:
6
- metadata.gz: 5825020630a1925537d2bf81c504f46e2370f4c6895668ff4b482fe8d16f6b0970bf8b4e93cd560f11d2a14ff68ddd5c125fcd232a8d7e312f5de05327722105
7
- data.tar.gz: 4655fd4e97b9bfcf06609063a6f58cbe2fac3c8f1f73d61c7890e74dfe15480f4fd9f01a9543776f8019087f55afa52433347283239001bc20ef18a4da7d331a
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: "master"
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: "master"
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/master/CHANGELOG.md",
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, opts_in))
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, opts_in)
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
- opts_in.each do |key, _value|
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
@@ -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, justify_width)
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], justify_width)
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, justify_width)
244
+ def upload(cookbooks)
224
245
  cookbooks.each do |cb|
225
- ui.info("Uploading #{cb.name.to_s.ljust(justify_width + 10)} [#{cb.version}]")
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 @server_side_cookbooks[cookbook_name].nil?
289
+ if server_side_cookbooks[cookbook_name].nil?
269
290
  false
270
291
  else
271
- versions = @server_side_cookbooks[cookbook_name]["versions"].collect { |versions| versions["version"] }
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
- @server_side_cookbooks[cookbook_name]["versions"].each do |versions_hash|
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[:file_cache_path].nil?
175
- client_rb << "file_cache_path \"#{chef_config[:file_cache_path]}\"\n"
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[:file_backup_path].nil?
179
- client_rb << "file_backup_path \"#{chef_config[:file_backup_path]}\"\n"
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(ChefConfig::Config.var_chef_dir(windows: true))}\\\\cache"
75
- file_backup_path "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.var_chef_dir(windows: true))}\\\\backup"
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 config[:config_log_level]
90
- client_rb << %Q{log_level :#{config[:config_log_level]}\n}
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
@@ -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
- def ssh_command(command, subsession = nil)
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
- begin
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
- chan.request_pty if pty
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
 
@@ -17,7 +17,7 @@
17
17
  class Chef
18
18
  class Knife
19
19
  KNIFE_ROOT = File.expand_path("../..", __dir__)
20
- VERSION = "17.2.38".freeze
20
+ VERSION = "17.4.46".freeze
21
21
  end
22
22
  end
23
23
 
File without changes
@@ -66,7 +66,7 @@ describe "knife cookbook download", :workstation do
66
66
  Downloading root_files
67
67
  Cookbook downloaded to #{tmpdir}/x-1.0.1
68
68
  EOM
69
- )
69
+ )
70
70
  end
71
71
  end
72
72
  end
@@ -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("/tmp/monkeypants", "w").and_yield(filehandle)
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
- describe "when specifying a cookbook name with missing dependencies" do
218
- let(:cookbook_dependency) { Chef::CookbookVersion.new("dependency", "/tmp/blah") }
217
+ context "when chef_dependencies config is disabled" do
218
+ before do
219
+ knife.config[:check_dependencies] = false
220
+ end
219
221
 
220
- before(:each) do
221
- cookbook.metadata.depends("dependency")
222
- allow(cookbook_loader).to receive(:[]) do |ckbk|
223
- { "test_cookbook" => cookbook,
224
- "dependency" => cookbook_dependency }[ckbk]
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
- it "should exit and not upload the cookbook" do
232
- expect(cookbook_loader).to receive(:[]).once.with("test_cookbook")
233
- expect(cookbook_uploader).not_to receive(:upload_cookbooks)
234
- expect { knife.run }.to raise_error(SystemExit)
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
- it "should output a message for a single missing dependency" do
238
- expect { knife.run }.to raise_error(SystemExit)
239
- expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
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
- it "should output a message for a multiple missing dependencies which are concatenated" do
245
- cookbook_dependency2 = Chef::CookbookVersion.new("dependency2")
246
- cookbook.metadata.depends("dependency2")
247
- allow(cookbook_loader).to receive(:[]) do |ckbk|
248
- { "test_cookbook" => cookbook,
249
- "dependency" => cookbook_dependency,
250
- "dependency2" => cookbook_dependency2 }[ckbk]
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 file_cache_path is set" do
84
- let(:chef_config) { { file_cache_path: "/home/opscode/cache" } }
85
- it "sets file_cache_path in the generated config file" do
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 file_backup_path is set" do
91
- let(:chef_config) { { file_backup_path: "/home/opscode/backup" } }
92
- it "sets file_backup_path in the generated config file" do
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
- file_cache_path: "c:/chef/cache",
158
- file_backup_path: "c:/chef/backup",
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 "C:\\\\chef\\\\cache"
169
- echo.file_backup_path "C:\\\\chef\\\\backup"
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 :auto
172
+ echo.log_level :info
173
173
  echo.log_location STDOUT
174
174
  EXPECTED
175
175
  expect(bootstrap_context.config_content).to eq expected
@@ -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.2.38
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-06-15 00:00:00.000000000 Z
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: '0'
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: '0'
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/master/CHANGELOG.md
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.15
1167
+ rubygems_version: 3.2.22
1167
1168
  signing_key:
1168
1169
  specification_version: 4
1169
1170
  summary: The knife CLI for Chef Infra.