From a0c99153f96c7f3953247d6869df268497e149bc Mon Sep 17 00:00:00 2001 From: Shane Lee Date: Mon, 25 Mar 2024 15:13:38 -0600 Subject: [PATCH] Fix script to support installing RC on Windows --- bootstrap-salt.ps1 | 347 +++++++++++++++++++++++++++------------------ 1 file changed, 206 insertions(+), 141 deletions(-) diff --git a/bootstrap-salt.ps1 b/bootstrap-salt.ps1 index b7d528e..17dcc1a 100644 --- a/bootstrap-salt.ps1 +++ b/bootstrap-salt.ps1 @@ -5,23 +5,18 @@ .DESCRIPTION The script will download the official Salt package from SaltProject. It will install a specific package version and accept parameters for the master and - minion ids. Finally, it can stop and set the Windows service to "manual" for + minion IDs. Finally, it can stop and set the Windows service to "manual" for local testing. .EXAMPLE ./bootstrap-salt.ps1 - Runs without any parameters. Uses all the default values/settings. + Runs without any parameters. Uses all the default values/settings. Will + install the latest version of Salt .EXAMPLE - ./bootstrap-salt.ps1 -version 2017.7.0 + ./bootstrap-salt.ps1 -version 3006.7 Specifies a particular version of the installer. -.EXAMPLE - ./bootstrap-salt.ps1 -pythonVersion 3 - Specifies the Python version of the installer. Can be "2" or "3". Defaults - to "2". Python 3 installers are only available for Salt 2017.7.0 and newer. - Starting with Python 3002 only Python 3 installers are available. - .EXAMPLE ./bootstrap-salt.ps1 -runservice false Specifies the salt-minion service to stop and be set to manual. Useful for @@ -33,33 +28,9 @@ installer values of host name for the minion id and "salt" for the master. .EXAMPLE - ./bootstrap-salt.ps1 -minion minion-box -master master-box -version 2017.7.0 -runservice false + ./bootstrap-salt.ps1 -minion minion-box -master master-box -version 3006.7 -runservice false Specifies all the optional parameters in no particular order. -.PARAMETER version - The version of the Salt minion to install. Default is "latest" which will - install the latest version of Salt minion available. - -.PARAMETER pythonVersion - The version of Python the installer should use. Specify either "2" or "3". - Beginning with Salt 2017.7.0, Salt will run on either Python 2 or Python 3. - The default is Python 2 if not specified. This parameter only works for Salt - -.PARAMETER runservice - Boolean flag to start or stop the minion service. True will start the minion - service. False will stop the minion service and set it to "manual". The - installer starts it by default. - -.PARAMETER minion - Name of the minion being installed on this host. Installer defaults to the - host name. - -.PARAMETER master - Name or IP of the master server. Installer defaults to "salt". - -.PARAMETER repourl - URL to the windows packages. Default is "https://repo.saltproject.io/windows" - .NOTES All of the parameters are optional. The default should be the latest version. The architecture is dynamically determined by the script. @@ -68,7 +39,7 @@ Salt Bootstrap GitHub Project (script home) - https://github.com/saltstack/salt-bootstrap Original Vagrant Provisioner Project - https://github.com/saltstack/salty-vagrant Vagrant Project (utilizes this script) - https://github.com/mitchellh/vagrant - Salt Download Location - https://repo.saltproject.io/windows/ + Salt Download Location - https://repo.saltproject.io/salt/py3/windows #> #=============================================================================== @@ -77,43 +48,62 @@ [CmdletBinding()] param( [Parameter(Mandatory=$false, ValueFromPipeline=$True)] - # Doesn't support versions prior to "YYYY.M.R-B" - # Supports new version and latest - # Option 1 means case insensitive [ValidatePattern('^(\d{4}(\.\d{1,2}){0,2}(\-\d{1})?)|(latest)$', Options=1)] [Alias("v")] + # The version of the Salt minion to install. Default is "latest" which will + # install the latest version of Salt minion available. Doesn't support + # versions prior to "YYYY.M.R-B" [String]$Version = "latest", - [Parameter(Mandatory=$false, ValueFromPipeline=$True)] - # Python 3 support was added in 2017. Python 2 support was dropped in - # version 3001. This parameter is ignored for all versions before 2017 and - # after 3000. - [ValidateSet("2","3")] - [Alias("p")] - [String]$PythonVersion = "3", - [Parameter(Mandatory=$false, ValueFromPipeline=$True)] [ValidateSet("true","false")] [Alias("s")] + # Boolean flag to start or stop the minion service. True will start the + # minion service. False will stop the minion service and set it to "manual". + # The installer starts it by default. [String]$RunService = "true", [Parameter(Mandatory=$false, ValueFromPipeline=$True)] [Alias("m")] + # Name of the minion being installed on this host. Installer defaults to the + # host name. [String]$Minion = "not-specified", [Parameter(Mandatory=$false, ValueFromPipeline=$True)] [Alias("a")] + #Name or IP of the master server. Installer defaults to "salt". [String]$Master = "not-specified", [Parameter(Mandatory=$false, ValueFromPipeline=$True)] [Alias("r")] + # URL to the windows packages. Will look for a file named repo.json at the + # root of the URL. This file is used to determine the name and location of + # the installer in the repo. If repo.json is not found, it will look for the + # file under the minor directory. + # Default is "https://repo.saltproject.io/salt/py3/windows" [String]$RepoUrl = "https://repo.saltproject.io/salt/py3/windows", [Parameter(Mandatory=$false, ValueFromPipeline=$True)] [Alias("c")] - [Switch]$ConfigureOnly + # Vagrant only + # Vagrant files are placed in "C:\tmp". Copies Salt config files from + # Vagrant (C:\tmp) to Salt config locations and exits. Does not run the + # installer + [Switch]$ConfigureOnly, + + [Parameter(Mandatory=$false)] + [Alias("h")] + # Displays help for this script. + [Switch] $Help ) +# We'll check for help first because it really has no requirements +if ($help) { + # Get the full script name + $this_script = & {$myInvocation.ScriptName} + Get-Help $this_script -Detailed + exit 0 +} #=============================================================================== # Script Preferences @@ -301,27 +291,11 @@ if (!(Get-IsAdministrator)) { $defaultUrl = "https://repo.saltproject.io/salt/py3/windows" $oldRepoUrl = "https://repo.saltproject.io/windows" $majorVersion = Get-MajorVersion -Version $Version -$customUrl = $true -if ( $Version.ToLower() -ne "latest" ) { - # A specific version has been passed - # We only want to modify the URL if a custom URL was not passed - $uri = [Uri]($RepoUrl) - if ( $uri.AbsoluteUri -eq $defaultUrl ) { - # No customURL passed, let's check for a pre 3006 version - $customUrl = $false - if ( $majorVersion -lt "3006" ) { - # This is an older version, use the old URL - $RepoUrl = $oldRepoUrl - } else { - # This is a new URL, and a version was passed, let's look in minor - if ( $Version.ToLower() -ne $majorVersion.ToLower() ) { - $RepoUrl = "$RepoUrl/minor" - } - } - } -} else { - if ( $RepoUrl -eq $defaultUrl ) { - $customUrl = $false +if ( [Uri]($RepoUrl).AbsoluteUri -eq $defaultUrl ) { + # No customURL passed, let's check for a pre 3006 version + if ($majorVersion -lt "3006") { + # This is an older version, use the old URL + $RepoUrl = $oldRepoUrl } } @@ -372,9 +346,6 @@ $ConfDir = "$RootDir\conf" $PkiDir = "$ConfDir\pki\minion" Write-Verbose "ConfDir: $ConfDir" -# Create C:\tmp\ -New-Item C:\tmp\ -ItemType directory -Force | Out-Null - #=============================================================================== # Copy Vagrant Files to their proper location. #=============================================================================== @@ -431,15 +402,52 @@ $saltFileName = "" $saltVersion = "" $saltSha512= "" $saltFileUrl = "" -if ( ($customUrl) -or ($majorVersion -lt 3006) ) { - $saltFileName = "Salt-Minion-$Version-Py3-$arch-Setup.exe" - $saltVersion = $Version - $saltFileUrl = "$RepoUrl/$saltFileName" -} else { - if ( $majorVersion -ge 3006 ) { - $enc = [System.Text.Encoding]::UTF8 +# Look for a repo.json file +try { + $response = Invoke-WebRequest "$RepoUrl/repo.json" ` + -DisableKeepAlive ` + -UseBasicParsing ` + -Method Head + if ( $response.StatusCode -eq "200" ) { + # This URL contains a repo.json file, let's use it + $use_repo_json = $true + } else { + # + $use_repo_json = $false + } +} catch { + $use_repo_json = $false +} +if ( $use_repo_json ) { + # We will use the json file to get the name of the installer + $enc = [System.Text.Encoding]::UTF8 + try { + $response = Invoke-WebRequest -Uri "$RepoUrl/repo.json" -UseBasicParsing + if ($response.Content.GetType().Name -eq "Byte[]") { + $psobj = $enc.GetString($response.Content) | ConvertFrom-Json + } else { + $psobj = $response.Content | ConvertFrom-Json + } + $hash = Convert-PSObjectToHashtable $psobj + } catch { + Write-Verbose "repo.json not found at: $RepoUrl" + $hash = @{} + } + + $searchVersion = $Version.ToLower() + if ( $hash.Contains($searchVersion)) { + foreach ($item in $hash.($searchVersion).Keys) { + if ( $item.EndsWith(".exe") ) { + if ( $item.Contains($arch) ) { + $saltFileName = $hash.($searchVersion).($item).name + $saltVersion = $hash.($searchVersion).($item).version + $saltSha512 = $hash.($searchVersion).($item).SHA512 + } + } + } + } else { try { - $response = Invoke-WebRequest -Uri "$RepoUrl/repo.json" -UseBasicParsing + $response = Invoke-WebRequest -Uri "$RepoUrl/minor/repo.json" -UseBasicParsing if ($response.Content.GetType().Name -eq "Byte[]") { $psobj = $enc.GetString($response.Content) | ConvertFrom-Json } else { @@ -450,8 +458,6 @@ if ( ($customUrl) -or ($majorVersion -lt 3006) ) { Write-Verbose "repo.json not found at: $RepoUrl" $hash = @{} } - - $searchVersion = $Version.ToLower() if ( $hash.Contains($searchVersion)) { foreach ($item in $hash.($searchVersion).Keys) { if ( $item.EndsWith(".exe") ) { @@ -462,17 +468,29 @@ if ( ($customUrl) -or ($majorVersion -lt 3006) ) { } } } - } - if ( $saltFileName -and $saltVersion -and $saltSha512 ) { - if ( $RepoUrl.Contains("minor") ) { - $saltFileUrl = @($RepoUrl, $saltVersion, $saltFileName) -join "/" - } else { - $saltFileUrl = @($RepoUrl, "minor", $saltVersion, $saltFileName) -join "/" - } + } else { + Write-Host "Version not found in $RepoUrl/repo.json" + exit 1 } } + if ( $saltFileName -and $saltVersion -and $saltSha512 ) { + if ( $RepoUrl.Contains("minor") ) { + $saltFileUrl = @($RepoUrl, $saltVersion, $saltFileName) -join "/" + } else { + $saltFileUrl = @($RepoUrl, "minor", $saltVersion, $saltFileName) -join "/" + } + } else { + Write-Host "Failed to get Name, Version, and Sha" + exit 1 + } +} else { + # We will guess the name of the installer + $saltFileName = "Salt-Minion-$Version-Py3-$arch-Setup.exe" + $saltVersion = $Version + $saltFileUrl = "$RepoUrl/$saltFileName" } + #=============================================================================== # Download minion setup file #=============================================================================== @@ -481,10 +499,13 @@ Write-Host " Bootstrapping Salt Minion" -ForegroundColor Green Write-Host " - version: $Version" Write-Host " - file name: $saltFileName" Write-Host " - file url: $saltFileUrl" +Write-Host " - master: $Master" +Write-Host " - minion id: $Minion" +Write-Host " - start service: $RunService" Write-Host "-------------------------------------------------------------------------------" -ForegroundColor Yellow Write-Host "Downloading Installer: " -NoNewline $webclient = New-Object System.Net.WebClient -$localFile = "C:\Windows\Temp\$saltFileName" +$localFile = "$env:TEMP\$saltFileName" $webclient.DownloadFile($saltFileUrl, $localFile) if ( Test-Path -Path $localFile ) { @@ -514,72 +535,116 @@ if ( $saltSha512 ) { $parameters = "" if($Minion -ne "not-specified") {$parameters = "/minion-name=$Minion"} if($Master -ne "not-specified") {$parameters = "$parameters /master=$Master"} -if($RunService -eq $false) {$parameters = "$parameters /start-service=0"} #=============================================================================== # Install minion silently #=============================================================================== #Wait for process to exit before continuing. Write-Host "Installing Salt Minion: " -NoNewline -Start-Process $localFile -ArgumentList "/S $parameters" -Wait -NoNewWindow -PassThru | Out-Null +$process = Start-Process $localFile ` + -ArgumentList "/S /start-service=0 $parameters" ` + -NoNewWindow -PassThru + +# Sometimes the installer hangs... we'll wait 60s and then kill it +$process | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue +$process.Refresh() + +if ( !$process.HasExited ) { + Write-Host "Timedout" -ForegroundColor Yellow + Write-Host "Killing hung installer: " -NoNewline + $process | Stop-Process + $process.Refresh() + if ( $process.HasExited ) { + Write-Host "Success" -ForegroundColor Green + } else { + Write-Host "Failed" -ForegroundColor Red + exit 1 + } + + Write-Host "Checking installed service: " -NoNewline +} + +# Wait for salt-minion service to be registered to verify successful +# installation +$service = Get-Service salt-minion -ErrorAction SilentlyContinue +$tries = 0 +$max_tries = 5 # We'll try for 10 seconds +while ( ! $service ) { + # We'll keep trying to get a service object until we're successful, or we + # reach max_tries + if ( $tries -le $max_tries ) { + $service = Get-Service salt-minion -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 + $tries += 1 + } else { + # If the salt-minion service is still not running, something + # probably went wrong and user intervention is required - report + # failure. + Write-Host "Failed" -ForegroundColor Red + Write-Host "Timed out waiting for the salt-minion service to be installed" + exit 1 + } +} +# If we get this far, the service was installed, we have a service object +Write-Host "Success" -ForegroundColor Green #=============================================================================== # Configure the minion service #=============================================================================== -# Wait for salt-minion service to be registered before trying to start it -$service = Get-Service salt-minion -ErrorAction SilentlyContinue -while (!$service) { - Start-Sleep -s 2 - $service = Get-Service salt-minion -ErrorAction SilentlyContinue -} -if ( $service ) { - Write-Host "Success" -ForegroundColor Green -} else { - Write-Host "Failed" -ForegroundColor Red - exit 1 -} - -if($RunService) { - # Start service +if( $RunService ) { + # Start the service Write-Host "Starting Service: " -NoNewline - Start-Service -Name "salt-minion" -ErrorAction SilentlyContinue - - # Check if service is started, otherwise retry starting the - # service 4 times. - $try = 0 - while (($service.Status -ne "Running") -and ($try -ne 4)) { - Start-Service -Name "salt-minion" -ErrorAction SilentlyContinue - $service = Get-Service salt-minion -ErrorAction SilentlyContinue - Start-Sleep -s 2 - $try += 1 + $tries = 0 + # We'll try for 2 minutes, sometimes the minion takes that long to start as + # it compiles python code for the first time + $max_tries = 60 + while ( $service.Status -ne "Running" ) { + if ( $service.Status -eq "Stopped" ) { + Start-Service -Name "salt-minion" -ErrorAction SilentlyContinue + } + Start-Sleep -Seconds 2 + $service.Refresh() + if ( $service.Status -eq "Running" ) { + Write-Host "Success" -ForegroundColor Green + } else { + if ( $tries -le $max_tries ) { + $tries += 1 + } else { + # If the salt-minion service is still not running, something + # probably went wrong and user intervention is required - report + # failure. + Write-Host "Failed" -ForegroundColor Red + Write-Host "Timed out waiting for the salt-minion service to start" + exit 1 + } + } } - - # If the salt-minion service is still not running, something probably - # went wrong and user intervention is required - report failure. - if ($service.Status -eq "Running") { - Write-Host "Success" -ForegroundColor Green - } else { - Write-Host "Failed" -ForegroundColor Red - exit 1 - } - } else { - Write-Host "Setting Service to 'Manual': " -NoNewline - Set-Service "salt-minion" -StartupType "Manual" - if ( (Get-Service "salt-minion").StartType -eq "Manual" ) { - Write-Host "Success" -ForegroundColor Green - } else { - Write-Host "Failed" -ForegroundColor Red - exit 1 + # Set the service to manual start + $service.Refresh() + if ( $service.StartType -ne "Manual" ) { + Write-Host "Setting Service Start Type to 'Manual': " -NoNewline + Set-Service "salt-minion" -StartupType "Manual" + $service.Refresh() + if ( $service.StartType -eq "Manual" ) { + Write-Host "Success" -ForegroundColor Green + } else { + Write-Host "Failed" -ForegroundColor Red + exit 1 + } } - - Write-Host "Stopping Service: " -NoNewline - Stop-Service "salt-minion" - if ( (Get-Service "salt-minion").Status -eq "Stopped" ) { - Write-Host "Success" -ForegroundColor Green - } else { - Write-Host "Failed" -ForegroundColor Red - exit 1 + # The installer should have installed the service stopped, but we'll make + # sure it is stopped here + if ( $service.Status -ne "Stopped" ) { + Write-Host "Stopping Service: " -NoNewline + Stop-Service "salt-minion" + $service.Refresh() + if ( $service.Status -eq "Stopped" ) { + Write-Host "Success" -ForegroundColor Green + } else { + Write-Host "Failed" -ForegroundColor Red + exit 1 + } } }