Class: Mixlib::Install

Inherits:
Object
  • Object
show all
Defined in:
lib/mixlib/install.rb,
lib/mixlib/install/cli.rb,
lib/mixlib/install/dist.rb,
lib/mixlib/install/util.rb,
lib/mixlib/install/backend.rb,
lib/mixlib/install/options.rb,
lib/mixlib/install/product.rb,
lib/mixlib/install/version.rb,
lib/mixlib/install/generator.rb,
lib/mixlib/install/backend/base.rb,
lib/mixlib/install/artifact_info.rb,
lib/mixlib/install/generator/base.rb,
lib/mixlib/install/generator/bourne.rb,
lib/mixlib/install/script_generator.rb,
lib/mixlib/install/generator/powershell.rb,
lib/mixlib/install/backend/package_router.rb

Defined Under Namespace

Classes: ArtifactInfo, Backend, Cli, Dist, Generator, Options, Product, ProductMatrix, ScriptGenerator, Util

Constant Summary collapse

VERSION =
"3.16.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Install

Returns a new instance of Install.



35
36
37
# File 'lib/mixlib/install.rb', line 35

def initialize(options = {})
  @options = Options.new(options)
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



33
34
35
# File 'lib/mixlib/install.rb', line 33

def options
  @options
end

Class Method Details

.available_versions(product_name, channel) ⇒ Array<String>

List available versions

product_name and channel.

Parameters:

  • product (String)

    name

  • channel (String, Symbol)

Returns:

  • (Array<String>)

    list of available versions for the given



68
69
70
71
72
73
74
75
# File 'lib/mixlib/install.rb', line 68

def self.available_versions(product_name, channel)
  Backend.available_versions(
    Mixlib::Install::Options.new(
      product_name: product_name,
      channel: channel.to_sym
    )
  )
end

.detect_platformObject

Returns a Hash containing the platform info options



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/mixlib/install.rb', line 250

def self.detect_platform
  output = if Gem.win_platform?
             # For Windows we write the detect platform script and execute the
             # powershell.exe program with Mixlib::ShellOut
             Dir.mktmpdir do |d|
               File.open(File.join(d, "detect_platform.ps1"), "w+") do |f|
                 f.puts detect_platform_ps1
               end

               # An update to most Windows versions > 2008r2 now sets the execution policy
               # to disallow unsigned powershell scripts. This changes it for just this
               # powershell session, which allows this to run even if the execution policy
               # is set higher.
               Mixlib::ShellOut.new("powershell.exe -NoProfile -file #{File.join(d, "detect_platform.ps1")}", :env => { "PSExecutionPolicyPreference" => "Bypass" }).run_command
             end
           else
             Mixlib::ShellOut.new(detect_platform_sh).run_command
           end

  platform_info = output.stdout.split

  {
    platform: platform_info[0],
    platform_version: platform_info[1],
    architecture: platform_info[2],
  }
end

.detect_platform_ps1Object

Returns the platform_detection.ps1 script



288
289
290
# File 'lib/mixlib/install.rb', line 288

def self.detect_platform_ps1
  Mixlib::Install::Generator::PowerShell.detect_platform_ps1
end

.detect_platform_shObject

Returns the platform_detection.sh script



281
282
283
# File 'lib/mixlib/install.rb', line 281

def self.detect_platform_sh
  Mixlib::Install::Generator::Bourne.detect_platform_sh
end

.install_ps1(context = {}) ⇒ Object

Returns the install.ps1 script Supported context parameters:


base_url [String]

url pointing to the omnitruck to be queried by the script.

license_id [String]

license ID for commercial or trial API access.
If license_id starts with 'free-' or 'trial-', trial API defaults are enforced.


330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/mixlib/install.rb', line 330

