mixlib-install 3.15.0 → 3.17.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -14
  3. data/lib/mixlib/install/backend/base.rb +1 -1
  4. data/lib/mixlib/install/backend/package_router.rb +101 -12
  5. data/lib/mixlib/install/cli.rb +5 -1
  6. data/lib/mixlib/install/dist.rb +24 -4
  7. data/lib/mixlib/install/generator/base.rb +1 -14
  8. data/lib/mixlib/install/generator/bourne/scripts/check_product.sh +1 -1
  9. data/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb +49 -19
  10. data/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh +47 -23
  11. data/lib/mixlib/install/generator/bourne/scripts/helpers.sh.erb +40 -38
  12. data/lib/mixlib/install/generator/bourne/scripts/install_package.sh +2 -2
  13. data/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh +22 -22
  14. data/lib/mixlib/install/generator/bourne/scripts/proxy_env.sh +4 -4
  15. data/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh.erb +9 -3
  16. data/lib/mixlib/install/generator/bourne.rb +5 -20
  17. data/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb +60 -36
  18. data/lib/mixlib/install/generator/powershell/scripts/helpers.ps1.erb +8 -4
  19. data/lib/mixlib/install/generator/powershell/scripts/install_project.ps1.erb +36 -16
  20. data/lib/mixlib/install/generator/powershell/scripts/platform_detection.ps1 +1 -0
  21. data/lib/mixlib/install/generator/powershell.rb +5 -9
  22. data/lib/mixlib/install/generator.rb +5 -4
  23. data/lib/mixlib/install/options.rb +40 -0
  24. data/lib/mixlib/install/product_matrix.rb +6 -1
  25. data/lib/mixlib/install/script_generator.rb +72 -22
  26. data/lib/mixlib/install/version.rb +1 -1
  27. data/lib/mixlib/install.rb +91 -16
  28. data/mixlib-install.gemspec +2 -0
  29. data/support/install_command.ps1 +3 -0
  30. metadata +17 -3
