mixlib-install 3.15.0 → 3.16.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -14
  3. data/lib/mixlib/install/backend/base.rb +15 -1
  4. data/lib/mixlib/install/backend/package_router.rb +60 -17
  5. data/lib/mixlib/install/dist.rb +24 -4
  6. data/lib/mixlib/install/generator/base.rb +1 -14
  7. data/lib/mixlib/install/generator/bourne/scripts/check_product.sh +1 -1
  8. data/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb +89 -19
  9. data/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh +47 -23
  10. data/lib/mixlib/install/generator/bourne/scripts/helpers.sh.erb +30 -32
  11. data/lib/mixlib/install/generator/bourne/scripts/install_package.sh +2 -2
  12. data/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh +22 -22
  13. data/lib/mixlib/install/generator/bourne/scripts/proxy_env.sh +4 -4
  14. data/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh.erb +5 -3
  15. data/lib/mixlib/install/generator/bourne.rb +5 -20
  16. data/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb +52 -36
  17. data/lib/mixlib/install/generator/powershell/scripts/helpers.ps1.erb +8 -4
  18. data/lib/mixlib/install/generator/powershell/scripts/install_project.ps1.erb +33 -16
  19. data/lib/mixlib/install/generator/powershell/scripts/platform_detection.ps1 +1 -0
  20. data/lib/mixlib/install/generator/powershell.rb +5 -9
  21. data/lib/mixlib/install/generator.rb +5 -4
  22. data/lib/mixlib/install/options.rb +40 -0
  23. data/lib/mixlib/install/product_matrix.rb +1 -1
  24. data/lib/mixlib/install/script_generator.rb +80 -23
  25. data/lib/mixlib/install/util.rb +47 -0
  26. data/lib/mixlib/install/version.rb +1 -1
  27. data/lib/mixlib/install.rb +82 -11
  28. data/support/install_command.ps1 +3 -0
  29. metadata +2 -2
