mixlib-install 3.13.0 → 3.14.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff7a4f9c339e539a6ceb50655b22976fa5c5f41c9c4f74a721dc55028d7df84f
4
- data.tar.gz: 30d398aefff7ada62fcf40368a0bd40a83031d11d765341a37d6572f9561fea9
3
+ metadata.gz: 40469e67a42c0b360b72c220d71d9a2a0c837dc691ed9a74a9923fc81a31240c
4
+ data.tar.gz: f816a7403f35f3c51a456783241a485f9e21ff05fea67c5120f0585c9d92e899
5
5
  SHA512:
6
- metadata.gz: ca10f06e8f5a7fd3323f23c954988c57e31fef1ea093738a6aefdf3c77bafd2c9ad44ec54a42514cfab1c81c18cc8739f55718ef21f3e5850d68f36fbb6e82cd
7
- data.tar.gz: 983a976ca909b8a0e56903b44f2ea38e62f79e49b502a81174383dc2946e96a8e6f30a2365801f5d5647f43df7a7b13c8faaba686230694df018a2e69f64d8b2
6
+ metadata.gz: 6f9b38fec3b7689879edac7229f5793cd3c4ea842d03cd93d1c4b68afb7f02853008863ef80898a18b0745d5d25fb94afb2f1f6ea6208535bd109b0cdad247bd
7
+ data.tar.gz: 0d2470b2ef4d1b130f28f00b06221cfbb8e34034b320c38426953310d5d1d3f91fe2c2be42ba341952582e5f07b73eecd46e6cf999c63a73e287f03904cf1587
data/bin/mixlib-install CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Signal.trap("INT") { exit 1 }
4
4
 
5
- $:.push File.expand_path("../lib", __FILE__)
5
+ $:.push File.expand_path("../../lib", __FILE__)
6
6
  $stdout.sync = true
7
7
 
8
8
  require "mixlib/install/cli"
@@ -64,7 +64,17 @@ module Mixlib
64
64
  #
65
65
  # @return [Array<Array<Hash>] Build records for available versions
66
66
  def versions
67
- items = get("/api/v1/#{options.channel}/#{omnibus_project}/versions")["results"]
67
+ # Commercial and trial APIs use a different URL structure
68
+ if use_licensed_api?
69
+ # Response is a JSON array of version strings
70
+ version_list = get("/#{options.channel}/#{omnibus_project}/versions/all")
71
+ # Convert to the expected format with properties
72
+ items = version_list.map do |version|
73
+ { "properties" => [{ "key" => "omnibus.version", "value" => version }] }
74
+ end
75
+ else
76
+ items = get("/api/v1/#{options.channel}/#{omnibus_project}/versions")["results"]
77
+ end
68
78
 
69
79
  # Circumvent early when there are no product artifacts in a specific channel
70
80
  if items.empty?
@@ -80,7 +90,7 @@ EOF
80
90
  # always complete. In fact we should not do this since for some arcane
81
91
  # builds like Chef Client 10.X we do not have build record created in
82
92
  # artifactory.
83
- if options.channel == :unstable
93
+ if options.channel == :unstable && !use_licensed_api?
84
94
  # We check if "artifacts" field contains something since it is only
85
95
  # populated with the build record if "artifact.module.build" exists.
86
96
  items.reject! { |i| i["artifacts"].nil? }
@@ -123,24 +133,49 @@ EOF
123
133
  # @return [Array<ArtifactInfo>] Array of info about found artifacts
124
134
  def artifacts_for_version(version)
125
135
  begin
126
- results = get("/api/v1/#{options.channel}/#{omnibus_project}/#{version}/artifacts")["results"]
136
+ if use_licensed_api?
137
+ # Commercial/trial APIs use the packages endpoint which returns metadata for all platforms
138
+ query = "v=#{version}"
139
+ packages_hash = get("/#{options.channel}/#{omnibus_project}/packages?#{query}")
140
+ # Response is a nested hash: platform -> platform_version -> architecture -> package_info
141
+ # Flatten it to an array of package metadata objects
142
+ results = []
143
+ packages_hash.each do |platform, platform_versions|
144
+ platform_versions.each do |platform_version, architectures|
145
+ architectures.each do |arch, pkg_info|
146
+ results << {
147
+ "omnibus.version" => pkg_info["version"],
148
+ "omnibus.platform" => platform,
149
+ "omnibus.platform_version" => platform_version,
150
+ "omnibus.architecture" => arch,
151
+ "omnibus.project" => omnibus_project,
152
+ "omnibus.license" => "Apache-2.0",
153
+ "omnibus.sha256" => pkg_info["sha256"],
154
+ "omnibus.sha1" => pkg_info.fetch("sha1", ""),
155
+ "omnibus.md5" => pkg_info.fetch("md5", ""),
156
+ }
157
+ end
158
+ end
159
+ end
160
+ else
161
+ results = get("/api/v1/#{options.channel}/#{omnibus_project}/#{version}/artifacts")["results"]
162
+ # Merge artifactory properties to a flat Hash
163
+ results.collect! do |result|
164
+ {
165
+ "filename" => result["name"],
166
+ }.merge(
167
+ map_properties(result["properties"])
168
+ )
169
+ end
170
+ end
127
171
  rescue Net::HTTPServerException => e
128
- if e.message =~ /404/
172
+ if e.message.match?(/404/)
129
173
  return []
130
174
  else
131
175
  raise e
132
176
  end
133
177
  end
134
178
 
135
- # Merge artifactory properties to a flat Hash
136
- results.collect! do |result|
137
- {
138
- "filename" => result["name"],
139
- }.merge(
140
- map_properties(result["properties"])
141
- )
142
- end
143
-
144
179
  # Convert results to build records
145
180
  results.map { |a| create_artifact(a) }
146
181
  end
@@ -153,6 +188,13 @@ EOF
153
188
  http = Net::HTTP.new(uri.host, uri.port)
154
189
  http.use_ssl = (uri.scheme == "https")
155
190
  full_path = File.join(uri.path, url)
191
+
192
+ # Add license_id as query parameter if using commercial or trial API
193
+ if use_licensed_api?
194
+ separator = full_path.include?("?") ? "&" : "?"
195
+ full_path = "#{full_path}#{separator}license_id=#{options.license_id}"
196
+ end
197
+
156
198
  res = http.request(create_http_request(full_path))
157
199
  res.value
158
200
  JSON.parse(res.body)
@@ -191,7 +233,7 @@ EOF
191
233
  software_dependencies = metadata.fetch("version_manifest", {})
192
234
  .fetch("software", nil)
193
235
  rescue Net::HTTPServerException => e
194
- if e.message =~ /404/
236
+ if e.message.match?(/404/)
195
237
  license_content, software_dependencies = nil
196
238
  else
197
239
  raise e
@@ -202,11 +244,22 @@ EOF
202
244
  end
203
245
 
204
246
  # create the download path with the correct endpoint
205
- base_url = if use_compat_download_url_endpoint?(platform, platform_version)
206
- COMPAT_DOWNLOAD_URL_ENDPOINT
207
- else
208
- endpoint
209
- end
247
+ if use_licensed_api?
248
+ # Commercial/trial APIs use the download endpoint with query parameters
249
+ # Construct platform parameters
250
+ p_param = platform
251
+ pv_param = platform_version
252
+ m_param = Util.normalize_architecture(artifact_map["omnibus.architecture"])
253
+ v_param = artifact_map["omnibus.version"]
254
+ download_url = "#{endpoint}/#{options.channel}/#{omnibus_project}/download?p=#{p_param}&pv=#{pv_param}&m=#{m_param}&v=#{v_param}&license_id=#{options.license_id}"
255
+ else
256
+ base_url = if use_compat_download_url_endpoint?(platform, platform_version)
257
+ COMPAT_DOWNLOAD_URL_ENDPOINT
258
+ else
259
+ endpoint
260
+ end
261
+ download_url = "#{base_url}/#{chef_standard_path}"
262
+ end
210
263
 
211
264
  ArtifactInfo.new(
212
265
  architecture: Util.normalize_architecture(artifact_map["omnibus.architecture"]),
@@ -220,7 +273,7 @@ EOF
220
273
  sha1: artifact_map["omnibus.sha1"],
221
274
  sha256: artifact_map["omnibus.sha256"],
222
275
  software_dependencies: software_dependencies,
223
- url: "#{base_url}/#{chef_standard_path}",
276
+ url: download_url,
224
277
  version: artifact_map["omnibus.version"]
225
278
  )
226
279
  end
@@ -241,6 +294,29 @@ EOF
241
294
  end
242
295
  end
243
296
 
297
+ # Public API detection methods for testing
298
+ def endpoint
299
+ @endpoint ||= if use_trial_api?
300
+ Mixlib::Install::Dist::TRIAL_API_ENDPOINT
301
+ elsif use_commercial_api?
302
+ Mixlib::Install::Dist::COMMERCIAL_API_ENDPOINT
303
+ else
304
+ PRODUCT_MATRIX.lookup(options.product_name, options.product_version).api_url
305
+ end
306
+ end
307
+
308
+ def use_trial_api?
309
+ !options.license_id.nil? && !options.license_id.to_s.empty? && options.license_id.start_with?("free-", "trial-")
310
+ end
311
+
312
+ def use_commercial_api?
313
+ !options.license_id.nil? && !options.license_id.to_s.empty? && !use_trial_api?
314
+ end
315
+
316
+ def use_licensed_api?
317
+ use_trial_api? || use_commercial_api?
318
+ end
319
+
244
320
  private
245
321
 
246
322
  # Converts Array<Hash> where the Hash is a key pair and
@@ -267,10 +343,6 @@ EOF
267
343
  path.join("/")
268
344
  end
269
345
 
270
- def endpoint
271
- @endpoint ||= PRODUCT_MATRIX.lookup(options.product_name, options.product_version).api_url
272
- end
273
-
274
346
  def omnibus_project
275
347
  @omnibus_project ||= PRODUCT_MATRIX.lookup(options.product_name, options.product_version).omnibus_project
276
348
  end
@@ -35,6 +35,11 @@ module Mixlib
35
35
  say Mixlib::Install.available_versions(product_name, channel).join("\n")
36
36
  end
37
37
 
38
+ desc "list-products", "list available products"
39
+ def list_products
40
+ say PRODUCT_MATRIX.products.join("\n")
41
+ end
42
+
38
43
  desc "download PRODUCT_NAME", "download an artifact"
39
44
  option :channel,
40
45
  default: :stable,
@@ -66,6 +71,10 @@ If no earlier version is found the earliest version available will be set.",
66
71
  option :attributes,
67
72
  desc: "Print artifact attributes",
68
73
  type: :boolean
74
+ option :license_id,
75
+ desc: "License ID for commercial API downloads",
76
+ aliases: ["-L"]
77
+
69
78
  def download(product_name)
70
79
  # Set minimum options
71
80
  mixlib_install_options = {
@@ -77,6 +86,7 @@ If no earlier version is found the earliest version available will be set.",
77
86
  }.tap do |opt|
78
87
  opt[:platform] = options[:platform] if options[:platform]
79
88
  opt[:platform_version] = options[:platform_version] if options[:platform_version]
89
+ opt[:license_id] = options[:license_id] if options[:license_id]
80
90
  end
81
91
 
82
92
  # auto detect platform options if not configured
@@ -114,6 +124,7 @@ If no earlier version is found the earliest version available will be set.",
114
124
  aliases: ["-t"],
115
125
  default: "sh",
116
126
  enum: Mixlib::Install::Options::SUPPORTED_SHELL_TYPES.map(&:to_s)
127
+
117
128
  def install_script
118
129
  context = {}
119
130
  context[:base_url] = options[:endpoint] if options[:endpoint]
@@ -8,6 +8,10 @@ module Mixlib
8
8
  PRODUCT_ENDPOINT = "https://packages.chef.io".freeze
9
9
  # Omnitruck endpoint
10
10
  OMNITRUCK_ENDPOINT = "https://omnitruck.chef.io".freeze
11
+ # Commercial API endpoint
12
+ COMMERCIAL_API_ENDPOINT = "https://chefdownload-commercial.chef.io".freeze
13
+ # Trial API endpoint
14
+ TRIAL_API_ENDPOINT = "https://chefdownload-trial.chef.io".freeze
11
15
  # Default product name
12
16
  DEFAULT_PRODUCT = "chef".freeze
13
17
  # Default download page URL
@@ -21,26 +21,68 @@ if test "x$download_url_override" = "x"; then
21
21
  echo "Getting information for $project $channel $version for $platform..."
22
22
 
23
23
  metadata_filename="$tmp_dir/metadata.txt"
24
- metadata_url="<%= base_url %>/$channel/$project/metadata?v=$version&p=$platform&pv=$platform_version&m=$machine"
24
+
25
+ # Use commercial API if license_id is provided, otherwise use omnitruck
26
+ if test "x$license_id" != "x"; then
27
+ # Check if license_id starts with 'free-' or 'trial-' for trial API
28
+ case "$license_id" in
29
+ free-*|trial-*)
30
+ # Trial API endpoint
31
+ base_api_url="https://chefdownload-trial.chef.io"
32
+ ;;
33
+ *)
34
+ # Commercial API endpoint
35
+ base_api_url="https://chefdownload-commercial.chef.io"
36
+ ;;
37
+ esac
38
+ metadata_url="$base_api_url/$channel/$project/metadata?v=$version&p=$platform&pv=$platform_version&m=$machine&license_id=$license_id"
39
+ else
40
+ # Omnitruck endpoint
41
+ metadata_url="<%= base_url %>/$channel/$project/metadata?v=$version&p=$platform&pv=$platform_version&m=$machine"
42
+ fi
25
43
 
26
44
  do_download "$metadata_url" "$metadata_filename"
27
45
 
28
46
  cat "$metadata_filename"
29
47
 
30
48
  echo ""
31
- # check that all the mandatory fields in the downloaded metadata are there
32
- if grep '^url' $metadata_filename > /dev/null && grep '^sha256' $metadata_filename > /dev/null; then
33
- echo "downloaded metadata file looks valid..."
49
+
50
+ # Commercial and trial APIs return JSON, omnitruck returns text format
51
+ if test "x$license_id" != "x"; then
52
+ # Parse JSON response from commercial/trial API
53
+ # Check if response looks like JSON
54
+ if grep -q '^{' "$metadata_filename" 2>/dev/null; then
55
+ # Extract url and sha256 from JSON
56
+ # Try using sed for simple JSON parsing (more portable than jq)
57
+ download_url=`sed -n 's/.*"url":"\([^"]*\)".*/\1/p' "$metadata_filename"`
58
+ sha256=`sed -n 's/.*"sha256":"\([^"]*\)".*/\1/p' "$metadata_filename"`
59
+
60
+ if test "x$download_url" != "x" && test "x$sha256" != "x"; then
61
+ echo "downloaded metadata file looks valid..."
62
+ else
63
+ echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..."
64
+ report_bug
65
+ exit 1
66
+ fi
67
+ else
68
+ echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..."
69
+ report_bug
70
+ exit 1
71
+ fi
34
72
  else
35
- echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..."
36
- # this generally means one of the download methods downloaded a 404 or something like that and then reported a successful exit code,
37
- # and this should be fixed in the function that was doing the download.
38
- report_bug
39
- exit 1
73
+ # Parse text response from omnitruck
74
+ if grep '^url' $metadata_filename > /dev/null && grep '^sha256' $metadata_filename > /dev/null; then
75
+ echo "downloaded metadata file looks valid..."
76
+ download_url=`awk '$1 == "url" { print $2 }' "$metadata_filename"`
77
+ sha256=`awk '$1 == "sha256" { print $2 }' "$metadata_filename"`
78
+ else
79
+ echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..."
80
+ # this generally means one of the download methods downloaded a 404 or something like that and then reported a successful exit code,
81
+ # and this should be fixed in the function that was doing the download.
82
+ report_bug
83
+ exit 1
84
+ fi
40
85
  fi
41
-
42
- download_url=`awk '$1 == "url" { print $2 }' "$metadata_filename"`
43
- sha256=`awk '$1 == "sha256" { print $2 }' "$metadata_filename"`
44
86
  else
45
87
  download_url=$download_url_override
