knife 18.8.68 → 19.0.102
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 +30 -9
- data/LICENSE +201 -201
- data/Rakefile +23 -0
- data/knife.gemspec +9 -15
- data/lib/chef/application/knife.rb +3 -5
- data/lib/chef/chef_fs/knife.rb +1 -1
- data/lib/chef/knife/acl_base.rb +1 -1
- data/lib/chef/knife/acl_bulk_add.rb +0 -1
- data/lib/chef/knife/acl_bulk_remove.rb +0 -1
- data/lib/chef/knife/bootstrap/templates/chef-full.erb +24 -5
- data/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb +121 -4
- data/lib/chef/knife/bootstrap.rb +25 -29
- data/lib/chef/knife/config_get.rb +1 -1
- data/lib/chef/knife/config_get_profile.rb +1 -1
- data/lib/chef/knife/config_list_profiles.rb +1 -1
- data/lib/chef/knife/config_show.rb +1 -1
- data/lib/chef/knife/config_use_profile.rb +3 -3
- data/lib/chef/knife/configure_client.rb +1 -1
- data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
- data/lib/chef/knife/cookbook_delete.rb +1 -1
- data/lib/chef/knife/cookbook_download.rb +1 -1
- data/lib/chef/knife/cookbook_upload.rb +1 -1
- data/lib/chef/knife/core/bootstrap_context.rb +36 -6
- data/lib/chef/knife/core/cookbook_scm_repo.rb +2 -2
- data/lib/chef/knife/core/cookbook_site_streaming_uploader.rb +1 -6
- data/lib/chef/knife/core/gem_glob_loader.rb +4 -4
- data/lib/chef/knife/core/object_loader.rb +1 -1
- data/lib/chef/knife/core/status_presenter.rb +1 -1
- data/lib/chef/knife/core/subcommand_loader.rb +1 -1
- data/lib/chef/knife/core/text_formatter.rb +0 -1
- data/lib/chef/knife/core/windows_bootstrap_context.rb +103 -37
- data/lib/chef/knife/data_bag_secret_options.rb +1 -1
- data/lib/chef/knife/download.rb +1 -1
- data/lib/chef/knife/environment_compare.rb +2 -3
- data/lib/chef/knife/key_create.rb +1 -5
- data/lib/chef/knife/key_edit.rb +1 -5
- data/lib/chef/knife/license.rb +6 -2
- data/lib/chef/knife/list.rb +1 -1
- data/lib/chef/knife/node_edit.rb +1 -1
- data/lib/chef/knife/raw.rb +1 -2
- data/lib/chef/knife/recipe_list.rb +1 -1
- data/lib/chef/knife/search.rb +2 -2
- data/lib/chef/knife/ssh.rb +5 -5
- data/lib/chef/knife/ssl_fetch.rb +1 -1
- data/lib/chef/knife/supermarket_share.rb +3 -2
- data/lib/chef/knife/upload.rb +1 -1
- data/lib/chef/knife/user_create.rb +11 -21
- data/lib/chef/knife/user_list.rb +3 -3
- data/lib/chef/knife/version.rb +1 -3
- data/lib/chef/knife/xargs.rb +0 -1
- data/lib/chef/knife.rb +4 -1
- data/lib/chef/utils/licensing_config.rb +0 -1
- data/lib/chef/utils/licensing_handler.rb +18 -28
- data/spec/data/apt/chef-integration-test-1.0/debian/rules +0 -0
- data/spec/data/apt/chef-integration-test-1.1/debian/rules +0 -0
- data/spec/data/apt/chef-integration-test2-1.0/debian/rules +0 -0
- data/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb +4 -4
- data/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb +2 -2
- data/spec/functional/cookbook_delete_spec.rb +4 -4
- data/spec/functional/version_spec.rb +2 -3
- data/spec/integration/config_use_spec.rb +8 -8
- data/spec/integration/cookbook_api_ipv6_spec.rb +2 -2
- data/spec/integration/node_environment_set_spec.rb +1 -1
- data/spec/integration/node_run_list_set_spec.rb +1 -1
- data/spec/knife_spec_helper.rb +0 -5
- data/spec/support/platforms/prof/gc.rb +1 -1
- data/spec/support/shared/integration/integration_helper.rb +1 -1
- data/spec/tiny_server.rb +2 -2
- data/spec/unit/knife/bootstrap/templates/windows_presigned_url_spec.rb +183 -0
- data/spec/unit/knife/bootstrap_chef19_spec.rb +122 -0
- data/spec/unit/knife/bootstrap_spec.rb +121 -7
- data/spec/unit/knife/configure_spec.rb +3 -3
- data/spec/unit/knife/cookbook_list_spec.rb +1 -1
- data/spec/unit/knife/core/bootstrap_context_spec.rb +137 -0
- data/spec/unit/knife/core/windows_bootstrap_context_chef19_spec.rb +82 -0
- data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +51 -64
- data/spec/unit/knife/license_spec.rb +1 -1
- data/spec/unit/knife/search_spec.rb +3 -3
- data/spec/unit/knife/supermarket_install_spec.rb +4 -5
- data/spec/unit/knife/utils/licensing_handler_chef19_spec.rb +144 -0
- metadata +61 -100
- data/spec/data/gems/chef-integration-test-0.1.0.gem +0 -0
- data/spec/data/nodes/default.rb +0 -15
- data/spec/data/nodes/test.example.com.rb +0 -17
- data/spec/data/nodes/test.rb +0 -15
- data/spec/unit/utils/licensing_handler_spec.rb +0 -140
|
@@ -75,7 +75,7 @@ describe Chef::Knife::CookbookDelete do
|
|
|
75
75
|
|
|
76
76
|
knife.run
|
|
77
77
|
|
|
78
|
-
expect(stdout.string).to match(/#{Regexp.escape(
|
|
78
|
+
expect(stdout.string).to match(/#{Regexp.escape("Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)")}/)
|
|
79
79
|
expect(cb100_deleted).to be_truthy
|
|
80
80
|
end
|
|
81
81
|
|
|
@@ -91,8 +91,8 @@ describe Chef::Knife::CookbookDelete do
|
|
|
91
91
|
|
|
92
92
|
knife.run
|
|
93
93
|
|
|
94
|
-
expect(stdout.string).to match(/#{Regexp.escape(
|
|
95
|
-
expect(stdout.string).to match(/#{Regexp.escape(
|
|
94
|
+
expect(stdout.string).to match(/#{Regexp.escape("Are you sure you want to purge files")}/)
|
|
95
|
+
expect(stdout.string).to match(/#{Regexp.escape("Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)")}/)
|
|
96
96
|
expect(cb100_deleted).to be_truthy
|
|
97
97
|
|
|
98
98
|
end
|
|
@@ -123,7 +123,7 @@ describe Chef::Knife::CookbookDelete do
|
|
|
123
123
|
end
|
|
124
124
|
|
|
125
125
|
it "asks which version to delete and deletes that when not given the -a flag" do
|
|
126
|
-
cb100_deleted =
|
|
126
|
+
cb100_deleted = nil
|
|
127
127
|
api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
|
|
128
128
|
stdin, stdout = StringIO.new, StringIO.new
|
|
129
129
|
allow(knife.ui).to receive(:stdin).and_return(stdin)
|
|
@@ -18,9 +18,8 @@ require "chef/mixin/shell_out"
|
|
|
18
18
|
|
|
19
19
|
describe "Knife Version", :executables do
|
|
20
20
|
include Chef::Mixin::ShellOut
|
|
21
|
-
let(:knife_dir) { File.join(__dir__, "..", ".."
|
|
22
|
-
|
|
21
|
+
let(:knife_dir) { File.join(__dir__, "..", "..") }
|
|
22
|
+
it "should be same" do
|
|
23
23
|
expect(shell_out!("bundle exec knife -v", cwd: knife_dir).stdout.chomp).to match(/.*: #{Chef::Knife::VERSION}/)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
|
-
|
|
@@ -159,10 +159,10 @@ describe "knife config use", :workstation do
|
|
|
159
159
|
before do
|
|
160
160
|
ENV["CHEF_HOME"] = path_to("chefhome"); file("chefhome/tmp", "")
|
|
161
161
|
file("chefhome/.chef/credentials", <<~EOH
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
[staging]
|
|
163
|
+
client_name = "testuser"
|
|
164
|
+
client_key = "testkey.pem"
|
|
165
|
+
chef_server_url = "https://example.com/organizations/testorg"
|
|
166
166
|
EOH
|
|
167
167
|
)
|
|
168
168
|
end
|
|
@@ -180,10 +180,10 @@ describe "knife config use", :workstation do
|
|
|
180
180
|
before do
|
|
181
181
|
ENV["KNIFE_HOME"] = path_to("knifehome"); file("knifehome/tmp", "")
|
|
182
182
|
file("knifehome/.chef/credentials", <<~EOH
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
[development]
|
|
184
|
+
client_name = "testuser"
|
|
185
|
+
client_key = "testkey.pem"
|
|
186
|
+
chef_server_url = "https://example.com/organizations/testorg"
|
|
187
187
|
EOH
|
|
188
188
|
)
|
|
189
189
|
end
|
|
@@ -19,7 +19,7 @@ require "knife_spec_helper"
|
|
|
19
19
|
require "support/shared/integration/integration_helper"
|
|
20
20
|
require "chef/mixin/shell_out"
|
|
21
21
|
|
|
22
|
-
describe "Knife cookbook API integration with IPv6", :workstation
|
|
22
|
+
describe "Knife cookbook API integration with IPv6", :workstation do
|
|
23
23
|
include IntegrationSupport
|
|
24
24
|
include Chef::Mixin::ShellOut
|
|
25
25
|
|
|
@@ -62,7 +62,7 @@ describe "Knife cookbook API integration with IPv6", :workstation, :not_supporte
|
|
|
62
62
|
Dir.mktmpdir
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
let(:chef_dir) { File.join(__dir__, "..", "..", "
|
|
65
|
+
let(:chef_dir) { File.join(__dir__, "..", "..", "bin") }
|
|
66
66
|
let(:knife) { "ruby '#{chef_dir}/knife'" }
|
|
67
67
|
|
|
68
68
|
let(:knife_config_flag) { "-c '#{path_to("config/knife.rb")}'" }
|
|
@@ -40,7 +40,7 @@ describe "knife node environment set", :workstation do
|
|
|
40
40
|
|
|
41
41
|
it "with no environment" do
|
|
42
42
|
knife("node environment set adam").should_fail stderr: "FATAL: You must specify a node name and an environment.\n",
|
|
43
|
-
|
|
43
|
+
stdout: /^USAGE: knife node environment set NODE ENVIRONMENT\n/
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -35,7 +35,7 @@ describe "knife node run list set", :workstation do
|
|
|
35
35
|
|
|
36
36
|
it "with no role or recipe" do
|
|
37
37
|
knife("node run list set cons").should_fail stderr: "FATAL: You must supply both a node name and a run list.\n",
|
|
38
|
-
|
|
38
|
+
stdout: /^USAGE: knife node run_list set NODE ENTRIES \(options\)/m
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
end
|
data/spec/knife_spec_helper.rb
CHANGED
|
@@ -98,11 +98,6 @@ TEST_PLATFORM = TEST_NODE["platform"]
|
|
|
98
98
|
TEST_PLATFORM_VERSION = TEST_NODE["platform_version"]
|
|
99
99
|
TEST_PLATFORM_FAMILY = TEST_NODE["platform_family"]
|
|
100
100
|
|
|
101
|
-
provider_priority_map ||= nil
|
|
102
|
-
resource_priority_map ||= nil
|
|
103
|
-
provider_handler_map ||= nil
|
|
104
|
-
resource_handler_map ||= nil
|
|
105
|
-
|
|
106
101
|
class UnexpectedSystemExit < RuntimeError
|
|
107
102
|
def self.from(system_exit)
|
|
108
103
|
new(system_exit.message).tap { |e| e.set_backtrace(system_exit.backtrace) }
|
|
@@ -24,7 +24,7 @@ module RSpec
|
|
|
24
24
|
# GC 1 invokes.
|
|
25
25
|
# Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
|
|
26
26
|
# 1 0.012 159240 212940 10647 0.00000000000001530000
|
|
27
|
-
LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)
|
|
27
|
+
LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)$/
|
|
28
28
|
|
|
29
29
|
def start
|
|
30
30
|
::GC::Profiler.enable unless ::GC::Profiler.enabled?
|
|
@@ -59,7 +59,7 @@ module IntegrationSupport
|
|
|
59
59
|
# TODO: "force" actually means "silence all exceptions". this
|
|
60
60
|
# silences a weird permissions error on Windows that we should track
|
|
61
61
|
# down, but for now there's no reason for it to blow up our CI.
|
|
62
|
-
FileUtils.remove_entry_secure(@repository_dir,
|
|
62
|
+
FileUtils.remove_entry_secure(@repository_dir, ChefUtils.windows?)
|
|
63
63
|
ensure
|
|
64
64
|
@repository_dir = nil
|
|
65
65
|
end
|
data/spec/tiny_server.rb
CHANGED
|
@@ -132,7 +132,7 @@ module TinyServer
|
|
|
132
132
|
end
|
|
133
133
|
|
|
134
134
|
def call(env)
|
|
135
|
-
if response = response_for_request(env)
|
|
135
|
+
if (response = response_for_request(env))
|
|
136
136
|
response.call
|
|
137
137
|
else
|
|
138
138
|
debug_info = { message: "no data matches the request for #{env["REQUEST_URI"]}",
|
|
@@ -144,7 +144,7 @@ module TinyServer
|
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def response_for_request(env)
|
|
147
|
-
if route = @routes[env["REQUEST_METHOD"]].find { |route| route.matches_request?(env["REQUEST_URI"]) }
|
|
147
|
+
if (route = @routes[env["REQUEST_METHOD"]].find { |route| route.matches_request?(env["REQUEST_URI"]) })
|
|
148
148
|
route.response
|
|
149
149
|
end
|
|
150
150
|
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright:: Copyright (c) Chef Software Inc.
|
|
3
|
+
# License:: Apache License, Version 2.0
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
require "knife_spec_helper"
|
|
19
|
+
require "chef/knife/bootstrap"
|
|
20
|
+
require "chef/knife/core/windows_bootstrap_context"
|
|
21
|
+
|
|
22
|
+
describe "windows-chef-client-msi.erb template with presigned URLs" do
|
|
23
|
+
let(:connection) { double("Train::Connection") }
|
|
24
|
+
let(:knife) do
|
|
25
|
+
k = Chef::Knife::Bootstrap.new(["-o", "winrm", "127.0.0.1"])
|
|
26
|
+
allow(k).to receive(:connection).and_return(connection)
|
|
27
|
+
allow(connection).to receive(:windows?).and_return(true)
|
|
28
|
+
allow(connection).to receive(:hostname).and_return("test.example.com")
|
|
29
|
+
allow(connection).to receive(:os).and_return(OpenStruct.new(family: "windows"))
|
|
30
|
+
k
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "with presigned S3 URL containing special characters" do
|
|
34
|
+
let(:presigned_url) do
|
|
35
|
+
"https://workstation-rc3-assets.s3.us-east-1.amazonaws.com/install.ps1" \
|
|
36
|
+
"?response-content-disposition=inline" \
|
|
37
|
+
"&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD" \
|
|
38
|
+
"&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEHQaCXVzLWVhc3QtMSJIMEYCIQCfUHkC2e0%2FzlRkBc%2F2dgmp" \
|
|
39
|
+
"&X-Amz-Algorithm=AWS4-HMAC-SHA256" \
|
|
40
|
+
"&X-Amz-Credential=ASIA5LOKXA7I3346RT45%2F20251112%2Fus-east-1%2Fs3%2Faws4_request" \
|
|
41
|
+
"&X-Amz-Date=20251112T194920Z" \
|
|
42
|
+
"&X-Amz-Expires=43200" \
|
|
43
|
+
"&X-Amz-SignedHeaders=host" \
|
|
44
|
+
"&X-Amz-Signature=34d6c57d80c9a59d9f524069aa12739cf50d45b907d2a1d27e41fbf76828ad3c"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
before do
|
|
48
|
+
knife.config[:bootstrap_url] = presigned_url
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
let(:rendered_template) { knife.render_template }
|
|
52
|
+
|
|
53
|
+
it "properly escapes percent signs in the URL for batch variable assignment" do
|
|
54
|
+
# Verify that percent signs are doubled for batch file handling
|
|
55
|
+
expect(rendered_template).to include('set "BOOTSTRAP_DOWNLOAD_URL=')
|
|
56
|
+
# Check that percent signs are properly escaped in the environment variable
|
|
57
|
+
expect(rendered_template).to match(/set "BOOTSTRAP_DOWNLOAD_URL=.*%%2F.*%%2F/)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "uses environment variable to pass URL to PowerShell for download" do
|
|
61
|
+
# Verify that the URL is passed via environment variable to avoid batch expansion issues
|
|
62
|
+
expect(rendered_template).to include("$env:BOOTSTRAP_DOWNLOAD_URL")
|
|
63
|
+
expect(rendered_template).to include("powershell.exe -ExecutionPolicy Unrestricted")
|
|
64
|
+
# Verify download uses -Command parameter with script block for custom URLs
|
|
65
|
+
expect(rendered_template).to match(/powershell\.exe -ExecutionPolicy Unrestricted -InputFormat None -NoProfile -NonInteractive -Command "& '.*wget\.ps1'/)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "uses -File parameter for install script execution" do
|
|
69
|
+
# Verify that the install script execution uses -File parameter for custom bootstrap URLs
|
|
70
|
+
expect(rendered_template).to match(/powershell\.exe -ExecutionPolicy Unrestricted -File "%LOCAL_DESTINATION_SCRIPT_PATH%"/)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "does not use batch variable expansion for custom bootstrap URLs" do
|
|
74
|
+
# Verify that we're not using %REMOTE_SOURCE_SCRIPT_URL% expansion for custom URLs
|
|
75
|
+
# which would cause the percent signs to be misinterpreted
|
|
76
|
+
lines_with_bootstrap_url = rendered_template.lines.select { |line| line.include?("BOOTSTRAP_DOWNLOAD_URL") }
|
|
77
|
+
expect(lines_with_bootstrap_url).not_to be_empty
|
|
78
|
+
|
|
79
|
+
# The custom URL should not go through %VAR% expansion
|
|
80
|
+
expect(rendered_template).not_to include("%BOOTSTRAP_DOWNLOAD_URL%")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "properly handles the URL in the cscript fallback" do
|
|
84
|
+
# Verify that the cscript fallback also uses the environment variable
|
|
85
|
+
expect(rendered_template).to include("cscript /nologo")
|
|
86
|
+
expect(rendered_template).to match(/wget\.vbs.*BOOTSTRAP_DOWNLOAD_URL/)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "preserves all query parameters in the URL" do
|
|
90
|
+
# Verify that important query parameters are preserved
|
|
91
|
+
expect(rendered_template).to include("X-Amz-Algorithm=AWS4-HMAC-SHA256")
|
|
92
|
+
expect(rendered_template).to include("X-Amz-Credential=")
|
|
93
|
+
expect(rendered_template).to include("X-Amz-Date=")
|
|
94
|
+
expect(rendered_template).to include("X-Amz-Signature=")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "does not corrupt encoded slashes in the credential parameter" do
|
|
98
|
+
# The most critical part - encoded slashes (%2F) must remain as %%2F in batch
|
|
99
|
+
# so they survive batch processing and become %2F when passed to PowerShell
|
|
100
|
+
expect(rendered_template).to match(/ASIA5LOKXA7I3346RT45%%2F20251112%%2Fus-east-1%%2Fs3%%2Faws4_request/)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "does not corrupt encoded plus signs" do
|
|
104
|
+
# Verify that %2B (encoded +) becomes %%2B
|
|
105
|
+
if presigned_url.include?("%2B")
|
|
106
|
+
expect(rendered_template).to include("%%2B")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "with standard bootstrap URL (no special characters)" do
|
|
112
|
+
before do
|
|
113
|
+
knife.config[:license_url] = "https://chefdownload-trial.chef.io/install.sh?license_id=test-license"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
let(:rendered_template) { knife.render_template }
|
|
117
|
+
|
|
118
|
+
it "uses REMOTE_SOURCE_SCRIPT_URL for standard URLs" do
|
|
119
|
+
expect(rendered_template).to include('set "REMOTE_SOURCE_SCRIPT_URL=')
|
|
120
|
+
expect(rendered_template).to include("%REMOTE_SOURCE_SCRIPT_URL%")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "uses license URL when no bootstrap_url is provided" do
|
|
124
|
+
expect(rendered_template).to include("https://chefdownload-trial.chef.io/install.ps1?license_id=test-license")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
describe "edge cases" do
|
|
129
|
+
context "when bootstrap URL contains single quotes" do
|
|
130
|
+
before do
|
|
131
|
+
knife.config[:bootstrap_url] = "https://example.com/install.ps1?param='value'"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
let(:rendered_template) { knife.render_template }
|
|
135
|
+
|
|
136
|
+
it "properly escapes single quotes in PowerShell command" do
|
|
137
|
+
# Single quotes should be doubled for PowerShell string escaping
|
|
138
|
+
expect(rendered_template).to include("''")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context "when bootstrap URL contains ampersands" do
|
|
143
|
+
before do
|
|
144
|
+
knife.config[:bootstrap_url] = "https://example.com/install.ps1?a=1&b=2&c=3"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
let(:rendered_template) { knife.render_template }
|
|
148
|
+
|
|
149
|
+
it "preserves ampersands in the URL" do
|
|
150
|
+
expect(rendered_template).to include("&b=2&")
|
|
151
|
+
expect(rendered_template).to include("&c=3")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
context "when bootstrap URL contains equals signs" do
|
|
156
|
+
before do
|
|
157
|
+
knife.config[:bootstrap_url] = "https://example.com/install.ps1?key=value"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
let(:rendered_template) { knife.render_template }
|
|
161
|
+
|
|
162
|
+
it "preserves equals signs in the URL" do
|
|
163
|
+
expect(rendered_template).to include("key=value")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe "error messages" do
|
|
169
|
+
before do
|
|
170
|
+
knife.config[:bootstrap_url] = "https://example.com/install.ps1?X-Amz-Signature=abc123"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
let(:rendered_template) { knife.render_template }
|
|
174
|
+
|
|
175
|
+
it "displays appropriate error message for custom URLs" do
|
|
176
|
+
expect(rendered_template).to include("Failed to download install script")
|
|
177
|
+
# Should not reference %REMOTE_SOURCE_SCRIPT_URL% for custom URLs
|
|
178
|
+
error_section = rendered_template.split("if NOT %DOWNLOAD_ERROR_STATUS%==0").last
|
|
179
|
+
custom_url_section = error_section.split("else").first
|
|
180
|
+
expect(custom_url_section).not_to include("%REMOTE_SOURCE_SCRIPT_URL%")
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright:: Copyright (c) Chef Software Inc.
|
|
3
|
+
# License:: Apache License, Version 2.0
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
require "knife_spec_helper"
|
|
19
|
+
require "chef/knife/bootstrap"
|
|
20
|
+
|
|
21
|
+
describe Chef::Knife::Bootstrap do
|
|
22
|
+
let(:knife) { described_class.new }
|
|
23
|
+
let(:stdout) { StringIO.new }
|
|
24
|
+
let(:stderr) { StringIO.new }
|
|
25
|
+
let(:stdin) { StringIO.new }
|
|
26
|
+
|
|
27
|
+
before do
|
|
28
|
+
knife.ui = Chef::Knife::UI.new(stdout, stderr, stdin, {})
|
|
29
|
+
knife.name_args = ["test-node"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "Chef Infra 19 licensing support" do
|
|
33
|
+
describe "#fetch_license" do
|
|
34
|
+
let(:bootstrap_context) { double("bootstrap_context") }
|
|
35
|
+
let(:license_handler) { double("license_handler") }
|
|
36
|
+
|
|
37
|
+
before do
|
|
38
|
+
allow(Chef::Knife::Core::BootstrapContext).to receive(:new).and_return(bootstrap_context)
|
|
39
|
+
allow(Chef::Utils::LicensingHandler).to receive(:validate!).and_return(license_handler)
|
|
40
|
+
allow(license_handler).to receive(:install_sh_url).and_return("https://example.com/install.sh")
|
|
41
|
+
allow(license_handler).to receive(:license_key).and_return("test-key")
|
|
42
|
+
allow(license_handler).to receive(:omnitruck_url).and_return("https://example.com/%s")
|
|
43
|
+
allow(license_handler).to receive(:license_type).and_return("trial")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context "when license is required for Chef Infra 19" do
|
|
47
|
+
before do
|
|
48
|
+
allow(bootstrap_context).to receive(:chef_ice?).and_return(true)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "fetches and sets license configuration" do
|
|
52
|
+
expect(ChefLicensing::Config).to receive(:require_license_for).and_yield
|
|
53
|
+
|
|
54
|
+
knife.config[:bootstrap_version] = "19.0.0"
|
|
55
|
+
knife.send(:fetch_license)
|
|
56
|
+
|
|
57
|
+
expect(knife.config[:license_url]).to eq("https://example.com/install.sh")
|
|
58
|
+
expect(knife.config[:license_id]).to eq("test-key")
|
|
59
|
+
expect(knife.config[:omnitruck_url]).to eq("https://example.com/%s")
|
|
60
|
+
expect(knife.config[:license_type]).to eq("trial")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context "when custom bootstrap URL is provided" do
|
|
65
|
+
it "skips license fetching" do
|
|
66
|
+
expect(ChefLicensing::Config).not_to receive(:require_license_for)
|
|
67
|
+
|
|
68
|
+
knife.config[:bootstrap_url] = "https://custom.example.com/install.sh"
|
|
69
|
+
knife.send(:fetch_license)
|
|
70
|
+
|
|
71
|
+
expect(knife.config[:license_url]).to be_nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "when custom bootstrap template is provided" do
|
|
76
|
+
it "skips license fetching" do
|
|
77
|
+
expect(ChefLicensing::Config).not_to receive(:require_license_for)
|
|
78
|
+
|
|
79
|
+
knife.config[:bootstrap_template] = "custom-template"
|
|
80
|
+
knife.send(:fetch_license)
|
|
81
|
+
|
|
82
|
+
expect(knife.config[:license_url]).to be_nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "command line options" do
|
|
88
|
+
it "accepts --bootstrap-product option" do
|
|
89
|
+
knife.config[:bootstrap_product] = "chef-ice"
|
|
90
|
+
expect(knife.config[:bootstrap_product]).to eq("chef-ice")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "accepts --license-key option" do
|
|
94
|
+
knife.config[:chef_license_key] = "test-license-key"
|
|
95
|
+
expect(knife.config[:chef_license_key]).to eq("test-license-key")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "accepts --license-url option" do
|
|
99
|
+
knife.config[:chef_license_server] = "https://example.com/license"
|
|
100
|
+
expect(knife.config[:chef_license_server]).to eq("https://example.com/license")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe "option descriptions" do
|
|
105
|
+
let(:bootstrap_product_option) { knife.class.options[:bootstrap_product] }
|
|
106
|
+
let(:license_key_option) { knife.class.options[:chef_license_key] }
|
|
107
|
+
let(:license_url_option) { knife.class.options[:chef_license_server] }
|
|
108
|
+
|
|
109
|
+
it "has proper description for bootstrap_product" do
|
|
110
|
+
expect(bootstrap_product_option[:description]).to include("Product")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "has proper description for license_key" do
|
|
114
|
+
expect(license_key_option[:description]).to include("Chef License key")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "has proper description for license_url" do
|
|
118
|
+
expect(license_url_option[:description]).to include("Chef License server")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -59,7 +59,7 @@ describe Chef::Knife::Bootstrap do
|
|
|
59
59
|
|
|
60
60
|
describe "when a license is not required" do
|
|
61
61
|
it "does not set the chef_license" do
|
|
62
|
-
expect(acceptor).to receive(:license_required?).and_return(false)
|
|
62
|
+
expect(acceptor).to receive(:license_required?).with("chef", anything).and_return(false)
|
|
63
63
|
knife.check_eula_license
|
|
64
64
|
expect(Chef::Config[:chef_license]).to eq(nil)
|
|
65
65
|
end
|
|
@@ -67,9 +67,9 @@ describe Chef::Knife::Bootstrap do
|
|
|
67
67
|
|
|
68
68
|
describe "when a license is required" do
|
|
69
69
|
it "sets the chef_license" do
|
|
70
|
-
expect(acceptor).to receive(:license_required?).and_return(true)
|
|
71
|
-
expect(acceptor).to receive(:id_from_mixlib).and_return("id")
|
|
72
|
-
expect(acceptor).to receive(:check_and_persist)
|
|
70
|
+
expect(acceptor).to receive(:license_required?).with("chef", anything).and_return(true)
|
|
71
|
+
expect(acceptor).to receive(:id_from_mixlib).with("chef").and_return("id")
|
|
72
|
+
expect(acceptor).to receive(:check_and_persist).with("id", anything)
|
|
73
73
|
expect(acceptor).to receive(:acceptance_value).and_return("accept-no-persist")
|
|
74
74
|
knife.check_eula_license
|
|
75
75
|
expect(Chef::Config[:chef_license]).to eq("accept-no-persist")
|
|
@@ -100,6 +100,118 @@ describe Chef::Knife::Bootstrap do
|
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
context "#fetch_license" do
|
|
104
|
+
let(:licensing_config_mock) { double("ChefLicensing::Config") }
|
|
105
|
+
let(:licensing_handler_mock) { double("Chef::Utils::LicensingHandler") }
|
|
106
|
+
let(:license_mock) { double("License") }
|
|
107
|
+
|
|
108
|
+
before do
|
|
109
|
+
stub_const("ChefLicensing::Config", licensing_config_mock)
|
|
110
|
+
stub_const("Chef::Utils::LicensingHandler", licensing_handler_mock)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context "when bootstrap_url is provided" do
|
|
114
|
+
it "returns early without calling licensing" do
|
|
115
|
+
knife.config[:bootstrap_url] = "https://example.com/bootstrap.sh"
|
|
116
|
+
expect(licensing_config_mock).not_to receive(:require_license_for)
|
|
117
|
+
|
|
118
|
+
knife.send(:fetch_license)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context "when bootstrap_template is provided" do
|
|
123
|
+
it "returns early without calling licensing" do
|
|
124
|
+
knife.config[:bootstrap_template] = "custom-template"
|
|
125
|
+
|
|
126
|
+
expect(licensing_config_mock).not_to receive(:require_license_for)
|
|
127
|
+
|
|
128
|
+
knife.send(:fetch_license)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context "when both bootstrap_url and bootstrap_template are provided" do
|
|
133
|
+
it "returns early without calling licensing" do
|
|
134
|
+
knife.config[:bootstrap_url] = "https://example.com/bootstrap.sh"
|
|
135
|
+
knife.config[:bootstrap_template] = "custom-template"
|
|
136
|
+
|
|
137
|
+
expect(licensing_config_mock).not_to receive(:require_license_for)
|
|
138
|
+
|
|
139
|
+
knife.send(:fetch_license)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context "when neither bootstrap_url nor bootstrap_template are provided" do
|
|
144
|
+
before do
|
|
145
|
+
knife.config[:bootstrap_url] = nil
|
|
146
|
+
knife.config[:bootstrap_template] = nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "validates license and sets license configuration" do
|
|
150
|
+
expect(license_mock).to receive(:install_sh_url).and_return("https://example.com/install.sh")
|
|
151
|
+
expect(license_mock).to receive(:license_key).and_return("test-license-key")
|
|
152
|
+
expect(license_mock).to receive(:omnitruck_url).and_return("https://example.com/omnitruck")
|
|
153
|
+
expect(license_mock).to receive(:license_type).and_return("commercial")
|
|
154
|
+
|
|
155
|
+
expect(licensing_handler_mock).to receive(:validate!).with(no_args).and_return(license_mock)
|
|
156
|
+
|
|
157
|
+
expect(licensing_config_mock).to receive(:require_license_for).and_yield
|
|
158
|
+
|
|
159
|
+
knife.send(:fetch_license)
|
|
160
|
+
|
|
161
|
+
expect(knife.config[:license_url]).to eq("https://example.com/install.sh")
|
|
162
|
+
expect(knife.config[:license_id]).to eq("test-license-key")
|
|
163
|
+
expect(knife.config[:omnitruck_url]).to eq("https://example.com/omnitruck")
|
|
164
|
+
expect(knife.config[:license_type]).to eq("commercial")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "handles licensing validation errors gracefully" do
|
|
168
|
+
expect(licensing_handler_mock).to receive(:validate!).with(no_args).and_raise(StandardError.new("License validation failed"))
|
|
169
|
+
|
|
170
|
+
expect(licensing_config_mock).to receive(:require_license_for).and_yield
|
|
171
|
+
|
|
172
|
+
expect { knife.send(:fetch_license) }.to raise_error(StandardError, "License validation failed")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "properly sets all license configuration values from the license object" do
|
|
176
|
+
expect(license_mock).to receive(:install_sh_url).and_return("https://omnitruck.chef.io/install.sh")
|
|
177
|
+
expect(license_mock).to receive(:license_key).and_return("ABCD-1234-EFGH-5678")
|
|
178
|
+
expect(license_mock).to receive(:omnitruck_url).and_return("https://omnitruck.chef.io")
|
|
179
|
+
expect(license_mock).to receive(:license_type).and_return("trial")
|
|
180
|
+
|
|
181
|
+
expect(licensing_handler_mock).to receive(:validate!).with(no_args).and_return(license_mock)
|
|
182
|
+
expect(licensing_config_mock).to receive(:require_license_for).and_yield
|
|
183
|
+
|
|
184
|
+
# Verify that config values are nil before the call
|
|
185
|
+
expect(knife.config[:license_url]).to be_nil
|
|
186
|
+
expect(knife.config[:license_id]).to be_nil
|
|
187
|
+
expect(knife.config[:omnitruck_url]).to be_nil
|
|
188
|
+
expect(knife.config[:license_type]).to be_nil
|
|
189
|
+
|
|
190
|
+
knife.send(:fetch_license)
|
|
191
|
+
|
|
192
|
+
# Verify all license values are properly set
|
|
193
|
+
expect(knife.config[:license_url]).to eq("https://omnitruck.chef.io/install.sh")
|
|
194
|
+
expect(knife.config[:license_id]).to eq("ABCD-1234-EFGH-5678")
|
|
195
|
+
expect(knife.config[:omnitruck_url]).to eq("https://omnitruck.chef.io")
|
|
196
|
+
expect(knife.config[:license_type]).to eq("trial")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context "when ChefLicensing::Config.require_license_for does not yield" do
|
|
200
|
+
it "does not call licensing handler" do
|
|
201
|
+
expect(licensing_config_mock).to receive(:require_license_for).and_return(nil)
|
|
202
|
+
expect(licensing_handler_mock).not_to receive(:validate!)
|
|
203
|
+
|
|
204
|
+
knife.send(:fetch_license)
|
|
205
|
+
|
|
206
|
+
expect(knife.config[:license_url]).to be_nil
|
|
207
|
+
expect(knife.config[:license_id]).to be_nil
|
|
208
|
+
expect(knife.config[:omnitruck_url]).to be_nil
|
|
209
|
+
expect(knife.config[:license_type]).to be_nil
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
103
215
|
context "#bootstrap_template" do
|
|
104
216
|
it "should default to chef-full" do
|
|
105
217
|
expect(knife.bootstrap_template).to be_a_kind_of(String)
|
|
@@ -371,6 +483,7 @@ describe Chef::Knife::Bootstrap do
|
|
|
371
483
|
knife.merge_configs
|
|
372
484
|
allow(knife).to receive(:validate_name_args!)
|
|
373
485
|
expect(knife).to receive(:check_eula_license)
|
|
486
|
+
expect(knife).to receive(:fetch_license)
|
|
374
487
|
|
|
375
488
|
expect { knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError)
|
|
376
489
|
jsonfile.close
|
|
@@ -586,7 +699,7 @@ describe Chef::Knife::Bootstrap do
|
|
|
586
699
|
end
|
|
587
700
|
|
|
588
701
|
it "creates /etc/chef/client.d" do
|
|
589
|
-
expect(rendered_template).to match("mkdir -p /etc/chef/client
|
|
702
|
+
expect(rendered_template).to match("mkdir -p /etc/chef/client.d")
|
|
590
703
|
end
|
|
591
704
|
|
|
592
705
|
context "a flat directory structure" do
|
|
@@ -797,7 +910,7 @@ describe Chef::Knife::Bootstrap do
|
|
|
797
910
|
context "validating use_sudo_password option" do
|
|
798
911
|
it "use_sudo_password contains description and long params for help" do
|
|
799
912
|
expect(knife.options).to(have_key(:use_sudo_password)) \
|
|
800
|
-
&& expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq(""))\
|
|
913
|
+
&& expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq("")) \
|
|
801
914
|
&& expect(knife.options[:use_sudo_password][:long].to_s).not_to(eq(""))
|
|
802
915
|
end
|
|
803
916
|
end
|
|
@@ -1144,7 +1257,6 @@ describe Chef::Knife::Bootstrap do
|
|
|
1144
1257
|
before do
|
|
1145
1258
|
# We will use knife's actual config since these tests
|
|
1146
1259
|
# have assumptions based on CLI default values
|
|
1147
|
-
config = {}
|
|
1148
1260
|
end
|
|
1149
1261
|
|
|
1150
1262
|
let(:expected_result) do
|
|
@@ -1710,6 +1822,7 @@ describe Chef::Knife::Bootstrap do
|
|
|
1710
1822
|
describe "#run" do
|
|
1711
1823
|
it "performs the steps we expect to run a bootstrap" do
|
|
1712
1824
|
expect(knife).to receive(:check_eula_license)
|
|
1825
|
+
expect(knife).to receive(:fetch_license)
|
|
1713
1826
|
expect(knife).to receive(:validate_name_args!).ordered
|
|
1714
1827
|
expect(knife).to receive(:validate_protocol!).ordered
|
|
1715
1828
|
expect(knife).to receive(:validate_first_boot_attributes!).ordered
|
|
@@ -1980,6 +2093,7 @@ describe Chef::Knife::Bootstrap do
|
|
|
1980
2093
|
it "verifies that a server to bootstrap was given as a command line arg" do
|
|
1981
2094
|
knife.name_args = nil
|
|
1982
2095
|
expect(knife).to receive(:check_eula_license)
|
|
2096
|
+
expect(knife).to receive(:fetch_license)
|
|
1983
2097
|
expect { knife.run }.to raise_error(SystemExit)
|
|
1984
2098
|
expect(stderr.string).to match(/ERROR:.+FQDN or ip/)
|
|
1985
2099
|
end
|