@@ -1,3 +1,4 @@
1
+ ################ install_project.ps1
1
2
  function Install-Project {
2
3
  <#
3
4
  .SYNOPSIS
@@ -5,12 +6,11 @@ function Install-Project {
5
6
  .DESCRIPTION
6
7
  Install a Chef Software, Inc. product
7
8
  .EXAMPLE
8
- iex (new-object net.webclient).downloadstring('https://omnitruck.chef.io/install.ps1'); Install-Project -project chef -channel stable
9
+ iex (new-object net.webclient).downloadstring('<%= base_url || "https://chefdownload-commercial.chef.io" %>/install.ps1<%= base_url && base_url.include?("chefdownload") ? "?license_id=$env:CHEF_LICENSE_KEY" : "" %>'); Install-Project -project chef -channel stable
9
10
 
10
11
  Installs the latest stable version of Chef.
11
12
  .EXAMPLE
12
- iex (irm 'https://omnitruck.chef.io/install.ps1'); Install-Project -project chefdk -channel current
13
-
13
+ iex (irm '<<%= base_url || "https://chefdownload-commercial.chef.io" %>/install.ps1<%= base_url && base_url.include?("chefdownload") ? "?license_id=$env:CHEF_LICENSE_KEY" : "" %>'); Install-Project -project chef -channel current
14
14
  Installs the latest integration build of the Chef Development Kit
15
15
  #>
16
16
  [cmdletbinding(SupportsShouldProcess=$true)]
@@ -57,9 +57,12 @@ function Install-Project {
57
57
  # Set to 'once' to skip install if project is detected
58
58
  [string]
59
59
  $install_strategy,
60
+ # Base server URI for metadata endpoint
61
+ [uri]
62
+ $base_server_uri,
60
63
  # License ID for commercial API access
61
64
  [string]
62
- $license_id
65
+ $license_id <%= "= '#{license_id}'" if license_id && !license_id.to_s.empty? %>
63
66
  )
64
67
 
65
68
  # Use CHEF_LICENSE_KEY environment variable if license_id not provided
@@ -103,7 +106,7 @@ function Install-Project {
103
106
  $download_url = $download_url_override
104
107
  $sha256 = $checksum
105
108
  } else {
106
- $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture -license_id $license_id
109
+ $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture -base_server_uri $base_server_uri -license_id $license_id
107
110
  $download_url = $package_metadata.url
108
111
  $sha256 = $package_metadata.sha256
109
112
  }
@@ -116,12 +119,18 @@ function Install-Project {
116
119
  }
117
120
  }
118
121
  else {
119
- # For licensed downloads, we won't know the filename until after download
120
- if ([string]::IsNullOrEmpty($license_id)) {
121
- $filename = (([System.Uri]$download_url).AbsolutePath -split '/')[-1]
122
- } else {
122
+ # Extract filename from URL path (without query params)
123
+ $urlPath = (([System.Uri]$download_url).AbsolutePath -split '/')[-1]
124
+
125
+ # Check if URL path has a package file extension
126
+ $hasPackageExtension = $urlPath -match '\.(msi|appx|pkg|dmg|rpm|deb)$'
127
+
128
+ # For licensed downloads or URLs without package extensions, we won't know the filename until after download
129
+ if (-not [string]::IsNullOrEmpty($license_id) -or -not $hasPackageExtension) {
123
130
  $filename = "chef-download-temp-$PID"
124
- Write-Host "Using temporary filename for licensed download: $filename"
131
+ Write-Host "Using temporary filename for content-disposition download: $filename"
132
+ } else {
133
+ $filename = $urlPath
125
134
  }
126
135
  }
127
136
  Write-Host "Download directory: $download_directory"
@@ -162,8 +171,8 @@ function Install-Project {
162
171
  Write-Host "Downloading $project from $($download_url) to $download_destination."
163
172
  $download_result = Get-WebContent $download_url -filepath $download_destination
164
173
 
165
- # For licensed downloads, extract actual filename from Content-Disposition
166
- if (-not [string]::IsNullOrEmpty($license_id) -and $download_result -and $download_result.Filename) {
174
+ # Extract actual filename from Content-Disposition if it was a temp filename
175
+ if ($filename -like "chef-download-temp-*" -and $download_result -and $download_result.Filename) {
167
176
  $actual_filename = $download_result.Filename
168
177
  Write-Host "Extracted filename from Content-Disposition: $actual_filename"
169
178
 
@@ -178,8 +187,8 @@ function Install-Project {
178
187
  move-item $download_destination $final_destination -force
179
188
  $download_destination = $final_destination
180
189
  }
181
- } elseif (-not [string]::IsNullOrEmpty($license_id)) {
182
- Write-Host "Warning: Could not extract filename from Content-Disposition header for licensed download."
190
+ } elseif ($filename -like "chef-download-temp-*") {
191
+ Write-Host "Warning: Could not extract filename from Content-Disposition header."
183
192
  Write-Host "Using temporary filename. Package installation may fail."
184
193
  }
185
194
  }
@@ -193,17 +202,25 @@ function Install-Project {
193
202
  Write-Host "Installing $project from $download_destination"
194
203
  $installingProject = $True
195
204
  $installAttempts = 0
205
+ $maxAttempts = 5
196
206
  while ($installingProject) {
197
207
  $installAttempts++
198
208
  $result = $false
199
- if($download_destination.EndsWith(".appx")) {
209
+ if ($download_destination.EndsWith(".appx")) {
200
210
  $result = Install-ChefAppx $download_destination $project
201
211
  }
202
212
  else {
203
213
  $result = Install-ChefMsi $download_destination $daemon
204
214
  }
205
- if(!$result) { continue }
215
+ if (!$result) {
216
+ if ($installAttempts -ge $maxAttempts) {
217
+ Write-Host "Failed to install $project after $installAttempts attempts."
218
+ throw "Installation failed after $installAttempts attempts."
219
+ }
220
+ continue
221
+ }
206
222
  $installingProject = $False
223
+ Write-Host "$project installation completed successfully."
207
224
  }
208
225
  }
209
226
  }
@@ -1,3 +1,4 @@
1
+ ################ platform_detection.ps1
1
2
  $platform_version = Get-PlatformVersion
2
3
  $architecture = Get-PlatformArchitecture
3
4
 
@@ -25,15 +25,10 @@ module Mixlib
25
25
  install_project_module = []
26
26
  install_project_module << get_script("helpers.ps1", context)
27
27
  install_project_module << get_script("get_project_metadata.ps1", context)
28
- install_project_module << get_script("install_project.ps1")
28
+ install_project_module << get_script("install_project.ps1", context)
29
29
 
30
30
  install_command = []
31
31
  install_command << ps1_modularize(install_project_module.join("\n"), "Installer-Module")
32
- # If license_id is provided in context, add it to the default install command
33
- if context[:license_id] && !context[:license_id].to_s.empty?
34
- install_command << "# License ID provided via context - adding to install command"
35
- install_command << "install -license_id '#{context[:license_id]}'"
36
- end
37
32
  install_command.join("\n\n")
38
33
  end
39
34
 
@@ -51,8 +46,8 @@ module Mixlib
51
46
  def install_command
52
47
  install_project_module = []
53
48
  install_project_module << get_script("helpers.ps1", user_agent_headers: options.user_agent_headers)
54
- install_project_module << get_script("get_project_metadata.ps1")
55
- install_project_module << get_script("install_project.ps1")
49
+ install_project_module << get_script("get_project_metadata.ps1", license_id: options.license_id, base_url: options.base_url)
50
+ install_project_module << get_script("install_project.ps1", license_id: options.license_id)
56
51
  install_command = []
57
52
  install_command << ps1_modularize(install_project_module.join("\n"), "Installer-Module")
58
53
  install_command << render_command
@@ -72,10 +67,11 @@ module Mixlib
72
67
  end
73
68
 
74
69
  def render_command
75
- cmd = "install -project #{options.product_name}"
70
+ cmd = "Install-Project -project #{options.product_name}"
76
71
  cmd << " -version #{options.product_version}"
77
72
  cmd << " -channel #{options.channel}"
78
73
  cmd << " -architecture #{options.architecture}" if options.architecture
74
+ cmd << " -base_server_uri '#{options.base_url}'" if options.base_url && !options.base_url.to_s.empty?
79
75
  cmd << " -license_id #{options.license_id}" if options.license_id && !options.license_id.to_s.empty?
80
76
  cmd << install_command_params if options.install_command_options
81
77
  cmd << "\n"
@@ -22,10 +22,11 @@ module Mixlib
22
22
  class Install
23
23
  class Generator
24
24
  def self.install_command(options)
25
- klass = options.for_ps1? ? PowerShell : Bourne
26
- meth = options.options[:new_omnibus_download_url] ? :install_sh_from_upstream : :install_command
27
-
28
- klass.new(options).send(meth)
25
+ if options.for_ps1?
26
+ PowerShell.new(options).install_command
27
+ else
28
+ Bourne.new(options).install_command
29
+ end
29
30
  end
30
31
  end
31
32
  end
@@ -64,6 +64,7 @@ module Mixlib
64
64
  :user_agent_headers,
65
65
  :install_command_options,
66
66
  :license_id,
67
+ :base_url,
67
68
  ]
68
69
 
69
70
  SUPPORTED_WINDOWS_DESKTOP_VERSIONS = %w{10}
@@ -90,6 +91,8 @@ module Mixlib
90
91
 
91
92
  map_windows_versions!
92
93
 
94
+ enforce_trial_api_defaults!
95
+
93
96
  validate!
94
97
  end
95
98
 
@@ -257,6 +260,43 @@ Must provide platform (-p), platform version (-l) and architecture (-a) when spe
257
260
  def all_or_none?(items)
258
261
  items.all? || items.compact.empty?
259
262
  end
263
+
264
+ # Trial API only supports stable channel and latest version
265
+ # If using trial API with other settings, default them and warn the user
266
+ def enforce_trial_api_defaults!
267
+ return unless use_trial_api?
268
+
269
+ original_channel = channel
270
+ original_version = product_version
271
+
272
+ # Force stable channel for trial API
273
+ if channel != :stable
274
+ options[:channel] = :stable
275
+ warn "WARNING: Trial API only supports 'stable' channel. Changing from '#{original_channel}' to 'stable'."
276
+ end
277
+
278
+ # Force latest version for trial API
279
+ if product_version != :latest && product_version.to_sym != :latest
280
+ options[:product_version] = :latest
281
+ warn "WARNING: Trial API only supports 'latest' version. Changing from '#{original_version}' to 'latest'."
282
+ end
283
+ end
284
+
285
+ def use_trial_api?
286
+ license_id = options[:license_id] || options["license_id"] || default_options[:license_id]
287
+ Mixlib::Install::Dist.trial_license?(license_id)
288
+ end
289
+
290
+ # Validate that licensed API usage conforms to API restrictions
291
+ # This is informational only - enforce_trial_api_defaults! already handles corrections
292
+ def validate_licensed_api_restrictions
293
+ return unless license_id && !license_id.to_s.empty?
294
+
295
+ if use_trial_api?
296
+ # These are enforced by enforce_trial_api_defaults! but we can add extra validation here if needed
297
+ # Currently no additional validation needed since defaults are auto-applied
298
+ end
299
+ end
260
300
  end
261
301
  end
262
302
  end
@@ -132,7 +132,7 @@ PRODUCT_MATRIX = Mixlib::Install::ProductMatrix.new do
132
132
  end
133
133
  config_file "/etc/delivery/delivery.rb"
134
134
  github_repo "chef/automate"
135
- downloads_product_page_url "https://downloads.chef.io/automate"
135
+ downloads_product_page_url "#{Mixlib::Install::Dist::DOWNLOADS_PAGE}/automate"
136
136
  end
137
137
 
138
138
  product "ha" do
@@ -21,7 +21,6 @@ require_relative "util"
21
21
  require_relative "generator/powershell"
22
22
  require_relative "dist"
23
23
  require "cgi"
24
- require "net/http" unless defined?(Net::HTTP)
25
24
 
26
25
  module Mixlib
27
26
  class Install
@@ -58,18 +57,23 @@ module Mixlib
58
57
  attr_accessor :omnibus_url
59
58
  attr_accessor :install_msi_url
60
59
 
60
+ attr_accessor :license_id
61
+ attr_accessor :base_url
62
+
61
63
  VALID_INSTALL_OPTS = %w{omnibus_url
62
64
  endpoint
63
65
  http_proxy
64
66
  https_proxy
65
67
  install_flags
66
68
  install_msi_url
69
+ license_id
67
70
  nightlies
68
71
  prerelease
69
72
  project
70
73
  root
71
74
  use_sudo
72
- sudo_command}
75
+ sudo_command
76
+ base_url}
73
77
 
74
78
  def initialize(version, powershell = false, opts = {})
75
79
  @version = (version || "latest").to_s.downcase
@@ -80,17 +84,29 @@ module Mixlib
80
84
  @prerelease = false
81
85
  @nightlies = false
82
86
  @endpoint = "metadata"
83
- @omnibus_url = "https://www.chef.io/chef/install.sh"
87
+ @omnibus_url = "#{Mixlib::Install::Dist::OMNITRUCK_ENDPOINT}/install.sh"
84
88
  @use_sudo = true
85
89
  @sudo_command = "sudo -E"
90
+ @license_id = nil
91
+ @project = Mixlib::Install::Dist::DEFAULT_PRODUCT.freeze
92
+ @channel = "stable"
86
93
 
87
94
  @root = if powershell
88
- "$env:systemdrive\\#{Mixlib::Install::Dist::WINDOWS_INSTALL_DIR}\\#{Mixlib::Install::Dist::DEFAULT_PRODUCT}"
95
+ "$env:systemdrive\\#{Mixlib::Install::Dist::OMNIBUS_WINDOWS_INSTALL_DIR}\\#{Mixlib::Install::Dist::DEFAULT_PRODUCT}"
89
96
  else
90
- "#{Mixlib::Install::Dist::LINUX_INSTALL_DIR}/#{Mixlib::Install::Dist::DEFAULT_PRODUCT}"
97
+ "#{Mixlib::Install::Dist::OMNIBUS_LINUX_INSTALL_DIR}/#{Mixlib::Install::Dist::DEFAULT_PRODUCT}"
91
98
  end
92
99
 
93
100
  parse_opts(opts)
101
+
102
+ # Update root for chef-ice to use Habitat install directories
103
+ if @project&.casecmp("chef-ice") == 0
104
+ @root = if powershell
105
+ "$env:systemdrive\\#{Mixlib::Install::Dist::HABITAT_WINDOWS_INSTALL_DIR}\\chef\\chef-infra-client\\*\\*"
106
+ else
107
+ "#{Mixlib::Install::Dist::HABITAT_LINUX_INSTALL_DIR}/chef/chef-infra-client/*/*"
108
+ end
109
+ end
94
110
  end
95
111
 
96
112
  def install_command
@@ -102,17 +118,6 @@ module Mixlib
102
118
  shell_code_from_file(vars)
103
119
  end
104
120
 
105
- def install_command_from_omnitruck(url)
106
- uri = URI.parse(url)
107
- response = Net::HTTP.get_response(uri)
108
-
109
- if response.code == "200"
110
- response.body
111
- else
112
- raise StandardError, "unable to fetch the install.sh"
113
- end
114
- end
115
-
116
121
  private
117
122
 
118
123
  # Generates the install command variables for Bourne shell-based
@@ -124,11 +129,12 @@ module Mixlib
124
129
  flags = %w{latest true nightlies}.include?(version) ? "" : "-v #{CGI.escape(version)}"
125
130
  flags << " " << "-n" if nightlies
126
131
  flags << " " << "-p" if prerelease
132
+ flags << " " << "-l #{license_id}" if license_id && !license_id.to_s.empty?
127
133
  flags << " " << install_flags if install_flags
128
134
 
129
135
  [
130
136
  shell_var("chef_omnibus_root", root),
131
- shell_var("chef_omnibus_url", omnibus_url),
137
+ shell_var("chef_omnibus_url", omnibus_url_for_license),
132
138
  shell_var("install_flags", flags.strip),
133
139
  shell_var("pretty_version", Util.pretty_version(version)),
134
140
  shell_var("sudo_sh", sudo("sh")),
@@ -151,6 +157,7 @@ module Mixlib
151
157
  shell_var("msi", "#{download_directory}\\chef-#{version}.msi"),
152
158
  shell_var("download_directory", download_directory),
153
159
  ].tap do |vars|
160
+ vars << shell_var("license_id", license_id) if license_id && !license_id.to_s.empty?
154
161
  if install_msi_url
155
162
  vars << shell_var("chef_msi_url", install_msi_url)
156
163
  else
@@ -172,6 +179,7 @@ module Mixlib
172
179
  validate_opts!(opt)
173
180
  case opt.to_s
174
181
  when "project", "endpoint"
182
+ @project = setting if opt.to_s == "project"
175
183
  self.endpoint = metadata_endpoint_from_project(setting)
176
184
  else
177
185
  send("#{opt.to_sym}=", setting)
@@ -216,23 +224,72 @@ module Mixlib
216
224
 
217
225
  # @return the correct Chef Omnitruck API metadata endpoint, based on project
218
226
  def metadata_endpoint_from_project(project = nil)
219
- if project.nil? || project.casecmp("chef") == 0
227
+ if project.nil? || project.casecmp(Mixlib::Install::Dist::DEFAULT_PRODUCT) == 0
220
228
  "metadata"
221
229
  else
222
230
  "metadata-#{project.downcase}"
223
231
  end
224
232
  end
225
233
 
234
+ # Returns the appropriate omnibus URL based on whether license_id is provided
235
+ # @return [String] the omnibus URL (commercial/trial or standard omnitruck)
236
+ # @api private
237
+ def omnibus_url_for_license
238
+ return omnibus_url if license_id.nil? || license_id.to_s.empty? || omnibus_url != "#{Mixlib::Install::Dist::OMNITRUCK_ENDPOINT}/install.sh"
239
+
240
+ # Use custom base_url if provided, otherwise determine from license type
241
+ endpoint_base = if @base_url
242
+ @base_url
243
+ elsif license_id.start_with?("free-", "trial-")
244
+ Mixlib::Install::Dist::TRIAL_API_ENDPOINT
245
+ else
246
+ Mixlib::Install::Dist::COMMERCIAL_API_ENDPOINT
247
+ end
248
+
249
+ # Add license_id as query param when using licensed endpoints
250
+ "#{endpoint_base}/install.sh?license_id=#{CGI.escape(license_id)}"
251
+ end
252
+
226
253
  def windows_metadata_url
227
- base = if omnibus_url.match?(%r{/install.sh$})
228
- "#{File.dirname(omnibus_url)}/"
229
- end
254
+ # Determine if we're using commercial/trial API
255
+ using_licensed_api = license_id && !license_id.to_s.empty?
256
+
257
+ if using_licensed_api
258
+ # Commercial/trial API: <base_url>/<channel>/<project>/metadata
259
+ # Use custom base_url if provided, otherwise determine from license type
260
+ endpoint_base = if @base_url
261
+ @base_url
262
+ elsif license_id.start_with?("free-", "trial-")
263
+ Mixlib::Install::Dist::TRIAL_API_ENDPOINT
264
+ else
265
+ Mixlib::Install::Dist::COMMERCIAL_API_ENDPOINT
266
+ end
267
+
268
+ product_name = @project
269
+ url = "#{endpoint_base}/#{@channel}/#{product_name}/metadata"
270
+ else
271
+ # Omnitruck API: use base from omnibus_url + endpoint
272
+ base = if omnibus_url_for_license.match?(%r{/install.sh})
273
+ # Ensure base URL ends with /
274
+ base_url = File.dirname(omnibus_url_for_license)
275
+ base_url += "/" unless base_url.end_with?("/")
276
+ base_url
277
+ end
278
+ url = "#{base}#{endpoint}"
279
+ end
230
280
 
231
- url = "#{base}#{endpoint}"
232
- url << "?p=windows&m=$platform_architecture&pv=$platform_version"
281
+ # chef-ice uses different parameters than chef
282
+ if @project.casecmp("chef-ice") == 0
283
+ # For chef-ice: p (platform), m (machine), pm (package_manager)
284
+ url << "?p=windows&m=$platform_architecture&pm=msi"
285
+ else
286
+ # For chef and other products: p (platform), pv (platform_version), m (machine)
287
+ url << "?p=windows&m=$platform_architecture&pv=$platform_version"
288
+ end
233
289
  url << "&v=#{CGI.escape(version)}" unless %w{latest true nightlies}.include?(version)
234
290
  url << "&prerelease=true" if prerelease
235
291
  url << "&nightlies=true" if nightlies
292
+ url << "&license_id=#{CGI.escape(license_id)}" if license_id && !license_id.to_s.empty?
236
293
  url
237
294
  end
238
295
 
@@ -172,6 +172,53 @@ module Mixlib
172
172
  architecture
173
173
  end
174
174
  end
175
+
176
+ #
177
+ # Determines package manager based on platform for commercial API
178
+ #
179
+ # @param [String] platform
180
+ #
181
+ # @return String [package_manager] (rpm, deb, tar, msi, dmg)
182
+ def determine_package_manager(platform)
183
+ case platform
184
+ when /^el/, /^centos/, /^rhel/, /^fedora/, /^amazon/, /^rocky/, /^opensuse/, /^sles/, /^scientific/
185
+ "rpm"
186
+ when /^debian/, /^ubuntu/, /^linuxmint/, /^raspbian/
187
+ "deb"
188
+ when /^mac_os_x/, /^macos/
189
+ "dmg"
190
+ when /^windows/
191
+ "msi"
192
+ when /^solaris/, /^smartos/, /^freebsd/, /^aix/, /^omnios/
193
+ "tar"
194
+ else
195
+ # Default to tar for unknown platforms
196
+ "tar"
197
+ end
198
+ end
199
+
200
+ #
201
+ # Normalizes platform name for commercial API (chef-ice)
202
+ # Maps specific platform names to generic categories
203
+ #
204
+ # @param [String] platform
205
+ #
206
+ # @return String [normalized_platform] (linux, macos, windows)
207
+ def normalize_platform_for_commercial(platform)
208
+ case platform
209
+ when /^el/, /^centos/, /^rhel/, /^fedora/, /^rocky/, /^scientific/, /^debian/, /^ubuntu/, /^linuxmint/, /^raspbian/, /^opensuse/, /^sles/, /^amazon/
210
+ "linux"
211
+ when /^mac_os_x/, /^macos/
212
+ "macos"
213
+ when /^windows/
214
+ "windows"
215
+ when /^freebsd/, /^aix/, /^solaris/, /^smartos/, /^omnios/
216
+ "unix"
217
+ else
218
+ # Default to linux for unknown platforms
219
+ "linux"
220
+ end
221
+ end
175
222
  end
176
223
  end
177
224
  end
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  class Install
3
- VERSION = "3.15.0"
3
+ VERSION = "3.16.0"
4
4
  end
5
5
  end
@@ -103,6 +103,7 @@ module Mixlib
103
103
  uri = URI.parse(artifact.url)
104
104
  filename = nil
105
105
  final_body = nil
106
+ final_uri = uri
106
107
 
107
108
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
108
109
  # Build the request path including query string
@@ -112,23 +113,27 @@ module Mixlib
112
113
  # Get the response, following redirects
113
114
  response = http.request_get(request_path)
114
115
 
116
+ # Try to extract filename from Content-Disposition in initial response
117
+ if response["content-disposition"]
118
+ filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
119
+ end
120
+
115
121
  # Follow redirects
116
122
  redirect_limit = 5
117
123
  while response.is_a?(Net::HTTPRedirection) && redirect_limit > 0
118
124
  redirect_uri = URI.parse(response["location"])
119
125
  # Handle relative redirects
120
126
  redirect_uri = uri + redirect_uri if redirect_uri.relative?
127
+ final_uri = redirect_uri
121
128
 
122
129
  Net::HTTP.start(redirect_uri.host, redirect_uri.port, use_ssl: redirect_uri.scheme == "https") do |redirect_http|
123
130
  redirect_path = redirect_uri.path
124
131
  redirect_path += "?#{redirect_uri.query}" if redirect_uri.query
125
132
  response = redirect_http.request_get(redirect_path)
126
133
 
127
- # Try to get filename from Content-Disposition or final URL
128
- if response["content-disposition"]
134
+ # Try to get filename from Content-Disposition in redirect response
135
+ if response["content-disposition"] && filename.nil?
129
136
  filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
130
- else
131
- filename = File.basename(redirect_uri.path)
132
137
  end
133
138
  end
134
139
 
@@ -136,9 +141,23 @@ module Mixlib
136
141
  end
137
142
 
138
143
  final_body = response.body
144
+
145
+ # Try Content-Disposition from final successful response
146
+ if response["content-disposition"] && filename.nil?
147
+ filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
148
+ end
149
+ end
150
+
151
+ # Fallback: extract filename from final URL path (works for direct package URLs)
152
+ if filename.nil?
153
+ path_filename = File.basename(final_uri.path.split("?").first)
154
+ # Only use path filename if it looks like a package file
155
+ if /\.(rpm|deb|pkg|msi|dmg|bff|p5p|sh|tar|gz|appx)$/.match?(path_filename)
156
+ filename = path_filename
157
+ end
139
158
  end
140
159
 
141
- # Use the extracted filename or fall back to basename of original URL
160
+ # Final fallback: use basename of original URL
142
161
  filename ||= File.basename(uri.path)
143
162
  file = File.join(directory, filename)
144
163
 
@@ -158,10 +177,19 @@ module Mixlib
158
177
  def root
159
178
  # This only works for chef and chefdk but they are the only projects
160
179
  # we are supporting as of now.
161
- if options.for_ps1?
162
- "$env:systemdrive\\#{Mixlib::Install::Dist::WINDOWS_INSTALL_DIR}\\#{options.product_name}"
180
+ # chef-ice uses Habitat install directories
181
+ if options.product_name.casecmp("chef-ice") == 0
182
+ if options.for_ps1?
183
+ "$env:systemdrive\\#{Mixlib::Install::Dist::HABITAT_WINDOWS_INSTALL_DIR}\\chef\\chef-infra-client\\*\\*"
184
+ else
185
+ "#{Mixlib::Install::Dist::HABITAT_LINUX_INSTALL_DIR}/chef/chef-infra-client/*/*"
186
+ end
163
187
  else
164
- "/opt/#{options.product_name}"
188
+ if options.for_ps1?
189
+ "$env:systemdrive\\#{Mixlib::Install::Dist::OMNIBUS_WINDOWS_INSTALL_DIR}\\#{options.product_name}"
190
+ else
191
+ "#{Mixlib::Install::Dist::OMNIBUS_LINUX_INSTALL_DIR}/#{options.product_name}"
192
+ end
165
193
  end
166
194
  end
167
195
 
@@ -175,10 +203,19 @@ module Mixlib
175
203
  # install directory which can be different than the product name (e.g.
176
204
  # chef-server -> /opt/opscode). But this is OK for now since
177
205
  # chef & chefdk are the only supported products.
178
- version_manifest_file = if options.for_ps1?
179
- "$env:systemdrive\\#{Mixlib::Install::Dist::WINDOWS_INSTALL_DIR}\\#{options.product_name}\\version-manifest.json"
206
+ # chef-ice uses Habitat install directories
207
+ version_manifest_file = if options.product_name.casecmp("chef-ice") == 0
208
+ if options.for_ps1?
209
+ "$env:systemdrive\\#{Mixlib::Install::Dist::HABITAT_WINDOWS_INSTALL_DIR}\\chef\\chef-infra-client\\*\\*\\version-manifest.json"
210
+ else
211
+ "#{Mixlib::Install::Dist::HABITAT_LINUX_INSTALL_DIR}/chef/chef-infra-client/*/*/version-manifest.json"
212
+ end
180
213
  else
181
- "/opt/#{options.product_name}/version-manifest.json"
214
+ if options.for_ps1?
215
+ "$env:systemdrive\\#{Mixlib::Install::Dist::OMNIBUS_WINDOWS_INSTALL_DIR}\\#{options.product_name}\\version-manifest.json"
216
+ else
217
+ "/opt/#{options.product_name}/version-manifest.json"
218
+ end
182
219
  end
183
220
 
184
221
  if File.exist? version_manifest_file
@@ -258,8 +295,25 @@ module Mixlib
258
295
  # ------------------
259
296
  # base_url [String]
260
297
  # url pointing to the omnitruck to be queried by the script.
298
+ # license_id [String]
299
+ # license ID for commercial or trial API access.
300
+ # If license_id starts with 'free-' or 'trial-', trial API defaults are enforced.
261
301
  #
262
302
  def self.install_sh(context = {})
303
+ # Apply trial API defaults if license_id indicates trial
304
+ if context[:license_id] && Mixlib::Install::Dist.trial_license?(context[:license_id])
305
+ # Warn and override if non-compliant values provided
306
+ if context[:channel] && context[:channel].to_s != "stable"
307
+ warn "WARNING: Trial API only supports 'stable' channel. Changing from '#{context[:channel]}' to 'stable'."
308
+ context[:channel] = "stable"
309
+ end
310
+
311
+ if context[:version] && !["latest", nil].include?(context[:version].to_s)
312
+ warn "WARNING: Trial API only supports 'latest' version. Changing from '#{context[:version]}' to 'latest'."
313
+ context[:version] = "latest"
314
+ end
315
+ end
316
+
263
317
  Mixlib::Install::Generator::Bourne.install_sh(context)
264
318
  end
265
319
 
@@ -269,8 +323,25 @@ module Mixlib
269
323
  # ------------------
270
324
  # base_url [String]
271
325
  # url pointing to the omnitruck to be queried by the script.
326
+ # license_id [String]
327
+ # license ID for commercial or trial API access.
328
+ # If license_id starts with 'free-' or 'trial-', trial API defaults are enforced.
272
329
  #
273
330
  def self.install_ps1(context = {})
331
+ # Apply trial API defaults if license_id indicates trial
332
+ if context[:license_id] && Mixlib::Install::Dist.trial_license?(context[:license_id])
333
+ # Warn and override if non-compliant values provided
334
+ if context[:channel] && context[:channel].to_s != "stable"
335
+ warn "WARNING: Trial API only supports 'stable' channel. Changing from '#{context[:channel]}' to 'stable'."
336
+ context[:channel] = "stable"
337
+ end
338
+
339
+ if context[:version] && !["latest", nil].include?(context[:version].to_s)
340
+ warn "WARNING: Trial API only supports 'latest' version. Changing from '#{context[:version]}' to 'latest'."
341
+ context[:version] = "latest"
342
+ end
343
+ end
344
+
274
345
  Mixlib::Install::Generator::PowerShell.install_ps1(context)
275
346
  end
276
347
  end