46
88
  # Set sha256 to empty string if checksum not set
@@ -8,26 +8,48 @@
8
8
  # Optional Inputs:
9
9
  # $cmdline_filename: Name of the package downloaded on local disk.
10
10
  # $cmdline_dl_dir: Name of the directory downloaded package will be saved to on local disk.
11
+ # $license_id: If set, indicates we're using commercial/trial API with content-disposition headers
11
12
  #
12
13
  # Outputs:
13
14
  # $download_filename: Name of the downloaded file on local disk.
14
15
  # $filetype: Type of the file downloaded.
15
16
  ############
16
17
 
17
- filename=`echo $download_url | sed -e 's/?.*//' | sed -e 's/^.*\///'`
18
- filetype=`echo $filename | sed -e 's/^.*\.//'`
19
-
20
- # use either $tmp_dir, the provided directory (-d) or the provided filename (-f)
21
- if test "x$cmdline_filename" != "x"; then
22
- download_filename="$cmdline_filename"
23
- elif test "x$cmdline_dl_dir" != "x"; then
24
- download_filename="$cmdline_dl_dir/$filename"
18
+ # For licensed APIs (commercial/trial), the URL is an endpoint, not a direct file URL
19
+ # The actual filename will come from the Content-Disposition header
20
+ if test "x$license_id" != "x"; then
21
+ # Use content-disposition to get the filename
22
+ use_content_disposition="true"
23
+ # We don't know the filename yet - it will come from Content-Disposition
24
+ # Just set the download directory
25
+ if test "x$cmdline_filename" != "x"; then
26
+ download_filename="$cmdline_filename"
27
+ download_dir=`dirname $download_filename`
28
+ use_content_disposition="false" # User specified exact filename
29
+ elif test "x$cmdline_dl_dir" != "x"; then
30
+ download_dir="$cmdline_dl_dir"
31
+ download_filename="" # Will be determined after download
32
+ else
33
+ download_dir="$tmp_dir"
34
+ download_filename="" # Will be determined after download
35
+ fi
36
+ filetype="" # Will be determined after we get the actual filename
25
37
  else
26
- download_filename="$tmp_dir/$filename"
27
- fi
38
+ # Traditional omnitruck URLs have the filename in the URL
39
+ use_content_disposition="false"
40
+ filename=`echo $download_url | sed -e 's/?.*//' | sed -e 's/^.*\///'`
41
+ filetype=`echo $filename | sed -e 's/^.*\.//'`
28
42
 
29
- # ensure the parent directory where we download the installer always exists
30
- download_dir=`dirname $download_filename`
43
+ # use either $tmp_dir, the provided directory (-d) or the provided filename (-f)
44
+ if test "x$cmdline_filename" != "x"; then
45
+ download_filename="$cmdline_filename"
46
+ elif test "x$cmdline_dl_dir" != "x"; then
47
+ download_filename="$cmdline_dl_dir/$filename"
48
+ else
49
+ download_filename="$tmp_dir/$filename"
50
+ fi
51
+ download_dir=`dirname $download_filename`
52
+ fi
31
53
  (umask 077 && mkdir -p $download_dir) || exit 1
32
54
 
33
55
  # check if we have that file locally available and if so verify the checksum
@@ -41,12 +63,16 @@ download_dir=`dirname $download_filename`
41
63
  cached_file_available="false"
42
64
  verify_checksum="true"
43
65
 
44
- if test -f $download_filename; then
66
+ # Skip caching checks when using content-disposition since we don't know the real filename yet
67
+ if test "x$use_content_disposition" = "xtrue"; then
68
+ cached_file_available="false"
69
+ verify_checksum="true"
70
+ elif test "x$download_filename" != "x" && test -f "$download_filename"; then
45
71
  echo "$download_filename exists"
46
72
  cached_file_available="true"
47
73
  fi
48
74
 
49
- if test "x$download_url_override" != "x"; then
75
+ if test "x$download_url_override" != "x" && test "x$use_content_disposition" = "xfalse"; then
50
76
  echo "Download URL override specified"
51
77
  if test "x$cached_file_available" = "xtrue"; then
52
78
  echo "Verifying local file"
@@ -75,7 +101,63 @@ if test "x$download_url_override" != "x"; then
75
101
  fi
76
102
 
77
103
  if test "x$cached_file_available" != "xtrue"; then
78
- do_download "$download_url" "$download_filename"
104
+ if test "x$use_content_disposition" = "xtrue"; then
105
+ # For licensed APIs, download to a temporary file and extract filename from response headers
106
+ # The download_dir was already set during initialization above
107
+
108
+ # Create temp file for download
109
+ temp_download="$download_dir/chef-download-temp.$$"
110
+
111
+ # Download to temp file
112
+ do_download "$download_url" "$temp_download"
113
+
114
+ # Extract filename from response headers (try multiple methods for compatibility)
115
+ if test -f "$tmp_dir/stderr"; then
116
+ # Method 1: Try to extract filename from content-disposition header
117
+ # Format: content-disposition: attachment; filename="chef-18.8.54-1.el9.x86_64.rpm"
118
+ actual_filename=`grep -i 'content-disposition' $tmp_dir/stderr | sed -n 's/.*filename="\([^"]*\)".*/\1/p' | head -1`
119
+
120
+ # Method 2: If content-disposition failed, try to extract from location redirect header
121
+ # Format: location: https://packages.chef.io/files/stable/chef/18.8.54/el/9/chef-18.8.54-1.el9.x86_64.rpm?licenseId=...
122
+ if test "x$actual_filename" = "x"; then
123
+ actual_filename=`grep -i '^location:' $tmp_dir/stderr | head -1 | sed 's/.*\///' | sed 's/?.*//'`
124
+ fi
125
+
126
+ # Method 3: Try extracting from any URL-like pattern in stderr
127
+ if test "x$actual_filename" = "x"; then
128
+ actual_filename=`grep -i '\.rpm\|\.deb\|\.pkg\|\.msi\|\.dmg' $tmp_dir/stderr | sed -n 's/.*\/\([^/?]*\.\(rpm\|deb\|pkg\|msi\|dmg\)\).*/\1/p' | head -1`
129
+ fi
130
+ fi
131
+
132
+ # If we still couldn't extract from headers, construct filename from metadata
133
+ if test "x$actual_filename" = "x"; then
134
+ echo "Warning: Could not extract filename from response headers, using fallback"
135
+ # Construct a reasonable filename from available metadata
136
+ # This is a fallback and may not match the exact package name
137
+ if test "x$platform" = "xel" || test "x$platform" = "xfedora" || test "x$platform" = "xamazon"; then
138
+ actual_filename="chef-${version}-1.${platform}${platform_version}.${machine}.rpm"
139
+ elif test "x$platform" = "xdebian" || test "x$platform" = "xubuntu"; then
140
+ actual_filename="chef_${version}-1_${machine}.deb"
141
+ elif test "x$platform" = "xmac_os_x"; then
142
+ actual_filename="chef-${version}.dmg"
143
+ else
144
+ actual_filename="chef-${version}.pkg"
145
+ fi
146
+ fi
147
+
148
+ download_filename="$download_dir/$actual_filename"
149
+
150
+ # Move temp file to final location
151
+ mv "$temp_download" "$download_filename"
152
+
153
+ # Extract filetype from actual filename
154
+ filetype=`echo $actual_filename | sed -e 's/^.*\.//'`
155
+
156
+ echo "Downloaded as: $download_filename (type: $filetype)"
157
+ else
158
+ # Traditional download with known filename
159
+ do_download "$download_url" "$download_filename"
160
+ fi
79
161
  fi
80
162
 
81
163
  if test "x$verify_checksum" = "xtrue"; then
