Merge pull request #82 from gabriel-samfira/add-execution-env
Add aditional external provider enablement
This commit is contained in:
commit
569803e5e6
45 changed files with 1295 additions and 276 deletions
2
Makefile
2
Makefile
|
|
@ -19,7 +19,7 @@ build-static:
|
|||
@echo Binaries are available in $(PWD)/bin
|
||||
|
||||
install:
|
||||
@$(GO) install ./...
|
||||
@$(GO) install -tags osusergo,netgo,sqlite_omit_load_extension ./...
|
||||
@echo Binaries available in ${GOPATH}
|
||||
|
||||
test: verify go-test
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ package cloudconfig
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
"github.com/cloudbase/garm/params"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -61,6 +63,15 @@ function fail() {
|
|||
sendStatus "downloading tools from {{ .DownloadURL }}"
|
||||
|
||||
TEMP_TOKEN=""
|
||||
GH_RUNNER_GROUP="{{.GitHubRunnerGroup}}"
|
||||
|
||||
# $RUNNER_GROUP_OPT will be added to the config.sh line. If it's empty, nothing happens
|
||||
# if it holds a value, it will be part of the command.
|
||||
RUNNER_GROUP_OPT=""
|
||||
if [ ! -z $GH_RUNNER_GROUP ]
|
||||
RUNNER_GROUP_OPT="--runnergroup=$GH_RUNNER_GROUP"
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -z "{{ .TempDownloadToken }}" ]; then
|
||||
TEMP_TOKEN="Authorization: Bearer {{ .TempDownloadToken }}"
|
||||
|
|
@ -79,11 +90,15 @@ cd /home/{{ .RunnerUsername }}/actions-runner
|
|||
sudo ./bin/installdependencies.sh || fail "failed to install dependencies"
|
||||
|
||||
sendStatus "configuring runner"
|
||||
sudo -u {{ .RunnerUsername }} -- ./config.sh --unattended --url "{{ .RepoURL }}" --token "$GITHUB_TOKEN" --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" --ephemeral || fail "failed to configure runner"
|
||||
sudo -u {{ .RunnerUsername }} -- ./config.sh --unattended --url "{{ .RepoURL }}" --token "$GITHUB_TOKEN" $RUNNER_GROUP_OPT --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" --ephemeral || fail "failed to configure runner"
|
||||
|
||||
sendStatus "installing runner service"
|
||||
./svc.sh install {{ .RunnerUsername }} || fail "failed to install service"
|
||||
|
||||
if [ -e "/sys/fs/selinux" ];then
|
||||
sudo chcon -R -t bin_t /home/runner/
|
||||
fi
|
||||
|
||||
sendStatus "starting service"
|
||||
./svc.sh start || fail "failed to start service"
|
||||
|
||||
|
|
@ -97,6 +112,233 @@ 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}}
|
||||
"@
|
||||
$GHRunnerGroup = "{{.GitHubRunnerGroup}}"
|
||||
|
||||
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")
|
||||
$runnerGroupOpt = ""
|
||||
if ($GHRunnerGroup.Length -gt 0){
|
||||
$runnerGroupOpt = "--runnergroup $GHRunnerGroup"
|
||||
}
|
||||
Update-GarmStatus -CallbackURL $CallbackURL -Message "configuring and starting runner"
|
||||
cd $runnerDir
|
||||
./config.cmd --unattended --url "{{ .RepoURL }}" --token $GithubRegistrationToken $runnerGroupOpt --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
|
||||
|
|
@ -109,17 +351,28 @@ type InstallRunnerParams struct {
|
|||
CallbackURL string
|
||||
CallbackToken string
|
||||
TempDownloadToken string
|
||||
CABundle string
|
||||
GitHubRunnerGroup 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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ var (
|
|||
poolExtraSpecsFile string
|
||||
poolExtraSpecs string
|
||||
poolAll bool
|
||||
poolGitHubRunnerGroup string
|
||||
)
|
||||
|
||||
// runnerCmd represents the runner command
|
||||
|
|
@ -196,6 +197,7 @@ var poolAddCmd = &cobra.Command{
|
|||
Tags: tags,
|
||||
Enabled: poolEnabled,
|
||||
RunnerBootstrapTimeout: poolRunnerBootstrapTimeout,
|
||||
GitHubRunnerGroup: poolGitHubRunnerGroup,
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("extra-specs") {
|
||||
|
|
@ -299,6 +301,10 @@ explicitly remove them using the runner delete command.
|
|||
}
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("runner-group") {
|
||||
poolUpdateParams.GitHubRunnerGroup = &poolGitHubRunnerGroup
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("enabled") {
|
||||
poolUpdateParams.Enabled = &poolEnabled
|
||||
}
|
||||
|
|
@ -348,6 +354,7 @@ func init() {
|
|||
poolUpdateCmd.Flags().StringVar(&poolRunnerPrefix, "runner-prefix", "", "The name prefix to use for runners in this pool.")
|
||||
poolUpdateCmd.Flags().UintVar(&poolMaxRunners, "max-runners", 5, "The maximum number of runner this pool will create.")
|
||||
poolUpdateCmd.Flags().UintVar(&poolMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
|
||||
poolUpdateCmd.Flags().StringVar(&poolGitHubRunnerGroup, "runner-group", "", "The GitHub runner group in which all runners of this pool will be added.")
|
||||
poolUpdateCmd.Flags().BoolVar(&poolEnabled, "enabled", false, "Enable this pool.")
|
||||
poolUpdateCmd.Flags().UintVar(&poolRunnerBootstrapTimeout, "runner-bootstrap-timeout", 20, "Duration in minutes after which a runner is considered failed if it does not join Github.")
|
||||
poolUpdateCmd.Flags().StringVar(&poolExtraSpecsFile, "extra-specs-file", "", "A file containing a valid json which will be passed to the IaaS provider managing the pool.")
|
||||
|
|
@ -363,6 +370,7 @@ func init() {
|
|||
poolAddCmd.Flags().StringVar(&poolOSArch, "os-arch", "amd64", "Operating system architecture (amd64, arm, etc).")
|
||||
poolAddCmd.Flags().StringVar(&poolExtraSpecsFile, "extra-specs-file", "", "A file containing a valid json which will be passed to the IaaS provider managing the pool.")
|
||||
poolAddCmd.Flags().StringVar(&poolExtraSpecs, "extra-specs", "", "A valid json which will be passed to the IaaS provider managing the pool.")
|
||||
poolAddCmd.Flags().StringVar(&poolGitHubRunnerGroup, "runner-group", "", "The GitHub runner group in which all runners of this pool will be added.")
|
||||
poolAddCmd.Flags().UintVar(&poolMaxRunners, "max-runners", 5, "The maximum number of runner this pool will create.")
|
||||
poolAddCmd.Flags().UintVar(&poolRunnerBootstrapTimeout, "runner-bootstrap-timeout", 20, "Duration in minutes after which a runner is considered failed if it does not join Github.")
|
||||
poolAddCmd.Flags().UintVar(&poolMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
|
||||
|
|
|
|||
|
|
@ -33,14 +33,15 @@ func (s *sqlDatabase) CreateInstance(ctx context.Context, poolID string, param p
|
|||
}
|
||||
|
||||
newInstance := Instance{
|
||||
Pool: pool,
|
||||
Name: param.Name,
|
||||
Status: param.Status,
|
||||
RunnerStatus: param.RunnerStatus,
|
||||
OSType: param.OSType,
|
||||
OSArch: param.OSArch,
|
||||
CallbackURL: param.CallbackURL,
|
||||
MetadataURL: param.MetadataURL,
|
||||
Pool: pool,
|
||||
Name: param.Name,
|
||||
Status: param.Status,
|
||||
RunnerStatus: param.RunnerStatus,
|
||||
OSType: param.OSType,
|
||||
OSArch: param.OSArch,
|
||||
CallbackURL: param.CallbackURL,
|
||||
MetadataURL: param.MetadataURL,
|
||||
GitHubRunnerGroup: param.GitHubRunnerGroup,
|
||||
}
|
||||
q := s.conn.Create(&newInstance)
|
||||
if q.Error != nil {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ type Pool struct {
|
|||
// ExtraSpecs is an opaque json that gets sent to the provider
|
||||
// as part of the bootstrap params for instances. It can contain
|
||||
// any kind of data needed by providers.
|
||||
ExtraSpecs datatypes.JSON
|
||||
ExtraSpecs datatypes.JSON
|
||||
GitHubRunnerGroup string
|
||||
|
||||
RepoID uuid.UUID `gorm:"index"`
|
||||
Repository Repository `gorm:"foreignKey:RepoID"`
|
||||
|
|
@ -136,21 +137,22 @@ type InstanceStatusUpdate struct {
|
|||
type Instance struct {
|
||||
Base
|
||||
|
||||
ProviderID *string `gorm:"uniqueIndex"`
|
||||
Name string `gorm:"uniqueIndex"`
|
||||
AgentID int64
|
||||
OSType params.OSType
|
||||
OSArch params.OSArch
|
||||
OSName string
|
||||
OSVersion string
|
||||
Addresses []Address `gorm:"foreignKey:InstanceID"`
|
||||
Status common.InstanceStatus
|
||||
RunnerStatus common.RunnerStatus
|
||||
CallbackURL string
|
||||
MetadataURL string
|
||||
ProviderFault []byte `gorm:"type:longblob"`
|
||||
CreateAttempt int
|
||||
TokenFetched bool
|
||||
ProviderID *string `gorm:"uniqueIndex"`
|
||||
Name string `gorm:"uniqueIndex"`
|
||||
AgentID int64
|
||||
OSType params.OSType
|
||||
OSArch params.OSArch
|
||||
OSName string
|
||||
OSVersion string
|
||||
Addresses []Address `gorm:"foreignKey:InstanceID"`
|
||||
Status common.InstanceStatus
|
||||
RunnerStatus common.RunnerStatus
|
||||
CallbackURL string
|
||||
MetadataURL string
|
||||
ProviderFault []byte `gorm:"type:longblob"`
|
||||
CreateAttempt int
|
||||
TokenFetched bool
|
||||
GitHubRunnerGroup string
|
||||
|
||||
PoolID uuid.UUID
|
||||
Pool Pool `gorm:"foreignKey:PoolID"`
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func (s *PoolsTestSuite) TestListAllPools() {
|
|||
|
||||
func (s *PoolsTestSuite) TestListAllPoolsDBFetchErr() {
|
||||
s.Fixtures.SQLMock.
|
||||
ExpectQuery(regexp.QuoteMeta("SELECT `pools`.`id`,`pools`.`created_at`,`pools`.`updated_at`,`pools`.`deleted_at`,`pools`.`provider_name`,`pools`.`runner_prefix`,`pools`.`max_runners`,`pools`.`min_idle_runners`,`pools`.`runner_bootstrap_timeout`,`pools`.`image`,`pools`.`flavor`,`pools`.`os_type`,`pools`.`os_arch`,`pools`.`enabled`,`pools`.`repo_id`,`pools`.`org_id`,`pools`.`enterprise_id` FROM `pools` WHERE `pools`.`deleted_at` IS NULL")).
|
||||
ExpectQuery(regexp.QuoteMeta("SELECT `pools`.`id`,`pools`.`created_at`,`pools`.`updated_at`,`pools`.`deleted_at`,`pools`.`provider_name`,`pools`.`runner_prefix`,`pools`.`max_runners`,`pools`.`min_idle_runners`,`pools`.`runner_bootstrap_timeout`,`pools`.`image`,`pools`.`flavor`,`pools`.`os_type`,`pools`.`os_arch`,`pools`.`enabled`,`pools`.`git_hub_runner_group`,`pools`.`repo_id`,`pools`.`org_id`,`pools`.`enterprise_id` FROM `pools` WHERE `pools`.`deleted_at` IS NULL")).
|
||||
WillReturnError(fmt.Errorf("mocked fetching all pools error"))
|
||||
|
||||
_, err := s.StoreSQLMocked.ListAllPools(context.Background())
|
||||
|
|
|
|||
|
|
@ -33,23 +33,24 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) params.Instance {
|
|||
id = *instance.ProviderID
|
||||
}
|
||||
ret := params.Instance{
|
||||
ID: instance.ID.String(),
|
||||
ProviderID: id,
|
||||
AgentID: instance.AgentID,
|
||||
Name: instance.Name,
|
||||
OSType: instance.OSType,
|
||||
OSName: instance.OSName,
|
||||
OSVersion: instance.OSVersion,
|
||||
OSArch: instance.OSArch,
|
||||
Status: instance.Status,
|
||||
RunnerStatus: instance.RunnerStatus,
|
||||
PoolID: instance.PoolID.String(),
|
||||
CallbackURL: instance.CallbackURL,
|
||||
MetadataURL: instance.MetadataURL,
|
||||
StatusMessages: []params.StatusMessage{},
|
||||
CreateAttempt: instance.CreateAttempt,
|
||||
UpdatedAt: instance.UpdatedAt,
|
||||
TokenFetched: instance.TokenFetched,
|
||||
ID: instance.ID.String(),
|
||||
ProviderID: id,
|
||||
AgentID: instance.AgentID,
|
||||
Name: instance.Name,
|
||||
OSType: instance.OSType,
|
||||
OSName: instance.OSName,
|
||||
OSVersion: instance.OSVersion,
|
||||
OSArch: instance.OSArch,
|
||||
Status: instance.Status,
|
||||
RunnerStatus: instance.RunnerStatus,
|
||||
PoolID: instance.PoolID.String(),
|
||||
CallbackURL: instance.CallbackURL,
|
||||
MetadataURL: instance.MetadataURL,
|
||||
StatusMessages: []params.StatusMessage{},
|
||||
CreateAttempt: instance.CreateAttempt,
|
||||
UpdatedAt: instance.UpdatedAt,
|
||||
TokenFetched: instance.TokenFetched,
|
||||
GitHubRunnerGroup: instance.GitHubRunnerGroup,
|
||||
}
|
||||
|
||||
if len(instance.ProviderFault) > 0 {
|
||||
|
|
@ -144,6 +145,7 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) params.Pool {
|
|||
Instances: make([]params.Instance, len(pool.Instances)),
|
||||
RunnerBootstrapTimeout: pool.RunnerBootstrapTimeout,
|
||||
ExtraSpecs: json.RawMessage(pool.ExtraSpecs),
|
||||
GitHubRunnerGroup: pool.GitHubRunnerGroup,
|
||||
}
|
||||
|
||||
if pool.RepoID != uuid.Nil {
|
||||
|
|
@ -281,6 +283,10 @@ func (s *sqlDatabase) updatePool(pool Pool, param params.UpdatePoolParams) (para
|
|||
pool.RunnerBootstrapTimeout = *param.RunnerBootstrapTimeout
|
||||
}
|
||||
|
||||
if param.GitHubRunnerGroup != nil {
|
||||
pool.GitHubRunnerGroup = *param.GitHubRunnerGroup
|
||||
}
|
||||
|
||||
if q := s.conn.Save(&pool); q.Error != nil {
|
||||
return params.Pool{}, errors.Wrap(q.Error, "saving database entry")
|
||||
}
|
||||
|
|
|
|||
12
go.mod
12
go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module github.com/cloudbase/garm
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
|
|
@ -14,8 +14,9 @@ require (
|
|||
github.com/jedib0t/go-pretty/v6 v6.4.6
|
||||
github.com/juju/clock v1.0.3
|
||||
github.com/juju/retry v1.0.0
|
||||
github.com/lxc/lxd v0.0.0-20230310224854-36b345fbd578
|
||||
github.com/lxc/lxd v0.0.0-20230325180147-8d608287b0ce
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
github.com/mattn/go-isatty v0.0.18
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
|
|
@ -42,6 +43,7 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect
|
||||
github.com/frankban/quicktest v1.14.3 // indirect
|
||||
github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1 // indirect
|
||||
github.com/go-macaroon-bakery/macaroonpb v1.0.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
|
|
@ -51,11 +53,12 @@ require (
|
|||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/juju/errors v1.0.0 // indirect
|
||||
github.com/juju/testing v1.0.2 // indirect
|
||||
github.com/juju/webbrowser v1.0.0 // indirect
|
||||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
|
|
@ -72,11 +75,10 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.29.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||
gopkg.in/httprequest.v1 v1.2.1 // indirect
|
||||
gopkg.in/macaroon.v2 v2.1.0 // indirect
|
||||
|
|
|
|||
33
go.sum
33
go.sum
|
|
@ -33,7 +33,8 @@ github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBav
|
|||
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1 h1:uvQJoKTHrFFu8zxoaopNKedRzwdy3+8H72we4T/5cGs=
|
||||
github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1/go.mod h1:H59IYeChwvD1po3dhGUPvq5na+4NVD7SJlbhGKvslr0=
|
||||
github.com/go-macaroon-bakery/macaroonpb v1.0.0 h1:It9exBaRMZ9iix1iJ6gwzfwsDE6ExNuwtAJ9e09v6XE=
|
||||
|
|
@ -70,6 +71,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZcz9HqcE=
|
||||
github.com/google/go-github/v48 v48.2.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y=
|
||||
|
|
@ -104,19 +106,16 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/juju/clock v1.0.3 h1:yJHIsWXeU8j3QcBdiess09SzfiXRRrsjKPn2whnMeds=
|
||||
github.com/juju/clock v1.0.3/go.mod h1:HIBvJ8kiV/n7UHwKuCkdYL4l/MDECztHR2sAvWDxxf0=
|
||||
github.com/juju/collections v1.0.2 h1:y9t99Nq/uUZksJgWehiWxIr2vB1UG3hUT7LBNy1xiH8=
|
||||
github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM=
|
||||
github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8=
|
||||
github.com/juju/loggo v1.0.0 h1:Y6ZMQOGR9Aj3BGkiWx7HBbIx6zNwNkxhVNOHU2i1bl0=
|
||||
github.com/juju/mgo/v2 v2.0.2 h1:ufYtW2OFNjniTuxOngecP3Mk5sSclo8Zl1mnmyGWUWA=
|
||||
github.com/juju/qthttptest v0.1.1/go.mod h1:aTlAv8TYaflIiTDIQYzxnl1QdPjAg8Q8qJMErpKy6A4=
|
||||
github.com/juju/qthttptest v0.1.3 h1:M0HdpwsK/UTHRGRcIw5zvh5z+QOgdqyK+ecDMN+swwM=
|
||||
github.com/juju/retry v1.0.0 h1:Tb1hFdDSPGLH/BGdYQOF7utQ9lA0ouVJX2imqgJK6tk=
|
||||
github.com/juju/retry v1.0.0/go.mod h1:SssN1eYeK3A2qjnFGTiVMbdzGJ2BfluaJblJXvuvgqA=
|
||||
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 h1:XEDzpuZb8Ma7vLja3+5hzUqVTvAqm5Y+ygvnDs5iTMM=
|
||||
github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0 h1:bn+2Adl1yWqYjm3KSFlFqsvfLg2eq+XNL7GGMYApdVw=
|
||||
github.com/juju/version v0.0.0-20210303051006-2015802527a8 h1:BTo6HzRR0zPBcXbs1Sy08aQNfvdm3ey8O+mBTiO3g00=
|
||||
github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 h1:wtEPbidt1VyHlb8RSztU6ySQj29FLsOQiI9XiJhXDM4=
|
||||
github.com/juju/testing v1.0.2 h1:OR90RqCd9CJONxXamZAjLknpZdtqDyxqW8IwCbgw3i4=
|
||||
github.com/juju/testing v1.0.2/go.mod h1:h3Vd2rzB57KrdsBEy6R7bmSKPzP76BnNavt7i8PerwQ=
|
||||
github.com/juju/utils/v3 v3.0.0 h1:Gg3n63mGPbBuoXCo+EPJuMi44hGZfloI8nlCIebHu2Q=
|
||||
github.com/juju/webbrowser v1.0.0 h1:JLdmbFtCGY6Qf2jmS6bVaenJFGIFkdF1/BjUm76af78=
|
||||
github.com/juju/webbrowser v1.0.0/go.mod h1:RwVlbBcF91Q4vS+iwlkJ6bZTE3EwlrjbYlM3WMVD6Bc=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
|
|
@ -127,16 +126,19 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
|||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lxc/lxd v0.0.0-20230310224854-36b345fbd578 h1:Rqzj0l43LLcTciHKIwKpTesAv9rSeC4LbUc+BsiIY6Q=
|
||||
github.com/lxc/lxd v0.0.0-20230310224854-36b345fbd578/go.mod h1:6Z1AwZwLm5Y+tzoW5CdKOo51fyDiCKJz3QgAXJ9/IYI=
|
||||
github.com/lxc/lxd v0.0.0-20230325180147-8d608287b0ce h1:3zb1HRvOAHOMZ8VGTDEBkKpCUVlF28zalZcb7RFjMnE=
|
||||
github.com/lxc/lxd v0.0.0-20230325180147-8d608287b0ce/go.mod h1:JJ1ShHzaOzMzU0B5TNcdI9+vq8Y45ijVeNYxE1wJ8zM=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
|
|
@ -151,6 +153,7 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJ
|
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
||||
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
|
|
@ -177,8 +180,9 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
|
|
@ -201,8 +205,6 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
|
|||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI=
|
||||
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI=
|
||||
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
|
@ -257,7 +259,6 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
@ -290,8 +291,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
|||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
|
||||
google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -90,37 +90,61 @@ type StatusMessage struct {
|
|||
type Instance struct {
|
||||
// ID is the database ID of this instance.
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// PeoviderID is the unique ID the provider associated
|
||||
// with the compute instance. We use this to identify the
|
||||
// instance in the provider.
|
||||
ProviderID string `json:"provider_id,omitempty"`
|
||||
|
||||
// AgentID is the github runner agent ID.
|
||||
AgentID int64 `json:"agent_id"`
|
||||
|
||||
// Name is the name associated with an instance. Depending on
|
||||
// the provider, this may or may not be useful in the context of
|
||||
// the provider, but we can use it internally to identify the
|
||||
// instance.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// OSType is the operating system type. For now, only Linux and
|
||||
// Windows are supported.
|
||||
OSType OSType `json:"os_type,omitempty"`
|
||||
|
||||
// OSName is the name of the OS. Eg: ubuntu, centos, etc.
|
||||
OSName string `json:"os_name,omitempty"`
|
||||
|
||||
// OSVersion is the version of the operating system.
|
||||
OSVersion string `json:"os_version,omitempty"`
|
||||
|
||||
// OSArch is the operating system architecture.
|
||||
OSArch OSArch `json:"os_arch,omitempty"`
|
||||
|
||||
// Addresses is a list of IP addresses the provider reports
|
||||
// for this instance.
|
||||
Addresses []Address `json:"addresses,omitempty"`
|
||||
// Status is the status of the instance inside the provider (eg: running, stopped, etc)
|
||||
Status common.InstanceStatus `json:"status,omitempty"`
|
||||
RunnerStatus common.RunnerStatus `json:"runner_status,omitempty"`
|
||||
PoolID string `json:"pool_id,omitempty"`
|
||||
ProviderFault []byte `json:"provider_fault,omitempty"`
|
||||
|
||||
// Status is the status of the instance inside the provider (eg: running, stopped, etc)
|
||||
Status common.InstanceStatus `json:"status,omitempty"`
|
||||
|
||||
// RunnerStatus is the github runner status as it appears on GitHub.
|
||||
RunnerStatus common.RunnerStatus `json:"runner_status,omitempty"`
|
||||
|
||||
// PoolID is the ID of the garm pool to which a runner belongs.
|
||||
PoolID string `json:"pool_id,omitempty"`
|
||||
|
||||
// ProviderFault holds any error messages captured from the IaaS provider that is
|
||||
// responsible for managing the lifecycle of the runner.
|
||||
ProviderFault []byte `json:"provider_fault,omitempty"`
|
||||
|
||||
// StatusMessages is a list of status messages sent back by the runner as it sets itself
|
||||
// up.
|
||||
StatusMessages []StatusMessage `json:"status_messages,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// UpdatedAt is the timestamp of the last update to this runner.
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// GithubRunnerGroup is the github runner group to which the runner belongs.
|
||||
// The runner group must be created by someone with access to the enterprise.
|
||||
GitHubRunnerGroup string `json:"github-runner-group"`
|
||||
|
||||
// Do not serialize sensitive info.
|
||||
CallbackURL string `json:"-"`
|
||||
|
|
@ -160,13 +184,36 @@ type BootstrapInstance struct {
|
|||
// all. We only validate that it's a proper json.
|
||||
ExtraSpecs json.RawMessage `json:"extra_specs,omitempty"`
|
||||
|
||||
// GitHubRunnerGroup is the github runner group in which the newly installed runner
|
||||
// should be added to. The runner group must be created by someone with access to the
|
||||
// enterprise.
|
||||
GitHubRunnerGroup string `json:"github-runner-group"`
|
||||
|
||||
// CACertBundle is a CA certificate bundle which will be sent to instances and which
|
||||
// will tipically be installed as a system wide trusted root CA. by either cloud-init
|
||||
// or whatever mechanism the provider will use to set up the runner.
|
||||
CACertBundle []byte `json:"ca-cert-bundle"`
|
||||
|
||||
OSArch OSArch `json:"arch"`
|
||||
Flavor string `json:"flavor"`
|
||||
Image string `json:"image"`
|
||||
// OSArch is the target OS CPU architecture of the runner.
|
||||
OSArch OSArch `json:"arch"`
|
||||
|
||||
// OSType is the target OS platform of the runner (windows, linux).
|
||||
OSType OSType `json:"os_type"`
|
||||
|
||||
// Flavor is the platform specific abstraction that defines what resources will be allocated
|
||||
// to the runner (CPU, RAM, disk space, etc). This field is meaningful to the provider which
|
||||
// handles the actual creation.
|
||||
Flavor string `json:"flavor"`
|
||||
|
||||
// Image is the platform specific identifier of the operating system template that will be used
|
||||
// to spin up a new machine.
|
||||
Image string `json:"image"`
|
||||
|
||||
// Labels are a list of github runner labels that will be added to the runner.
|
||||
Labels []string `json:"labels"`
|
||||
PoolID string `json:"pool_id"`
|
||||
|
||||
// PoolID is the ID of the garm pool to which this runner belongs.
|
||||
PoolID string `json:"pool_id"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
|
|
@ -201,6 +248,9 @@ type Pool struct {
|
|||
// nothing to garm itself. We don't act on the information in this field at
|
||||
// all. We only validate that it's a proper json.
|
||||
ExtraSpecs json.RawMessage `json:"extra_specs,omitempty"`
|
||||
// GithubRunnerGroup is the github runner group in which the runners will be added.
|
||||
// The runner group must be created by someone with access to the enterprise.
|
||||
GitHubRunnerGroup string `json:"github-runner-group"`
|
||||
}
|
||||
|
||||
func (p Pool) GetID() string {
|
||||
|
|
|
|||
|
|
@ -118,17 +118,24 @@ type UpdatePoolParams struct {
|
|||
OSType OSType `json:"os_type"`
|
||||
OSArch OSArch `json:"os_arch"`
|
||||
ExtraSpecs json.RawMessage `json:"extra_specs,omitempty"`
|
||||
// GithubRunnerGroup is the github runner group in which the runners of this
|
||||
// pool will be added to.
|
||||
// The runner group must be created by someone with access to the enterprise.
|
||||
GitHubRunnerGroup *string `json:"github-runner-group,omitempty"`
|
||||
}
|
||||
|
||||
type CreateInstanceParams struct {
|
||||
Name string
|
||||
OSType OSType
|
||||
OSArch OSArch
|
||||
Status common.InstanceStatus
|
||||
RunnerStatus common.RunnerStatus
|
||||
CallbackURL string
|
||||
MetadataURL string
|
||||
CreateAttempt int `json:"-"`
|
||||
Name string
|
||||
OSType OSType
|
||||
OSArch OSArch
|
||||
Status common.InstanceStatus
|
||||
RunnerStatus common.RunnerStatus
|
||||
CallbackURL string
|
||||
MetadataURL string
|
||||
// GithubRunnerGroup is the github runner group to which the runner belongs.
|
||||
// The runner group must be created by someone with access to the enterprise.
|
||||
GitHubRunnerGroup string
|
||||
CreateAttempt int `json:"-"`
|
||||
}
|
||||
|
||||
type CreatePoolParams struct {
|
||||
|
|
@ -145,6 +152,10 @@ type CreatePoolParams struct {
|
|||
Enabled bool `json:"enabled"`
|
||||
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout"`
|
||||
ExtraSpecs json.RawMessage `json:"extra_specs,omitempty"`
|
||||
// GithubRunnerGroup is the github runner group in which the runners of this
|
||||
// pool will be added to.
|
||||
// The runner group must be created by someone with access to the enterprise.
|
||||
GitHubRunnerGroup string `json:"github-runner-group"`
|
||||
}
|
||||
|
||||
func (p *CreatePoolParams) Validate() error {
|
||||
|
|
|
|||
|
|
@ -179,13 +179,13 @@ func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func instanceInList(instanceName string, instances []params.Instance) bool {
|
||||
func instanceInList(instanceName string, instances []params.Instance) (params.Instance, bool) {
|
||||
for _, val := range instances {
|
||||
if val.Name == instanceName {
|
||||
return true
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return params.Instance{}, false
|
||||
}
|
||||
|
||||
// cleanupOrphanedGithubRunners will forcefully remove any github runners that appear
|
||||
|
|
@ -252,6 +252,7 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
|
|||
var poolInstances []params.Instance
|
||||
poolInstances, ok = poolInstanceCache[pool.ID]
|
||||
if !ok {
|
||||
log.Printf("updating instances cache for pool %s", pool.ID)
|
||||
poolInstances, err = provider.ListInstances(r.ctx, pool.ID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "fetching instances for pool %s", pool.ID)
|
||||
|
|
@ -259,7 +260,8 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
|
|||
poolInstanceCache[pool.ID] = poolInstances
|
||||
}
|
||||
|
||||
if !instanceInList(dbInstance.Name, poolInstances) {
|
||||
providerInstance, ok := instanceInList(dbInstance.Name, poolInstances)
|
||||
if !ok {
|
||||
// The runner instance is no longer on the provider, and it appears offline in github.
|
||||
// It should be safe to force remove it.
|
||||
log.Printf("Runner instance for %s is no longer on the provider, removing from github", dbInstance.Name)
|
||||
|
|
@ -286,16 +288,18 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
|
|||
continue
|
||||
}
|
||||
|
||||
if providerCommon.InstanceStatus(dbInstance.Status) == providerCommon.InstanceRunning {
|
||||
if providerInstance.Status == providerCommon.InstanceRunning {
|
||||
// instance is running, but github reports runner as offline. Log the event.
|
||||
// This scenario requires manual intervention.
|
||||
// Perhaps it just came online and github did not yet change it's status?
|
||||
log.Printf("instance %s is online but github reports runner as offline", dbInstance.Name)
|
||||
continue
|
||||
}
|
||||
//start the instance
|
||||
if err := provider.Start(r.ctx, dbInstance.ProviderID); err != nil {
|
||||
return errors.Wrapf(err, "starting instance %s", dbInstance.ProviderID)
|
||||
} else {
|
||||
log.Printf("instance %s was found in stopped state; starting", dbInstance.Name)
|
||||
//start the instance
|
||||
if err := provider.Start(r.ctx, dbInstance.ProviderID); err != nil {
|
||||
return errors.Wrapf(err, "starting instance %s", dbInstance.ProviderID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -417,14 +421,15 @@ func (r *basePoolManager) AddRunner(ctx context.Context, poolID string) error {
|
|||
name := fmt.Sprintf("%s-%s", pool.GetRunnerPrefix(), util.NewID())
|
||||
|
||||
createParams := params.CreateInstanceParams{
|
||||
Name: name,
|
||||
Status: providerCommon.InstancePendingCreate,
|
||||
RunnerStatus: providerCommon.RunnerPending,
|
||||
OSArch: pool.OSArch,
|
||||
OSType: pool.OSType,
|
||||
CallbackURL: r.helper.GetCallbackURL(),
|
||||
MetadataURL: r.helper.GetMetadataURL(),
|
||||
CreateAttempt: 1,
|
||||
Name: name,
|
||||
Status: providerCommon.InstancePendingCreate,
|
||||
RunnerStatus: providerCommon.RunnerPending,
|
||||
OSArch: pool.OSArch,
|
||||
OSType: pool.OSType,
|
||||
CallbackURL: r.helper.GetCallbackURL(),
|
||||
MetadataURL: r.helper.GetMetadataURL(),
|
||||
CreateAttempt: 1,
|
||||
GitHubRunnerGroup: pool.GitHubRunnerGroup,
|
||||
}
|
||||
|
||||
_, err = r.store.CreateInstance(r.ctx, poolID, createParams)
|
||||
|
|
@ -603,19 +608,21 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
|
|||
}
|
||||
|
||||
bootstrapArgs := params.BootstrapInstance{
|
||||
Name: instance.Name,
|
||||
Tools: r.tools,
|
||||
RepoURL: r.helper.GithubURL(),
|
||||
MetadataURL: instance.MetadataURL,
|
||||
CallbackURL: instance.CallbackURL,
|
||||
InstanceToken: jwtToken,
|
||||
OSArch: pool.OSArch,
|
||||
Flavor: pool.Flavor,
|
||||
Image: pool.Image,
|
||||
ExtraSpecs: pool.ExtraSpecs,
|
||||
Labels: labels,
|
||||
PoolID: instance.PoolID,
|
||||
CACertBundle: r.credsDetails.CABundle,
|
||||
Name: instance.Name,
|
||||
Tools: r.tools,
|
||||
RepoURL: r.helper.GithubURL(),
|
||||
MetadataURL: instance.MetadataURL,
|
||||
CallbackURL: instance.CallbackURL,
|
||||
InstanceToken: jwtToken,
|
||||
OSArch: pool.OSArch,
|
||||
OSType: pool.OSType,
|
||||
Flavor: pool.Flavor,
|
||||
Image: pool.Image,
|
||||
ExtraSpecs: pool.ExtraSpecs,
|
||||
Labels: labels,
|
||||
PoolID: instance.PoolID,
|
||||
CACertBundle: r.credsDetails.CABundle,
|
||||
GitHubRunnerGroup: instance.GitHubRunnerGroup,
|
||||
}
|
||||
|
||||
var instanceIDToDelete string
|
||||
|
|
@ -1031,11 +1038,11 @@ func (r *basePoolManager) deletePendingInstances() {
|
|||
|
||||
err = r.deleteInstanceFromProvider(instance)
|
||||
if err != nil {
|
||||
log.Printf("failed to delete instance from provider: %+v", err)
|
||||
return errors.Wrap(err, "removing instance from provider")
|
||||
}
|
||||
|
||||
if err := r.store.DeleteInstance(r.ctx, instance.PoolID, instance.Name); err != nil {
|
||||
return errors.Wrap(err, "deleting instance from database")
|
||||
if deleteErr := r.store.DeleteInstance(r.ctx, instance.PoolID, instance.Name); deleteErr != nil {
|
||||
return errors.Wrap(deleteErr, "deleting instance from database")
|
||||
}
|
||||
return
|
||||
}(instance) //nolint
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ func (r *Runner) UpdatePoolByID(ctx context.Context, poolID string, param params
|
|||
}
|
||||
|
||||
if param.Tags != nil && len(param.Tags) > 0 {
|
||||
newTags, err := r.processTags(string(pool.OSArch), string(pool.OSType), param.Tags)
|
||||
newTags, err := r.processTags(string(pool.OSArch), pool.OSType, param.Tags)
|
||||
if err != nil {
|
||||
return params.Pool{}, errors.Wrap(err, "processing tags")
|
||||
}
|
||||
|
|
|
|||
13
runner/providers/external/execution/commands.go
vendored
Normal file
13
runner/providers/external/execution/commands.go
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package execution
|
||||
|
||||
type ExecutionCommand string
|
||||
|
||||
const (
|
||||
CreateInstanceCommand ExecutionCommand = "CreateInstance"
|
||||
DeleteInstanceCommand ExecutionCommand = "DeleteInstance"
|
||||
GetInstanceCommand ExecutionCommand = "GetInstance"
|
||||
ListInstancesCommand ExecutionCommand = "ListInstances"
|
||||
StartInstanceCommand ExecutionCommand = "StartInstance"
|
||||
StopInstanceCommand ExecutionCommand = "StopInstance"
|
||||
RemoveAllInstancesCommand ExecutionCommand = "RemoveAllInstances"
|
||||
)
|
||||
165
runner/providers/external/execution/execution.go
vendored
Normal file
165
runner/providers/external/execution/execution.go
vendored
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package execution
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cloudbase/garm/params"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
func GetEnvironment() (Environment, error) {
|
||||
env := Environment{
|
||||
Command: ExecutionCommand(os.Getenv("GARM_COMMAND")),
|
||||
ControllerID: os.Getenv("GARM_CONTROLLER_ID"),
|
||||
PoolID: os.Getenv("GARM_POOL_ID"),
|
||||
ProviderConfigFile: os.Getenv("GARM_PROVIDER_CONFIG_FILE"),
|
||||
InstanceID: os.Getenv("GARM_INSTANCE_ID"),
|
||||
}
|
||||
|
||||
// If this is a CreateInstance command, we need to get the bootstrap params
|
||||
// from stdin
|
||||
if env.Command == CreateInstanceCommand {
|
||||
if isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) {
|
||||
return Environment{}, fmt.Errorf("%s requires data passed into stdin", CreateInstanceCommand)
|
||||
}
|
||||
|
||||
var data bytes.Buffer
|
||||
if _, err := io.Copy(&data, os.Stdin); err != nil {
|
||||
return Environment{}, fmt.Errorf("failed to copy bootstrap params")
|
||||
}
|
||||
|
||||
if data.Len() == 0 {
|
||||
return Environment{}, fmt.Errorf("%s requires data passed into stdin", CreateInstanceCommand)
|
||||
}
|
||||
|
||||
var bootstrapParams params.BootstrapInstance
|
||||
if err := json.Unmarshal(data.Bytes(), &bootstrapParams); err != nil {
|
||||
return Environment{}, fmt.Errorf("failed to decode instance params: %w", err)
|
||||
}
|
||||
env.BootstrapParams = bootstrapParams
|
||||
}
|
||||
|
||||
if err := env.Validate(); err != nil {
|
||||
return Environment{}, fmt.Errorf("failed to validate execution environment: %w", err)
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
Command ExecutionCommand
|
||||
ControllerID string
|
||||
PoolID string
|
||||
ProviderConfigFile string
|
||||
InstanceID string
|
||||
BootstrapParams params.BootstrapInstance
|
||||
}
|
||||
|
||||
func (e Environment) Validate() error {
|
||||
if e.Command == "" {
|
||||
return fmt.Errorf("missing GARM_COMMAND")
|
||||
}
|
||||
|
||||
if e.ProviderConfigFile == "" {
|
||||
return fmt.Errorf("missing GARM_PROVIDER_CONFIG_FILE")
|
||||
}
|
||||
|
||||
if _, err := os.Lstat(e.ProviderConfigFile); err != nil {
|
||||
return fmt.Errorf("error accessing config file: %w", err)
|
||||
}
|
||||
|
||||
if e.ControllerID == "" {
|
||||
return fmt.Errorf("missing GARM_CONTROLLER_ID")
|
||||
}
|
||||
|
||||
switch e.Command {
|
||||
case CreateInstanceCommand:
|
||||
if e.BootstrapParams.Name == "" {
|
||||
return fmt.Errorf("missing bootstrap params")
|
||||
}
|
||||
if e.ControllerID == "" {
|
||||
return fmt.Errorf("missing controller ID")
|
||||
}
|
||||
if e.PoolID == "" {
|
||||
return fmt.Errorf("missing pool ID")
|
||||
}
|
||||
case DeleteInstanceCommand, GetInstanceCommand,
|
||||
StartInstanceCommand, StopInstanceCommand:
|
||||
if e.InstanceID == "" {
|
||||
return fmt.Errorf("missing instance ID")
|
||||
}
|
||||
case ListInstancesCommand:
|
||||
if e.PoolID == "" {
|
||||
return fmt.Errorf("missing pool ID")
|
||||
}
|
||||
case RemoveAllInstancesCommand:
|
||||
if e.ControllerID == "" {
|
||||
return fmt.Errorf("missing controller ID")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown GARM_COMMAND: %s", e.Command)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Run(ctx context.Context, provider ExternalProvider, env Environment) (string, error) {
|
||||
var ret string
|
||||
switch env.Command {
|
||||
case CreateInstanceCommand:
|
||||
instance, err := provider.CreateInstance(ctx, env.BootstrapParams)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create instance in provider: %w", err)
|
||||
}
|
||||
|
||||
asJs, err := json.Marshal(instance)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal response: %w", err)
|
||||
}
|
||||
ret = string(asJs)
|
||||
case GetInstanceCommand:
|
||||
instance, err := provider.GetInstance(ctx, env.InstanceID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get instance from provider: %w", err)
|
||||
}
|
||||
asJs, err := json.Marshal(instance)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal response: %w", err)
|
||||
}
|
||||
ret = string(asJs)
|
||||
case ListInstancesCommand:
|
||||
instances, err := provider.ListInstances(ctx, env.PoolID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to list instances from provider: %w", err)
|
||||
}
|
||||
asJs, err := json.Marshal(instances)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal response: %w", err)
|
||||
}
|
||||
ret = string(asJs)
|
||||
case DeleteInstanceCommand:
|
||||
if err := provider.DeleteInstance(ctx, env.InstanceID); err != nil {
|
||||
return "", fmt.Errorf("failed to delete instance from provider: %w", err)
|
||||
}
|
||||
case RemoveAllInstancesCommand:
|
||||
if err := provider.RemoveAllInstances(ctx); err != nil {
|
||||
return "", fmt.Errorf("failed to destroy environment: %w", err)
|
||||
}
|
||||
case StartInstanceCommand:
|
||||
if err := provider.Start(ctx, env.InstanceID); err != nil {
|
||||
return "", fmt.Errorf("failed to start instance: %w", err)
|
||||
}
|
||||
case StopInstanceCommand:
|
||||
if err := provider.Stop(ctx, env.InstanceID, true); err != nil {
|
||||
return "", fmt.Errorf("failed to stop instance: %w", err)
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("invalid command: %s", env.Command)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
26
runner/providers/external/execution/exit_codes.go
vendored
Normal file
26
runner/providers/external/execution/exit_codes.go
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package execution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
gErrors "github.com/cloudbase/garm/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// ExitCodeNotFound is an exit code that indicates a Not Found error
|
||||
ExitCodeNotFound int = 30
|
||||
// ExitCodeDuplicate is an exit code that indicates a duplicate error
|
||||
ExitCodeDuplicate int = 31
|
||||
)
|
||||
|
||||
func ResolveErrorToExitCode(err error) int {
|
||||
if err != nil {
|
||||
if errors.Is(err, gErrors.ErrNotFound) {
|
||||
return ExitCodeNotFound
|
||||
} else if errors.Is(err, gErrors.ErrDuplicateEntity) {
|
||||
return ExitCodeDuplicate
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
27
runner/providers/external/execution/interface.go
vendored
Normal file
27
runner/providers/external/execution/interface.go
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package execution
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudbase/garm/params"
|
||||
)
|
||||
|
||||
// ExternalProvider defines an interface that external providers need to implement.
|
||||
// This is very similar to the common.Provider interface, and was redefined here to
|
||||
// decouple it, in case it may diverge from native providers.
|
||||
type ExternalProvider interface {
|
||||
// CreateInstance creates a new compute instance in the provider.
|
||||
CreateInstance(ctx context.Context, bootstrapParams params.BootstrapInstance) (params.Instance, error)
|
||||
// Delete instance will delete the instance in a provider.
|
||||
DeleteInstance(ctx context.Context, instance string) error
|
||||
// GetInstance will return details about one instance.
|
||||
GetInstance(ctx context.Context, instance string) (params.Instance, error)
|
||||
// ListInstances will list all instances for a provider.
|
||||
ListInstances(ctx context.Context, poolID string) ([]params.Instance, error)
|
||||
// RemoveAllInstances will remove all instances created by this provider.
|
||||
RemoveAllInstances(ctx context.Context) error
|
||||
// Stop shuts down the instance.
|
||||
Stop(ctx context.Context, instance string, force bool) error
|
||||
// Start boots up an instance.
|
||||
Start(ctx context.Context, instance string) error
|
||||
}
|
||||
72
runner/providers/external/external.go
vendored
72
runner/providers/external/external.go
vendored
|
|
@ -5,13 +5,15 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
|
||||
"github.com/cloudbase/garm/config"
|
||||
garmErrors "github.com/cloudbase/garm/errors"
|
||||
"github.com/cloudbase/garm/params"
|
||||
"github.com/cloudbase/garm/runner/common"
|
||||
providerCommon "github.com/cloudbase/garm/runner/providers/common"
|
||||
"github.com/cloudbase/garm/util/exec"
|
||||
"github.com/cloudbase/garm/runner/providers/external/execution"
|
||||
garmExec "github.com/cloudbase/garm/util/exec"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
|
@ -42,11 +44,7 @@ type external struct {
|
|||
execPath string
|
||||
}
|
||||
|
||||
func (e *external) configEnvVar() string {
|
||||
return fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile)
|
||||
}
|
||||
|
||||
func (e *external) validateCreateResult(inst params.Instance, bootstrapParams params.BootstrapInstance) error {
|
||||
func (e *external) validateCreateResult(inst params.Instance) error {
|
||||
if inst.ProviderID == "" {
|
||||
return garmErrors.NewProviderError("missing provider ID after create call")
|
||||
}
|
||||
|
|
@ -68,18 +66,19 @@ func (e *external) validateCreateResult(inst params.Instance, bootstrapParams pa
|
|||
|
||||
// CreateInstance creates a new compute instance in the provider.
|
||||
func (e *external) CreateInstance(ctx context.Context, bootstrapParams params.BootstrapInstance) (params.Instance, error) {
|
||||
asEnv := bootstrapParamsToEnv(bootstrapParams)
|
||||
asEnv = append(asEnv, createInstanceCommand)
|
||||
asEnv = append(asEnv, fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID))
|
||||
asEnv = append(asEnv, fmt.Sprintf("GARM_POOL_ID=%s", bootstrapParams.PoolID))
|
||||
asEnv = append(asEnv, e.configEnvVar())
|
||||
asEnv := []string{
|
||||
fmt.Sprintf("GARM_COMMAND=%s", execution.CreateInstanceCommand),
|
||||
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
||||
fmt.Sprintf("GARM_POOL_ID=%s", bootstrapParams.PoolID),
|
||||
fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile),
|
||||
}
|
||||
|
||||
asJs, err := json.Marshal(bootstrapParams)
|
||||
if err != nil {
|
||||
return params.Instance{}, errors.Wrap(err, "serializing bootstrap params")
|
||||
}
|
||||
|
||||
out, err := exec.Exec(ctx, e.execPath, asJs, asEnv)
|
||||
out, err := garmExec.Exec(ctx, e.execPath, asJs, asEnv)
|
||||
if err != nil {
|
||||
return params.Instance{}, garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
}
|
||||
|
|
@ -89,7 +88,7 @@ func (e *external) CreateInstance(ctx context.Context, bootstrapParams params.Bo
|
|||
return params.Instance{}, garmErrors.NewProviderError("failed to decode response from binary: %s", err)
|
||||
}
|
||||
|
||||
if err := e.validateCreateResult(param, bootstrapParams); err != nil {
|
||||
if err := e.validateCreateResult(param); err != nil {
|
||||
return params.Instance{}, garmErrors.NewProviderError("failed to validate result: %s", err)
|
||||
}
|
||||
|
||||
|
|
@ -101,14 +100,19 @@ func (e *external) CreateInstance(ctx context.Context, bootstrapParams params.Bo
|
|||
// Delete instance will delete the instance in a provider.
|
||||
func (e *external) DeleteInstance(ctx context.Context, instance string) error {
|
||||
asEnv := []string{
|
||||
deleteInstanceCommand,
|
||||
e.configEnvVar(),
|
||||
fmt.Sprintf("GARM_COMMAND=%s", execution.DeleteInstanceCommand),
|
||||
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
||||
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
||||
fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile),
|
||||
}
|
||||
|
||||
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
_, err := garmExec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
if err != nil {
|
||||
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
var exitErr exec.ExitError
|
||||
if !errors.As(err, &exitErr) || exitErr.ExitCode() != execution.ExitCodeNotFound {
|
||||
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -116,14 +120,15 @@ func (e *external) DeleteInstance(ctx context.Context, instance string) error {
|
|||
// GetInstance will return details about one instance.
|
||||
func (e *external) GetInstance(ctx context.Context, instance string) (params.Instance, error) {
|
||||
asEnv := []string{
|
||||
getInstanceCommand,
|
||||
e.configEnvVar(),
|
||||
fmt.Sprintf("GARM_COMMAND=%s", execution.GetInstanceCommand),
|
||||
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
||||
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
||||
fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile),
|
||||
}
|
||||
|
||||
// TODO(gabriel-samfira): handle error types. Of particular insterest is to
|
||||
// know when the error is ErrNotFound.
|
||||
out, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
out, err := garmExec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
if err != nil {
|
||||
return params.Instance{}, garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
}
|
||||
|
|
@ -138,12 +143,13 @@ func (e *external) GetInstance(ctx context.Context, instance string) (params.Ins
|
|||
// ListInstances will list all instances for a provider.
|
||||
func (e *external) ListInstances(ctx context.Context, poolID string) ([]params.Instance, error) {
|
||||
asEnv := []string{
|
||||
listInstancesCommand,
|
||||
e.configEnvVar(),
|
||||
fmt.Sprintf("GARM_COMMAND=%s", execution.ListInstancesCommand),
|
||||
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
||||
fmt.Sprintf("GARM_POOL_ID=%s", poolID),
|
||||
fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile),
|
||||
}
|
||||
|
||||
out, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
out, err := garmExec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
if err != nil {
|
||||
return []params.Instance{}, garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
}
|
||||
|
|
@ -158,11 +164,11 @@ func (e *external) ListInstances(ctx context.Context, poolID string) ([]params.I
|
|||
// RemoveAllInstances will remove all instances created by this provider.
|
||||
func (e *external) RemoveAllInstances(ctx context.Context) error {
|
||||
asEnv := []string{
|
||||
removeAllInstancesCommand,
|
||||
e.configEnvVar(),
|
||||
fmt.Sprintf("GARM_COMMAND=%s", execution.RemoveAllInstancesCommand),
|
||||
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
||||
fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile),
|
||||
}
|
||||
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
_, err := garmExec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
if err != nil {
|
||||
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
}
|
||||
|
|
@ -172,11 +178,12 @@ func (e *external) RemoveAllInstances(ctx context.Context) error {
|
|||
// Stop shuts down the instance.
|
||||
func (e *external) Stop(ctx context.Context, instance string, force bool) error {
|
||||
asEnv := []string{
|
||||
stopInstanceCommand,
|
||||
e.configEnvVar(),
|
||||
fmt.Sprintf("GARM_COMMAND=%s", execution.StopInstanceCommand),
|
||||
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
||||
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
||||
fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile),
|
||||
}
|
||||
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
_, err := garmExec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
if err != nil {
|
||||
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
}
|
||||
|
|
@ -186,11 +193,12 @@ func (e *external) Stop(ctx context.Context, instance string, force bool) error
|
|||
// Start boots up an instance.
|
||||
func (e *external) Start(ctx context.Context, instance string) error {
|
||||
asEnv := []string{
|
||||
startInstanceCommand,
|
||||
e.configEnvVar(),
|
||||
fmt.Sprintf("GARM_COMMAND=%s", execution.StartInstanceCommand),
|
||||
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
||||
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
||||
fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile),
|
||||
}
|
||||
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
_, err := garmExec.Exec(ctx, e.execPath, nil, asEnv)
|
||||
if err != nil {
|
||||
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
||||
}
|
||||
|
|
|
|||
49
runner/providers/external/util.go
vendored
49
runner/providers/external/util.go
vendored
|
|
@ -1,49 +0,0 @@
|
|||
package external
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudbase/garm/params"
|
||||
)
|
||||
|
||||
const (
|
||||
envPrefix = "GARM"
|
||||
|
||||
createInstanceCommand = "GARM_COMMAND=CreateInstance"
|
||||
deleteInstanceCommand = "GARM_COMMAND=DeleteInstance"
|
||||
getInstanceCommand = "GARM_COMMAND=GetInstance"
|
||||
listInstancesCommand = "GARM_COMMAND=ListInstances"
|
||||
startInstanceCommand = "GARM_COMMAND=StartInstance"
|
||||
stopInstanceCommand = "GARM_COMMAND=StopInstance"
|
||||
removeAllInstancesCommand = "GARM_COMMAND=RemoveAllInstances"
|
||||
)
|
||||
|
||||
func bootstrapParamsToEnv(param params.BootstrapInstance) []string {
|
||||
ret := []string{
|
||||
fmt.Sprintf("%s_BOOTSTRAP_NAME='%s'", envPrefix, param.Name),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_OS_ARCH='%s'", envPrefix, param.OSArch),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_FLAVOR='%s'", envPrefix, param.Flavor),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_IMAGE='%s'", envPrefix, param.Image),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_POOL_ID='%s'", envPrefix, param.PoolID),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_INSTANCE_TOKEN='%s'", envPrefix, param.InstanceToken),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_CALLBACK_URL='%s'", envPrefix, param.CallbackURL),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_REPO_URL='%s'", envPrefix, param.RepoURL),
|
||||
fmt.Sprintf("%s_BOOTSTRAP_LABELS='%s'", envPrefix, strings.Join(param.Labels, ",")),
|
||||
}
|
||||
|
||||
for idx, tool := range param.Tools {
|
||||
ret = append(ret, fmt.Sprintf("%s_BOOTSTRAP_TOOLS_DOWNLOAD_URL_%d='%s'", envPrefix, idx, *tool.DownloadURL))
|
||||
ret = append(ret, fmt.Sprintf("%s_BOOTSTRAP_TOOLS_ARCH_%d='%s'", envPrefix, idx, *tool.Architecture))
|
||||
ret = append(ret, fmt.Sprintf("%s_BOOTSTRAP_TOOLS_OS_%d='%s'", envPrefix, idx, *tool.OS))
|
||||
ret = append(ret, fmt.Sprintf("%s_BOOTSTRAP_TOOLS_FILENAME_%d='%s'", envPrefix, idx, *tool.Filename))
|
||||
ret = append(ret, fmt.Sprintf("%s_BOOTSTRAP_TOOLS_SHA256_%d='%s'", envPrefix, idx, *tool.SHA256Checksum))
|
||||
}
|
||||
|
||||
for idx, sshKey := range param.SSHKeys {
|
||||
ret = append(ret, fmt.Sprintf("%s_BOOTSTRAP_SSH_KEY_%d='%s'", envPrefix, idx, sshKey))
|
||||
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ package lxd
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudbase/garm/config"
|
||||
|
|
@ -38,6 +39,16 @@ const (
|
|||
// created by us or not.
|
||||
controllerIDKeyName = "user.runner-controller-id"
|
||||
poolIDKey = "user.runner-pool-id"
|
||||
|
||||
// osTypeKeyName is the key we use in the instance config to indicate the OS
|
||||
// platform a runner is supposed to have. This value is defined in the pool and
|
||||
// passed into the provider as bootstrap params.
|
||||
osTypeKeyName = "user.os-type"
|
||||
|
||||
// osArchKeyNAme is the key we use in the instance config to indicate the OS
|
||||
// architecture a runner is supposed to have. This value is defined in the pool and
|
||||
// passed into the provider as bootstrap params.
|
||||
osArchKeyNAme = "user.os-arch"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -159,7 +170,7 @@ func (l *LXD) getProfiles(flavor string) ([]string, error) {
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (l *LXD) getTools(image *api.Image, tools []*github.RunnerApplicationDownload) (github.RunnerApplicationDownload, error) {
|
||||
func (l *LXD) getTools(image *api.Image, tools []*github.RunnerApplicationDownload, assumedOSType params.OSType) (github.RunnerApplicationDownload, error) {
|
||||
if image == nil {
|
||||
return github.RunnerApplicationDownload{}, fmt.Errorf("nil image received")
|
||||
}
|
||||
|
|
@ -170,7 +181,8 @@ func (l *LXD) getTools(image *api.Image, tools []*github.RunnerApplicationDownlo
|
|||
|
||||
osType, err := util.OSToOSType(osName)
|
||||
if err != nil {
|
||||
return github.RunnerApplicationDownload{}, errors.Wrap(err, "fetching OS type")
|
||||
log.Printf("failed to determine OS type from image, assuming %s", assumedOSType)
|
||||
osType = assumedOSType
|
||||
}
|
||||
|
||||
// Validate image OS. Linux only for now.
|
||||
|
|
@ -232,7 +244,7 @@ func (l *LXD) getCreateInstanceArgs(bootstrapParams params.BootstrapInstance) (a
|
|||
return api.InstancesPost{}, errors.Wrap(err, "getting image details")
|
||||
}
|
||||
|
||||
tools, err := l.getTools(image, bootstrapParams.Tools)
|
||||
tools, err := l.getTools(image, bootstrapParams.Tools, bootstrapParams.OSType)
|
||||
if err != nil {
|
||||
return api.InstancesPost{}, errors.Wrap(err, "getting tools")
|
||||
}
|
||||
|
|
@ -244,6 +256,8 @@ func (l *LXD) getCreateInstanceArgs(bootstrapParams params.BootstrapInstance) (a
|
|||
|
||||
configMap := map[string]string{
|
||||
"user.user-data": cloudCfg,
|
||||
osTypeKeyName: string(bootstrapParams.OSType),
|
||||
osArchKeyNAme: string(bootstrapParams.OSArch),
|
||||
controllerIDKeyName: l.controllerID,
|
||||
poolIDKey: bootstrapParams.PoolID,
|
||||
}
|
||||
|
|
@ -370,11 +384,17 @@ func (l *LXD) DeleteInstance(ctx context.Context, instance string) error {
|
|||
|
||||
op, err := cli.DeleteInstance(instance)
|
||||
if err != nil {
|
||||
if isNotFoundError(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "removing instance")
|
||||
}
|
||||
|
||||
err = op.Wait()
|
||||
if err != nil {
|
||||
if isNotFoundError(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "waiting for instance deletion")
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
//lint:ignore ST1005 imported error from lxd
|
||||
errInstanceIsStopped error = fmt.Errorf("The instance is already stopped")
|
||||
)
|
||||
|
||||
|
|
@ -61,15 +62,24 @@ func isNotFoundError(err error) bool {
|
|||
}
|
||||
|
||||
func lxdInstanceToAPIInstance(instance *api.InstanceFull) params.Instance {
|
||||
os, ok := instance.ExpandedConfig["image.os"]
|
||||
lxdOS, ok := instance.ExpandedConfig["image.os"]
|
||||
if !ok {
|
||||
log.Printf("failed to find OS in instance config")
|
||||
}
|
||||
|
||||
osType, err := util.OSToOSType(os)
|
||||
osType, err := util.OSToOSType(lxdOS)
|
||||
if err != nil {
|
||||
log.Printf("failed to find OS type for OS %s", os)
|
||||
log.Printf("failed to find OS type for OS %s", lxdOS)
|
||||
}
|
||||
|
||||
if osType == "" {
|
||||
osTypeFromTag, ok := instance.ExpandedConfig[osTypeKeyName]
|
||||
if !ok {
|
||||
log.Printf("failed to find OS type in fallback location")
|
||||
}
|
||||
osType = params.OSType(osTypeFromTag)
|
||||
}
|
||||
|
||||
osRelease, ok := instance.ExpandedConfig["image.release"]
|
||||
if !ok {
|
||||
log.Printf("failed to find OS release instance config")
|
||||
|
|
@ -101,7 +111,7 @@ func lxdInstanceToAPIInstance(instance *api.InstanceFull) params.Instance {
|
|||
ProviderID: instance.Name,
|
||||
Name: instance.Name,
|
||||
OSType: osType,
|
||||
OSName: strings.ToLower(os),
|
||||
OSName: strings.ToLower(lxdOS),
|
||||
OSVersion: osRelease,
|
||||
Addresses: addresses,
|
||||
Status: lxdStatusToProviderStatus(state.Status),
|
||||
|
|
|
|||
|
|
@ -690,7 +690,7 @@ func (r *Runner) appendTagsToCreatePoolParams(param params.CreatePoolParams) (pa
|
|||
return params.CreatePoolParams{}, runnerErrors.NewBadRequestError("no such provider %s", param.ProviderName)
|
||||
}
|
||||
|
||||
newTags, err := r.processTags(string(param.OSArch), string(param.OSType), param.Tags)
|
||||
newTags, err := r.processTags(string(param.OSArch), param.OSType, param.Tags)
|
||||
if err != nil {
|
||||
return params.CreatePoolParams{}, errors.Wrap(err, "processing tags")
|
||||
}
|
||||
|
|
@ -700,7 +700,7 @@ func (r *Runner) appendTagsToCreatePoolParams(param params.CreatePoolParams) (pa
|
|||
return param, nil
|
||||
}
|
||||
|
||||
func (r *Runner) processTags(osArch, osType string, tags []string) ([]string, error) {
|
||||
func (r *Runner) processTags(osArch string, osType params.OSType, tags []string) ([]string, error) {
|
||||
// github automatically adds the "self-hosted" tag as well as the OS type (linux, windows, etc)
|
||||
// and architecture (arm, x64, etc) to all self hosted runners. When a workflow job comes in, we try
|
||||
// to find a pool based on the labels that are set in the workflow. If we don't explicitly define these
|
||||
|
|
@ -713,7 +713,7 @@ func (r *Runner) processTags(osArch, osType string, tags []string) ([]string, er
|
|||
return nil, errors.Wrap(err, "invalid arch")
|
||||
}
|
||||
|
||||
ghOSType, err := util.ResolveToGithubOSType(osType)
|
||||
ghOSType, err := util.ResolveToGithubTag(osType)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid os type")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ 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.Linux: {},
|
||||
params.Windows: {},
|
||||
}
|
||||
|
||||
// These are the architectures that Github supports.
|
||||
|
|
|
|||
168
util/util.go
168
util/util.go
|
|
@ -15,6 +15,8 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
|
@ -22,6 +24,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
|
@ -31,6 +34,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/cloudbase/garm/cloudconfig"
|
||||
"github.com/cloudbase/garm/config"
|
||||
|
|
@ -89,8 +93,17 @@ var (
|
|||
"linux": "linux",
|
||||
"windows": "win",
|
||||
}
|
||||
|
||||
//
|
||||
githubOSTag = map[params.OSType]string{
|
||||
params.Linux: "Linux",
|
||||
params.Windows: "Windows",
|
||||
}
|
||||
)
|
||||
|
||||
// ResolveToGithubArch returns the cpu architecture as it is defined in the GitHub
|
||||
// tools download list. We use it to find the proper tools for the OS/Arch combo we're
|
||||
// deploying.
|
||||
func ResolveToGithubArch(arch string) (string, error) {
|
||||
ghArch, ok := githubArchMapping[arch]
|
||||
if !ok {
|
||||
|
|
@ -100,6 +113,9 @@ func ResolveToGithubArch(arch string) (string, error) {
|
|||
return ghArch, nil
|
||||
}
|
||||
|
||||
// ResolveToGithubArch returns the OS type as it is defined in the GitHub
|
||||
// tools download list. We use it to find the proper tools for the OS/Arch combo we're
|
||||
// deploying.
|
||||
func ResolveToGithubOSType(osType string) (string, error) {
|
||||
ghOS, ok := githubOSTypeMap[osType]
|
||||
if !ok {
|
||||
|
|
@ -109,6 +125,18 @@ func ResolveToGithubOSType(osType string) (string, error) {
|
|||
return ghOS, nil
|
||||
}
|
||||
|
||||
// ResolveToGithubTag returns the default OS tag that self hosted runners automatically
|
||||
// (and forcefully) adds to every runner that gets deployed. We need to keep track of those
|
||||
// tags internally as well.
|
||||
func ResolveToGithubTag(os params.OSType) (string, error) {
|
||||
ghOS, ok := githubOSTag[os]
|
||||
if !ok {
|
||||
return "", runnerErrors.NewNotFoundError("os %s is unknown", os)
|
||||
}
|
||||
|
||||
return ghOS, nil
|
||||
}
|
||||
|
||||
// IsValidEmail returs a bool indicating if an email is valid
|
||||
func IsValidEmail(email string) bool {
|
||||
if len(email) > 254 || !rxEmail.MatchString(email) {
|
||||
|
|
@ -198,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")
|
||||
}
|
||||
|
|
@ -225,29 +251,84 @@ func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.Runne
|
|||
RunnerLabels: strings.Join(bootstrapParams.Labels, ","),
|
||||
CallbackURL: bootstrapParams.CallbackURL,
|
||||
CallbackToken: bootstrapParams.InstanceToken,
|
||||
GitHubRunnerGroup: bootstrapParams.GitHubRunnerGroup,
|
||||
}
|
||||
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")
|
||||
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)
|
||||
}
|
||||
|
||||
if bootstrapParams.CACertBundle != nil && len(bootstrapParams.CACertBundle) > 0 {
|
||||
if err := cloudCfg.AddCACert(bootstrapParams.CACertBundle); err != nil {
|
||||
return "", errors.Wrap(err, "adding CA cert bundle")
|
||||
return asStr, nil
|
||||
}
|
||||
|
||||
func GetTools(osType params.OSType, osArch params.OSArch, tools []*github.RunnerApplicationDownload) (github.RunnerApplicationDownload, error) {
|
||||
// Validate image OS. Linux only for now.
|
||||
switch osType {
|
||||
case params.Linux:
|
||||
case params.Windows:
|
||||
default:
|
||||
return github.RunnerApplicationDownload{}, fmt.Errorf("unsupported OS type: %s", osType)
|
||||
}
|
||||
|
||||
switch osArch {
|
||||
case params.Amd64:
|
||||
case params.Arm:
|
||||
case params.Arm64:
|
||||
default:
|
||||
return github.RunnerApplicationDownload{}, fmt.Errorf("unsupported OS arch: %s", osArch)
|
||||
}
|
||||
|
||||
// Find tools for OS/Arch.
|
||||
for _, tool := range tools {
|
||||
if tool == nil {
|
||||
continue
|
||||
}
|
||||
if tool.OS == nil || tool.Architecture == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ghArch, err := ResolveToGithubArch(string(osArch))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ghOS, err := ResolveToGithubOSType(string(osType))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if *tool.Architecture == ghArch && *tool.OS == ghOS {
|
||||
return *tool, nil
|
||||
}
|
||||
}
|
||||
|
||||
asStr, err := cloudCfg.Serialize()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "creating cloud config")
|
||||
}
|
||||
return asStr, nil
|
||||
return github.RunnerApplicationDownload{}, fmt.Errorf("failed to find tools for OS %s and arch %s", osType, osArch)
|
||||
}
|
||||
|
||||
// GetRandomString returns a secure random string
|
||||
|
|
@ -350,3 +431,58 @@ func NewID() string {
|
|||
newUUID := uuid.New()
|
||||
return toBase62(newUUID[:])
|
||||
}
|
||||
|
||||
func UTF16FromString(s string) ([]uint16, error) {
|
||||
buf := make([]uint16, 0, len(s)*2+1)
|
||||
for _, r := range s {
|
||||
buf = utf16.AppendRune(buf, r)
|
||||
}
|
||||
return utf16.AppendRune(buf, '\x00'), nil
|
||||
}
|
||||
|
||||
func UTF16ToString(s []uint16) string {
|
||||
for i, v := range s {
|
||||
if v == 0 {
|
||||
s = s[0:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
return string(utf16.Decode(s))
|
||||
}
|
||||
|
||||
func Uint16ToByteArray(u []uint16) []byte {
|
||||
ret := make([]byte, (len(u)-1)*2)
|
||||
for i := 0; i < len(u)-1; i++ {
|
||||
binary.LittleEndian.PutUint16(ret[i*2:], uint16(u[i]))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func UTF16EncodedByteArrayFromString(s string) ([]byte, error) {
|
||||
asUint16, err := UTF16FromString(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode to uint16: %w", err)
|
||||
}
|
||||
asBytes := Uint16ToByteArray(asUint16)
|
||||
return asBytes, nil
|
||||
}
|
||||
|
||||
func CompressData(data []byte) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
gz := gzip.NewWriter(&b)
|
||||
|
||||
_, err := gz.Write(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compress data: %w", err)
|
||||
}
|
||||
|
||||
if err = gz.Flush(); err != nil {
|
||||
return nil, fmt.Errorf("failed to flush buffer: %w", err)
|
||||
}
|
||||
|
||||
if err = gz.Close(); err != nil {
|
||||
return nil, fmt.Errorf("failed to close buffer: %w", err)
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
|
|
|||
4
vendor/github.com/lxc/lxd/client/connection.go
generated
vendored
4
vendor/github.com/lxc/lxd/client/connection.go
generated
vendored
|
|
@ -84,11 +84,11 @@ func ConnectLXD(url string, args *ConnectionArgs) (InstanceServer, error) {
|
|||
//
|
||||
// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
|
||||
func ConnectLXDWithContext(ctx context.Context, url string, args *ConnectionArgs) (InstanceServer, error) {
|
||||
logger.Debug("Connecting to a remote LXD over HTTPS")
|
||||
|
||||
// Cleanup URL
|
||||
url = strings.TrimSuffix(url, "/")
|
||||
|
||||
logger.Debug("Connecting to a remote LXD over HTTPS", logger.Ctx{"url": url})
|
||||
|
||||
return httpsLXD(ctx, url, args)
|
||||
}
|
||||
|
||||
|
|
|
|||
2
vendor/github.com/lxc/lxd/client/lxd.go
generated
vendored
2
vendor/github.com/lxc/lxd/client/lxd.go
generated
vendored
|
|
@ -440,7 +440,7 @@ func (r *ProtocolLXD) rawWebsocket(url string) (*websocket.Conn, error) {
|
|||
if remoteTCP != nil {
|
||||
err = tcp.SetTimeouts(remoteTCP, 0)
|
||||
if err != nil {
|
||||
logger.Error("Failed setting TCP timeouts on remote connection", logger.Ctx{"err": err})
|
||||
logger.Warn("Failed setting TCP timeouts on remote connection", logger.Ctx{"err": err})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
8
vendor/github.com/lxc/lxd/client/lxd_containers.go
generated
vendored
8
vendor/github.com/lxc/lxd/client/lxd_containers.go
generated
vendored
|
|
@ -651,8 +651,8 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
|
|||
}
|
||||
|
||||
// Call the control handler with a connection to the control socket
|
||||
if args.Control != nil && fds["control"] != "" {
|
||||
conn, err := r.GetOperationWebsocket(opAPI.ID, fds["control"])
|
||||
if args.Control != nil && fds[api.SecretNameControl] != "" {
|
||||
conn, err := r.GetOperationWebsocket(opAPI.ID, fds[api.SecretNameControl])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1546,11 +1546,11 @@ func (r *ProtocolLXD) ConsoleContainer(containerName string, console api.Contain
|
|||
|
||||
var controlConn *websocket.Conn
|
||||
// Call the control handler with a connection to the control socket
|
||||
if fds["control"] == "" {
|
||||
if fds[api.SecretNameControl] == "" {
|
||||
return nil, fmt.Errorf("Did not receive a file descriptor for the control channel")
|
||||
}
|
||||
|
||||
controlConn, err = r.GetOperationWebsocket(opAPI.ID, fds["control"])
|
||||
controlConn, err = r.GetOperationWebsocket(opAPI.ID, fds[api.SecretNameControl])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
30
vendor/github.com/lxc/lxd/client/lxd_instances.go
generated
vendored
30
vendor/github.com/lxc/lxd/client/lxd_instances.go
generated
vendored
|
|
@ -1046,8 +1046,8 @@ func (r *ProtocolLXD) ExecInstance(instanceName string, exec api.InstanceExecPos
|
|||
}
|
||||
|
||||
// Call the control handler with a connection to the control socket
|
||||
if args.Control != nil && fds["control"] != "" {
|
||||
conn, err := r.GetOperationWebsocket(opAPI.ID, fds["control"])
|
||||
if args.Control != nil && fds[api.SecretNameControl] != "" {
|
||||
conn, err := r.GetOperationWebsocket(opAPI.ID, fds[api.SecretNameControl])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -2209,11 +2209,11 @@ func (r *ProtocolLXD) ConsoleInstance(instanceName string, console api.InstanceC
|
|||
|
||||
var controlConn *websocket.Conn
|
||||
// Call the control handler with a connection to the control socket
|
||||
if fds["control"] == "" {
|
||||
if fds[api.SecretNameControl] == "" {
|
||||
return nil, fmt.Errorf("Did not receive a file descriptor for the control channel")
|
||||
}
|
||||
|
||||
controlConn, err = r.GetOperationWebsocket(opAPI.ID, fds["control"])
|
||||
controlConn, err = r.GetOperationWebsocket(opAPI.ID, fds[api.SecretNameControl])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -2296,11 +2296,11 @@ func (r *ProtocolLXD) ConsoleInstanceDynamic(instanceName string, console api.In
|
|||
}
|
||||
|
||||
// Call the control handler with a connection to the control socket.
|
||||
if fds["control"] == "" {
|
||||
if fds[api.SecretNameControl] == "" {
|
||||
return nil, nil, fmt.Errorf("Did not receive a file descriptor for the control channel")
|
||||
}
|
||||
|
||||
controlConn, err := r.GetOperationWebsocket(opAPI.ID, fds["control"])
|
||||
controlConn, err := r.GetOperationWebsocket(opAPI.ID, fds[api.SecretNameControl])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -2600,7 +2600,7 @@ func (r *ProtocolLXD) proxyMigration(targetOp *operation, targetSecrets map[stri
|
|||
}
|
||||
}
|
||||
|
||||
if targetSecrets["control"] == "" {
|
||||
if targetSecrets[api.SecretNameControl] == "" {
|
||||
return fmt.Errorf("Migration target didn't setup the required \"control\" socket")
|
||||
}
|
||||
|
||||
|
|
@ -2614,17 +2614,17 @@ func (r *ProtocolLXD) proxyMigration(targetOp *operation, targetSecrets map[stri
|
|||
proxies := map[string]*proxy{}
|
||||
|
||||
// Connect the control socket
|
||||
sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets["control"])
|
||||
sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets[api.SecretNameControl])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets["control"])
|
||||
targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[api.SecretNameControl])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxies["control"] = &proxy{
|
||||
proxies[api.SecretNameControl] = &proxy{
|
||||
done: shared.WebsocketProxy(sourceConn, targetConn),
|
||||
sourceConn: sourceConn,
|
||||
targetConn: targetConn,
|
||||
|
|
@ -2632,7 +2632,7 @@ func (r *ProtocolLXD) proxyMigration(targetOp *operation, targetSecrets map[stri
|
|||
|
||||
// Connect the data sockets
|
||||
for name := range sourceSecrets {
|
||||
if name == "control" {
|
||||
if name == api.SecretNameControl {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -2657,13 +2657,13 @@ func (r *ProtocolLXD) proxyMigration(targetOp *operation, targetSecrets map[stri
|
|||
// Cleanup once everything is done
|
||||
go func() {
|
||||
// Wait for control socket
|
||||
<-proxies["control"].done
|
||||
_ = proxies["control"].sourceConn.Close()
|
||||
_ = proxies["control"].targetConn.Close()
|
||||
<-proxies[api.SecretNameControl].done
|
||||
_ = proxies[api.SecretNameControl].sourceConn.Close()
|
||||
_ = proxies[api.SecretNameControl].targetConn.Close()
|
||||
|
||||
// Then deal with the others
|
||||
for name, proxy := range proxies {
|
||||
if name == "control" {
|
||||
if name == api.SecretNameControl {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
10
vendor/github.com/lxc/lxd/shared/api/migration.go
generated
vendored
Normal file
10
vendor/github.com/lxc/lxd/shared/api/migration.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package api
|
||||
|
||||
// SecretNameControl is the secret name used for the migration control connection.
|
||||
const SecretNameControl = "control"
|
||||
|
||||
// SecretNameFilesystem is the secret name used for the migration filesystem connection.
|
||||
const SecretNameFilesystem = "fs"
|
||||
|
||||
// SecretNameState is the secret name used for the migration state connection.
|
||||
const SecretNameState = "criu" // Legacy value used for backward compatibility for clients.
|
||||
9
vendor/github.com/lxc/lxd/shared/instance.go
generated
vendored
9
vendor/github.com/lxc/lxd/shared/instance.go
generated
vendored
|
|
@ -150,6 +150,7 @@ var InstanceConfigKeysAny = map[string]func(value string) error{
|
|||
"volatile.apply_quota": validate.IsAny,
|
||||
"volatile.uuid": validate.Optional(validate.IsUUID),
|
||||
"volatile.vsock_id": validate.Optional(validate.IsInt64),
|
||||
"volatile.uuid.generation": validate.Optional(validate.IsUUID),
|
||||
|
||||
// Caller is responsible for full validation of any raw.* value.
|
||||
"raw.idmap": validate.IsAny,
|
||||
|
|
@ -256,8 +257,12 @@ var InstanceConfigKeysVM = map[string]func(value string) error{
|
|||
"raw.qemu": validate.IsAny,
|
||||
"raw.qemu.conf": validate.IsAny,
|
||||
|
||||
"security.agent.metrics": validate.Optional(validate.IsBool),
|
||||
"security.secureboot": validate.Optional(validate.IsBool),
|
||||
"security.agent.metrics": validate.Optional(validate.IsBool),
|
||||
"security.secureboot": validate.Optional(validate.IsBool),
|
||||
"security.sev": validate.Optional(validate.IsBool),
|
||||
"security.sev.policy.es": validate.Optional(validate.IsBool),
|
||||
"security.sev.session.dh": validate.Optional(validate.IsAny),
|
||||
"security.sev.session.data": validate.Optional(validate.IsAny),
|
||||
|
||||
"agent.nic_config": validate.Optional(validate.IsBool),
|
||||
|
||||
|
|
|
|||
26
vendor/github.com/lxc/lxd/shared/network.go
generated
vendored
26
vendor/github.com/lxc/lxd/shared/network.go
generated
vendored
|
|
@ -375,10 +375,14 @@ func DefaultWriter(conn *websocket.Conn, w io.WriteCloser, writeDone chan<- bool
|
|||
type WebsocketIO struct {
|
||||
Conn *websocket.Conn
|
||||
reader io.Reader
|
||||
mu sync.Mutex
|
||||
mur sync.Mutex
|
||||
muw sync.Mutex
|
||||
}
|
||||
|
||||
func (w *WebsocketIO) Read(p []byte) (n int, err error) {
|
||||
w.mur.Lock()
|
||||
defer w.mur.Unlock()
|
||||
|
||||
// Get new message if no active one.
|
||||
if w.reader == nil {
|
||||
var mt int
|
||||
|
|
@ -410,26 +414,22 @@ func (w *WebsocketIO) Read(p []byte) (n int, err error) {
|
|||
return n, nil
|
||||
}
|
||||
|
||||
func (w *WebsocketIO) Write(p []byte) (n int, err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
wr, err := w.Conn.NextWriter(websocket.BinaryMessage)
|
||||
func (w *WebsocketIO) Write(p []byte) (int, error) {
|
||||
w.muw.Lock()
|
||||
defer w.muw.Unlock()
|
||||
|
||||
err := w.Conn.WriteMessage(websocket.BinaryMessage, p)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
n, err = wr.Write(p)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return n, wr.Close()
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Close sends a control message indicating the stream is finished, but it does not actually close the socket.
|
||||
func (w *WebsocketIO) Close() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.muw.Lock()
|
||||
defer w.muw.Unlock()
|
||||
// Target expects to get a control message indicating stream is finished.
|
||||
return w.Conn.WriteMessage(websocket.TextMessage, []byte{})
|
||||
}
|
||||
|
|
|
|||
8
vendor/github.com/lxc/lxd/shared/simplestreams/simplestreams.go
generated
vendored
8
vendor/github.com/lxc/lxd/shared/simplestreams/simplestreams.go
generated
vendored
|
|
@ -138,6 +138,10 @@ func (s *SimpleStreams) cachedDownload(path string) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
return nil, fmt.Errorf("No content in download from %q", uri)
|
||||
}
|
||||
|
||||
// Attempt to store in cache
|
||||
if s.cachePath != "" {
|
||||
cacheName := filepath.Join(s.cachePath, fileName)
|
||||
|
|
@ -159,11 +163,13 @@ func (s *SimpleStreams) parseStream() (*Stream, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
pathURL, _ := shared.JoinUrls(s.url, path)
|
||||
|
||||
// Parse the idnex
|
||||
stream := Stream{}
|
||||
err = json.Unmarshal(body, &stream)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed decoding stream JSON from %q: %w", path, err)
|
||||
return nil, fmt.Errorf("Failed decoding stream JSON from %q: %w (%q)", pathURL, err, string(body))
|
||||
}
|
||||
|
||||
s.cachedStream = &stream
|
||||
|
|
|
|||
9
vendor/github.com/mattn/go-isatty/LICENSE
generated
vendored
Normal file
9
vendor/github.com/mattn/go-isatty/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
||||
|
||||
MIT License (Expat)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
50
vendor/github.com/mattn/go-isatty/README.md
generated
vendored
Normal file
50
vendor/github.com/mattn/go-isatty/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# go-isatty
|
||||
|
||||
[](http://godoc.org/github.com/mattn/go-isatty)
|
||||
[](https://codecov.io/gh/mattn/go-isatty)
|
||||
[](https://coveralls.io/github/mattn/go-isatty?branch=master)
|
||||
[](https://goreportcard.com/report/mattn/go-isatty)
|
||||
|
||||
isatty for golang
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattn/go-isatty"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Terminal")
|
||||
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Cygwin/MSYS2 Terminal")
|
||||
} else {
|
||||
fmt.Println("Is Not Terminal")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-isatty
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
|
||||
## Thanks
|
||||
|
||||
* k-takata: base idea for IsCygwinTerminal
|
||||
|
||||
https://github.com/k-takata/go-iscygpty
|
||||
2
vendor/github.com/mattn/go-isatty/doc.go
generated
vendored
Normal file
2
vendor/github.com/mattn/go-isatty/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// Package isatty implements interface to isatty
|
||||
package isatty
|
||||
12
vendor/github.com/mattn/go-isatty/go.test.sh
generated
vendored
Normal file
12
vendor/github.com/mattn/go-isatty/go.test.sh
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
||||
19
vendor/github.com/mattn/go-isatty/isatty_bsd.go
generated
vendored
Normal file
19
vendor/github.com/mattn/go-isatty/isatty_bsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine
|
||||
// +build darwin freebsd openbsd netbsd dragonfly hurd
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
16
vendor/github.com/mattn/go-isatty/isatty_others.go
generated
vendored
Normal file
16
vendor/github.com/mattn/go-isatty/isatty_others.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//go:build appengine || js || nacl || wasm
|
||||
// +build appengine js nacl wasm
|
||||
|
||||
package isatty
|
||||
|
||||
// IsTerminal returns true if the file descriptor is terminal which
|
||||
// is always false on js and appengine classic which is a sandboxed PaaS.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
23
vendor/github.com/mattn/go-isatty/isatty_plan9.go
generated
vendored
Normal file
23
vendor/github.com/mattn/go-isatty/isatty_plan9.go
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
//go:build plan9
|
||||
// +build plan9
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
path, err := syscall.Fd2path(int(fd))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
21
vendor/github.com/mattn/go-isatty/isatty_solaris.go
generated
vendored
Normal file
21
vendor/github.com/mattn/go-isatty/isatty_solaris.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//go:build solaris && !appengine
|
||||
// +build solaris,!appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
// see: https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermio(int(fd), unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
19
vendor/github.com/mattn/go-isatty/isatty_tcgets.go
generated
vendored
Normal file
19
vendor/github.com/mattn/go-isatty/isatty_tcgets.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//go:build (linux || aix || zos) && !appengine
|
||||
// +build linux aix zos
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
125
vendor/github.com/mattn/go-isatty/isatty_windows.go
generated
vendored
Normal file
125
vendor/github.com/mattn/go-isatty/isatty_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
//go:build windows && !appengine
|
||||
// +build windows,!appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
objectNameInfo uintptr = 1
|
||||
fileNameInfo = 2
|
||||
fileTypePipe = 3
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
||||
procGetFileType = kernel32.NewProc("GetFileType")
|
||||
procNtQueryObject = ntdll.NewProc("NtQueryObject")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Check if GetFileInformationByHandleEx is available.
|
||||
if procGetFileInformationByHandleEx.Find() != nil {
|
||||
procGetFileInformationByHandleEx = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
|
||||
// Check pipe name is used for cygwin/msys2 pty.
|
||||
// Cygwin/MSYS2 PTY has a name like:
|
||||
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
|
||||
func isCygwinPipeName(name string) bool {
|
||||
token := strings.Split(name, "-")
|
||||
if len(token) < 5 {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[0] != `\msys` &&
|
||||
token[0] != `\cygwin` &&
|
||||
token[0] != `\Device\NamedPipe\msys` &&
|
||||
token[0] != `\Device\NamedPipe\cygwin` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[1] == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(token[2], "pty") {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[3] != `from` && token[3] != `to` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[4] != "master" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
|
||||
// since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion
|
||||
// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
|
||||
// Windows vista to 10
|
||||
// see https://stackoverflow.com/a/18792477 for details
|
||||
func getFileNameByHandle(fd uintptr) (string, error) {
|
||||
if procNtQueryObject == nil {
|
||||
return "", errors.New("ntdll.dll: NtQueryObject not supported")
|
||||
}
|
||||
|
||||
var buf [4 + syscall.MAX_PATH]uint16
|
||||
var result int
|
||||
r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
|
||||
fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if r != 0 {
|
||||
return "", e
|
||||
}
|
||||
return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
if procGetFileInformationByHandleEx == nil {
|
||||
name, err := getFileNameByHandle(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isCygwinPipeName(name)
|
||||
}
|
||||
|
||||
// Cygwin/msys's pty is a pipe.
|
||||
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
|
||||
if ft != fileTypePipe || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var buf [2 + syscall.MAX_PATH]uint16
|
||||
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
|
||||
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
|
||||
uintptr(len(buf)*2), 0, 0)
|
||||
if r == 0 || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
l := *(*uint32)(unsafe.Pointer(&buf))
|
||||
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
|
||||
}
|
||||
6
vendor/google.golang.org/protobuf/internal/encoding/text/decode_number.go
generated
vendored
6
vendor/google.golang.org/protobuf/internal/encoding/text/decode_number.go
generated
vendored
|
|
@ -88,15 +88,15 @@ func parseNumber(input []byte) number {
|
|||
neg = true
|
||||
s = s[1:]
|
||||
size++
|
||||
if len(s) == 0 {
|
||||
return number{}
|
||||
}
|
||||
// Consume any whitespace or comments between the
|
||||
// negative sign and the rest of the number
|
||||
lenBefore := len(s)
|
||||
s = consume(s, 0)
|
||||
sep = lenBefore - len(s)
|
||||
size += sep
|
||||
if len(s) == 0 {
|
||||
return number{}
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
|
|
|
|||
2
vendor/google.golang.org/protobuf/internal/version/version.go
generated
vendored
2
vendor/google.golang.org/protobuf/internal/version/version.go
generated
vendored
|
|
@ -51,7 +51,7 @@ import (
|
|||
// 10. Send out the CL for review and submit it.
|
||||
const (
|
||||
Major = 1
|
||||
Minor = 29
|
||||
Minor = 30
|
||||
Patch = 0
|
||||
PreRelease = ""
|
||||
)
|
||||
|
|
|
|||
15
vendor/modules.txt
vendored
15
vendor/modules.txt
vendored
|
|
@ -20,6 +20,8 @@ github.com/felixge/httpsnoop
|
|||
# github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3
|
||||
## explicit; go 1.14
|
||||
github.com/flosch/pongo2
|
||||
# github.com/frankban/quicktest v1.14.3
|
||||
## explicit; go 1.13
|
||||
# github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1
|
||||
## explicit; go 1.17
|
||||
github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery
|
||||
|
|
@ -82,6 +84,8 @@ github.com/juju/errors
|
|||
# github.com/juju/retry v1.0.0
|
||||
## explicit; go 1.17
|
||||
github.com/juju/retry
|
||||
# github.com/juju/testing v1.0.2
|
||||
## explicit; go 1.17
|
||||
# github.com/juju/webbrowser v1.0.0
|
||||
## explicit; go 1.11
|
||||
github.com/juju/webbrowser
|
||||
|
|
@ -94,9 +98,9 @@ github.com/kballard/go-shellquote
|
|||
# github.com/kr/fs v0.1.0
|
||||
## explicit
|
||||
github.com/kr/fs
|
||||
# github.com/kr/pretty v0.3.0
|
||||
# github.com/kr/pretty v0.3.1
|
||||
## explicit; go 1.12
|
||||
# github.com/lxc/lxd v0.0.0-20230310224854-36b345fbd578
|
||||
# github.com/lxc/lxd v0.0.0-20230325180147-8d608287b0ce
|
||||
## explicit; go 1.18
|
||||
github.com/lxc/lxd/client
|
||||
github.com/lxc/lxd/lxd/device/config
|
||||
|
|
@ -118,6 +122,9 @@ github.com/lxc/lxd/shared/validate
|
|||
github.com/manifoldco/promptui
|
||||
github.com/manifoldco/promptui/list
|
||||
github.com/manifoldco/promptui/screenbuf
|
||||
# github.com/mattn/go-isatty v0.0.18
|
||||
## explicit; go 1.15
|
||||
github.com/mattn/go-isatty
|
||||
# github.com/mattn/go-runewidth v0.0.14
|
||||
## explicit; go 1.9
|
||||
github.com/mattn/go-runewidth
|
||||
|
|
@ -207,8 +214,6 @@ github.com/stretchr/testify/suite
|
|||
# github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
|
||||
## explicit; go 1.18
|
||||
github.com/teris-io/shortid
|
||||
# github.com/xdg-go/stringprep v1.0.3
|
||||
## explicit; go 1.11
|
||||
# golang.org/x/crypto v0.7.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/crypto/bcrypt
|
||||
|
|
@ -262,7 +267,7 @@ google.golang.org/appengine/internal/log
|
|||
google.golang.org/appengine/internal/remote_api
|
||||
google.golang.org/appengine/internal/urlfetch
|
||||
google.golang.org/appengine/urlfetch
|
||||
# google.golang.org/protobuf v1.29.0
|
||||
# google.golang.org/protobuf v1.30.0
|
||||
## explicit; go 1.11
|
||||
google.golang.org/protobuf/encoding/prototext
|
||||
google.golang.org/protobuf/encoding/protowire
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue