diff --git a/lib/mixlib/install/generator/base.rb b/lib/mixlib/install/generator/base.rb index aabce1f5..69b33f97 100644 --- a/lib/mixlib/install/generator/base.rb +++ b/lib/mixlib/install/generator/base.rb @@ -70,6 +70,34 @@ def get_script(name, context = {}) self.class.get_script(name, context) end + def license_availble? + options.license_id && !options.license_id.to_s.empty? + end + + def license_type + return nil unless license_availble? + + case options.license_id + when /trial/ + "trial" + when /free/ + "free" + else + "commercial" + end + end + + def omnitruck_endpoint + case license_type + when "trial", "free" + Mixlib::Install::Dist::TRIAL_API_ENDPOINT + when "commercial" + Mixlib::Install::Dist::COMMERCIAL_API_ENDPOINT + else + Mixlib::Install::Dist::OMNITRUCK_ENDPOINT + end + end + def install_sh_from_upstream uri = URI.parse(options.options[:new_omnibus_download_url]) response = Net::HTTP.get_response(uri) diff --git a/lib/mixlib/install/generator/bourne.rb b/lib/mixlib/install/generator/bourne.rb index 367b6c82..e3b86dab 100644 --- a/lib/mixlib/install/generator/bourne.rb +++ b/lib/mixlib/install/generator/bourne.rb @@ -28,8 +28,8 @@ def self.install_sh(context) install_command << get_script("check_product.sh") install_command << get_script("platform_detection.sh") install_command << get_script("proxy_env.sh") - install_command << get_script("fetch_metadata.sh", context) - install_command << get_script("fetch_package.sh") + install_command << get_script("fetch_metadata.sh", context.merge(omnitruck_endpoint: omnitruck_endpoint)) + install_command << get_script("fetch_package.sh", context.merge(omnitruck_endpoint: omnitruck_endpoint)) install_command << get_script("install_package.sh") install_command.join("\n\n") end @@ -49,8 +49,8 @@ def install_command install_command << get_script("check_product.sh") install_command << get_script("platform_detection.sh") install_command << get_script("proxy_env.sh") - install_command << get_script("fetch_metadata.sh") - install_command << get_script("fetch_package.sh") + install_command << get_script("fetch_metadata.sh", omnitruck_endpoint: omnitruck_endpoint) + install_command << get_script("fetch_package.sh", omnitruck_endpoint: omnitruck_endpoint) install_command << get_script("install_package.sh") install_command.join("\n\n") @@ -63,7 +63,7 @@ def render_variables channel=#{options.channel} EOS # Add license_id if provided - if options.license_id && !options.license_id.to_s.empty? + if license_availble? vars += "license_id=#{options.license_id}\n" end vars += install_command_vars diff --git a/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb b/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb index 0c28ce70..158d6be7 100644 --- a/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb +++ b/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb @@ -11,7 +11,8 @@ # $platform_version: # $machine: # $tmp_dir: -# +# $license_id +# $package_manager: # Outputs: # $download_url: # $sha256: @@ -24,65 +25,34 @@ if test "x$download_url_override" = "x"; then # Use commercial API if license_id is provided, otherwise use omnitruck if test "x$license_id" != "x"; then - # Check if license_id starts with 'free-' or 'trial-' for trial API - case "$license_id" in - free-*|trial-*) - # Trial API endpoint - base_api_url="https://chefdownload-trial.chef.io" - ;; - *) - # Commercial API endpoint - base_api_url="https://chefdownload-commercial.chef.io" - ;; - esac - metadata_url="$base_api_url/$channel/$project/metadata?v=$version&p=$platform&pv=$platform_version&m=$machine&license_id=$license_id" + if [ ${project} = "chef-ice" ]; then + metadata_url="<%= omnitruck_endpoint %>/$channel/$project/metadata?v=$version&p=$platform&m=$machine&pm=$package_manager&license_id=$license_id" + else + metadata_url="<%= omnitruck_endpoint %>/$channel/$project/metadata?v=$version&p=$platform&pv=$platform_version&m=$machine&license_id=$license_id" + fi else # Omnitruck endpoint metadata_url="<%= base_url %>/$channel/$project/metadata?v=$version&p=$platform&pv=$platform_version&m=$machine" fi do_download "$metadata_url" "$metadata_filename" - + meta_response=$(cat "$metadata_filename") + modify_response $meta_response $metadata_filename cat "$metadata_filename" - echo "" - - # Commercial and trial APIs return JSON, omnitruck returns text format - if test "x$license_id" != "x"; then - # Parse JSON response from commercial/trial API - # Check if response looks like JSON - if grep -q '^{' "$metadata_filename" 2>/dev/null; then - # Extract url and sha256 from JSON - # Try using sed for simple JSON parsing (more portable than jq) - download_url=`sed -n 's/.*"url":"\([^"]*\)".*/\1/p' "$metadata_filename"` - sha256=`sed -n 's/.*"sha256":"\([^"]*\)".*/\1/p' "$metadata_filename"` - - if test "x$download_url" != "x" && test "x$sha256" != "x"; then - echo "downloaded metadata file looks valid..." - else - echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..." - report_bug - exit 1 - fi - else - echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..." - report_bug - exit 1 - fi + # check that all the mandatory fields in the downloaded metadata are there + if grep '^url' $metadata_filename > /dev/null && grep '^sha256' $metadata_filename > /dev/null; then + echo "downloaded metadata file looks valid..." else - # Parse text response from omnitruck - if grep '^url' $metadata_filename > /dev/null && grep '^sha256' $metadata_filename > /dev/null; then - echo "downloaded metadata file looks valid..." - download_url=`awk '$1 == "url" { print $2 }' "$metadata_filename"` - sha256=`awk '$1 == "sha256" { print $2 }' "$metadata_filename"` - else - echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..." - # this generally means one of the download methods downloaded a 404 or something like that and then reported a successful exit code, - # and this should be fixed in the function that was doing the download. - report_bug - exit 1 - fi + echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..." + # this generally means one of the download methods downloaded a 404 or something like that and then reported a successful exit code, + # and this should be fixed in the function that was doing the download. + report_bug + exit 1 fi + + download_url=`awk '$1 == "url" { print $2 }' "$metadata_filename"` + sha256=`awk '$1 == "sha256" { print $2 }' "$metadata_filename"` else download_url=$download_url_override # Set sha256 to empty string if checksum not set diff --git a/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh b/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh deleted file mode 100644 index bee6bb1e..00000000 --- a/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh +++ /dev/null @@ -1,169 +0,0 @@ -# fetch_package.sh -############ -# This section fetches a package from $download_url and verifies its metadata. -# -# Inputs: -# $download_url: -# $tmp_dir: -# Optional Inputs: -# $cmdline_filename: Name of the package downloaded on local disk. -# $cmdline_dl_dir: Name of the directory downloaded package will be saved to on local disk. -# $license_id: If set, indicates we're using commercial/trial API with content-disposition headers -# -# Outputs: -# $download_filename: Name of the downloaded file on local disk. -# $filetype: Type of the file downloaded. -############ - -# For licensed APIs (commercial/trial), the URL is an endpoint, not a direct file URL -# The actual filename will come from the Content-Disposition header -if test "x$license_id" != "x"; then - # Use content-disposition to get the filename - use_content_disposition="true" - # We don't know the filename yet - it will come from Content-Disposition - # Just set the download directory - if test "x$cmdline_filename" != "x"; then - download_filename="$cmdline_filename" - download_dir=`dirname $download_filename` - use_content_disposition="false" # User specified exact filename - elif test "x$cmdline_dl_dir" != "x"; then - download_dir="$cmdline_dl_dir" - download_filename="" # Will be determined after download - else - download_dir="$tmp_dir" - download_filename="" # Will be determined after download - fi - filetype="" # Will be determined after we get the actual filename -else - # Traditional omnitruck URLs have the filename in the URL - use_content_disposition="false" - filename=`echo $download_url | sed -e 's/?.*//' | sed -e 's/^.*\///'` - filetype=`echo $filename | sed -e 's/^.*\.//'` - - # use either $tmp_dir, the provided directory (-d) or the provided filename (-f) - if test "x$cmdline_filename" != "x"; then - download_filename="$cmdline_filename" - elif test "x$cmdline_dl_dir" != "x"; then - download_filename="$cmdline_dl_dir/$filename" - else - download_filename="$tmp_dir/$filename" - fi - download_dir=`dirname $download_filename` -fi -(umask 077 && mkdir -p $download_dir) || exit 1 - -# check if we have that file locally available and if so verify the checksum -# Use cases -# 1) metadata - new download -# 2) metadata - cached download when cmdline_dl_dir set -# 3) url override - no checksum new download -# 4) url override - with checksum new download -# 5) url override - with checksum cached download when cmdline_dl_dir set - -cached_file_available="false" -verify_checksum="true" - -# Skip caching checks when using content-disposition since we don't know the real filename yet -if test "x$use_content_disposition" = "xtrue"; then - cached_file_available="false" - verify_checksum="true" -elif test "x$download_filename" != "x" && test -f "$download_filename"; then - echo "$download_filename exists" - cached_file_available="true" -fi - -if test "x$download_url_override" != "x" && test "x$use_content_disposition" = "xfalse"; then - echo "Download URL override specified" - if test "x$cached_file_available" = "xtrue"; then - echo "Verifying local file" - if test "x$sha256" = "x"; then - echo "Checksum not specified, ignoring existing file" - cached_file_available="false" # download new file - verify_checksum="false" # no checksum to compare after download - elif do_checksum "$download_filename" "$sha256"; then - echo "Checksum match, using existing file" - cached_file_available="true" # don't need to download file - verify_checksum="false" # don't need to checksum again - else - echo "Checksum mismatch, ignoring existing file" - cached_file_available="false" # download new file - verify_checksum="true" # checksum new downloaded file - fi - else - echo "$download_filename not found" - cached_file_available="false" # download new file - if test "x$sha256" = "x"; then - verify_checksum="false" # no checksum to compare after download - else - verify_checksum="true" # checksum new downloaded file - fi - fi -fi - -if test "x$cached_file_available" != "xtrue"; then - if test "x$use_content_disposition" = "xtrue"; then - # For licensed APIs, download to a temporary file and extract filename from response headers - # The download_dir was already set during initialization above - - # Create temp file for download - temp_download="$download_dir/chef-download-temp.$$" - - # Download to temp file - do_download "$download_url" "$temp_download" - - # Extract filename from response headers (try multiple methods for compatibility) - if test -f "$tmp_dir/stderr"; then - # Method 1: Try to extract filename from content-disposition header - # Format: content-disposition: attachment; filename="chef-18.8.54-1.el9.x86_64.rpm" - actual_filename=`grep -i 'content-disposition' $tmp_dir/stderr | sed -n 's/.*filename="\([^"]*\)".*/\1/p' | head -1` - - # Method 2: If content-disposition failed, try to extract from location redirect header - # Format: location: https://packages.chef.io/files/stable/chef/18.8.54/el/9/chef-18.8.54-1.el9.x86_64.rpm?licenseId=... - if test "x$actual_filename" = "x"; then - actual_filename=`grep -i '^location:' $tmp_dir/stderr | head -1 | sed 's/.*\///' | sed 's/?.*//'` - fi - - # Method 3: Try extracting from any URL-like pattern in stderr - if test "x$actual_filename" = "x"; then - actual_filename=`grep -i '\.rpm\|\.deb\|\.pkg\|\.msi\|\.dmg' $tmp_dir/stderr | sed -n 's/.*\/\([^/?]*\.\(rpm\|deb\|pkg\|msi\|dmg\)\).*/\1/p' | head -1` - fi - fi - - # If we still couldn't extract from headers, construct filename from metadata - if test "x$actual_filename" = "x"; then - echo "Warning: Could not extract filename from response headers, using fallback" - # Construct a reasonable filename from available metadata - # This is a fallback and may not match the exact package name - if test "x$platform" = "xel" || test "x$platform" = "xfedora" || test "x$platform" = "xamazon"; then - actual_filename="chef-${version}-1.${platform}${platform_version}.${machine}.rpm" - elif test "x$platform" = "xdebian" || test "x$platform" = "xubuntu"; then - actual_filename="chef_${version}-1_${machine}.deb" - elif test "x$platform" = "xmac_os_x"; then - actual_filename="chef-${version}.dmg" - else - actual_filename="chef-${version}.pkg" - fi - fi - - download_filename="$download_dir/$actual_filename" - - # Move temp file to final location - mv "$temp_download" "$download_filename" - - # Extract filetype from actual filename - filetype=`echo $actual_filename | sed -e 's/^.*\.//'` - - echo "Downloaded as: $download_filename (type: $filetype)" - else - # Traditional download with known filename - do_download "$download_url" "$download_filename" - fi -fi - -if test "x$verify_checksum" = "xtrue"; then - do_checksum "$download_filename" "$sha256" || checksum_mismatch -fi - -############ -# end of fetch_package.sh -############ diff --git a/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh.erb b/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh.erb new file mode 100644 index 00000000..c86bb110 --- /dev/null +++ b/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh.erb @@ -0,0 +1,105 @@ +# fetch_package.sh +############ +# This section fetches a package from $download_url and verifies its metadata. +# +# Inputs: +# $download_url: +# $tmp_dir: +# Optional Inputs: +# $cmdline_filename: Name of the package downloaded on local disk. +# $cmdline_dl_dir: Name of the directory downloaded package will be saved to on local disk. +# $license_id: If set, indicates we're using new API with fileName endpoint +# +# Outputs: +# $download_filename: Name of the downloaded file on local disk. +# $filetype: Type of the file downloaded. +############ + +# For new API with license_id, fetch the filename separately +if test "x$license_id" != "x"; then + if [ ${project} = "chef-ice" ]; then + filenameurl="<%= omnitruck_endpoint %>/$channel/$project/fileName?v=$version&p=$platform&m=$machine&pm=$package_manager&license_id=$license_id" + else + filenameurl="<%= omnitruck_endpoint %>/$channel/$project/fileName?v=$version&p=$platform&pv=$platform_version&m=$machine&license_id=$license_id" + fi + + filepath="$tmp_dir/filename.txt" + do_download $filenameurl $filepath + filename=$(cat $filepath | awk -F "\"" '{print $4}') + filetype=`echo $filename | sed -e 's/^.*\.//'` + + echo "$filename" +else + # Traditional omnitruck URLs have the filename in the URL + filename=`echo $download_url | sed -e 's/?.*//' | sed -e 's/^.*\///'` + filetype=`echo $filename | sed -e 's/^.*\.//'` +fi + +# use either $tmp_dir, the provided directory (-d) or the provided filename (-f) +if test "x$cmdline_filename" != "x"; then + download_filename="$cmdline_filename" +elif test "x$cmdline_dl_dir" != "x"; then + download_filename="$cmdline_dl_dir/$filename" +else + download_filename="$tmp_dir/$filename" +fi + +# ensure the parent directory where we download the installer always exists +download_dir=`dirname $download_filename` +(umask 077 && mkdir -p $download_dir) || exit 1 + +# check if we have that file locally available and if so verify the checksum +# Use cases +# 1) metadata - new download +# 2) metadata - cached download when cmdline_dl_dir set +# 3) url override - no checksum new download +# 4) url override - with checksum new download +# 5) url override - with checksum cached download when cmdline_dl_dir set + +cached_file_available="false" +verify_checksum="true" + +if test -f $download_filename; then + echo "$download_filename exists" + cached_file_available="true" +fi + +if test "x$download_url_override" != "x"; then + echo "Download URL override specified" + if test "x$cached_file_available" = "xtrue"; then + echo "Verifying local file" + if test "x$sha256" = "x"; then + echo "Checksum not specified, ignoring existing file" + cached_file_available="false" # download new file + verify_checksum="false" # no checksum to compare after download + elif do_checksum "$download_filename" "$sha256"; then + echo "Checksum match, using existing file" + cached_file_available="true" # don't need to download file + verify_checksum="false" # don't need to checksum again + else + echo "Checksum mismatch, ignoring existing file" + cached_file_available="false" # download new file + verify_checksum="true" # checksum new downloaded file + fi + else + echo "$download_filename not found" + cached_file_available="false" # download new file + if test "x$sha256" = "x"; then + verify_checksum="false" # no checksum to compare after download + else + verify_checksum="true" # checksum new downloaded file + fi + fi +fi + +if test "x$cached_file_available" != "xtrue"; then + do_download "$download_url" "$download_filename" +fi + +if test "x$verify_checksum" = "xtrue"; then + do_checksum "$download_filename" "$sha256" || checksum_mismatch +fi + +############ +# end of fetch_package.sh +############ diff --git a/lib/mixlib/install/generator/bourne/scripts/helpers.sh.erb b/lib/mixlib/install/generator/bourne/scripts/helpers.sh.erb index 98e9cc5c..c6c76859 100644 --- a/lib/mixlib/install/generator/bourne/scripts/helpers.sh.erb +++ b/lib/mixlib/install/generator/bourne/scripts/helpers.sh.erb @@ -144,6 +144,14 @@ do_wget() { return 0 } +modify_response() { + # curlres=$(curl -A "User-Agent: <%= user_agent_string %>" --retry 5 -sL -D $tmp_dir/stderr "$1") + url=$(echo $1 | awk -F "," '{print $3}' | awk -F "\"" '{print $4}') + version=$(echo $1 | awk -F "," '{print $4}' | awk -F "\"" '{print $4}') + sha256=$(echo $1 | awk -F "," '{print $2}' | awk -F "\"" '{print $4}') + printf "sha256 $sha256\nurl $url\nversion $version" > "$2" +} + # do_curl URL FILENAME do_curl() { echo "trying curl..." diff --git a/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh b/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh index 9f817df1..e664b224 100644 --- a/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh +++ b/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh @@ -46,7 +46,7 @@ elif test -f "/etc/Eos-release"; then elif test -f "/etc/redhat-release"; then platform=`sed 's/^\(.\+\) release.*/\1/' /etc/redhat-release | tr '[A-Z]' '[a-z]'` platform_version=`sed 's/^.\+ release \([.0-9]\+\).*/\1/' /etc/redhat-release` - + if test "$platform" = "rocky linux"; then source /etc/os-release os="${REDHAT_SUPPORT_PRODUCT}" @@ -206,7 +206,58 @@ if test "x$platform" = "xsolaris2"; then export PATH fi -echo "$platform $platform_version $machine" +determine_package_type() { + case "$platform" in + "debian"|"ubuntu"|"linuxmint") + echo "deb" + ;; + "centos"|"redhat"|"fedora"|"rocky"|"amazon"|"el") + echo "rpm" + ;; + "sles"|"opensuseleap") + echo "rpm" + ;; + "aix") + echo "bff" + ;; + "solaris2") + echo "pkg" + ;; + "mac_os_x") + echo "dmg" + ;; + *) + echo "Unknown package type" + report_bug + exit 1 + ;; + esac +} + +if [ "$package_manager" = "tar" ]; then + echo "Error: Script generation is not supported package_manager 'tar'." + exit 1 +fi + +# Ensure the package_manager field is correctly set based on the -i flag or dynamically determined. +if [ "$project" = "chef-ice" ]; then + if [ -z "$package_manager" ]; then + package_manager=$(determine_package_type) + platform="linux" + platform_version="pv" + else + package_manager="$package_manager" + platform="linux" + platform_version="pv" + fi +else + package_manager="pm" +fi + +if [ "$project" = "migrate-ice" ]; then + echo "Error: Script generation is not supported for the product 'migrate-ice'." + exit 1 +fi ############ # end of platform_detection.sh diff --git a/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh.erb b/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh.erb index fa3ef2ca..38aa0c50 100644 --- a/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh.erb +++ b/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh.erb @@ -7,6 +7,7 @@ # $version: Requested version to be installed. # $channel: Channel to install the product from # $project: Project to be installed +# $package-manager: Package manager used to install the product. # $cmdline_filename: Name of the package downloaded on local disk. # $cmdline_dl_dir: Name of the directory downloaded package will be saved to on local disk. # $install_strategy: Method of package installations. default strategy is to always install upon exec. Set to "once" to skip if project is installed @@ -18,8 +19,9 @@ # Defaults channel="stable" project="<%= default_product %>" +package_manager="" -while getopts pnv:c:f:P:d:s:l:a:L: opt +while getopts pnv:c:f:P:d:s:l:a:L:i: opt do case "$opt" in @@ -34,11 +36,19 @@ do l) download_url_override="$OPTARG";; a) checksum="$OPTARG";; L) license_id="$OPTARG";; + i) package_manager="$OPTARG";; \?) # unknown flag echo >&2 \ - "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]" + "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] [-i package_manager]" exit 1;; esac done shift `expr $OPTIND - 1` + +if test -d "/opt/$project" && test "x$install_strategy" = "xonce"; then + echo "$project installation detected" + echo "install_strategy set to 'once'" + echo "Nothing to install" + exit +fi diff --git a/lib/mixlib/install/generator/powershell.rb b/lib/mixlib/install/generator/powershell.rb index 44b96530..e1df42f1 100644 --- a/lib/mixlib/install/generator/powershell.rb +++ b/lib/mixlib/install/generator/powershell.rb @@ -24,7 +24,8 @@ class PowerShell < Base def self.install_ps1(context) install_project_module = [] install_project_module << get_script("helpers.ps1", context) - install_project_module << get_script("get_project_metadata.ps1", context) + install_project_module << get_script("get_project_metadata.ps1", context.merge(omnitruck_endpoint: omnitruck_endpoint)) + install_project_module << get_script("get_project_filename.ps1", context.merge(omnitruck_endpoint: omnitruck_endpoint)) install_project_module << get_script("install_project.ps1") install_command = [] @@ -46,7 +47,8 @@ def self.script_base_path def install_command install_project_module = [] install_project_module << get_script("helpers.ps1", user_agent_headers: options.user_agent_headers) - install_project_module << get_script("get_project_metadata.ps1") + install_project_module << get_script("get_project_metadata.ps1", omnitruck_endpoint: omnitruck_endpoint) + install_project_module << get_script("get_project_filename.ps1", omnitruck_endpoint: omnitruck_endpoint) install_project_module << get_script("install_project.ps1") install_command = [] install_command << ps1_modularize(install_project_module.join("\n"), "Installer-Module") diff --git a/lib/mixlib/install/generator/powershell/scripts/get_project_filename.ps1.erb b/lib/mixlib/install/generator/powershell/scripts/get_project_filename.ps1.erb new file mode 100644 index 00000000..bd1cdbd4 --- /dev/null +++ b/lib/mixlib/install/generator/powershell/scripts/get_project_filename.ps1.erb @@ -0,0 +1,121 @@ +function Get-ProjectFileName { + <# + .SYNOPSIS + Get filename for a Chef Software, Inc. project package + .DESCRIPTION + Get the actual filename for a project package from the download API + .EXAMPLE + Get-ProjectFileName -project chef -channel stable -version latest + + Gets the filename for the latest stable release of Chef. + #> + [cmdletbinding()] + param ( + # Base url to retrieve filename from. + [uri]$base_server_uri = '<%= omnitruck_endpoint %>', + # Project to install + [string] + $project = 'chef', + # Version of the application to install + [string] + $version, + # Release channel to install from + [validateset('current', 'stable', 'unstable')] + [string] + $channel = 'stable', + # The following legacy switches are just aliases for the current channel + [switch] + $prerelease, + [switch] + $nightlies, + [validateset('auto', 'i386', 'x86_64')] + [string] + $architecture = 'auto', + # Package manager type + [validateset('msi', 'pm')] + [string] + $package_manager, + # License ID for commercial API access + [string] + $license_id + ) + + # The following legacy switches are just aliases for the current channel + if (($prerelease -eq $true)) { $channel = 'current'} + if (($nightlies -eq $true)) { $channel = 'current'} + + # PowerShell is only on Windows ATM + $platform = 'windows' + Write-Verbose "Platform: $platform" + + $platform_version = Get-PlatformVersion + Write-Verbose "Platform Version: $platform_version" + + if ($architecture -eq 'auto') { + $architecture = Get-PlatformArchitecture + } + + Write-Verbose "Architecture: $architecture" + Write-Verbose "Project: $project" + + # Validate unsupported projects + if ($project -eq 'migrate-ice') { + throw "The project 'migrate-ice' is not supported by this script." + } + + # Use commercial API if license_id is provided, otherwise use omnitruck + if ($license_id) { + # Check if license_id starts with 'free-' or 'trial-' for trial API + $base_server_uri = '<%= omnitruck_endpoint %>' + } + + $filename_base_url = "/$($channel)/$($project)/filename" + + # chef-ice uses different parameters + if ($project -eq 'chef-ice') { + $filename_array = @( + "?v=$($version)", + "p=$platform", + "m=$architecture", + "pm=msi" + ) + } else { + $filename_array = @( + "?v=$($version)", + "p=$platform", + "pv=$platform_version", + "m=$architecture" + ) + } + + # Add license_id to query parameters if provided + if ($license_id) { + $filename_array += "license_id=$license_id" + } + + $filename_base_url += [string]::join('&', $filename_array) + $filename_url = new-uri $base_server_uri $filename_base_url + + Write-Verbose "Downloading $project filename from $filename_url" + $response = (Get-WebContent $filename_url).trim() + + # Commercial and trial APIs return JSON, omnitruck returns text format + if ($license_id) { + # Parse JSON response from commercial/trial API + try { + $json = $response | ConvertFrom-Json + $package_filename = @{ + filename = $json.filename + } + } catch { + throw "Failed to parse JSON response from API: $_" + } + } else { + # Parse text response from omnitruck (if needed in future) + # For now, omnitruck doesn't have a filename endpoint + throw "Filename endpoint requires license_id parameter" + } + + Write-Verbose "Filename: $($package_filename.filename)" + $package_filename +} diff --git a/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb b/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb index 21a0a1d9..b3bfa865 100644 --- a/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb +++ b/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb @@ -40,6 +40,10 @@ function Get-ProjectMetadata { [validateset('auto', 'i386', 'x86_64')] [string] $architecture = 'auto', + # Package manager type + [validateset('msi', 'pm')] + [string] + $package_manager, # License ID for commercial API access [string] $license_id @@ -63,35 +67,47 @@ function Get-ProjectMetadata { Write-Verbose "Architecture: $architecture" Write-Verbose "Project: $project" + # Validate unsupported projects + if ($project -eq 'migrate-ice') { + throw "The project 'migrate-ice' is not supported by this script." + } + # Use commercial API if license_id is provided, otherwise use omnitruck if ($license_id) { - # Check if license_id starts with 'free-' or 'trial-' for trial API - if ($license_id -match '^(free-|trial-)') { - $base_server_uri = 'https://chefdownload-trial.chef.io' - Write-Verbose "Using Trial API with license ID" - } else { - $base_server_uri = 'https://chefdownload-commercial.chef.io' - Write-Verbose "Using Commercial API with license ID" - } + $base_server_uri = '<%= omnitruck_endpoint %>' + Write-Verbose "Using New Download API at $base_server_uri" } $metadata_base_url = "/$($channel)/$($project)/metadata" - $metadata_array = ("?v=$($version)", - "p=$platform", - "pv=$platform_version", - "m=$architecture") - + + # chef-ice uses different parameters + if ($project -eq 'chef-ice') { + $metadata_array = @( + "?v=$($version)", + "p=$platform", + "m=$architecture", + "pm=msi" + ) + } else { + $metadata_array = @( + "?v=$($version)", + "p=$platform", + "pv=$platform_version", + "m=$architecture" + ) + } + # Add license_id to query parameters if provided if ($license_id) { $metadata_array += "license_id=$license_id" } - + $metadata_base_url += [string]::join('&', $metadata_array) $metadata_url = new-uri $base_server_uri $metadata_base_url Write-Verbose "Downloading $project details from $metadata_url" $response = (Get-WebContent $metadata_url).trim() - + # Commercial and trial APIs return JSON, omnitruck returns text format if ($license_id) { # Parse JSON response from commercial/trial API diff --git a/lib/mixlib/install/generator/powershell/scripts/install_project.ps1.erb b/lib/mixlib/install/generator/powershell/scripts/install_project.ps1.erb index c8767242..eedf1b73 100644 --- a/lib/mixlib/install/generator/powershell/scripts/install_project.ps1.erb +++ b/lib/mixlib/install/generator/powershell/scripts/install_project.ps1.erb @@ -57,11 +57,28 @@ function Install-Project { # Set to 'once' to skip install if project is detected [string] $install_strategy, + # Package manager type + [validateset('msi', 'pm')] + [string] + $package_manager, # License ID for commercial API access [string] $license_id ) + # Set package_manager for chef-ice + if ($project -eq 'chef-ice') { + $package_manager = 'msi' + $platform_version = 'pv' + } else { + $package_manager = 'pm' + } + + # Validate unsupported projects + if ($project -eq 'migrate-ice') { + throw "The project 'migrate-ice' is not supported by this script." + } + # Check for chef-client command in various locations $chef_locations = @( "$env:systemdrive\hab\pkgs\chef\chef-infra-client\*\*\bin\chef-client.bat", @@ -89,7 +106,7 @@ function Install-Project { $download_url = $download_url_override $sha256 = $checksum } else { - $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture -license_id $license_id + $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture -package_manager $package_manager -license_id $license_id $download_url = $package_metadata.url $sha256 = $package_metadata.sha256 } @@ -102,12 +119,19 @@ function Install-Project { } } else { - # For licensed downloads, we won't know the filename until after download - if ([string]::IsNullOrEmpty($license_id)) { - $filename = (([System.Uri]$download_url).AbsolutePath -split '/')[-1] + # For licensed downloads, fetch the actual filename from the API + if (-not [string]::IsNullOrEmpty($license_id)) { + try { + $filename_resp = Get-ProjectFileName -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture -package_manager $package_manager -license_id $license_id + $filename = $filename_resp.filename + } catch { + Write-Verbose "Could not fetch filename from API: $_" + # Fallback to extracting from URL if available + $filename = (([System.Uri]$download_url).AbsolutePath -split '/')[-1] + } } else { - $filename = "chef-download-temp-$PID" - Write-Host "Using temporary filename for licensed download: $filename" + # Traditional omnitruck downloads have filename in URL + $filename = (([System.Uri]$download_url).AbsolutePath -split '/')[-1] } } Write-Host "Download directory: $download_directory" @@ -146,28 +170,7 @@ function Install-Project { if (-not ($cached_installer_available)) { if ($pscmdlet.ShouldProcess("$($download_url)", "Download $project")) { Write-Host "Downloading $project from $($download_url) to $download_destination." - $download_result = Get-WebContent $download_url -filepath $download_destination - - # For licensed downloads, extract actual filename from Content-Disposition - if (-not [string]::IsNullOrEmpty($license_id) -and $download_result -and $download_result.Filename) { - $actual_filename = $download_result.Filename - Write-Host "Extracted filename from Content-Disposition: $actual_filename" - - $final_destination = join-path $download_directory $actual_filename - - # Move temp file to final location with correct name - if ($download_destination -ne $final_destination) { - Write-Host "Moving to final location: $final_destination" - if (test-path $final_destination) { - remove-item $final_destination -force - } - move-item $download_destination $final_destination -force - $download_destination = $final_destination - } - } elseif (-not [string]::IsNullOrEmpty($license_id)) { - Write-Host "Warning: Could not extract filename from Content-Disposition header for licensed download." - Write-Host "Using temporary filename. Package installation may fail." - } + Get-WebContent $download_url -filepath $download_destination } } @@ -196,14 +199,17 @@ function Install-Project { set-alias install -value Install-Project Function Install-ChefMsi($msi, $addlocal) { + $logPath = Join-Path $env:TEMP "chef-msi-install-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" + Write-Host "MSI installation log will be written to: $logPath" + if ($addlocal -eq "service") { - $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /i $msi ADDLOCAL=`"ChefClientFeature,ChefServiceFeature`"" -Passthru -Wait -NoNewWindow + $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /l*v `"$logPath`" /i `"$msi`" ADDLOCAL=`"ChefClientFeature,ChefServiceFeature`"" -Passthru -Wait -NoNewWindow } ElseIf ($addlocal -eq "task") { - $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /i $msi ADDLOCAL=`"ChefClientFeature,ChefSchTaskFeature`"" -Passthru -Wait -NoNewWindow + $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /l*v `"$logPath`" /i `"$msi`" ADDLOCAL=`"ChefClientFeature,ChefSchTaskFeature`"" -Passthru -Wait -NoNewWindow } ElseIf ($addlocal -eq "auto") { - $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /i $msi" -Passthru -Wait -NoNewWindow + $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /l*v `"$logPath`" /i `"$msi`"" -Passthru -Wait -NoNewWindow } $p.WaitForExit() @@ -211,8 +217,15 @@ Function Install-ChefMsi($msi, $addlocal) { Write-Host "$((Get-Date).ToString()) - Another msi install is in progress (exit code 1618), retrying ($($installAttempts))..." return $false } elseif ($p.ExitCode -ne 0) { - throw "msiexec was not successful. Received exit code $($p.ExitCode)" + Write-Host "MSI installation failed. Check log file: $logPath" + if (Test-Path $logPath) { + $logTail = Get-Content $logPath -Tail 50 + Write-Host "Last 50 lines of MSI log:" + Write-Host $logTail + } + throw "msiexec was not successful. Received exit code $($p.ExitCode). Log: $logPath" } + Write-Host "MSI installation completed successfully" return $true } @@ -239,4 +252,4 @@ Function Install-ChefAppx($appx, $project) { return $true } -export-modulemember -function 'Install-Project','Get-ProjectMetadata' -alias 'install' +export-modulemember -function 'Install-Project','Get-ProjectMetadata','Get-ProjectFileName' -alias 'install'