@@ -115,7 +115,12 @@ capture_tmp_stderr() {
115
115
  # do_wget URL FILENAME
116
116
  do_wget() {
117
117
  echo "trying wget..."
118
- wget --user-agent="User-Agent: <%= user_agent_string %>" -O "$2" "$1" 2>$tmp_dir/stderr
118
+ # If filename is empty, use --content-disposition to get filename from server
119
+ if test "x$2" = "x"; then
120
+ wget --user-agent="User-Agent: <%= user_agent_string %>" --content-disposition "$1" 2>$tmp_dir/stderr
121
+ else
122
+ wget --user-agent="User-Agent: <%= user_agent_string %>" -O "$2" "$1" 2>$tmp_dir/stderr
123
+ fi
119
124
  rc=$?
120
125
  # check for 404
121
126
  grep "ERROR 404" $tmp_dir/stderr 2>&1 >/dev/null
@@ -124,8 +129,14 @@ do_wget() {
124
129
  http_404_error
125
130
  fi
126
131
 
127
- # check for bad return status or empty output
128
- if test $rc -ne 0 || test ! -s "$2"; then
132
+ # check for bad return status (skip empty output check if using content-disposition)
133
+ if test $rc -ne 0; then
134
+ capture_tmp_stderr "wget"
135
+ return 1
136
+ fi
137
+
138
+ # Only check for empty output if we specified a filename
139
+ if test "x$2" != "x" && test ! -s "$2"; then
129
140
  capture_tmp_stderr "wget"
130
141
  return 1
131
142
  fi
@@ -136,8 +147,15 @@ do_wget() {
136
147
  # do_curl URL FILENAME
137
148
  do_curl() {
138
149
  echo "trying curl..."
139
- curl -A "User-Agent: <%= user_agent_string %>" --retry 5 -sL -D $tmp_dir/stderr "$1" > "$2"
140
- rc=$?
150
+ # If filename is empty, use -O and -J to get filename from Content-Disposition header
151
+ if test "x$2" = "x"; then
152
+ curl -A "User-Agent: <%= user_agent_string %>" --retry 5 -sL -D $tmp_dir/stderr -O -J "$1"
153
+ rc=$?
154
+ else
155
+ curl -A "User-Agent: <%= user_agent_string %>" --retry 5 -sL -D $tmp_dir/stderr "$1" > "$2"
156
+ rc=$?
157
+ fi
158
+
141
159
  # check for 404
142
160
  grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
143
161
  if test $? -eq 0; then
@@ -145,8 +163,14 @@ do_curl() {
145
163
  http_404_error
146
164
  fi
147
165
 
148
- # check for bad return status or empty output
149
- if test $rc -ne 0 || test ! -s "$2"; then
166
+ # check for bad return status
167
+ if test $rc -ne 0; then
168
+ capture_tmp_stderr "curl"
169
+ return 1
170
+ fi
171
+
172
+ # Only check for empty output if we specified a filename
173
+ if test "x$2" != "x" && test ! -s "$2"; then
150
174
  capture_tmp_stderr "curl"
151
175
  return 1
152
176
  fi
@@ -12,13 +12,14 @@
12
12
  # $install_strategy: Method of package installations. default strategy is to always install upon exec. Set to "once" to skip if project is installed
13
13
  # $download_url_override: Install package downloaded from a direct URL.
14
14
  # $checksum: SHA256 for download_url_override file (optional)
15
+ # $license_id: License ID for commercial API access (optional)
15
16
  ############
16
17
 
17
18
  # Defaults
18
19
  channel="stable"
19
20
  project="<%= default_product %>"
20
21
 
21
- while getopts pnv:c:f:P:d:s:l:a opt
22
+ while getopts pnv:c:f:P:d:s:l:a:L: opt
22
23
  do
23
24
  case "$opt" in
24
25
 
@@ -32,9 +33,10 @@ do
32
33
  s) install_strategy="$OPTARG";;
33
34
  l) download_url_override="$OPTARG";;
34
35
  a) checksum="$OPTARG";;
36
+ L) license_id="$OPTARG";;
35
37
  \?) # unknown flag
36
38
  echo >&2 \
37
- "usage: $0 [-P project] [-c release_channel] [-v version] [-f filename | -d download_dir] [-s install_strategy] [-l download_url_override] [-a checksum]"
39
+ "usage: $0 [-P project] [-c release_channel] [-v version] [-f filename | -d download_dir] [-s install_strategy] [-l download_url_override] [-a checksum] [-L license_id]"
38
40
  exit 1;;
39
41
  esac
40
42
  done
@@ -57,16 +57,21 @@ module Mixlib
57
57
  end
58
58
 
59
59
  def render_variables
60
- <<EOS
60
+ vars = <<EOS
61
61
  project=#{options.product_name}
62
62
  version=#{options.product_version}
63
63
  channel=#{options.channel}
64
- #{install_command_vars}
65
64
  EOS
65
+ # Add license_id if provided
66
+ if options.license_id && !options.license_id.to_s.empty?
67
+ vars += "license_id=#{options.license_id}\n"
68
+ end
69
+ vars += install_command_vars
70
+ vars
66
71
  end
67
72
 
68
73
  def install_command_vars
69
- return if options.install_command_options.nil?
74
+ return "" if options.install_command_options.nil?
70
75
  options.install_command_options.map { |key, value| "#{key}='#{value}'" }.join("\n")
71
76
  end
72
77
  end
@@ -39,7 +39,10 @@ function Get-ProjectMetadata {
39
39
  $nightlies,
40
40
  [validateset('auto', 'i386', 'x86_64')]
41
41
  [string]
42
- $architecture = 'auto'
42
+ $architecture = 'auto',
43
+ # License ID for commercial API access
44
+ [string]
45
+ $license_id
43
46
  )
44
47
 
45
48
  # The following legacy switches are just aliases for the current channel
@@ -60,17 +63,56 @@ function Get-ProjectMetadata {
60
63
  Write-Verbose "Architecture: $architecture"
61
64
  Write-Verbose "Project: $project"
62
65
 
66
+ # Use commercial API if license_id is provided, otherwise use omnitruck
67
+ if ($license_id) {
68
+ # Check if license_id starts with 'free-' or 'trial-' for trial API
69
+ if ($license_id -match '^(free-|trial-)') {
70
+ $base_server_uri = 'https://chefdownload-trial.chef.io'
71
+ Write-Verbose "Using Trial API with license ID"
72
+ } else {
73
+ $base_server_uri = 'https://chefdownload-commercial.chef.io'
74
+ Write-Verbose "Using Commercial API with license ID"
75
+ }
76
+ }
77
+
63
78
  $metadata_base_url = "/$($channel)/$($project)/metadata"
64
79
  $metadata_array = ("?v=$($version)",
65
80
  "p=$platform",
66
81
  "pv=$platform_version",
67
82
  "m=$architecture")
83
+
84
+ # Add license_id to query parameters if provided
85
+ if ($license_id) {
86
+ $metadata_array += "license_id=$license_id"
87
+ }
88
+
68
89
  $metadata_base_url += [string]::join('&', $metadata_array)
69
90
  $metadata_url = new-uri $base_server_uri $metadata_base_url
70
91
 
71
92
  Write-Verbose "Downloading $project details from $metadata_url"
72
- $package_metadata = (Get-WebContent $metadata_url).trim() -split '\n' |
73
- foreach { $hash = @{} } {$key, $value = $_ -split '\s+'; $hash.Add($key, $value)} {$hash}
93
+ $response = (Get-WebContent $metadata_url).trim()
94
+
95
+ # Commercial and trial APIs return JSON, omnitruck returns text format
96
+ if ($license_id) {
97
+ # Parse JSON response from commercial/trial API
98
+ try {
99
+ $json = $response | ConvertFrom-Json
100
+ $package_metadata = @{
101
+ url = $json.url
102
+ sha256 = $json.sha256
103
+ version = $json.version
104
+ }
105
+ if ($json.sha1) {
106
+ $package_metadata['sha1'] = $json.sha1
107
+ }
108
+ } catch {
109
+ throw "Failed to parse JSON response from API: $_"
110
+ }
111
+ } else {
112
+ # Parse text response from omnitruck
113
+ $package_metadata = $response -split '\n' |
114
+ foreach { $hash = @{} } {$key, $value = $_ -split '\s+'; $hash.Add($key, $value)} {$hash}
115
+ }
74
116
 
75
117
  Write-Verbose "Project details: "
76
118
  foreach ($key in $package_metadata.keys) {
@@ -6,13 +6,12 @@ function Get-PlatformVersion {
6
6
 
7
7
  $platform_version = switch ($osVersion) {
8
8
  # Windows Server build numbers from: https://betawiki.net/wiki/Microsoft_Windows
9
+ { $_ -ge [version]'10.0.26100' } { '2025'; break }
9
10
  { $_ -ge [version]'10.0.20145' } { '2022'; break }
10
11
  { $_ -ge [version]'10.0.17609' } { '2019'; break }
11
12
  { $_ -ge [version]'10.0.0' } { '2016'; break }
12
13
  { $_ -ge [version]'6.3.0' } { '2012r2'; break }
13
14
  { $_ -ge [version]'6.2.0' } { '2012'; break }
14
- { $_ -ge [version]'6.1.0' } { '2008r2'; break }
15
- { $_ -ge [version]'6.0.0' } { '2008'; break }
16
15
  }
17
16
 
18
17
  if(Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels') {
@@ -58,10 +57,10 @@ function Get-WebContent {
58
57
 
59
58
  try {
60
59
  if($PSVersionTable.PSEdition -eq 'Core') {
61
- Get-WebContentOnCore $uri $filepath
60
+ return Get-WebContentOnCore $uri $filepath
62
61
  }
63
62
  else {
64
- Get-WebContentOnFullNet $uri $filepath
63
+ return Get-WebContentOnFullNet $uri $filepath
65
64
  }
66
65
  }
67
66
  catch {
@@ -98,6 +97,28 @@ function Get-WebContentOnFullNet {
98
97
  }
99
98
  else {
100
99
  $wc.downloadfile($uri, $filepath)
100
+
101
+ # Try to extract filename from Content-Disposition header
102
+ $contentDisposition = $wc.ResponseHeaders["Content-Disposition"]
103
+ $extractedFilename = $null
104
+
105
+ if ($contentDisposition) {
106
+ # Try to match filename="..." or filename*=UTF-8''...
107
+ if ($contentDisposition -match 'filename="([^"]+)"') {
108
+ $extractedFilename = $matches[1]
109
+ }
110
+ elseif ($contentDisposition -match "filename='([^']+)'") {
111
+ $extractedFilename = $matches[1]
112
+ }
113
+ elseif ($contentDisposition -match "filename=([^;]+)") {
114
+ $extractedFilename = $matches[1].Trim()
115
+ }
116
+ }
117
+
118
+ return @{
119
+ Filename = $extractedFilename
120
+ Downloaded = $true
121
+ }
101
122
  }
102
123
  }
103
124
 
@@ -125,6 +146,19 @@ function Get-WebContentOnCore {
125
146
  if ($copyStreamOp.Exception -ne $null) {
126
147
  throw $copyStreamOp.Exception
127
148
  }
149
+
150
+ # Try to extract filename from Content-Disposition header
151
+ $extractedFilename = $null
152
+ $contentDisposition = $response.Content.Headers.ContentDisposition
153
+
154
+ if ($contentDisposition -and $contentDisposition.FileName) {
155
+ $extractedFilename = $contentDisposition.FileName.Trim('"')
156
+ }
157
+
158
+ return @{
159
+ Filename = $extractedFilename
160
+ Downloaded = $true
161
+ }
128
162
  }
129
163
  }
130
164
  }
@@ -56,7 +56,10 @@ function Install-Project {
56
56
  $checksum,
57
57
  # Set to 'once' to skip install if project is detected
58
58
  [string]
59
- $install_strategy
59
+ $install_strategy,
60
+ # License ID for commercial API access
61
+ [string]
62
+ $license_id
60
63
  )
61
64
 
62
65
  # Check for chef-client command in various locations
@@ -86,7 +89,7 @@ function Install-Project {
86
89
  $download_url = $download_url_override
87
90
  $sha256 = $checksum
88
91
  } else {
89
- $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture
92
+ $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture -license_id $license_id
90
93
  $download_url = $package_metadata.url
91
94
  $sha256 = $package_metadata.sha256
92
95
  }
@@ -99,7 +102,13 @@ function Install-Project {
99
102
  }
100
103
  }
101
104
  else {
102
- $filename = (([System.Uri]$download_url).AbsolutePath -split '/')[-1]
105
+ # For licensed downloads, we won't know the filename until after download
106
+ if ([string]::IsNullOrEmpty($license_id)) {
107
+ $filename = (([System.Uri]$download_url).AbsolutePath -split '/')[-1]
108
+ } else {
109
+ $filename = "chef-download-temp-$PID"
110
+ Write-Host "Using temporary filename for licensed download: $filename"
111
+ }
103
112
  }
104
113
  Write-Host "Download directory: $download_directory"
105
114
  Write-Host "Filename: $filename"
@@ -137,7 +146,28 @@ function Install-Project {
137
146
  if (-not ($cached_installer_available)) {
138
147
  if ($pscmdlet.ShouldProcess("$($download_url)", "Download $project")) {
139
148
  Write-Host "Downloading $project from $($download_url) to $download_destination."
140
- Get-WebContent $download_url -filepath $download_destination
149
+ $download_result = Get-WebContent $download_url -filepath $download_destination
150
+
151
+ # For licensed downloads, extract actual filename from Content-Disposition
152
+ if (-not [string]::IsNullOrEmpty($license_id) -and $download_result -and $download_result.Filename) {
153
+ $actual_filename = $download_result.Filename
154
+ Write-Host "Extracted filename from Content-Disposition: $actual_filename"
155
+
156
+ $final_destination = join-path $download_directory $actual_filename
157
+
158
+ # Move temp file to final location with correct name
159
+ if ($download_destination -ne $final_destination) {
160
+ Write-Host "Moving to final location: $final_destination"
161
+ if (test-path $final_destination) {
162
+ remove-item $final_destination -force
163
+ }
164
+ move-item $download_destination $final_destination -force
165
+ $download_destination = $final_destination
166
+ }
167
+ } elseif (-not [string]::IsNullOrEmpty($license_id)) {
168
+ Write-Host "Warning: Could not extract filename from Content-Disposition header for licensed download."
169
+ Write-Host "Using temporary filename. Package installation may fail."
170
+ }
141
171
  }
142
172
  }
143
173
 
@@ -28,7 +28,7 @@ module Mixlib
28
28
  install_project_module << get_script("install_project.ps1")
29
29
 
30
30
  install_command = []
31
- install_command << ps1_modularize(install_project_module.join("\n"), "Omnitruck")
31
+ install_command << ps1_modularize(install_project_module.join("\n"), "Installer-Module")
32
32
  install_command.join("\n\n")
33
33
  end
34
34
 
@@ -49,7 +49,7 @@ module Mixlib
49
49
  install_project_module << get_script("get_project_metadata.ps1")
50
50
  install_project_module << get_script("install_project.ps1")
51
51
  install_command = []
52
- install_command << ps1_modularize(install_project_module.join("\n"), "Omnitruck")
52
+ install_command << ps1_modularize(install_project_module.join("\n"), "Installer-Module")
53
53
  install_command << render_command
54
54
  install_command.join("\n\n")
55
55
  end
@@ -71,6 +71,7 @@ module Mixlib
71
71
  cmd << " -version #{options.product_version}"
72
72
  cmd << " -channel #{options.channel}"
73
73
  cmd << " -architecture #{options.architecture}" if options.architecture
74
+ cmd << " -license_id #{options.license_id}" if options.license_id && !options.license_id.to_s.empty?
74
75
  cmd << install_command_params if options.install_command_options
75
76
  cmd << "\n"
76
77
  end
@@ -63,6 +63,7 @@ module Mixlib
63
63
  :include_metadata,
64
64
  :user_agent_headers,
65
65
  :install_command_options,
66
+ :license_id,
66
67
  ]
67
68
 
68
69
  SUPPORTED_WINDOWS_DESKTOP_VERSIONS = %w{10}
@@ -47,6 +47,11 @@ PRODUCT_MATRIX = Mixlib::Install::ProductMatrix.new do
47
47
  package_name "chef"
48
48
  end
49
49
 
50
+ product "chef-ice" do
51
+ product_name "Chef Infra Client Enterprise"
52
+ package_name "chef-ice"
53
+ end
54
+
50
55
  product "chef-foundation" do
51
56
  product_name "Chef Foundation"
52
57
  package_name "chef-foundation"
@@ -224,7 +224,7 @@ module Mixlib
224
224
  end
225
225
 
226
226
  def windows_metadata_url
227
- base = if omnibus_url =~ %r{/install.sh$}
227
+ base = if omnibus_url.match?(%r{/install.sh$})
228
228
  "#{File.dirname(omnibus_url)}/"
229
229
  end
230
230
 
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  class Install
3
- VERSION = "3.13.0"
3
+ VERSION = "3.14.0"
4
4
  end
5
5
  end
@@ -98,14 +98,53 @@ module Mixlib
98
98
  artifact = artifact_info
99
99
 
100
100
  FileUtils.mkdir_p directory
101
- file = File.join(directory, File.basename(artifact.url))
102
101
 
102
+ # Handle the full URL including query string and redirects
103
103
  uri = URI.parse(artifact.url)
104
- Net::HTTP.start(uri.host) do |http|
105
- resp = http.get(uri.path)
106
- open(file, "wb") do |io|
107
- io.write(resp.body)
104
+ filename = nil
105
+ final_body = nil
106
+
107
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
108
+ # Build the request path including query string
109
+ request_path = uri.path
110
+ request_path += "?#{uri.query}" if uri.query
111
+
112
+ # Get the response, following redirects
113
+ response = http.request_get(request_path)
114
+
115
+ # Follow redirects
116
+ redirect_limit = 5
117
+ while response.is_a?(Net::HTTPRedirection) && redirect_limit > 0
118
+ redirect_uri = URI.parse(response["location"])
119
+ # Handle relative redirects
120
+ redirect_uri = uri + redirect_uri if redirect_uri.relative?
121
+
122
+ Net::HTTP.start(redirect_uri.host, redirect_uri.port, use_ssl: redirect_uri.scheme == "https") do |redirect_http|
123
+ redirect_path = redirect_uri.path
124
+ redirect_path += "?#{redirect_uri.query}" if redirect_uri.query
125
+ response = redirect_http.request_get(redirect_path)
126
+
127
+ # Try to get filename from Content-Disposition or final URL
128
+ if response["content-disposition"]
129
+ filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
130
+ else
131
+ filename = File.basename(redirect_uri.path)
132
+ end
133
+ end
134
+
135
+ redirect_limit -= 1
108
136
  end
137
+
138
+ final_body = response.body
139
+ end
140
+
141
+ # Use the extracted filename or fall back to basename of original URL
142
+ filename ||= File.basename(uri.path)
143
+ file = File.join(directory, filename)
144
+
145
+ # Write the final response body to file
146
+ File.open(file, "wb") do |io|
147
+ io.write(final_body)
109
148
  end
110
149
 
111
150
  file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixlib-install
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.0
4
+ version: 3.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thom May
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-01-06 00:00:00.000000000 Z
12
+ date: 2026-01-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mixlib-shellout