knife-azure 4.0.0 → 5.0.1
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/lib/azure/azure_interface.rb +1 -1
- data/lib/azure/custom_errors.rb +1 -1
- data/lib/azure/helpers.rb +1 -1
- data/lib/azure/resource_management/ARM_deployment_template.rb +77 -22
- data/lib/azure/resource_management/ARM_interface.rb +115 -119
- data/lib/azure/resource_management/rest/arm_client.rb +208 -0
- data/lib/azure/resource_management/rest/environments.rb +89 -0
- data/lib/azure/resource_management/rest/errors.rb +57 -0
- data/lib/azure/resource_management/rest/http.rb +335 -0
- data/lib/azure/resource_management/rest/rest_object.rb +103 -0
- data/lib/azure/resource_management/rest/secure_connection.rb +57 -0
- data/lib/azure/resource_management/rest/token_provider.rb +125 -0
- data/lib/azure/resource_management/vnet_config.rb +4 -4
- data/lib/azure/resource_management/windows_credentials.rb +1 -1
- data/lib/chef/knife/azurerm_server_create.rb +21 -9
- data/lib/chef/knife/azurerm_server_delete.rb +1 -1
- data/lib/chef/knife/azurerm_server_list.rb +1 -1
- data/lib/chef/knife/azurerm_server_show.rb +1 -1
- data/lib/chef/knife/bootstrap/bootstrapper.rb +137 -4
- data/lib/chef/knife/bootstrap/common_bootstrap_options.rb +4 -6
- data/lib/chef/knife/bootstrap_azurerm.rb +1 -1
- data/lib/chef/knife/helpers/azurerm_base.rb +168 -7
- data/lib/knife-azure/version.rb +2 -2
- metadata +25 -70
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
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
|
+
module Azure
|
|
19
|
+
class ResourceManagement
|
|
20
|
+
module Rest
|
|
21
|
+
# Wraps a parsed ARM JSON response (a Hash) so existing consumers can keep
|
|
22
|
+
# using SDK-style dot notation. The retired Azure SDK exposed models with
|
|
23
|
+
# snake_case accessors and flattened the ARM "properties" envelope; this
|
|
24
|
+
# wrapper reproduces both behaviours:
|
|
25
|
+
#
|
|
26
|
+
# * Key matching ignores case and underscores, so camelCase ARM keys
|
|
27
|
+
# line up with the snake_case names the code already uses
|
|
28
|
+
# (e.g. "osType" <-> os_type, "publicIPAddress" <-> public_ipaddress).
|
|
29
|
+
# * When a name is not found at the top level, the nested "properties"
|
|
30
|
+
# object is searched too, mirroring the SDK's flattened models
|
|
31
|
+
# (e.g. vm.provisioning_state, vm.storage_profile).
|
|
32
|
+
class RestObject
|
|
33
|
+
def initialize(data)
|
|
34
|
+
@data = data || {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Wraps a raw value (Hash, Array or scalar) into RestObjects as needed.
|
|
38
|
+
def self.wrap(value)
|
|
39
|
+
case value
|
|
40
|
+
when RestObject
|
|
41
|
+
value
|
|
42
|
+
when Hash
|
|
43
|
+
new(value)
|
|
44
|
+
when Array
|
|
45
|
+
value.map { |v| wrap(v) }
|
|
46
|
+
else
|
|
47
|
+
value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_h
|
|
52
|
+
@data
|
|
53
|
+
end
|
|
54
|
+
alias_method :to_hash, :to_h
|
|
55
|
+
|
|
56
|
+
def [](key)
|
|
57
|
+
RestObject.wrap(@data[key])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def key?(name)
|
|
61
|
+
return true unless lookup_key(name).nil?
|
|
62
|
+
|
|
63
|
+
properties = @data["properties"]
|
|
64
|
+
properties.is_a?(Hash) && !lookup_key(name, properties).nil?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def nil?
|
|
68
|
+
@data.nil? || @data.empty?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def respond_to_missing?(_name, _include_private = false)
|
|
72
|
+
true
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def method_missing(name, *args)
|
|
76
|
+
key = lookup_key(name)
|
|
77
|
+
return RestObject.wrap(@data[key]) unless key.nil?
|
|
78
|
+
|
|
79
|
+
properties = @data["properties"]
|
|
80
|
+
if properties.is_a?(Hash)
|
|
81
|
+
pkey = lookup_key(name, properties)
|
|
82
|
+
return RestObject.wrap(properties[pkey]) unless pkey.nil?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def lookup_key(name, hash = @data)
|
|
91
|
+
return nil unless hash.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
target = normalize(name)
|
|
94
|
+
hash.keys.find { |k| normalize(k) == target }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def normalize(str)
|
|
98
|
+
str.to_s.delete("_").downcase
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
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 "net/http" unless defined?(Net::HTTP)
|
|
19
|
+
require "openssl" unless defined?(OpenSSL)
|
|
20
|
+
|
|
21
|
+
module Azure
|
|
22
|
+
class ResourceManagement
|
|
23
|
+
module Rest
|
|
24
|
+
# Builds Net::HTTP connections with TLS certificate verification always
|
|
25
|
+
# enabled for HTTPS, so bearer tokens and client secrets are never sent
|
|
26
|
+
# over a channel where a forged/man-in-the-middle certificate could be
|
|
27
|
+
# accepted. All outbound connections in this layer go through here.
|
|
28
|
+
module SecureConnection
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# Bounded, overridable defaults. Net::HTTP defaults both timeouts to
|
|
32
|
+
# unlimited, so without these a stalled Azure or token endpoint could
|
|
33
|
+
# block the CLI forever (and the HTTP layer's LRO deadline can't help
|
|
34
|
+
# while http.request itself is blocked).
|
|
35
|
+
DEFAULT_OPEN_TIMEOUT = 60 # seconds to establish the connection
|
|
36
|
+
DEFAULT_READ_TIMEOUT = 120 # seconds to wait for each response
|
|
37
|
+
|
|
38
|
+
def build(uri, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT)
|
|
39
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
40
|
+
http.open_timeout = open_timeout
|
|
41
|
+
http.read_timeout = read_timeout
|
|
42
|
+
if uri.scheme == "https"
|
|
43
|
+
http.use_ssl = true
|
|
44
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
45
|
+
http.cert_store = default_cert_store
|
|
46
|
+
end
|
|
47
|
+
http
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# System CA trust store, loaded once and reused.
|
|
51
|
+
def default_cert_store
|
|
52
|
+
@default_cert_store ||= OpenSSL::X509::Store.new.tap(&:set_default_paths)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
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 "net/http" unless defined?(Net::HTTP)
|
|
19
|
+
require "uri" unless defined?(URI)
|
|
20
|
+
require "json" unless defined?(JSON)
|
|
21
|
+
require_relative "environments"
|
|
22
|
+
require_relative "errors"
|
|
23
|
+
require_relative "secure_connection"
|
|
24
|
+
|
|
25
|
+
module Azure
|
|
26
|
+
class ResourceManagement
|
|
27
|
+
module Rest
|
|
28
|
+
# Provides a bearer token for ARM requests. It supports the same two
|
|
29
|
+
# inputs the caller already produces (see azurerm_base.rb):
|
|
30
|
+
#
|
|
31
|
+
# * service principal: {azure_tenant_id, azure_client_id,
|
|
32
|
+
# azure_client_secret} -> client-credentials grant, cached until it
|
|
33
|
+
# is close to expiry;
|
|
34
|
+
# * pre-fetched token (the `az login` path): {token, tokentype} -> used
|
|
35
|
+
# as-is.
|
|
36
|
+
class TokenProvider
|
|
37
|
+
# Refresh a client-credentials token this many seconds before expiry.
|
|
38
|
+
EXPIRY_SKEW = 120
|
|
39
|
+
|
|
40
|
+
def initialize(params = {}, environment = Environments.default)
|
|
41
|
+
@params = params
|
|
42
|
+
@environment = environment
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def authorization_header
|
|
46
|
+
"#{token_type} #{access_token}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def token_type
|
|
50
|
+
@params[:tokentype] || "Bearer"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def access_token
|
|
54
|
+
if @params[:azure_client_secret]
|
|
55
|
+
client_credentials_token
|
|
56
|
+
else
|
|
57
|
+
@params[:token]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def client_credentials_token
|
|
64
|
+
if @cached_token && @expires_at && Time.now.utc < (@expires_at - EXPIRY_SKEW)
|
|
65
|
+
return @cached_token
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
response = request_client_credentials_token
|
|
69
|
+
@cached_token = response["access_token"]
|
|
70
|
+
@expires_at = Time.now.utc + response["expires_in"].to_i
|
|
71
|
+
@cached_token
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def request_client_credentials_token
|
|
75
|
+
uri = URI.parse("#{@environment.active_directory_endpoint_url}/#{@params[:azure_tenant_id]}/oauth2/token")
|
|
76
|
+
http = SecureConnection.build(uri)
|
|
77
|
+
|
|
78
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
79
|
+
request.set_form_data(
|
|
80
|
+
"grant_type" => "client_credentials",
|
|
81
|
+
"client_id" => @params[:azure_client_id],
|
|
82
|
+
"client_secret" => @params[:azure_client_secret],
|
|
83
|
+
"resource" => @environment.token_audience
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
raw = http.request(request)
|
|
87
|
+
if raw.code.to_i >= 400
|
|
88
|
+
raise OperationError.new(
|
|
89
|
+
token_error_message(raw),
|
|
90
|
+
body: raw.body,
|
|
91
|
+
http_status: raw.code.to_i
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
JSON.parse(raw.body)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The AAD/OAuth token endpoint returns a flat error shape
|
|
99
|
+
# ({ "error": "invalid_client", "error_description": "..." }); tolerate
|
|
100
|
+
# an ARM-style nested error too, and fall back gracefully when the body
|
|
101
|
+
# is missing or not JSON.
|
|
102
|
+
def token_error_message(raw)
|
|
103
|
+
base = "Failed to acquire an Azure access token (HTTP #{raw.code})."
|
|
104
|
+
parsed = parse_json(raw.body)
|
|
105
|
+
return base unless parsed.is_a?(Hash)
|
|
106
|
+
|
|
107
|
+
detail =
|
|
108
|
+
if parsed["error"].is_a?(Hash)
|
|
109
|
+
parsed["error"]["message"]
|
|
110
|
+
else
|
|
111
|
+
parsed["error_description"] || parsed["error"]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
detail ? "#{base} #{detail}" : base
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def parse_json(body)
|
|
118
|
+
JSON.parse(body.to_s)
|
|
119
|
+
rescue JSON::ParserError
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Aliasgar Batterywala (aliasgar.batterywala@clogeny.com)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -36,8 +36,8 @@ module Azure::ARM
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def get_vnet(resource_group_name, vnet_name)
|
|
39
|
-
|
|
40
|
-
rescue
|
|
39
|
+
arm_client.get_virtual_network(resource_group_name, vnet_name)
|
|
40
|
+
rescue Azure::ResourceManagement::Rest::OperationError => error
|
|
41
41
|
if error.body
|
|
42
42
|
err_json = JSON.parse(error.response.body)
|
|
43
43
|
if err_json["error"]["code"] == "ResourceNotFound"
|
|
@@ -50,7 +50,7 @@ module Azure::ARM
|
|
|
50
50
|
|
|
51
51
|
## lists all subnets under a virtual network or lists subnets of only a particular address space ##
|
|
52
52
|
def subnets_list(resource_group_name, vnet_name, address_prefix = nil)
|
|
53
|
-
list =
|
|
53
|
+
list = arm_client.list_subnets(resource_group_name, vnet_name)
|
|
54
54
|
!address_prefix.nil? && !list.empty? ? subnets_list_for_specific_address_space(address_prefix, list) : list
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Nimisha Sharad (nimisha.sharad@clogeny.com)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Aliasgar Batterywala (aliasgar.batterywala@clogeny.com)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -41,13 +41,14 @@ class Chef
|
|
|
41
41
|
|
|
42
42
|
option :azure_storage_account_type,
|
|
43
43
|
long: "--azure-storage-account-type TYPE",
|
|
44
|
-
description: "Optional.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
description: "Optional. Sets the storage account type (SKU) of the VM's managed OS
|
|
45
|
+
disk (case-sensitive). One of the following:
|
|
46
|
+
Standard_LRS (Standard HDD locally-redundant storage)
|
|
47
|
+
StandardSSD_LRS (Standard SSD locally-redundant storage)
|
|
48
|
+
Premium_LRS (Premium SSD locally-redundant storage)
|
|
49
|
+
StandardSSD_ZRS (Standard SSD zone-redundant storage)
|
|
50
|
+
Premium_ZRS (Premium SSD zone-redundant storage)",
|
|
51
|
+
default: "Standard_LRS"
|
|
51
52
|
|
|
52
53
|
option :azure_image_reference_publisher,
|
|
53
54
|
long: "--azure-image-reference-publisher PUBLISHER_NAME",
|
|
@@ -128,6 +129,7 @@ class Chef
|
|
|
128
129
|
:azure_service_location
|
|
129
130
|
)
|
|
130
131
|
validate_params!
|
|
132
|
+
warn_if_storage_account_ignored!
|
|
131
133
|
end
|
|
132
134
|
|
|
133
135
|
def plugin_create_instance!
|
|
@@ -181,7 +183,7 @@ class Chef
|
|
|
181
183
|
|
|
182
184
|
server_def[:tcp_endpoints] = config[:tcp_endpoints] if config[:tcp_endpoints]
|
|
183
185
|
|
|
184
|
-
# We assign azure_vm_name to chef_node_name
|
|
186
|
+
# We assign azure_vm_name to chef_node_name if node name is nil.
|
|
185
187
|
config[:chef_node_name] ||= config[:azure_vm_name]
|
|
186
188
|
|
|
187
189
|
server_def[:azure_storage_account] = config[:azure_vm_name] if server_def[:azure_storage_account].nil?
|
|
@@ -239,6 +241,16 @@ class Chef
|
|
|
239
241
|
|
|
240
242
|
private
|
|
241
243
|
|
|
244
|
+
def warn_if_storage_account_ignored!
|
|
245
|
+
return if config[:azure_storage_account].nil?
|
|
246
|
+
|
|
247
|
+
ui.warn("--azure-storage-account is deprecated and has no effect: VM OS disks now use " \
|
|
248
|
+
"Azure managed disks instead of an unmanaged VHD in a storage account. This option " \
|
|
249
|
+
"is kept only for backward compatibility with existing command invocations and will " \
|
|
250
|
+
"be removed in a future release. Use --azure-storage-account-type to control the " \
|
|
251
|
+
"managed disk SKU instead.")
|
|
252
|
+
end
|
|
253
|
+
|
|
242
254
|
def set_defaults
|
|
243
255
|
# set_default_image_reference!
|
|
244
256
|
set_configs
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Author:: Barry Davis (barryd@jetstreamsoftware.com)
|
|
3
3
|
# Author:: Adam Jacob (<adam@chef.io>)
|
|
4
4
|
# Author:: Seth Chisamore (<schisamo@chef.io>)
|
|
5
|
-
# Copyright:: Copyright (c)
|
|
5
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
6
6
|
# License:: Apache License, Version 2.0
|
|
7
7
|
#
|
|
8
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Adam Jacob (<adam@chef.io>)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Meera Navale (meera.navale@msystechnologies.com)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Aliasgar Batterywala (aliasgar.batterywala@clogeny.com)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -68,12 +68,33 @@ class Chef
|
|
|
68
68
|
|
|
69
69
|
def get_chef_extension_public_params
|
|
70
70
|
pub_config = {}
|
|
71
|
+
|
|
71
72
|
if config[:azure_extension_client_config]
|
|
72
73
|
pub_config[:client_rb] = File.read(File.expand_path(config[:azure_extension_client_config]))
|
|
73
74
|
else
|
|
74
|
-
|
|
75
|
+
# `chef_license` is set here (rather than left to interactive/env-var
|
|
76
|
+
# acceptance) because the VM extension's first chef-client run
|
|
77
|
+
# otherwise fails with "Chef Infra Client cannot execute without
|
|
78
|
+
# accepting the license" -- there's no TTY/CHEF_LICENSE env var
|
|
79
|
+
# available inside the freshly-provisioned VM.
|
|
80
|
+
pub_config[:client_rb] = "chef_server_url \t #{Chef::Config[:chef_server_url].to_json}\nvalidation_client_name\t#{Chef::Config[:validation_client_name].to_json}\nchef_license\t\"accept-no-persist\""
|
|
75
81
|
end
|
|
76
82
|
|
|
83
|
+
# The `chef_license` line in client_rb above only covers chef-client
|
|
84
|
+
# invocations that read that config file (`-c client.rb`). The VM
|
|
85
|
+
# extension also runs a separate `chef-apply -e "cron '...' do ... end"`
|
|
86
|
+
# step (with no `-c` flag) to install the periodic chef-client cron job,
|
|
87
|
+
# which does not read client.rb and fails with the same license error.
|
|
88
|
+
# The extension's own shared.sh reads this top-level "CHEF_LICENSE"
|
|
89
|
+
# public setting and exports it as an environment variable before
|
|
90
|
+
# running any step, so setting it here covers that cron step too.
|
|
91
|
+
pub_config[:CHEF_LICENSE] = "accept-no-persist"
|
|
92
|
+
|
|
93
|
+
# chef_license_key is now forwarded via get_chef_extension_private_params
|
|
94
|
+
# below (protectedSettings), not here -- see
|
|
95
|
+
# chef-partners/azure-chef-extension#413 (version 1210.15.11.1,
|
|
96
|
+
# rolled out to all regions), which added a decrypt path for it.
|
|
97
|
+
|
|
77
98
|
pub_config[:runlist] = config[:run_list].empty? ? "" : config[:run_list].join(",").to_json
|
|
78
99
|
pub_config[:custom_json_attr] = config[:json_attributes] || {}
|
|
79
100
|
pub_config[:extendedLogs] = config[:extended_logs] ? "true" : "false"
|
|
@@ -83,12 +104,29 @@ class Chef
|
|
|
83
104
|
|
|
84
105
|
# bootstrap attributes
|
|
85
106
|
pub_config[:bootstrap_options] = {}
|
|
86
|
-
|
|
107
|
+
# The Chef VM extension always renders this value into a "-E <value>"
|
|
108
|
+
# chef-client argument. If it's left blank (nil, or an explicitly
|
|
109
|
+
# empty string, both of which are truthy in Ruby), chef-client's
|
|
110
|
+
# option parser fails with "missing argument: -E" because the flag
|
|
111
|
+
# gets emitted without a value. Default to Chef's standard
|
|
112
|
+
# "_default" environment whenever --environment isn't supplied or
|
|
113
|
+
# was supplied blank.
|
|
114
|
+
pub_config[:bootstrap_options][:environment] = config[:environment].to_s.empty? ? "_default" : config[:environment]
|
|
87
115
|
pub_config[:bootstrap_options][:chef_node_name] = config[:chef_node_name] if config[:chef_node_name]
|
|
88
116
|
pub_config[:bootstrap_options][:chef_server_url] = Chef::Config[:chef_server_url] if Chef::Config[:chef_server_url]
|
|
89
117
|
pub_config[:bootstrap_options][:validation_client_name] = Chef::Config[:validation_client_name] if Chef::Config[:validation_client_name]
|
|
90
118
|
pub_config[:bootstrap_options][:node_verify_api_cert] = config[:node_verify_api_cert] ? "true" : "false" if config.key?(:node_verify_api_cert)
|
|
91
|
-
|
|
119
|
+
# If --bootstrap-version isn't given, the extension's chef-install.sh
|
|
120
|
+
# treats bootstrap_version as blank, can't tell the major version is
|
|
121
|
+
# >= 19, and falls back to installing the legacy "chef" product
|
|
122
|
+
# (currently capped in the 18.x line) instead of "chef-ice" (19.x+).
|
|
123
|
+
# Chef::Knife::Bootstrap#version_to_install (used by the stock
|
|
124
|
+
# SSH-based chef-full.erb bootstrap that knife-ec2/knife-google rely
|
|
125
|
+
# on) defaults to the major version of the `chef` gem bundled
|
|
126
|
+
# alongside `knife` itself (`Chef::VERSION.split(".").first`) in
|
|
127
|
+
# that same situation, so mirror that default here too.
|
|
128
|
+
resolved_bootstrap_version = config[:bootstrap_version] || Chef::VERSION.split(".").first
|
|
129
|
+
pub_config[:bootstrap_options][:bootstrap_version] = resolved_bootstrap_version
|
|
92
130
|
pub_config[:bootstrap_options][:node_ssl_verify_mode] = config[:node_ssl_verify_mode] if config[:node_ssl_verify_mode]
|
|
93
131
|
pub_config[:bootstrap_options][:bootstrap_proxy] = config[:bootstrap_proxy] if config[:bootstrap_proxy]
|
|
94
132
|
pub_config
|
|
@@ -158,9 +196,104 @@ class Chef
|
|
|
158
196
|
# encrypted_data_bag_secret key for encrypting/decrypting the data bags
|
|
159
197
|
pri_config[:encrypted_data_bag_secret] = load_correct_secret
|
|
160
198
|
|
|
199
|
+
# The extension's install scripts (chef-install.sh/shared.sh,
|
|
200
|
+
# chef-install.psm1/shared.ps1) now decrypt protectedSettings for
|
|
201
|
+
# chef_license_key and prefer it over the deprecated publicSettings
|
|
202
|
+
# location (chef-partners/azure-chef-extension#413, shipped as
|
|
203
|
+
# version 1210.15.11.1, rolled out to all regions for both
|
|
204
|
+
# ChefClient and LinuxChefClient). Forwarding it here instead of via
|
|
205
|
+
# get_chef_extension_public_params means it is delivered to the VM
|
|
206
|
+
# encrypted (CMS/PKCS7, decrypted locally using the cert the Azure
|
|
207
|
+
# Guest Agent provisions), matching the treatment already given to
|
|
208
|
+
# validation_key/chef_server_crt/encrypted_data_bag_secret above,
|
|
209
|
+
# instead of as recoverable plaintext.
|
|
210
|
+
#
|
|
211
|
+
# chef_license_key is optional, not required: when it (and
|
|
212
|
+
# config[:license_id]) are both absent, the extension's
|
|
213
|
+
# chef-install.sh/shared.sh simply fall back to the unlicensed
|
|
214
|
+
# omnitruck.chef.io download host with a warning; there is no
|
|
215
|
+
# "chef_license_bypass" setting to set and no exit/failure path
|
|
216
|
+
# to work around.
|
|
217
|
+
#
|
|
218
|
+
# NOTE: chef-partners/azure-chef-extension#384 briefly made
|
|
219
|
+
# chef_license_key required-by-default with a chef_license_bypass
|
|
220
|
+
# escape hatch, but #409 ("Route to omnitruck when no license_id
|
|
221
|
+
# is specified, instead of chef_license_bypass") removed
|
|
222
|
+
# chef_license_bypass entirely and restored the always-optional,
|
|
223
|
+
# warning-only fallback described above for both the Linux and
|
|
224
|
+
# Windows install scripts. Do not reintroduce bypass handling
|
|
225
|
+
# here based on #384 alone -- check the extension's current HEAD
|
|
226
|
+
# first, since that requirement no longer exists as of #409.
|
|
227
|
+
#
|
|
228
|
+
# Auto-forwarding here when a key IS available
|
|
229
|
+
# (rather than requiring --chef-license-key on every single
|
|
230
|
+
# invocation) mirrors exactly what upstream
|
|
231
|
+
# Chef::Knife::Core::BootstrapContext/WindowsBootstrapContext do for
|
|
232
|
+
# the stock SSH-based bootstrap that knife-ec2/knife-google rely on:
|
|
233
|
+
# prefer an explicitly passed --chef-license-key, else fall back to
|
|
234
|
+
# config[:license_id] (the already-persisted/validated local
|
|
235
|
+
# license that Chef::Knife::Bootstrap#run populates via
|
|
236
|
+
# fetch_license before any plugin_* hook runs). Unlike the stock
|
|
237
|
+
# SSH-based context classes, there is no knife-azure-specific
|
|
238
|
+
# handling of --disable-license-activation: that upstream option
|
|
239
|
+
# isn't honored by this VM-extension delivery path, matching
|
|
240
|
+
# knife-ec2/knife-google, which also have no special-case code for
|
|
241
|
+
# it (their stock bootstrap context handles it generically for
|
|
242
|
+
# SSH/WinRM delivery only, which doesn't apply here either).
|
|
243
|
+
license_key = config[:chef_license_key] || config[:license_id]
|
|
244
|
+
if license_key
|
|
245
|
+
reject_pinned_extension_without_protected_license_support!(license_key)
|
|
246
|
+
pri_config[:chef_license_key] = license_key
|
|
247
|
+
end
|
|
248
|
+
|
|
161
249
|
pri_config
|
|
162
250
|
end
|
|
163
251
|
|
|
252
|
+
# chef-partners/azure-chef-extension release that first decrypts
|
|
253
|
+
# protectedSettings for chef_license_key (see the comment above the
|
|
254
|
+
# pri_config[:chef_license_key] assignment); builds older than this
|
|
255
|
+
# only read the deprecated, plaintext publicSettings location.
|
|
256
|
+
MIN_LICENSE_CAPABLE_EXTENSION_VERSION = "1210.15.11.1".freeze
|
|
257
|
+
|
|
258
|
+
# --azure-chef-extension-version lets a user pin an older extension
|
|
259
|
+
# build via get_chef_extension_version. If that pinned build predates
|
|
260
|
+
# MIN_LICENSE_CAPABLE_EXTENSION_VERSION, it cannot read the
|
|
261
|
+
# protectedSettings-only chef_license_key we now forward, so the VM
|
|
262
|
+
# would silently receive no usable license and the licensed Chef
|
|
263
|
+
# Infra Client install would fail. Fail fast here instead, before the
|
|
264
|
+
# ARM deployment is even built.
|
|
265
|
+
def reject_pinned_extension_without_protected_license_support!(license_key)
|
|
266
|
+
return unless license_key
|
|
267
|
+
|
|
268
|
+
pinned_version = config[:azure_chef_extension_version]
|
|
269
|
+
# Not pinned (resolved via get_latest_chef_extension_version) or a
|
|
270
|
+
# "<major>.*" family selector: Azure resolves either to a current
|
|
271
|
+
# build that supports protectedSettings, so there's nothing to
|
|
272
|
+
# reject here.
|
|
273
|
+
return if pinned_version.nil? || pinned_version.include?("*")
|
|
274
|
+
|
|
275
|
+
begin
|
|
276
|
+
pinned = Gem::Version.new(pinned_version)
|
|
277
|
+
rescue ArgumentError
|
|
278
|
+
# Not a comparable version string; let the Azure API validate (or
|
|
279
|
+
# reject) it instead of guessing here.
|
|
280
|
+
return
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
return if pinned >= Gem::Version.new(MIN_LICENSE_CAPABLE_EXTENSION_VERSION)
|
|
284
|
+
|
|
285
|
+
ui.error(
|
|
286
|
+
"--azure-chef-extension-version #{pinned_version} is older than " \
|
|
287
|
+
"#{MIN_LICENSE_CAPABLE_EXTENSION_VERSION}, the chef-partners/azure-chef-extension " \
|
|
288
|
+
"release that added protectedSettings support for chef_license_key. That version " \
|
|
289
|
+
"only reads the deprecated, plaintext publicSettings location, so the forwarded " \
|
|
290
|
+
"license key would be silently ignored and the licensed Chef Infra Client install " \
|
|
291
|
+
"would fail. Pin a version >= #{MIN_LICENSE_CAPABLE_EXTENSION_VERSION}, or omit " \
|
|
292
|
+
"--azure-chef-extension-version to use the latest extension build."
|
|
293
|
+
)
|
|
294
|
+
exit 1
|
|
295
|
+
end
|
|
296
|
+
|
|
164
297
|
end
|
|
165
298
|
end
|
|
166
299
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Aliasgar Batterywala (aliasgar.batterywala@clogeny.com)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -51,11 +51,9 @@ class Chef
|
|
|
51
51
|
option :azure_storage_account,
|
|
52
52
|
short: "-a NAME",
|
|
53
53
|
long: "--azure-storage-account NAME",
|
|
54
|
-
description: "
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
This name is the DNS prefix name and can be used to access blobs, queues, and tables in the storage account.
|
|
58
|
-
For example: http://ServiceName.blob.core.windows.net/mycontainer/"
|
|
54
|
+
description: "Deprecated. No longer has any effect: VM OS disks now use Azure managed
|
|
55
|
+
disks instead of an unmanaged VHD in a storage account. Kept only for
|
|
56
|
+
backward compatibility with existing command invocations."
|
|
59
57
|
|
|
60
58
|
option :azure_vm_name,
|
|
61
59
|
long: "--azure-vm-name NAME",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Author:: Nimisha Sharad (nimisha.sharad@clogeny.com)
|
|
3
|
-
# Copyright:: Copyright (c)
|
|
3
|
+
# Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
4
|
# License:: Apache License, Version 2.0
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|