Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cmd/garm-cli/cmd/scalesets.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var (
scalesetGitHubRunnerGroup string
scaleSetTemplateNameOrID string
scalesetEnableShell bool
scalesetLabels string
)

type scalesetPayloadGetter interface {
Expand Down Expand Up @@ -235,6 +236,11 @@ var scaleSetAddCmd = &cobra.Command{
EnableShell: scalesetEnableShell,
}

if cmd.Flags().Changed("labels") {
labels := strings.Split(scalesetLabels, ",")
newScaleSetParams.Labels = labels
}

if cmd.Flags().Changed("extra-specs") {
data, err := asRawMessage([]byte(scalesetExtraSpecs))
if err != nil {
Expand Down Expand Up @@ -554,6 +560,7 @@ func init() {
scaleSetAddCmd.Flags().BoolVar(&scalesetEnableShell, "enable-shell", false, "Enable shell access for runners in this scale set.")
scaleSetAddCmd.Flags().StringVar(&endpointName, "endpoint", "", "When using the name of an entity, the endpoint must be specified when multiple entities with the same name exist.")
scaleSetAddCmd.Flags().StringVar(&scaleSetTemplateNameOrID, "runner-install-template", "", "The runner install template name or ID to use for this scale set.")
scaleSetAddCmd.Flags().StringVar(&scalesetLabels, "labels", "", "A comma separated list of labels to assign to this scale set.")
scaleSetAddCmd.MarkFlagRequired("provider-name") //nolint
scaleSetAddCmd.MarkFlagRequired("name") //nolint
scaleSetAddCmd.MarkFlagRequired("image") //nolint
Expand Down Expand Up @@ -665,6 +672,14 @@ func formatOneScaleSet(scaleSet params.ScaleSet) {
t.AppendRow(table.Row{"Extra specs", string(scaleSet.ExtraSpecs)})
t.AppendRow(table.Row{"GitHub Runner Group", scaleSet.GitHubRunnerGroup})

if len(scaleSet.Tags) > 0 {
tagNames := make([]string, len(scaleSet.Tags))
for i, tag := range scaleSet.Tags {
tagNames[i] = tag.Name
}
t.AppendRow(table.Row{"Labels", strings.Join(tagNames, ", ")})
}

if len(scaleSet.Instances) > 0 {
for _, instance := range scaleSet.Instances {
t.AppendRow(table.Row{"Instances", fmt.Sprintf("%s (%s)", instance.Name, instance.ID)}, rowConfigAutoMerge)
Expand Down
13 changes: 6 additions & 7 deletions database/sql/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ type ControllerInfo struct {
type Tag struct {
Base

Name string `gorm:"type:varchar(64);uniqueIndex"`
Pools []*Pool `gorm:"many2many:pool_tags;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Name string `gorm:"type:varchar(64);uniqueIndex"`
Pools []*Pool `gorm:"many2many:pool_tags;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
ScaleSets []*ScaleSet `gorm:"many2many:scaleset_tags;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
}

type Template struct {
Expand Down Expand Up @@ -145,11 +146,8 @@ type Pool struct {
Priority uint `gorm:"index:idx_pool_priority"`
}

// ScaleSet represents a github scale set. Scale sets are almost identical to pools with a few
// notable exceptions:
// - Labels are no longer relevant
// - Workflows will use the scaleset name to target runners.
// - A scale set is a stand alone unit. If a workflow targets a scale set, no other runner will pick up that job.
// ScaleSet represents a github scale set. Scale sets are almost identical to pools with
// the notable difference that scheduling happens server side in github.
type ScaleSet struct {
gorm.Model

Expand Down Expand Up @@ -205,6 +203,7 @@ type ScaleSet struct {
TemplateID *uint `gorm:"index"`
Template Template `gorm:"foreignKey:TemplateID"`

Tags []*Tag `gorm:"many2many:scaleset_tags;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Instances []Instance `gorm:"foreignKey:ScaleSetFkID"`
}

Expand Down
30 changes: 23 additions & 7 deletions database/sql/scalesets.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func (s *sqlDatabase) ListAllScaleSets(_ context.Context) ([]params.ScaleSet, er
Preload("Repository.Endpoint").
Preload("Enterprise").
Preload("Enterprise.Endpoint").
Preload("Tags").
Omit("extra_specs").
Omit("status_messages").
Find(&scaleSets)
Expand Down Expand Up @@ -114,6 +115,20 @@ func (s *sqlDatabase) CreateEntityScaleSet(ctx context.Context, entity params.Fo
return fmt.Errorf("error creating scale set: %w", q.Error)
}

if len(param.Labels) > 0 {
var tags []*Tag
for _, val := range param.Labels {
t, err := s.getOrCreateTag(tx, val)
if err != nil {
return fmt.Errorf("error creating tag: %w", err)
}
tags = append(tags, &t)
}
if err := tx.Model(&newScaleSet).Association("Tags").Append(tags); err != nil {
return fmt.Errorf("error associating tags: %w", err)
}
}

return nil
})
if err != nil {
Expand Down Expand Up @@ -179,7 +194,7 @@ func (s *sqlDatabase) listEntityScaleSets(tx *gorm.DB, entityType params.ForgeEn
}

func (s *sqlDatabase) ListEntityScaleSets(_ context.Context, entity params.ForgeEntity) ([]params.ScaleSet, error) {
scaleSets, err := s.listEntityScaleSets(s.conn, entity.EntityType, entity.ID)
scaleSets, err := s.listEntityScaleSets(s.conn, entity.EntityType, entity.ID, "Tags")
if err != nil {
return nil, fmt.Errorf("error fetching scale sets: %w", err)
}
Expand All @@ -203,7 +218,7 @@ func (s *sqlDatabase) UpdateEntityScaleSet(ctx context.Context, entity params.Fo
}
}()
err = s.conn.Transaction(func(tx *gorm.DB) error {
scaleSet, err := s.getEntityScaleSet(tx, entity.EntityType, entity.ID, scaleSetID, "Instances")
scaleSet, err := s.getEntityScaleSet(tx, entity.EntityType, entity.ID, scaleSetID, "Instances", "Tags")
if err != nil {
return fmt.Errorf("error fetching scale set: %w", err)
}
Expand Down Expand Up @@ -370,16 +385,16 @@ func (s *sqlDatabase) updateScaleSet(tx *gorm.DB, scaleSet ScaleSet, param param

var rowsAffected int64
if len(updates) > 0 {
q := tx.Model(&scaleSet).Omit("Repository", "Organization", "Enterprise", "Instances", "Template").Updates(updates)
q := tx.Model(&scaleSet).Omit("Repository", "Organization", "Enterprise", "Instances", "Template", "Tags").Updates(updates)
if q.Error != nil {
return params.ScaleSet{}, 0, fmt.Errorf("error saving database entry: %w", q.Error)
}
rowsAffected = q.RowsAffected
}

// Reload to get the updated values
if err := tx.First(&scaleSet, scaleSet.ID).Error; err != nil {
return params.ScaleSet{}, 0, fmt.Errorf("error reloading scale set: %w", err)
}
// Reload to get the updated values
if err := tx.Preload("Tags").First(&scaleSet, scaleSet.ID).Error; err != nil {
return params.ScaleSet{}, 0, fmt.Errorf("error reloading scale set: %w", err)
}

commonScaleSet, err := s.sqlToCommonScaleSet(scaleSet)
Expand All @@ -401,6 +416,7 @@ func (s *sqlDatabase) GetScaleSetByID(_ context.Context, scaleSet uint) (params.
"Repository",
"Repository.Endpoint",
"Template",
"Tags",
)
if err != nil {
return params.ScaleSet{}, fmt.Errorf("error fetching scale set by ID: %w", err)
Expand Down
5 changes: 5 additions & 0 deletions database/sql/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ func (s *sqlDatabase) sqlToCommonScaleSet(scaleSet ScaleSet) (params.ScaleSet, e
OSType: scaleSet.OSType,
Enabled: scaleSet.Enabled,
Instances: make([]params.Instance, len(scaleSet.Instances)),
Tags: make([]params.Tag, len(scaleSet.Tags)),
RunnerBootstrapTimeout: scaleSet.RunnerBootstrapTimeout,
ExtraSpecs: json.RawMessage(scaleSet.ExtraSpecs),
GitHubRunnerGroup: scaleSet.GitHubRunnerGroup,
Expand Down Expand Up @@ -437,6 +438,10 @@ func (s *sqlDatabase) sqlToCommonScaleSet(scaleSet ScaleSet) (params.ScaleSet, e
}
}

for idx, val := range scaleSet.Tags {
ret.Tags[idx] = s.sqlToCommonTags(*val)
}

return ret, nil
}

Expand Down
18 changes: 18 additions & 0 deletions params/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ type ScaleSet struct {
// The runner group must be created by someone with access to the enterprise.
GitHubRunnerGroup string `json:"github-runner-group,omitempty"`

Tags []Tag `json:"tags,omitempty"`
StatusMessages []StatusMessage `json:"status_messages"`

RepoID string `json:"repo_id,omitempty"`
Expand All @@ -682,6 +683,23 @@ type ScaleSet struct {
LastMessageID int64 `json:"-"`
}

// GitHubLabels returns the label list for GitHub scale set API calls.
// The scale set name is always included as the first "System" label,
// matching actions-runner-controller behavior. Additional tag names
// are appended with deduplication.
func (p ScaleSet) GitHubLabels() []Label {
labels := []Label{{Name: p.Name, Type: "System"}}
seen := map[string]bool{p.Name: true}
for _, t := range p.Tags {
if seen[t.Name] {
continue
}
seen[t.Name] = true
labels = append(labels, Label{Name: t.Name})
}
return labels
}

func (p ScaleSet) BelongsTo(entity ForgeEntity) bool {
switch p.ScaleSetType() {
case ForgeEntityTypeRepository:
Expand Down
22 changes: 20 additions & 2 deletions params/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,9 @@ type CreateScaleSetParams struct {
// 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"`
TemplateID *uint `json:"template_id,omitempty"`
GitHubRunnerGroup string `json:"github-runner-group,omitempty"`
TemplateID *uint `json:"template_id,omitempty"`
Labels []string `json:"labels,omitempty"`
}

func (s *CreateScaleSetParams) Validate() error {
Expand Down Expand Up @@ -640,6 +641,23 @@ func (s *CreateScaleSetParams) Validate() error {
return nil
}

// GitHubLabels returns the label list for GitHub scale set API calls.
// The scale set name is always included as the first "System" label,
// matching actions-runner-controller behavior. Additional labels
// are appended with deduplication.
func (s CreateScaleSetParams) GitHubLabels() []Label {
labels := []Label{{Name: s.Name, Type: "System"}}
seen := map[string]bool{s.Name: true}
for _, l := range s.Labels {
if seen[l] {
continue
}
seen[l] = true
labels = append(labels, Label{Name: l})
}
return labels
}

// swagger:model UpdateScaleSetParams
type UpdateScaleSetParams struct {
RunnerPrefix
Expand Down
7 changes: 1 addition & 6 deletions runner/scalesets.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,7 @@ func (r *Runner) CreateEntityScaleSet(ctx context.Context, entityType params.For
createParam := &params.RunnerScaleSet{
Name: param.Name,
RunnerGroupID: runnerGroupID,
Labels: []params.Label{
{
Name: param.Name,
Type: "System",
},
},
Labels: param.GitHubLabels(),
RunnerSetting: params.RunnerSetting{
Ephemeral: true,
DisableUpdate: param.DisableUpdate,
Expand Down
11 changes: 11 additions & 0 deletions util/github/scalesets/scalesets.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ func (s *ScaleSetClient) ListRunnerScaleSets(ctx context.Context) (_ *params.Run
return &runnerScaleSetList, nil
}

func applyDefaultLabelTypes(labels []params.Label) []params.Label {
for i, label := range labels {
if label.Type == "" {
labels[i].Type = "System"
}
}
return labels
}

// CreateRunnerScaleSet creates a new runner scale set in the target GitHub entity.
func (s *ScaleSetClient) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *params.RunnerScaleSet) (_ params.RunnerScaleSet, err error) {
s.recordOperation("CreateRunnerScaleSet")
Expand All @@ -136,6 +145,8 @@ func (s *ScaleSetClient) CreateRunnerScaleSet(ctx context.Context, runnerScaleSe
}
}()

runnerScaleSet.Labels = applyDefaultLabelTypes(runnerScaleSet.Labels)

body, err := json.Marshal(runnerScaleSet)
if err != nil {
return params.RunnerScaleSet{}, err
Expand Down
24 changes: 24 additions & 0 deletions webapp/src/lib/api/generated/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,12 @@ export interface CreateScaleSetParams {
* @memberof CreateScaleSetParams
*/
'image'?: string;
/**
*
* @type {Array<string>}
* @memberof CreateScaleSetParams
*/
'labels'?: Array<string>;
/**
*
* @type {number}
Expand Down Expand Up @@ -1306,6 +1312,12 @@ export interface ForgeEntity {
* @memberof ForgeEntity
*/
'pool_balancing_type'?: string;
/**
*
* @type {PoolManagerStatus}
* @memberof ForgeEntity
*/
'pool_manager_status'?: PoolManagerStatus;
/**
*
* @type {string}
Expand Down Expand Up @@ -2806,6 +2818,12 @@ export interface ScaleSet {
* @memberof ScaleSet
*/
'status_messages'?: Array<StatusMessage>;
/**
*
* @type {Array<Tag>}
* @memberof ScaleSet
*/
'tags'?: Array<Tag>;
/**
*
* @type {number}
Expand Down Expand Up @@ -3316,6 +3334,12 @@ export interface UpdateScaleSetParams {
* @memberof UpdateScaleSetParams
*/
'image'?: string;
/**
*
* @type {Array<string>}
* @memberof UpdateScaleSetParams
*/
'labels'?: Array<string>;
/**
*
* @type {number}
Expand Down
Loading
Loading