From 1770e7189a158ae900d363fe6ab8b3887daf5814 Mon Sep 17 00:00:00 2001 From: Jamie Phillips Date: Wed, 20 Oct 2021 12:01:57 -0400 Subject: [PATCH] Installation script for Rancher Rancher needs a very specific configuration when using Wins as an agent. This installation script handles that configuration and setup. Signed-off-by: Jamie Phillips --- install.ps1 | 588 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 588 insertions(+) create mode 100644 install.ps1 diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 00000000..3f9af276 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,588 @@ +<# +.SYNOPSIS + Installs Rancher Wins to create Windows Worker Nodes. +.DESCRIPTION + Run the script to install all Rancher Wins related needs. +.NOTES + Environment variables: + System Agent Variables + - CATTLE_AGENT_LOGLEVEL (default: debug) + - CATTLE_AGENT_CONFIG_DIR (default: C:/etc/rancher/agent) + - CATTLE_AGENT_VAR_DIR (default: C:/var/lib/rancher/agent) + Rancher 2.6+ Variables + - CATTLE_SERVER + - CATTLE_TOKEN + - CATTLE_CA_CHECKSUM + - CATTLE_ROLE_CONTROLPLANE=false + - CATTLE_ROLE_ETCD=false + - CATTLE_ROLE_WORKER=false + - CATTLE_LABELS + - CATTLE_TAINTS + Advanced Environment Variables + - CATTLE_AGENT_BINARY_URL (default: latest GitHub release) + - CATTLE_PRESERVE_WORKDIR (default: false) + - CATTLE_REMOTE_ENABLED (default: true) + - CATTLE_ID (default: autogenerate) + - CATTLE_AGENT_BINARY_LOCAL (default: false) + - CATTLE_AGENT_BINARY_LOCAL_LOCATION (default: ) +.EXAMPLE + +#> +#Make sure this params matches the CmdletBinding below +param ( + [Parameter()] + [String] + $Address, + [Parameter()] + [String] + $CaChecksum, + [Parameter()] + [String] + $InternalAddress, + [Parameter()] + [String] + $Label, + [Parameter()] + [String] + $NodeName, + [Parameter()] + [String] + $Server, + [Parameter()] + [String] + $Taint, + [Parameter()] + [String] + $Token, + [Parameter()] + [Switch] + $Worker +) +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$FALLBACK = "v0.1.1" + +function Invoke-WinsInstaller { + [CmdletBinding()] + param ( + [Parameter()] + [String] + $Address, + [Parameter()] + [String] + $CaChecksum, + [Parameter()] + [String] + $InternalAddress, + [Parameter()] + [String] + $Label, + [Parameter()] + [String] + $NodeName, + [Parameter()] + [String] + $Server, + [Parameter()] + [String] + $Taint, + [Parameter()] + [String] + $Token, + [Parameter()] + [Switch] + $Worker + ) + + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 + + function Write-LogInfo { + Write-Host -NoNewline -ForegroundColor Blue "INFO: " + Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) + } + function Write-LogWarn { + Write-Host -NoNewline -ForegroundColor DarkYellow "WARN: " + Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) + } + function Write-LogError { + Write-Host -NoNewline -ForegroundColor DarkRed "ERROR: " + Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) + } + function Write-LogFatal { + Write-Host -NoNewline -ForegroundColor DarkRed "FATA: " + Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) + exit 255 + } + + function Get-StringHash { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Value + ) + $stringAsStream = [System.IO.MemoryStream]::new() + $writer = [System.IO.StreamWriter]::new($stringAsStream) + $writer.write($Value) + $writer.Flush() + $stringAsStream.Position = 0 + return (Get-FileHash -InputStream $stringAsStream -Algorithm SHA256).Hash.ToLower() + } + + function Get-Args { + if ($Address) { + $env:CATTLE_ADDRESS = $Address + } + + if ($CaChecksum) { + $env:CATTLE_CA_CHECKSUM = $CaChecksum + } + + if ($InternalAddress) { + $env:CATTLE_INTERNAL_ADDRESS = $InternalAddress + } + + if ($Label) { + if ($env:CATTLE_LABELS) { + $env:CATTLE_LABELS += ",$Label" + } + else { + $env:CATTLE_LABELS = $Label + } + } + + if ($NodeName) { + $env:CATTLE_NODE_NAME = $NodeName + } + + if ($Server) { + $env:CATTLE_SERVER = $Server + } + + if ($Taint) { + if ($env:CATTLE_TAINTS) { + $env:CATTLE_TAINTS += ",$Taint" + } + else { + $env:CATTLE_TAINTS = $Taint + } + } + + if ($Token) { + $env:CATTLE_TOKEN = $Token + } + + if ($Worker) { + $env:CATTLE_ROLE_WORKER = "true" + } + } + + function Set-Path { + $env:PATH += ";C:\var\lib\rancher\rke2\bin;C:\usr\local\bin" + $environment = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + $environment = $environment.Insert($environment.Length, ";C:\var\lib\rancher\rke2\bin;C:\usr\local\bin") + [System.Environment]::SetEnvironmentVariable("Path", $environment, "Machine") + } + + function Set-Environment { + if (-Not $env:CATTLE_ROLE_CONTROLPLANE) { + $env:CATTLE_ROLE_CONTROLPLANE = "false" + } + + if (-Not $env:CATTLE_ROLE_ETCD) { + $env:CATTLE_ROLE_ETCD = "false" + } + + if (-Not $env:CATTLE_ROLE_WORKER) { + $env:CATTLE_ROLE_WORKER = "false" + } + + if (-Not $env:CATTLE_REMOTE_ENABLED) { + $env:CATTLE_REMOTE_ENABLED = "true" + } + else { + $env:CATTLE_REMOTE_ENABLED = $env:CATTLE_REMOTE_ENABLED.ToLower() + } + + if (-Not $env:CATTLE_PRESERVE_WORKDIR) { + $env:CATTLE_PRESERVE_WORKDIR = "false" + } + else { + $env:CATTLE_PRESERVE_WORKDIR = $env:CATTLE_PRESERVE_WORKDIR.ToLower() + } + + if (-Not $env:CATTLE_AGENT_LOGLEVEL) { + $env:CATTLE_AGENT_LOGLEVEL = "debug" + } + else { + $env:CATTLE_AGENT_LOGLEVEL = $env:CATTLE_AGENT_LOGLEVEL.ToLower() + } + + if ($env:CATTLE_AGENT_BINARY_LOCAL -eq "true") { + if (-Not $env:CATTLE_AGENT_BINARY_LOCAL_LOCATION) { + Write-LogFatal "No local binary location was specified" + } + $env:BINARY_SOURCE = "local" + } + else { + $env:BINARY_SOURCE = "remote" + if (-Not $env:CATTLE_AGENT_BINARY_URL -and $env:CATTLE_AGENT_BINARY_BASE_URL) { + $env:CATTLE_AGENT_BINARY_URL = "$env:CATTLE_AGENT_BINARY_BASE_URL/wins.exe" + } + + if (-Not $env:CATTLE_AGENT_BINARY_URL) { + if ($(Invoke-RestMethod -Uri https://api.github.com/rate_limit -TimeoutSec 60).rate.remaining -eq 0) { + Write-LogInfo "Error contacting GitHub to retrieve the latest version, falling back to version: $FALLBACK" + $env:VERSION = $FALLBACK + } + else { + try { + $env:VERSION = $(Invoke-RestMethod https://api.github.com/repos/rancher/wins/releases/latest -TimeoutSec 60).tag_name + } + catch { + Write-LogInfo "Error contacting GitHub to retrieve the latest version, falling back to version: $FALLBACK" + $env:VERSION = $FALLBACK + } + } + + $env:CATTLE_AGENT_BINARY_URL = "https://github.com/rancher/wins/releases/download/$env:VERSION/wins.exe" + $env:BINARY_SOURCE = "upstream" + } + } + + if ($env:CATTLE_REMOTE_ENABLED -eq "true") { + if (-Not $env:CATTLE_TOKEN) { + Write-LogFatal "Environment variable CATTLE_TOKEN was not set. Will not retrieve a remote connection configuration from Rancher2" + } + if (-Not $env:CATTLE_SERVER) { + Write-LogFatal "Environment variable CATTLE_SERVER was not set" + } + } + + if (-Not $env:CATTLE_AGENT_CONFIG_DIR) { + $env:CATTLE_AGENT_CONFIG_DIR = "C:/etc/rancher/wins" + Write-LogInfo "Using default agent configuration directory $( $env:CATTLE_AGENT_CONFIG_DIR )" + } + if (-Not (Test-Path $env:CATTLE_AGENT_CONFIG_DIR)) { + New-Item -Path $env:CATTLE_AGENT_CONFIG_DIR -ItemType Directory -Force | Out-Null + } + + if (-Not $env:CATTLE_AGENT_VAR_DIR) { + $env:CATTLE_AGENT_VAR_DIR = "C:/var/lib/rancher/agent" + Write-LogInfo "Using default agent var directory $( $env:CATTLE_AGENT_VAR_DIR )" + } + if (-Not (Test-Path $env:CATTLE_AGENT_VAR_DIR)) { + New-Item -Path $env:CATTLE_AGENT_VAR_DIR -ItemType Directory -Force | Out-Null + } + + if (-Not $env:CATTLE_AGENT_BIN_PREFIX) { + $env:CATTLE_AGENT_BIN_PREFIX = "c:/usr/local" + } + if (-Not (Test-Path $env:CATTLE_AGENT_BIN_PREFIX)) { + New-Item -Path $env:CATTLE_AGENT_BIN_PREFIX -ItemType Directory -Force | Out-Null + } + + $env:CATTLE_ADDRESS = Get-Address -Value $env:CATTLE_ADDRESS + $env:CATTLE_INTERNAL_ADDRESS = Get-Address -Value $env:CATTLE_INTERNAL_ADDRESS + } + + function Test-Architecture() { + if ($env:PROCESSOR_ARCHITECTURE -ne "AMD64") { + Write-LogFatal "Unsupported architecture $( $env:PROCESSOR_ARCHITECTUR )" + } + } + + function Invoke-WinsAgentDownload() { + if (-Not (Test-Path "$env:CATTLE_AGENT_BIN_PREFIX/bin")) { + New-Item -Path "$env:CATTLE_AGENT_BIN_PREFIX/bin" -ItemType Directory -Force | Out-Null + } + + if ($env:CATTLE_AGENT_BINARY_LOCAL -eq "true") { + Write-LogInfo "Using local Wins installer from $($env:CATTLE_AGENT_BINARY_LOCAL_LOCATION)" + Copy-Item -Path $env:CATTLE_AGENT_BINARY_LOCAL -Destination "$env:CATTLE_AGENT_BIN_PREFIX/bin/wins.exe" + } + else { + Write-LogInfo "Downloading Wins from $($env:CATTLE_AGENT_BINARY_URL)" + if ($env:BINARY_SOURCE -ne "upstream") { + $env:CURL_BIN_CAFLAG = $env:CURL_CAFLAG + } + else { + $env:CURL_BIN_CAFLAG = "" + } + + $retries = 0 + while ($retries -lt 6) { + $responseCode = $(curl.exe --connect-timeout 60 --max-time 300 --write-out "%{http_code}\n" $env:CURL_BIN_CAFLAG -sfL "$($env:CATTLE_AGENT_BINARY_URL)" -o "$env:CATTLE_AGENT_BIN_PREFIX/bin/wins.exe") + + switch ( $responseCode ) { + { "ok200", 200 } { + Write-LogInfo "Successfully downloaded the wins binary." + $retries = 99 + break + } + default { + Write-LogError "$responseCode received while downloading the wins binary. Sleeping for 5 seconds and trying again." + Start-Sleep -Seconds 5 + $retries++ + continue + } + } + } + } + if (-Not (Test-Path "$env:CATTLE_AGENT_BIN_PREFIX/bin/wins.exe")) { + Write-LogFatal "Wins.exe doesn't appear to have been installed." + } + } + + function Test-CaCheckSum() { + $caCertsPath = "cacerts" + $env:RANCHER_CERT = Join-Path -Path $env:CATTLE_AGENT_CONFIG_DIR -ChildPath "ranchercert" + if (-Not $env:CATTLE_CA_CHECKSUM) { + return + } + + curl.exe --insecure -sfL $env:CATTLE_SERVER/$caCertsPath -o $env:RANCHER_CERT + if (-Not(Test-Path -Path $env:RANCHER_CERT)) { + Write-Error "The environment variable CATTLE_CA_CHECKSUM is set but there is no CA certificate configured at $( $env:CATTLE_SERVER )/$( $caCertsPath )) " + exit 1 + } + + if ($LASTEXITCODE -ne 0) { + Write-Error "Value from $( $env:CATTLE_SERVER )/$( $caCertsPath ) does not look like an x509 certificate, exited with $( $LASTEXITCODE ) " + Write-Error "Retrieved cacerts:" + Get-Content $env:RANCHER_CERT + exit 1 + } + else { + Write-LogInfo "Value from $( $env:CATTLE_SERVER )/$( $caCertsPath ) is an x509 certificate" + } + $env:CATTLE_SERVER_CHECKSUM = (Get-FileHash -Path $env:RANCHER_CERT -Algorithm SHA256).Hash.ToLower() + if ($env:CATTLE_SERVER_CHECKSUM -ne $env:CATTLE_CA_CHECKSUM) { + Remove-Item -Path $env:RANCHER_CERT -Force + Write-LogError "Configured cacerts checksum $( $env:CATTLE_SERVER_CHECKSUM ) does not match given -CaCheckSum $( $env:CATTLE_CA_CHECKSUM ) " + Write-LogError "Please check if the correct certificate is configured at $( $env:CATTLE_SERVER )/$( $caCertsPath ) ." + exit 1 + } + Import-Certificate -FilePath $env:RANCHER_CERT -CertStoreLocation Cert:\LocalMachine\Root | Out-Null + $env:CURL_CAFLAG = "--ssl-no-revoke" + } + + function Test-RancherConnection { + $env:RANCHER_SUCCESS = $false + if ($env:CATTLE_SERVER -and ($env:CATTLE_REMOTE_ENABLED -eq "true")) { + $retries = 0 + while ($retries -lt 6) { + $responseCode = $(curl.exe --connect-timeout 60 --max-time 60 --write-out "%{http_code}\n" $env:CURL_CAFLAG -sfL "$env:CATTLE_SERVER/healthz") + switch ( $responseCode ) { + { "ok200", 200 } { + Write-LogInfo "Successfully tested Rancher connection." + $env:RANCHER_SUCCESS = $true + $retries = 99 + break + } + default { + Write-LogError "$responseCode received while testing Rancher connection. Sleeping for 5 seconds and trying again." + Start-Sleep -Seconds 5 + $retries++ + continue + } + } + } + if (!$env:RANCHER_SUCCESS) { + Write-LogFatal "Error connecting to Rancher. Perhaps -CaCheckSum needs to be set?" + } + } + } + + function Test-CaRequired { + $env:CA_REQUIRED = $false + if ($env:CATTLE_SERVER -and ($env:CATTLE_REMOTE_ENABLED -eq "true")) { + $retries = 0 + while ($retries -lt 6) { + curl.exe --connect-timeout 60 --max-time 60 -sfL "$env:CATTLE_SERVER/healthz" + switch ($LASTEXITCODE) { + 0 { + Write-LogInfo "Determined CA is not necessary to connect to Rancher." + $env:CATTLE_CA_CHECKSUM = "" + $retries = 99 + break + } + { 60, 77 } { + Write-LogInfo "Determined CA is necessary to connect to Rancher." + $env:CA_REQUIRED = $true + $retries = 99 + break + } + default { + Write-LogError "Error while connecting to Rancher to verify CA necessity. Sleeping for 5 seconds and trying again." + Start-Sleep -Seconds 5 + $retries++ + continue + } + } + } + } + } + + function Get-RancherConnectionInfo() { + if ($env:CATTLE_REMOTE_ENABLED -eq "true") { + $retries = 0 + while ($retries -lt 6) { + $responseCode = $(curl.exe --connect-timeout 60 --max-time 60 --write-out "%{http_code}\n" $env:CURL_CAFLAG -sfL "$env:CATTLE_SERVER/v3/connect/agent" -o $env:CATTLE_AGENT_VAR_DIR/rancher2_connection_info.json -H "Authorization: Bearer $($env:CATTLE_TOKEN)" -H "X-Cattle-Id: $($env:CATTLE_ID)" -H "X-Cattle-Role-Worker: $($env:CATTLE_ROLE_WORKER)" -H "X-Cattle-Labels: $($env:CATTLE_LABELS)" -H "X-Cattle-Taints: $($env:CATTLE_TAINTS)" -H "X-Cattle-Address: $($env:CATTLE_ADDRESS)" -H "X-Cattle-Internal-Address: $($env:CATTLE_INTERNAL_ADDRESS)" -H "Content-Type: application/json") + switch ( $responseCode ) { + { "ok200", 200 } { + Write-LogInfo "Successfully downloaded Rancher connection information." + $retries = 99 + break + } + default { + Write-LogError "$responseCode received while downloading Rancher connection information. Sleeping for 5 seconds and trying again." + Start-Sleep -Seconds 5 + $retries++ + continue + } + } + } + } + } + + function Set-WinsConfig() { + $agentConfig = + @" +systemagent: + workDirectory: $($env:CATTLE_AGENT_VAR_DIR)/work + appliedPlanDirectory: $($env:CATTLE_AGENT_VAR_DIR)/applied + remoteEnabled: $($env:CATTLE_REMOTE_ENABLED) + preserveWorkDirectory: $($env:CATTLE_PRESERVE_WORKDIR) +"@ + Set-Content -Path $env:CATTLE_AGENT_CONFIG_DIR/config -Value $agentConfig + if ($env:CATTLE_REMOTE_ENABLED -eq "true") { + Add-Content -Path $env:CATTLE_AGENT_CONFIG_DIR/config -Value " connectionInfoFile: $env:CATTLE_AGENT_VAR_DIR/rancher2_connection_info.json" + } + } + + function Stop-Agent() { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $ServiceName + ) + Write-LogInfo "Checking if $ServiceName service exists" + if ((Get-Service -Name $ServiceName -ErrorAction SilentlyContinue)) { + Write-LogInfo "$ServiceName service found, stopping now" + Stop-Service -Name $ServiceName + while ((Get-Service -Name wins).Status -ne 'Stopped') { + Write-LogInfo "Waiting for $ServiceName service to stop" + Start-Sleep -s 5 + } + } + else { + Write-LogInfo "$ServiceName isn't installed, continuing" + } + } + + function New-CattleId() { + if (-Not $env:CATTLE_ID) { + Write-LogInfo "Generating Cattle ID" + + if (Test-Path -Path "$($env:CATTLE_AGENT_CONFIG_DIR)/cattle-id") { + $env:CATTLE_ID = Get-Content -Path "$($env:CATTLE_AGENT_CONFIG_DIR)/cattle-id" + Write-LogInfo "Cattle ID was already detected as $($env:CATTLE_ID). Not generating a new one." + return + } + $stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($env:COMPUTERNAME)) + $env:CATTLE_ID = (Get-FileHash -InputStream $stream -Algorithm SHA256).Hash.ToLower().Substring(0, 62) + Set-Content -Path "$($env:CATTLE_AGENT_CONFIG_DIR)/cattle-id" -Value $env:CATTLE_ID + return + } + Write-LogInfo "Not generating Cattle ID" + } + + function Get-Address() { + [CmdletBinding()] + param ( + [Parameter()] + [String] + $Value + ) + if (!$Value) { + # If nothing is given, return empty (it will be automatically determined later if empty) + return "" + } + # If given address is a network interface on the system, retrieve configured IP on that interface (only the first configured IP is taken) + elseif (Get-NetAdapter -Name $Value -ErrorAction SilentlyContinue) { + return $(Get-NetIpConfiguration | Where-Object { $null -ne $_.IPv4DefaultGateway -and $_.NetAdapter.Status -ne "Disconnected" }).IPv4Address.IPAddress + } + # Loop through cloud provider options to get IP from metadata, if not found return given value + else { + switch ($Value) { + awslocal { return curl.exe --connect-timeout 60 --max-time 60 -s http://169.254.169.254/latest/meta-data/local-ipv4 } + awspublic { return curl.exe --connect-timeout 60 --max-time 60 -s http://169.254.169.254/latest/meta-data/public-ipv4 } + doprivate { return curl.exe --connect-timeout 60 --max-time 60 -s http://169.254.169.254/metadata/v1/interfaces/private/0/ipv4/address } + dopublic { return curl.exe --connect-timeout 60 --max-time 60 -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address } + azprivate { return curl.exe --connect-timeout 60 --max-time 60 -s -H Metadata:true "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress?api-version=2017-08-01&format=text" } + azpublic { return curl.exe --connect-timeout 60 --max-time 60 -s -H Metadata:true "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2017-08-01&format=text" } + gceinternal { return curl.exe --connect-timeout 60 --max-time 60 -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip } + gceexternal { return curl.exe --connect-timeout 60 --max-time 60 -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip } + packetlocal { return curl.exe --connect-timeout 60 --max-time 60 -s https://metadata.packet.net/2009-04-04/meta-data/local-ipv4 } + packetpublic { return curl.exe --connect-timeout 60 --max-time 60 -s https://metadata.packet.net/2009-04-04/meta-data/public-ipv4 } + ipify { return curl.exe --connect-timeout 60 --max-time 60 -s https://api.ipify.org } + default { + return $Value + } + } + } + } + + function Invoke-WinsAgentInstall() { + $serviceName = "rancher-wins" + Get-Args + Set-Environment + Set-Path + Test-CaCheckSum + + if ($env:CATTLE_CA_CHECKSUM) { + Test-CaRequired + } + + Test-RancherConnection + Stop-Agent -ServiceName $serviceName + Invoke-WinsAgentDownload + Set-WinsConfig + + if ($env:CATTLE_TOKEN) { + New-CattleId + Get-RancherConnectionInfo + } + + $newEnv = @() + $PROXY_ENV_INFO = Get-ChildItem env: | Where-Object { $_.Name -Match "^(NO|HTTP|HTTPS)_PROXY" } | ForEach-Object { "$($_.Name)=$($_.Value)" } + if ($PROXY_ENV_INFO) { + $newEnv += $PROXY_ENV_INFO + Set-ItemProperty HKLM:SYSTEM\CurrentControlSet\Services\$serviceName -Name Environment -PropertyType MultiString -Value $newEnv + } + + try { + Write-LogInfo "Checking if $serviceName service exists." + Get-Service -Name $serviceName + } + catch { + Write-LogInfo "$serviceName service not found, enabling agent service." + Push-Location c:\usr\local\bin + wins.exe srv app run --register + Pop-Location + Start-Sleep -s 5 + } + + Write-LogInfo "Starting $serviceName service." + Start-Service -Name $serviceName + while ((Get-Service $serviceName).Status -ne 'Running') { + Write-LogInfo "Waiting for $serviceName service to start." + Start-Sleep -s 5 + } + } + + Invoke-WinsAgentInstall +}