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.
@@ -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) Chef Software Inc.
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
- network_resource_client.virtual_networks.get(resource_group_name, vnet_name)
40
- rescue MsRestAzure::AzureOperationError => error
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 = network_resource_client.subnets.list(resource_group_name, vnet_name)
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) Chef Software Inc.
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) Chef Software Inc.
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. One of the following account types (case-sensitive):
45
- Standard_LRS (Standard Locally-redundant storage)
46
- Standard_ZRS (Standard Zone-redundant storage)
47
- Standard_GRS (Standard Geo-redundant storage)
48
- Standard_RAGRS (Standard Read access geo-redundant storage)
49
- Premium_LRS (Premium Locally-redundant storage)",
50
- default: "Standard_GRS"
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 If node name is nill because storage account name is combination of hash value and 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) Chef Software Inc.
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) Chef Software Inc.
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) Chef Software Inc.
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) Chef Software Inc.
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
- 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}"
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
- pub_config[:bootstrap_options][:environment] = config[:environment] if config[:environment]
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
- pub_config[:bootstrap_options][:bootstrap_version] = config[:bootstrap_version] if config[:bootstrap_version]
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) Chef Software Inc.
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: "Required for advanced server-create option.
55
- A name for the storage account that is unique within Windows Azure. Storage account names must be
56
- between 3 and 24 characters in length and use numbers and lower-case letters only.
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) Chef Software Inc.
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");