def self.install_ps1(context = {})
  # Apply trial API defaults if license_id indicates trial
  if context[:license_id] && Mixlib::Install::Dist.trial_license?(context[:license_id])
    # Warn and override if non-compliant values provided
    if context[:channel] && context[:channel].to_s != "stable"
      warn "WARNING: Trial API only supports 'stable' channel. Changing from '#{context[:channel]}' to 'stable'."
      context[:channel] = "stable"
    end

    if context[:version] && !["latest", nil].include?(context[:version].to_s)
      warn "WARNING: Trial API only supports 'latest' version. Changing from '#{context[:version]}' to 'latest'."
      context[:version] = "latest"
    end
  end

  Mixlib::Install::Generator::PowerShell.install_ps1(context)
end

.install_sh(context = {}) ⇒ Object

Returns the install.sh script Supported context parameters:


base_url [String]

url pointing to the omnitruck to be queried by the script.

license_id [String]

license ID for commercial or trial API access.
If license_id starts with 'free-' or 'trial-', trial API defaults are enforced.


302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/mixlib/install.rb', line 302

def self.install_sh(context = {})
  # Apply trial API defaults if license_id indicates trial
  if context[:license_id] && Mixlib::Install::Dist.trial_license?(context[:license_id])
    # Warn and override if non-compliant values provided
    if context[:channel] && context[:channel].to_s != "stable"
      warn "WARNING: Trial API only supports 'stable' channel. Changing from '#{context[:channel]}' to 'stable'."
      context[:channel] = "stable"
    end

    if context[:version] && !["latest", nil].include?(context[:version].to_s)
      warn "WARNING: Trial API only supports 'latest' version. Changing from '#{context[:version]}' to 'latest'."
      context[:version] = "latest"
    end
  end

  Mixlib::Install::Generator::Bourne.install_sh(context)
end

Instance Method Details

#artifact_infoArray<ArtifactInfo>, ArtifactInfo

Fetch artifact metadata information

channel, product name, and product version. channel, product name, product version and platform info

Returns:

  • (Array<ArtifactInfo>)

    list of fetched artifact data for the configured

  • (ArtifactInfo)

    fetched artifact data for the configured



46
47
48
# File 'lib/mixlib/install.rb', line 46

def artifact_info
  Backend.info(options)
end

#available_versionsArray<String>

List available versions

product_name and channel.

Returns:

  • (Array<String>)

    list of available versions for the given



55
56
57
# File 'lib/mixlib/install.rb', line 55

def available_versions
  self.class.available_versions(options.product_name, options.channel)
end

#current_versionObject

Returns the current version of the installed product. Returns nil if the product is not installed.



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/mixlib/install.rb', line 200

def current_version
  # Note that this logic does not work for products other than
  # chef & chefdk since version-manifest is created under the
  # install directory which can be different than the product name (e.g.
  # chef-server -> /opt/opscode). But this is OK for now since
  # chef & chefdk are the only supported products.
  # chef-ice uses Habitat install directories
  version_manifest_file = if options.product_name.casecmp("chef-ice") == 0
                            if options.for_ps1?
                              "$env:systemdrive\\#{Mixlib::Install::Dist::HABITAT_WINDOWS_INSTALL_DIR}\\chef\\chef-infra-client\\*\\*\\version-manifest.json"
                            else
                              "#{Mixlib::Install::Dist::HABITAT_LINUX_INSTALL_DIR}/chef/chef-infra-client/*/*/version-manifest.json"
                            end
                          else
                            if options.for_ps1?
                              "$env:systemdrive\\#{Mixlib::Install::Dist::OMNIBUS_WINDOWS_INSTALL_DIR}\\#{options.product_name}\\version-manifest.json"
                            else
                              "/opt/#{options.product_name}/version-manifest.json"
                            end
                          end

  if File.exist? version_manifest_file
    JSON.parse(File.read(version_manifest_file))["build_version"]
  end
end

#detect_platformObject

Automatically set the platform options



242
243
244
245
# File 'lib/mixlib/install.rb', line 242

def detect_platform
  options.set_platform_info(self.class.detect_platform)
  self
end

#download_artifact(directory = Dir.pwd) ⇒ String

Download a single artifact

Parameters:

  • download (String)

    directory. Default: Dir.pwd

Returns:

  • (String)

    file path of downloaded artifact



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/mixlib/install.rb', line 93