@@ -1,4 +1,8 @@
1
- [Console]::OutputEncoding = New-Object -typename System.Text.ASCIIEncoding
1
+ ################ helpers.ps1
2
+ # Set strict error handling to ensure errors cause script failures
3
+ $ErrorActionPreference = 'Stop'
4
+
5
+ try { [Console]::OutputEncoding = New-Object -typename System.Text.ASCIIEncoding } catch { }
2
6
  [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls,Tls11,Tls12'
3
7
 
4
8
  function Get-PlatformVersion {
@@ -168,11 +172,11 @@ function Test-ProjectPackage {
168
172
  [cmdletbinding()]
169
173
  param ($Path, $Algorithm = 'SHA256', $Hash)
170
174
  if (!$env:Valid_ProjectPackage){
171
- Write-Verbose "Testing the $Algorithm hash for $path."
175
+ Write-Host "Testing the $Algorithm hash for $path."
172
176
  $ActualHash = (Custom-GetFileHash -Algorithm $Algorithm -Path $Path).Hash.ToLower()
173
177
 
174
- Write-Verbose "`tDesired Hash - '$Hash'"
175
- Write-Verbose "`tActual Hash - '$ActualHash'"
178
+ Write-Host "`tDesired Hash - '$Hash'"
179
+ Write-Host "`tActual Hash - '$ActualHash'"
176
180
  $env:Valid_ProjectPackage = $ActualHash -eq $Hash
177
181
  if (-not $env:Valid_ProjectPackage) {
178
182
  Write-Error "Failed to validate the downloaded installer. The expected $Algorithm hash was '$Hash' and the actual hash was '$ActualHash' for $path"
@@ -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,15 @@ 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? %>,
66
+ # Package manager override (e.g. msi, zip). If omitted, the server derives it from platform.
67
+ [string]
68
+ $package_manager
63
69
  )
64
70
 
65
71
  # Use CHEF_LICENSE_KEY environment variable if license_id not provided
@@ -103,7 +109,7 @@ function Install-Project {
103
109
  $download_url = $download_url_override
104
110
  $sha256 = $checksum
105
111
  } else {
106
- $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture -license_id $license_id
112
+ $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 -package_manager $package_manager
107
113
  $download_url = $package_metadata.url
108
114
  $sha256 = $package_metadata.sha256
109
115
  }
@@ -116,12 +122,18 @@ function Install-Project {
116
122
  }
117
123
  }
118
124
  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 {
125
+ # Extract filename from URL path (without query params)
126
+ $urlPath = (([System.Uri]$download_url).AbsolutePath -split '/')[-1]
127
+
128
+ # Check if URL path has a package file extension
129
+ $hasPackageExtension = $urlPath -match '\.(msi|appx|pkg|dmg|rpm|deb)$'
130
+
131
+ # For licensed downloads or URLs without package extensions, we won't know the filename until after download
132
+ if (-not [string]::IsNullOrEmpty($license_id) -or -not $hasPackageExtension) {
123
133
  $filename = "chef-download-temp-$PID"
124
- Write-Host "Using temporary filename for licensed download: $filename"
134
+ Write-Host "Using temporary filename for content-disposition download: $filename"
135
+ } else {
136
+ $filename = $urlPath
125
137
  }
126
138
  }
127
139
  Write-Host "Download directory: $download_directory"
@@ -162,8 +174,8 @@ function Install-Project {
162
174
  Write-Host "Downloading $project from $($download_url) to $download_destination."
163
175
  $download_result = Get-WebContent $download_url -filepath $download_destination
164
176
 
165
- # For licensed downloads, extract actual filename from Content-Disposition
166
- if (-not [string]::IsNullOrEmpty($license_id) -and $download_result -and $download_result.Filename) {
177
+ # Extract actual filename from Content-Disposition if it was a temp filename
178
+ if ($filename -like "chef-download-temp-*" -and $download_result -and $download_result.Filename) {
167
179
  $actual_filename = $download_result.Filename
168
180
  Write-Host "Extracted filename from Content-Disposition: $actual_filename"
169
181
 
@@ -178,8 +190,8 @@ function Install-Project {
178
190
  move-item $download_destination $final_destination -force
179
191
  $download_destination = $final_destination
180
192
  }
181
- } elseif (-not [string]::IsNullOrEmpty($license_id)) {
182
- Write-Host "Warning: Could not extract filename from Content-Disposition header for licensed download."
193
+ } elseif ($filename -like "chef-download-temp-*") {
194
+ Write-Host "Warning: Could not extract filename from Content-Disposition header."
183
195
  Write-Host "Using temporary filename. Package installation may fail."
184
196
  }
185
197
  }
@@ -193,17 +205,25 @@ function Install-Project {
193
205
  Write-Host "Installing $project from $download_destination"
194
206
  $installingProject = $True
195
207
  $installAttempts = 0
208
+ $maxAttempts = 5
196
209
  while ($installingProject) {
197
210
  $installAttempts++
198
211
  $result = $false
199
- if($download_destination.EndsWith(".appx")) {
212
+ if ($download_destination.EndsWith(".appx")) {
200
213
  $result = Install-ChefAppx $download_destination $project
201
214
  }
202
215
  else {
203
216
  $result = Install-ChefMsi $download_destination $daemon
204
217
  }
205
- if(!$result) { continue }
218
+ if (!$result) {
219
+ if ($installAttempts -ge $maxAttempts) {
220
+ Write-Host "Failed to install $project after $installAttempts attempts."
221
+ throw "Installation failed after $installAttempts attempts."
222
+ }
223
+ continue
224
+ }
206
225
  $installingProject = $False
226
+ Write-Host "$project installation completed successfully."
207
227
  }
208
228
  }
209
229
  }
@@ -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
@@ -155,6 +155,11 @@ PRODUCT_MATRIX = Mixlib::Install::ProductMatrix.new do
155
155
  package_name "inspec"
156
156
  end
157
157
 
158
+ product "inspec-enterprise" do
159
+ product_name "Chef InSpec Enterprise"
160
+ package_name "inspec-enterprise"
161
+ end
162
+
158
163
  product "mac-bootstrapper" do
159
164
  product_name "Habitat Mac Bootstrapper"
160
165
  package_name "mac-bootstrapper"
@@ -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,65 @@ 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
281
  url << "?p=windows&m=$platform_architecture&pv=$platform_version"
233
282
  url << "&v=#{CGI.escape(version)}" unless %w{latest true nightlies}.include?(version)
234
283
  url << "&prerelease=true" if prerelease
235
284
  url << "&nightlies=true" if nightlies
285
+ url << "&license_id=#{CGI.escape(license_id)}" if license_id && !license_id.to_s.empty?
236
286
  url
237
287
  end
238
288
 
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  class Install
3
- VERSION = "3.15.0"
3
+ VERSION = "3.17.0"
4
4
  end
5
5
  end
@@ -63,14 +63,18 @@ module Mixlib
63
63
  #
64
64
  # @param [String, Symbol] channel
65
65
  #
66
+ # @param [String] license_id (optional)
67
+ #
66
68
  # @return [Array<String>] list of available versions for the given
67
69
  # product_name and channel.
68
- def self.available_versions(product_name, channel)
70
+ def self.available_versions(product_name, channel, license_id: nil)
71
+ opts = {
72
+ product_name: product_name,
73
+ channel: channel.to_sym,
74
+ }
75
+ opts[:license_id] = license_id if license_id
69
76
  Backend.available_versions(
70
- Mixlib::Install::Options.new(
71
- product_name: product_name,
72
- channel: channel.to_sym
73
- )
77
+ Mixlib::Install::Options.new(opts)
74
78
  )
75
79
  end
76
80
 
@@ -103,6 +107,7 @@ module Mixlib
103
107
  uri = URI.parse(artifact.url)
104
108
  filename = nil
105
109
  final_body = nil
110
+ final_uri = uri
106
111
 
107
112
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
108
113
  # Build the request path including query string
@@ -112,23 +117,27 @@ module Mixlib
112
117
  # Get the response, following redirects
113
118
  response = http.request_get(request_path)
114
119
 
120
+ # Try to extract filename from Content-Disposition in initial response
121
+ if response["content-disposition"]
122
+ filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
123
+ end
124
+
115
125
  # Follow redirects
116
126
  redirect_limit = 5
117
127
  while response.is_a?(Net::HTTPRedirection) && redirect_limit > 0
118
128
  redirect_uri = URI.parse(response["location"])
119
129
  # Handle relative redirects
120
130
  redirect_uri = uri + redirect_uri if redirect_uri.relative?
131
+ final_uri = redirect_uri
121
132
 
122
133
  Net::HTTP.start(redirect_uri.host, redirect_uri.port, use_ssl: redirect_uri.scheme == "https") do |redirect_http|
123
134
  redirect_path = redirect_uri.path
124
135
  redirect_path += "?#{redirect_uri.query}" if redirect_uri.query
125
136
  response = redirect_http.request_get(redirect_path)
126
137
 
127
- # Try to get filename from Content-Disposition or final URL
128
- if response["content-disposition"]
138
+ # Try to get filename from Content-Disposition in redirect response
139
+ if response["content-disposition"] && filename.nil?
129
140
  filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
130
- else
131
- filename = File.basename(redirect_uri.path)
132
141
  end
133
142
  end
134
143
 
@@ -136,9 +145,23 @@ module Mixlib
136
145
  end
137
146
 
138
147
  final_body = response.body
148
+
149
+ # Try Content-Disposition from final successful response
150
+ if response["content-disposition"] && filename.nil?
151
+ filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
152
+ end
139
153
  end
140
154
 
141
- # Use the extracted filename or fall back to basename of original URL
155
+ # Fallback: extract filename from final URL path (works for direct package URLs)
156
+ if filename.nil?
157
+ path_filename = File.basename(final_uri.path.split("?").first)
158
+ # Only use path filename if it looks like a package file
159
+ if /\.(rpm|deb|pkg|msi|dmg|bff|p5p|sh|tar|gz|appx)$/.match?(path_filename)
160
+ filename = path_filename
161
+ end
162
+ end
163
+
164
+ # Final fallback: use basename of original URL
142
165
  filename ||= File.basename(uri.path)
143
166
  file = File.join(directory, filename)
144
167
 
@@ -158,10 +181,19 @@ module Mixlib
158
181
  def root
159
182
  # This only works for chef and chefdk but they are the only projects
160
183
  # we are supporting as of now.
161
- if options.for_ps1?
162
- "$env:systemdrive\\#{Mixlib::Install::Dist::WINDOWS_INSTALL_DIR}\\#{options.product_name}"
184
+ # chef-ice uses Habitat install directories
185
+ if options.product_name.casecmp("chef-ice") == 0
186
+ if options.for_ps1?
187
+ "$env:systemdrive\\#{Mixlib::Install::Dist::HABITAT_WINDOWS_INSTALL_DIR}\\chef\\chef-infra-client\\*\\*"
188
+ else
189
+ "#{Mixlib::Install::Dist::HABITAT_LINUX_INSTALL_DIR}/chef/chef-infra-client/*/*"
190
+ end
163
191
  else
164
- "/opt/#{options.product_name}"
192
+ if options.for_ps1?
193
+ "$env:systemdrive\\#{Mixlib::Install::Dist::OMNIBUS_WINDOWS_INSTALL_DIR}\\#{options.product_name}"
194
+ else
195
+ "#{Mixlib::Install::Dist::OMNIBUS_LINUX_INSTALL_DIR}/#{options.product_name}"
196
+ end
165
197
  end
166
198
  end
167
199
 
@@ -175,10 +207,19 @@ module Mixlib
175
207
  # install directory which can be different than the product name (e.g.
176
208
  # chef-server -> /opt/opscode). But this is OK for now since
177
209
  # 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"
210
+ # chef-ice uses Habitat install directories
211
+ version_manifest_file = if options.product_name.casecmp("chef-ice") == 0
212
+ if options.for_ps1?
213
+ "$env:systemdrive\\#{Mixlib::Install::Dist::HABITAT_WINDOWS_INSTALL_DIR}\\chef\\chef-infra-client\\*\\*\\version-manifest.json"
214
+ else
215
+ "#{Mixlib::Install::Dist::HABITAT_LINUX_INSTALL_DIR}/chef/chef-infra-client/*/*/version-manifest.json"
216
+ end
180
217
  else
181
- "/opt/#{options.product_name}/version-manifest.json"
218
+ if options.for_ps1?
219
+ "$env:systemdrive\\#{Mixlib::Install::Dist::OMNIBUS_WINDOWS_INSTALL_DIR}\\#{options.product_name}\\version-manifest.json"
220
+ else
221
+ "/opt/#{options.product_name}/version-manifest.json"
222
+ end
182
223
  end
183
224
 
184
225
  if File.exist? version_manifest_file
@@ -258,8 +299,25 @@ module Mixlib
258
299
  # ------------------
259
300
  # base_url [String]
260
301
  # url pointing to the omnitruck to be queried by the script.
302
+ # license_id [String]
303
+ # license ID for commercial or trial API access.
304
+ # If license_id starts with 'free-' or 'trial-', trial API defaults are enforced.
261
305
  #
262
306
  def self.install_sh(context = {})
307
+ # Apply trial API defaults if license_id indicates trial
308
+ if context[:license_id] && Mixlib::Install::Dist.trial_license?(context[:license_id])
309
+ # Warn and override if non-compliant values provided
310
+ if context[:channel] && context[:channel].to_s != "stable"
311
+ warn "WARNING: Trial API only supports 'stable' channel. Changing from '#{context[:channel]}' to 'stable'."
312
+ context[:channel] = "stable"
313
+ end
314
+
315
+ if context[:version] && !["latest", nil].include?(context[:version].to_s)
316
+ warn "WARNING: Trial API only supports 'latest' version. Changing from '#{context[:version]}' to 'latest'."
317
+ context[:version] = "latest"
318
+ end
319
+ end
320
+
263
321
  Mixlib::Install::Generator::Bourne.install_sh(context)
264
322
  end
265
323
 
@@ -269,8 +327,25 @@ module Mixlib
269
327
  # ------------------
270
328
  # base_url [String]
271
329
  # url pointing to the omnitruck to be queried by the script.
330
+ # license_id [String]
331
+ # license ID for commercial or trial API access.
332
+ # If license_id starts with 'free-' or 'trial-', trial API defaults are enforced.
272
333
  #
273
334
  def self.install_ps1(context = {})
335
+ # Apply trial API defaults if license_id indicates trial
336
+ if context[:license_id] && Mixlib::Install::Dist.trial_license?(context[:license_id])
337
+ # Warn and override if non-compliant values provided
338
+ if context[:channel] && context[:channel].to_s != "stable"
339
+ warn "WARNING: Trial API only supports 'stable' channel. Changing from '#{context[:channel]}' to 'stable'."
340
+ context[:channel] = "stable"
341
+ end
342
+
343
+ if context[:version] && !["latest", nil].include?(context[:version].to_s)
344
+ warn "WARNING: Trial API only supports 'latest' version. Changing from '#{context[:version]}' to 'latest'."
345
+ context[:version] = "latest"
346
+ end
347
+ end
348
+
274
349
  Mixlib::Install::Generator::PowerShell.install_ps1(context)
275
350
  end
276
351
  end