Add Windows userdata
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
parent
47790b565e
commit
c6366ab896
3 changed files with 263 additions and 19 deletions
|
|
@ -16,8 +16,10 @@ package cloudconfig
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
"github.com/cloudbase/garm/params"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -101,6 +103,229 @@ set -e
|
|||
success "runner successfully installed" $AGENT_ID
|
||||
`
|
||||
|
||||
var WindowsSetupScriptTemplate = `#ps1_sysnative
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Token="{{.CallbackToken}}"
|
||||
)
|
||||
|
||||
$ErrorActionPreference="Stop"
|
||||
|
||||
function Invoke-FastWebRequest {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline=$true,Position=0)]
|
||||
[System.Uri]$Uri,
|
||||
[Parameter(Position=1)]
|
||||
[string]$OutFile,
|
||||
[Hashtable]$Headers=@{},
|
||||
[switch]$SkipIntegrityCheck=$false
|
||||
)
|
||||
PROCESS
|
||||
{
|
||||
if(!([System.Management.Automation.PSTypeName]'System.Net.Http.HttpClient').Type)
|
||||
{
|
||||
$assembly = [System.Reflection.Assembly]::LoadWithPartialName("System.Net.Http")
|
||||
}
|
||||
|
||||
if(!$OutFile) {
|
||||
$OutFile = $Uri.PathAndQuery.Substring($Uri.PathAndQuery.LastIndexOf("/") + 1)
|
||||
if(!$OutFile) {
|
||||
throw "The ""OutFile"" parameter needs to be specified"
|
||||
}
|
||||
}
|
||||
|
||||
$fragment = $Uri.Fragment.Trim('#')
|
||||
if ($fragment) {
|
||||
$details = $fragment.Split("=")
|
||||
$algorithm = $details[0]
|
||||
$hash = $details[1]
|
||||
}
|
||||
|
||||
if (!$SkipIntegrityCheck -and $fragment -and (Test-Path $OutFile)) {
|
||||
try {
|
||||
return (Test-FileIntegrity -File $OutFile -Algorithm $algorithm -ExpectedHash $hash)
|
||||
} catch {
|
||||
Remove-Item $OutFile
|
||||
}
|
||||
}
|
||||
|
||||
$client = new-object System.Net.Http.HttpClient
|
||||
foreach ($k in $Headers.Keys){
|
||||
$client.DefaultRequestHeaders.Add($k, $Headers[$k])
|
||||
}
|
||||
$task = $client.GetStreamAsync($Uri)
|
||||
$response = $task.Result
|
||||
if($task.IsFaulted) {
|
||||
$msg = "Request for URL '{0}' is faulted. Task status: {1}." -f @($Uri, $task.Status)
|
||||
if($task.Exception) {
|
||||
$msg += "Exception details: {0}" -f @($task.Exception)
|
||||
}
|
||||
Throw $msg
|
||||
}
|
||||
$outStream = New-Object IO.FileStream $OutFile, Create, Write, None
|
||||
|
||||
try {
|
||||
$totRead = 0
|
||||
$buffer = New-Object Byte[] 1MB
|
||||
while (($read = $response.Read($buffer, 0, $buffer.Length)) -gt 0) {
|
||||
$totRead += $read
|
||||
$outStream.Write($buffer, 0, $read);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$outStream.Close()
|
||||
}
|
||||
if(!$SkipIntegrityCheck -and $fragment) {
|
||||
Test-FileIntegrity -File $OutFile -Algorithm $algorithm -ExpectedHash $hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Import-Certificate() {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$CertificatePath,
|
||||
[parameter(Mandatory=$true)]
|
||||
[System.Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation="LocalMachine",
|
||||
[parameter(Mandatory=$true)]
|
||||
[System.Security.Cryptography.X509Certificates.StoreName]$StoreName="TrustedPublisher"
|
||||
)
|
||||
PROCESS
|
||||
{
|
||||
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
|
||||
$StoreName, $StoreLocation)
|
||||
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
|
||||
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
|
||||
$CertificatePath)
|
||||
$store.Add($cert)
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-APICall() {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[parameter(Mandatory=$true)]
|
||||
[object]$Payload,
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$CallbackURL
|
||||
)
|
||||
PROCESS{
|
||||
Invoke-WebRequest -UseBasicParsing -Method Post -Headers @{"Accept"="application/json"; "Authorization"="Bearer $Token"} -Uri $CallbackURL -Body (ConvertTo-Json $Payload) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Update-GarmStatus() {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$Message,
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$CallbackURL
|
||||
)
|
||||
PROCESS{
|
||||
$body = @{
|
||||
"status"="installing"
|
||||
"message"=$Message
|
||||
}
|
||||
Invoke-APICall -Payload $body -CallbackURL $CallbackURL | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-GarmSuccess() {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$Message,
|
||||
[parameter(Mandatory=$true)]
|
||||
[int64]$AgentID,
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$CallbackURL
|
||||
)
|
||||
PROCESS{
|
||||
$body = @{
|
||||
"status"="idle"
|
||||
"message"=$Message
|
||||
"agent_id"=$AgentID
|
||||
}
|
||||
Invoke-APICall -Payload $body -CallbackURL $CallbackURL | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-GarmFailure() {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$Message,
|
||||
[parameter(Mandatory=$true)]
|
||||
[string]$CallbackURL
|
||||
)
|
||||
PROCESS{
|
||||
$body = @{
|
||||
"status"="failed"
|
||||
"message"=$Message
|
||||
}
|
||||
Invoke-APICall -Payload $body -CallbackURL $CallbackURL | Out-Null
|
||||
Throw $Message
|
||||
}
|
||||
}
|
||||
|
||||
$PEMData = @"
|
||||
{{.CABundle}}
|
||||
"@
|
||||
|
||||
function Install-Runner() {
|
||||
$CallbackURL="{{.CallbackURL}}"
|
||||
if ($Token.Length -eq 0) {
|
||||
Throw "missing callback authentication token"
|
||||
}
|
||||
try {
|
||||
$MetadataURL="{{.MetadataURL}}"
|
||||
$DownloadURL="{{.DownloadURL}}"
|
||||
if($MetadataURL -eq ""){
|
||||
Throw "missing metadata URL"
|
||||
}
|
||||
|
||||
if($PEMData.Trim().Length -gt 0){
|
||||
Set-Content $env:TMP\garm-ca.pem $PEMData
|
||||
Import-Certificate -CertificatePath $env:TMP\garm-ca.pem
|
||||
}
|
||||
|
||||
$GithubRegistrationToken = Invoke-WebRequest -UseBasicParsing -Headers @{"Accept"="application/json"; "Authorization"="Bearer $Token"} -Uri $MetadataURL/runner-registration-token/
|
||||
Update-GarmStatus -CallbackURL $CallbackURL -Message "downloading tools from $DownloadURL"
|
||||
|
||||
$downloadToken="{{.TempDownloadToken}}"
|
||||
$DownloadTokenHeaders=@{}
|
||||
if ($downloadToken.Length -gt 0) {
|
||||
$DownloadTokenHeaders=@{
|
||||
"Authorization"="Bearer $downloadToken"
|
||||
}
|
||||
}
|
||||
$downloadPath = Join-Path $env:TMP {{.FileName}}
|
||||
Invoke-FastWebRequest -Uri $DownloadURL -OutFile $downloadPath -Headers $DownloadTokenHeaders
|
||||
|
||||
$runnerDir = "C:\runner"
|
||||
mkdir $runnerDir
|
||||
|
||||
Update-GarmStatus -CallbackURL $CallbackURL -Message "extracting runner"
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($downloadPath, "$runnerDir")
|
||||
|
||||
Update-GarmStatus -CallbackURL $CallbackURL -Message "configuring and starting runner"
|
||||
cd $runnerDir
|
||||
./config.cmd --unattended --url "{{ .RepoURL }}" --token $GithubRegistrationToken --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" --ephemeral --runasservice
|
||||
|
||||
$agentInfoFile = Join-Path $runnerDir ".runner"
|
||||
$agentInfo = ConvertFrom-Json (gc -raw $agentInfoFile)
|
||||
Invoke-GarmSuccess -CallbackURL $CallbackURL -Message "runner successfully installed" -AgentID $agentInfo.agentId
|
||||
} catch {
|
||||
Invoke-GarmFailure -CallbackURL $CallbackURL -Message $_
|
||||
}
|
||||
}
|
||||
Install-Runner
|
||||
`
|
||||
|
||||
type InstallRunnerParams struct {
|
||||
FileName string
|
||||
DownloadURL string
|
||||
|
|
@ -113,17 +338,27 @@ type InstallRunnerParams struct {
|
|||
CallbackURL string
|
||||
CallbackToken string
|
||||
TempDownloadToken string
|
||||
CABundle string
|
||||
}
|
||||
|
||||
func InstallRunnerScript(params InstallRunnerParams) ([]byte, error) {
|
||||
func InstallRunnerScript(installParams InstallRunnerParams, osType params.OSType) ([]byte, error) {
|
||||
var tpl string
|
||||
switch osType {
|
||||
case params.Linux:
|
||||
tpl = CloudConfigTemplate
|
||||
case params.Windows:
|
||||
tpl = WindowsSetupScriptTemplate
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported os type: %s", osType)
|
||||
}
|
||||
|
||||
t, err := template.New("").Parse(CloudConfigTemplate)
|
||||
t, err := template.New("").Parse(tpl)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parsing template")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := t.Execute(&buf, params); err != nil {
|
||||
if err := t.Execute(&buf, installParams); err != nil {
|
||||
return nil, errors.Wrap(err, "rendering template")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
// Linux only for now. Will add Windows soon. (famous last words?)
|
||||
supportedOSType map[params.OSType]struct{} = map[params.OSType]struct{}{
|
||||
params.Linux: {},
|
||||
params.Windows: {},
|
||||
|
|
|
|||
40
util/util.go
40
util/util.go
|
|
@ -226,8 +226,6 @@ func GithubClient(ctx context.Context, token string, credsDetails params.GithubC
|
|||
}
|
||||
|
||||
func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.RunnerApplicationDownload, runnerName string) (string, error) {
|
||||
cloudCfg := cloudconfig.NewDefaultCloudInitConfig()
|
||||
|
||||
if tools.Filename == nil {
|
||||
return "", fmt.Errorf("missing tools filename")
|
||||
}
|
||||
|
|
@ -254,27 +252,39 @@ func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.Runne
|
|||
CallbackURL: bootstrapParams.CallbackURL,
|
||||
CallbackToken: bootstrapParams.InstanceToken,
|
||||
}
|
||||
if bootstrapParams.CACertBundle != nil && len(bootstrapParams.CACertBundle) > 0 {
|
||||
installRunnerParams.CABundle = string(bootstrapParams.CACertBundle)
|
||||
}
|
||||
|
||||
installScript, err := cloudconfig.InstallRunnerScript(installRunnerParams)
|
||||
installScript, err := cloudconfig.InstallRunnerScript(installRunnerParams, bootstrapParams.OSType)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "generating script")
|
||||
}
|
||||
|
||||
cloudCfg.AddSSHKey(bootstrapParams.SSHKeys...)
|
||||
cloudCfg.AddFile(installScript, "/install_runner.sh", "root:root", "755")
|
||||
cloudCfg.AddRunCmd("/install_runner.sh")
|
||||
cloudCfg.AddRunCmd("rm -f /install_runner.sh")
|
||||
|
||||
if bootstrapParams.CACertBundle != nil && len(bootstrapParams.CACertBundle) > 0 {
|
||||
if err := cloudCfg.AddCACert(bootstrapParams.CACertBundle); err != nil {
|
||||
return "", errors.Wrap(err, "adding CA cert bundle")
|
||||
var asStr string
|
||||
switch bootstrapParams.OSType {
|
||||
case params.Linux:
|
||||
cloudCfg := cloudconfig.NewDefaultCloudInitConfig()
|
||||
cloudCfg.AddSSHKey(bootstrapParams.SSHKeys...)
|
||||
cloudCfg.AddFile(installScript, "/install_runner.sh", "root:root", "755")
|
||||
cloudCfg.AddRunCmd("/install_runner.sh")
|
||||
cloudCfg.AddRunCmd("rm -f /install_runner.sh")
|
||||
if bootstrapParams.CACertBundle != nil && len(bootstrapParams.CACertBundle) > 0 {
|
||||
if err := cloudCfg.AddCACert(bootstrapParams.CACertBundle); err != nil {
|
||||
return "", errors.Wrap(err, "adding CA cert bundle")
|
||||
}
|
||||
}
|
||||
var err error
|
||||
asStr, err = cloudCfg.Serialize()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "creating cloud config")
|
||||
}
|
||||
case params.Windows:
|
||||
asStr = string(installScript)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown os type: %s", bootstrapParams.OSType)
|
||||
}
|
||||
|
||||
asStr, err := cloudCfg.Serialize()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "creating cloud config")
|
||||
}
|
||||
return asStr, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue