diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d98bc4c0..a8b27602d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -565,7 +565,7 @@ - add apt-transport-https for ubuntu. (epcim) #896 - Fix expanding shell script position parameters with nounset enabled. (vutny) #895 - RFC: Add tests for bootstrap-salt.ps1. (themalkolm) #893 -- Keep original name of salt executable executable. (themalkolm) #857 +- Keep original name of salt executable. (themalkolm) #857 # v2016.06.27: diff --git a/bootstrap-salt.ps1 b/bootstrap-salt.ps1 index b7d528e46..6481ae6d6 100644 --- a/bootstrap-salt.ps1 +++ b/bootstrap-salt.ps1 @@ -5,61 +5,32 @@ .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 + ./bootstrap-salt.ps1 -RunService false Specifies the salt-minion service to stop and be set to manual. Useful for testing locally from the command line with the --local switch .EXAMPLE - ./bootstrap-salt.ps1 -minion minion-box -master master-box + ./bootstrap-salt.ps1 -Minion minion-box -Master master-box Specifies the minion and master ids in the minion config. Defaults to the 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 @@ -253,6 +243,7 @@ function Get-FileHash { } } catch { Write-Verbose "Error hashing: $Path" + Write-Verbose "ERROR: $_" return @{} } finally { if ($null -ne $data) { @@ -301,27 +292,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 +347,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 +403,61 @@ $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 { + Write-Verbose "Looking for $RepoUrl/repo.json" + $response = Invoke-WebRequest "$RepoUrl/repo.json" ` + -DisableKeepAlive ` + -UseBasicParsing ` + -Method Head + if ( $response.StatusCode -eq "200" ) { + Write-Verbose "Found $RepoUrl/repo.json" + # This URL contains a repo.json file, let's use it + $use_repo_json = $true + } else { + Write-Verbose "Did not find $RepoUrl/repo.json" + # No repo.json file found at the default location + $use_repo_json = $false + } +} catch { + Write-Verbose "There was an error looking up $RepoUrl/repo.json" + Write-Verbose "ERROR: $_" + $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 { + Write-Verbose "Downloading $RepoUrl/repo.json" + $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" + Write-Host "ERROR: $_" + $hash = @{} + } + + $searchVersion = $Version.ToLower() + if ( $hash.Contains($searchVersion)) { + Write-Verbose "Found $searchVersion in $RepoUrl/repo.json" + foreach ($item in $hash.($searchVersion).Keys) { + if ( $item.ToLower().EndsWith(".exe") ) { + if ( $item.ToLower().Contains($arch.ToLower()) ) { + $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 + Write-Verbose "Searching for $searchVersion in $RepoUrl/minor/repo.json" + $response = Invoke-WebRequest -Uri "$RepoUrl/minor/repo.json" -UseBasicParsing if ($response.Content.GetType().Name -eq "Byte[]") { $psobj = $enc.GetString($response.Content) | ConvertFrom-Json } else { @@ -447,32 +465,78 @@ if ( ($customUrl) -or ($majorVersion -lt 3006) ) { } $hash = Convert-PSObjectToHashtable $psobj } catch { - Write-Verbose "repo.json not found at: $RepoUrl" + Write-Verbose "repo.json not found at: $RepoUrl/minor/repo.json" + Write-Verbose "ERROR: $_" $hash = @{} } - - $searchVersion = $Version.ToLower() if ( $hash.Contains($searchVersion)) { + Write-Verbose "Found $searchVersion in $RepoUrl/minor/repo.json" foreach ($item in $hash.($searchVersion).Keys) { - if ( $item.EndsWith(".exe") ) { - if ( $item.Contains($arch) ) { + if ( $item.ToLower().EndsWith(".exe") ) { + if ( $item.ToLower().Contains($arch.ToLower()) ) { $saltFileName = $hash.($searchVersion).($item).name $saltVersion = $hash.($searchVersion).($item).version $saltSha512 = $hash.($searchVersion).($item).SHA512 } } } + } else { + Write-Verbose "Version not found in $RepoUrl/minor/repo.json" } - if ( $saltFileName -and $saltVersion -and $saltSha512 ) { - if ( $RepoUrl.Contains("minor") ) { - $saltFileUrl = @($RepoUrl, $saltVersion, $saltFileName) -join "/" - } else { - $saltFileUrl = @($RepoUrl, "minor", $saltVersion, $saltFileName) -join "/" - } + } +} + +if ( $saltFileName -and $saltVersion -and $saltSha512 ) { + Write-Verbose "Found Name, Version, and Sha" +} else { + # We will guess the name of the installer + Write-Verbose "Failed to get Name, Version, and Sha from repo.json" + Write-Verbose "We'll try to find the file in standard paths" + $saltFileName = "Salt-Minion-$Version-Py3-$arch-Setup.exe" + $saltVersion = $Version +} + +Write-Verbose "Creating list of urls using the following:" +Write-Verbose "RepoUrl: $RepoUrl" +Write-Verbose "Version: $saltVersion" +Write-Verbose "File Name: $saltFileName" +$urls = $(@($RepoUrl, $saltVersion, $saltFileName) -join "/"), + $(@($RepoUrl, "minor", $saltVersion, $saltFileName) -join "/"), + $(@($RepoUrl, $saltFileName) -join "/"), + $(@($oldRepoUrl, $saltFileName) -join "/") + +$saltFileUrl = $null + +foreach ($url in $urls) { + try { + Write-Verbose "Looking for installer at: $url" + $response = Invoke-WebRequest "$url" ` + -DisableKeepAlive ` + -UseBasicParsing ` + -Method Head + if ( $response.StatusCode -eq "200" ) { + Write-Verbose "Found installer" + # This URL contains a repo.json file, let's use it + $saltFileUrl = $url + break + } else { + Write-Verbose "Installer not found: $url" } + } catch { + Write-Verbose "ERROR: $url" } } +if ( !$saltFileUrl ) { + Write-Host "Could not find an installer:" + Write-Verbose "Here are the urls searched:" + foreach ($url in $urls) { + Write-Verbose $url + } + exit 1 +} + + #=============================================================================== # Download minion setup file #=============================================================================== @@ -481,10 +545,19 @@ 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 + +$localFile = "$env:TEMP\$saltFileName" + Write-Host "Downloading Installer: " -NoNewline +Write-Verbose "" +Write-Verbose "Salt File URL: $saltFileUrl" +Write-Verbose "Local File: $localFile" + $webclient = New-Object System.Net.WebClient -$localFile = "C:\Windows\Temp\$saltFileName" $webclient.DownloadFile($saltFileUrl, $localFile) if ( Test-Path -Path $localFile ) { @@ -496,6 +569,9 @@ if ( Test-Path -Path $localFile ) { if ( $saltSha512 ) { $localSha512 = (Get-FileHash -Path $localFile -Algorithm SHA512).Hash Write-Host "Comparing Hash: " -NoNewline + Write-Verbose "" + Write-Verbose "Local Hash: $localSha512" + Write-Verbose "Remote Hash: $saltSha512" if ( $localSha512 -eq $saltSha512 ) { Write-Host "Success" -ForegroundColor Green } else { @@ -514,72 +590,124 @@ 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 - -#=============================================================================== -# 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 - 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 - } - - # 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 "Installing Salt Minion (5 min timeout): " -NoNewline +Write-Verbose "" +Write-Verbose "Local File: $localFile" +Write-Verbose "Parameters: $parameters" +$process = Start-Process $localFile ` + -WorkingDirectory $(Split-Path $localFile -Parent) ` + -ArgumentList "/S /start-service=0 $parameters" ` + -NoNewWindow -PassThru + +# Sometimes the installer hangs... we'll wait 5 minutes and then kill it +Write-Verbose "" +Write-Verbose "Waiting for installer to finish" +$process | Wait-Process -Timeout 300 -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 } -} 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 + 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 = 15 # We'll try for 30 seconds +Write-Verbose "Checking that the service is installed" +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 - 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 +#=============================================================================== +# Configure the minion service +#=============================================================================== +if( $RunService ) { + # Start the service + Write-Host "Starting Service: " -NoNewline + Write-Verbose "" + $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 + Write-Verbose "Checking the service status" + $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 + } + } + } +} else { + # 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 + } + } + # 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 + } } } diff --git a/kitchen.windows.yml b/kitchen.windows.yml index a21ffa685..a3cb442d7 100644 --- a/kitchen.windows.yml +++ b/kitchen.windows.yml @@ -9,7 +9,7 @@ driver: provisioner: salt_bootstrap_url: D:/a/salt-bootstrap/salt-bootstrap/bootstrap-salt.ps1 - salt_bootstrap_options: -pythonVersion 3 -version %s + salt_bootstrap_options: -Version %s -Verbose init_environment: '' platforms: