Add runner group option to pools

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2023-03-27 08:40:22 +00:00
parent 80e8f6dc1e
commit 6b3ea50ca5
10 changed files with 180 additions and 86 deletions

View file

@ -63,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 }}"
@ -81,7 +90,7 @@ 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"
@ -274,6 +283,7 @@ function Invoke-GarmFailure() {
$PEMData = @"
{{.CABundle}}
"@
$GHRunnerGroup = "{{.GitHubRunnerGroup}}"
function Install-Runner() {
$CallbackURL="{{.CallbackURL}}"
@ -311,10 +321,13 @@ function Install-Runner() {
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 --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" --ephemeral --runasservice
./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)
@ -339,6 +352,7 @@ type InstallRunnerParams struct {
CallbackToken string
TempDownloadToken string
CABundle string
GitHubRunnerGroup string
}
func InstallRunnerScript(installParams InstallRunnerParams, osType params.OSType) ([]byte, error) {

View file

@ -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.")

View file

@ -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 {

View file

@ -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"`

View file

@ -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())

View file

@ -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")
}

View file

@ -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,14 +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"`
OSType OSType `json:"os_type"`
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 {
@ -202,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 {

View file

@ -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 {

View file

@ -417,14 +417,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,20 +604,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,
OSType: pool.OSType,
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

View file

@ -251,6 +251,7 @@ 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)