def download_artifact(directory = Dir.pwd)
  if options.platform.nil? || options.platform_version.nil? || options.architecture.nil?
    raise "Must provide platform options to download a specific artifact"
  end

  artifact = artifact_info

  FileUtils.mkdir_p directory

  # Handle the full URL including query string and redirects
  uri = URI.parse(artifact.url)
  filename = nil
  final_body = nil
  final_uri = uri

  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
    # Build the request path including query string
    request_path = uri.path
    request_path += "?#{uri.query}" if uri.query

    # Get the response, following redirects
    response = http.request_get(request_path)

    # Try to extract filename from Content-Disposition in initial response
    if response["content-disposition"]
      filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
    end

    # Follow redirects
    redirect_limit = 5
    while response.is_a?(Net::HTTPRedirection) && redirect_limit > 0
      redirect_uri = URI.parse(response["location"])
      # Handle relative redirects
      redirect_uri = uri + redirect_uri if redirect_uri.relative?
      final_uri = redirect_uri

      Net::HTTP.start(redirect_uri.host, redirect_uri.port, use_ssl: redirect_uri.scheme == "https") do |redirect_http|
        redirect_path = redirect_uri.path
        redirect_path += "?#{redirect_uri.query}" if redirect_uri.query
        response = redirect_http.request_get(redirect_path)

        # Try to get filename from Content-Disposition in redirect response
        if response["content-disposition"] && filename.nil?
          filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
        end
      end

      redirect_limit -= 1
    end

    final_body = response.body

    # Try Content-Disposition from final successful response
    if response["content-disposition"] && filename.nil?
      filename = response["content-disposition"][/filename="?([^"]+)"?/, 1]
    end
  end

  # Fallback: extract filename from final URL path (works for direct package URLs)
  if filename.nil?
    path_filename = File.basename(final_uri.path.split("?").first)
    # Only use path filename if it looks like a package file
    if /\.(rpm|deb|pkg|msi|dmg|bff|p5p|sh|tar|gz|appx)$/.match?(path_filename)
      filename = path_filename
    end
  end

  # Final fallback: use basename of original URL
  filename ||= File.basename(uri.path)
  file = File.join(directory, filename)

  # Write the final response body to file
  File.open(file, "wb") do |io|
    io.write(final_body)
  end

  file
end

#install_commandString

Returns an install script for the given options

Returns:

  • (String)

    script for installing with given options



82
83
84
# File 'lib/mixlib/install.rb', line 82

def install_command
  Generator.install_command(options)
end

#rootString

Returns the base installation directory for the given options

Returns:

  • (String)

    the installation directory for the project



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/mixlib/install.rb', line 177

def root
  # This only works for chef and chefdk but they are the only projects
  # we are supporting as of now.
  # chef-ice uses Habitat install directories
  if options.product_name.casecmp("chef-ice") == 0
    if options.for_ps1?
      "$env:systemdrive\\#{Mixlib::Install::Dist::HABITAT_WINDOWS_INSTALL_DIR}\\chef\\chef-infra-client\\*\\*"
    else
      "#{Mixlib::Install::Dist::HABITAT_LINUX_INSTALL_DIR}/chef/chef-infra-client/*/*"
    end
  else
    if options.for_ps1?
      "$env:systemdrive\\#{Mixlib::Install::Dist::OMNIBUS_WINDOWS_INSTALL_DIR}\\#{options.product_name}"
    else
      "#{Mixlib::Install::Dist::OMNIBUS_LINUX_INSTALL_DIR}/#{options.product_name}"
    end
  end
end

#upgrade_available?Boolean

Returns true if an upgradable version is available, false otherwise.

Returns:

  • (Boolean)


229
230
231
232
233
234
235
236
237
# File 'lib/mixlib/install.rb', line 229

def upgrade_available?
  return true if current_version.nil?

  artifact = artifact_info
  artifact = artifact.first if artifact.is_a? Array
  available_ver = Mixlib::Versioning.parse(artifact.version)
  current_ver = Mixlib::Versioning.parse(current_version)
  (available_ver > current_ver)
end