Add ScaleSet models, functions and types
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
parent
5ba53adf84
commit
85eac363d5
9 changed files with 772 additions and 12 deletions
|
|
@ -19,6 +19,7 @@ const (
|
|||
ControllerEntityType DatabaseEntityType = "controller"
|
||||
GithubCredentialsEntityType DatabaseEntityType = "github_credentials" // #nosec G101
|
||||
GithubEndpointEntityType DatabaseEntityType = "github_endpoint"
|
||||
ScaleSetEntityType DatabaseEntityType = "scaleset"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -288,6 +288,25 @@ func (s *sqlDatabase) ListPoolInstances(_ context.Context, poolID string) ([]par
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) ListScaleSetInstances(_ context.Context, scalesetID uint) ([]params.Instance, error) {
|
||||
var instances []Instance
|
||||
query := s.conn.Model(&Instance{}).Preload("Job").Where("scale_set_id = ?", scalesetID)
|
||||
|
||||
if err := query.Find(&instances); err.Error != nil {
|
||||
return nil, errors.Wrap(err.Error, "fetching instances")
|
||||
}
|
||||
|
||||
var err error
|
||||
ret := make([]params.Instance, len(instances))
|
||||
for idx, inst := range instances {
|
||||
ret[idx], err = s.sqlToParamsInstance(inst)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "converting instance")
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) ListAllInstances(_ context.Context) ([]params.Instance, error) {
|
||||
var instances []Instance
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,54 @@ 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.
|
||||
type ScaleSet struct {
|
||||
gorm.Model
|
||||
|
||||
// ScaleSetID is the github ID of the scale set. This field may not be set if
|
||||
// the scale set was ceated in GARM but has not yet been created in GitHub.
|
||||
ScaleSetID int `gorm:"index:idx_scale_set"`
|
||||
Name string `gorm:"index:idx_name"`
|
||||
DisableUpdate bool
|
||||
|
||||
// State stores the provisioning state of the scale set in GitHub
|
||||
State params.ScaleSetState
|
||||
// ExtendedState stores a more detailed message regarding the State.
|
||||
// If an error occurs, the reason for the error will be stored here.
|
||||
ExtendedState string
|
||||
|
||||
ProviderName string
|
||||
RunnerPrefix string
|
||||
MaxRunners uint
|
||||
MinIdleRunners uint
|
||||
RunnerBootstrapTimeout uint
|
||||
Image string
|
||||
Flavor string
|
||||
OSType commonParams.OSType
|
||||
OSArch commonParams.OSArch
|
||||
Enabled bool
|
||||
// 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
|
||||
GitHubRunnerGroup string
|
||||
|
||||
RepoID *uuid.UUID `gorm:"index"`
|
||||
Repository Repository `gorm:"foreignKey:RepoID;"`
|
||||
|
||||
OrgID *uuid.UUID `gorm:"index"`
|
||||
Organization Organization `gorm:"foreignKey:OrgID"`
|
||||
|
||||
EnterpriseID *uuid.UUID `gorm:"index"`
|
||||
Enterprise Enterprise `gorm:"foreignKey:EnterpriseID"`
|
||||
|
||||
Instances []Instance `gorm:"foreignKey:ScaleSetFkID"`
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
Base
|
||||
|
||||
|
|
@ -98,6 +146,7 @@ type Repository struct {
|
|||
Name string `gorm:"index:idx_owner_nocase,unique,collate:nocase"`
|
||||
WebhookSecret []byte
|
||||
Pools []Pool `gorm:"foreignKey:RepoID"`
|
||||
ScaleSets []ScaleSet `gorm:"foreignKey:RepoID"`
|
||||
Jobs []WorkflowJob `gorm:"foreignKey:RepoID;constraint:OnDelete:SET NULL"`
|
||||
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
|
||||
|
||||
|
|
@ -116,6 +165,7 @@ type Organization struct {
|
|||
Name string `gorm:"index:idx_org_name_nocase,collate:nocase"`
|
||||
WebhookSecret []byte
|
||||
Pools []Pool `gorm:"foreignKey:OrgID"`
|
||||
ScaleSet []ScaleSet `gorm:"foreignKey:OrgID"`
|
||||
Jobs []WorkflowJob `gorm:"foreignKey:OrgID;constraint:OnDelete:SET NULL"`
|
||||
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
|
||||
|
||||
|
|
@ -134,6 +184,7 @@ type Enterprise struct {
|
|||
Name string `gorm:"index:idx_ent_name_nocase,collate:nocase"`
|
||||
WebhookSecret []byte
|
||||
Pools []Pool `gorm:"foreignKey:EnterpriseID"`
|
||||
ScaleSet []ScaleSet `gorm:"foreignKey:EnterpriseID"`
|
||||
Jobs []WorkflowJob `gorm:"foreignKey:EnterpriseID;constraint:OnDelete:SET NULL"`
|
||||
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
|
||||
|
||||
|
|
@ -187,6 +238,9 @@ type Instance struct {
|
|||
PoolID uuid.UUID
|
||||
Pool Pool `gorm:"foreignKey:PoolID"`
|
||||
|
||||
ScaleSetFkID *uint
|
||||
ScaleSet ScaleSet `gorm:"foreignKey:ScaleSetFkID"`
|
||||
|
||||
StatusMessages []InstanceStatusUpdate `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
|
||||
|
||||
Job *WorkflowJob `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
|
||||
|
|
|
|||
381
database/sql/scalesets.go
Normal file
381
database/sql/scalesets.go
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
// Copyright 2024 Cloudbase Solutions SRL
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
|
||||
"github.com/cloudbase/garm/database/common"
|
||||
"github.com/cloudbase/garm/params"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (s *sqlDatabase) ListAllScaleSets(_ context.Context) ([]params.ScaleSet, error) {
|
||||
var scaleSets []ScaleSet
|
||||
|
||||
q := s.conn.Model(&ScaleSet{}).
|
||||
Preload("Organization").
|
||||
Preload("Repository").
|
||||
Preload("Enterprise").
|
||||
Omit("extra_specs").
|
||||
Omit("status_messages").
|
||||
Find(&scaleSets)
|
||||
if q.Error != nil {
|
||||
return nil, errors.Wrap(q.Error, "fetching all scale sets")
|
||||
}
|
||||
|
||||
ret := make([]params.ScaleSet, len(scaleSets))
|
||||
var err error
|
||||
for idx, val := range scaleSets {
|
||||
ret[idx], err = s.sqlToCommonScaleSet(val)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "converting scale sets")
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) CreateEntityScaleSet(_ context.Context, entity params.GithubEntity, param params.CreateScaleSetParams) (scaleSet params.ScaleSet, err error) {
|
||||
if err := param.Validate(); err != nil {
|
||||
return params.ScaleSet{}, fmt.Errorf("failed to validate create params: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
s.sendNotify(common.ScaleSetEntityType, common.CreateOperation, scaleSet)
|
||||
}
|
||||
}()
|
||||
|
||||
newScaleSet := ScaleSet{
|
||||
Name: param.Name,
|
||||
ScaleSetID: param.ScaleSetID,
|
||||
DisableUpdate: param.DisableUpdate,
|
||||
ProviderName: param.ProviderName,
|
||||
RunnerPrefix: param.GetRunnerPrefix(),
|
||||
MaxRunners: param.MaxRunners,
|
||||
MinIdleRunners: param.MinIdleRunners,
|
||||
RunnerBootstrapTimeout: param.RunnerBootstrapTimeout,
|
||||
Image: param.Image,
|
||||
Flavor: param.Flavor,
|
||||
OSType: param.OSType,
|
||||
OSArch: param.OSArch,
|
||||
Enabled: param.Enabled,
|
||||
GitHubRunnerGroup: param.GitHubRunnerGroup,
|
||||
State: params.ScaleSetPendingCreate,
|
||||
}
|
||||
|
||||
if len(param.ExtraSpecs) > 0 {
|
||||
newScaleSet.ExtraSpecs = datatypes.JSON(param.ExtraSpecs)
|
||||
}
|
||||
|
||||
entityID, err := uuid.Parse(entity.ID)
|
||||
if err != nil {
|
||||
return params.ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
||||
switch entity.EntityType {
|
||||
case params.GithubEntityTypeRepository:
|
||||
newScaleSet.RepoID = &entityID
|
||||
case params.GithubEntityTypeOrganization:
|
||||
newScaleSet.OrgID = &entityID
|
||||
case params.GithubEntityTypeEnterprise:
|
||||
newScaleSet.EnterpriseID = &entityID
|
||||
}
|
||||
err = s.conn.Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.hasGithubEntity(tx, entity.EntityType, entity.ID); err != nil {
|
||||
return errors.Wrap(err, "checking entity existence")
|
||||
}
|
||||
|
||||
q := tx.Create(&newScaleSet)
|
||||
if q.Error != nil {
|
||||
return errors.Wrap(q.Error, "creating scale set")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return params.ScaleSet{}, err
|
||||
}
|
||||
|
||||
dbScaleSet, err := s.getScaleSetByID(s.conn, newScaleSet.ID, "Instances", "Enterprise", "Organization", "Repository")
|
||||
if err != nil {
|
||||
return params.ScaleSet{}, errors.Wrap(err, "fetching scale set")
|
||||
}
|
||||
|
||||
return s.sqlToCommonScaleSet(dbScaleSet)
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) listEntityScaleSets(tx *gorm.DB, entityType params.GithubEntityType, entityID string, preload ...string) ([]ScaleSet, error) {
|
||||
if _, err := uuid.Parse(entityID); err != nil {
|
||||
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
||||
if err := s.hasGithubEntity(tx, entityType, entityID); err != nil {
|
||||
return nil, errors.Wrap(err, "checking entity existence")
|
||||
}
|
||||
|
||||
var preloadEntity string
|
||||
var fieldName string
|
||||
switch entityType {
|
||||
case params.GithubEntityTypeRepository:
|
||||
fieldName = entityTypeRepoName
|
||||
preloadEntity = "Repository"
|
||||
case params.GithubEntityTypeOrganization:
|
||||
fieldName = entityTypeOrgName
|
||||
preloadEntity = "Organization"
|
||||
case params.GithubEntityTypeEnterprise:
|
||||
fieldName = entityTypeEnterpriseName
|
||||
preloadEntity = "Enterprise"
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid entityType: %v", entityType)
|
||||
}
|
||||
|
||||
q := tx
|
||||
q = q.Preload(preloadEntity)
|
||||
if len(preload) > 0 {
|
||||
for _, item := range preload {
|
||||
q = q.Preload(item)
|
||||
}
|
||||
}
|
||||
|
||||
var scaleSets []ScaleSet
|
||||
condition := fmt.Sprintf("%s = ?", fieldName)
|
||||
err := q.Model(&ScaleSet{}).
|
||||
Where(condition, entityID).
|
||||
Omit("extra_specs").
|
||||
Omit("status_messages").
|
||||
Find(&scaleSets).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []ScaleSet{}, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "fetching scale sets")
|
||||
}
|
||||
|
||||
return scaleSets, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) ListEntityScaleSets(_ context.Context, entity params.GithubEntity) ([]params.ScaleSet, error) {
|
||||
scaleSets, err := s.listEntityScaleSets(s.conn, entity.EntityType, entity.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching scale sets")
|
||||
}
|
||||
|
||||
ret := make([]params.ScaleSet, len(scaleSets))
|
||||
for idx, set := range scaleSets {
|
||||
ret[idx], err = s.sqlToCommonScaleSet(set)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "conbverting scale set")
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) UpdateEntityScaleSet(_ context.Context, entity params.GithubEntity, scaleSetID uint, param params.UpdateScaleSetParams, callback func(old, new params.ScaleSet) error) (updatedScaleSet params.ScaleSet, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
s.sendNotify(common.ScaleSetEntityType, common.UpdateOperation, updatedScaleSet)
|
||||
}
|
||||
}()
|
||||
err = s.conn.Transaction(func(tx *gorm.DB) error {
|
||||
scaleSet, err := s.getEntityScaleSet(tx, entity.EntityType, entity.ID, scaleSetID, "Instances")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching scale set")
|
||||
}
|
||||
|
||||
old, err := s.sqlToCommonScaleSet(scaleSet)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "converting scale set")
|
||||
}
|
||||
|
||||
updatedScaleSet, err = s.updateScaleSet(tx, scaleSet, param)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "updating scale set")
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
if err := callback(old, updatedScaleSet); err != nil {
|
||||
return errors.Wrap(err, "executing update callback")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return params.ScaleSet{}, err
|
||||
}
|
||||
return updatedScaleSet, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getEntityScaleSet(tx *gorm.DB, entityType params.GithubEntityType, entityID string, scaleSetID uint, preload ...string) (ScaleSet, error) {
|
||||
if entityID == "" {
|
||||
return ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing entity id")
|
||||
}
|
||||
|
||||
if scaleSetID == 0 {
|
||||
return ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing scaleset id")
|
||||
}
|
||||
|
||||
var fieldName string
|
||||
var entityField string
|
||||
switch entityType {
|
||||
case params.GithubEntityTypeRepository:
|
||||
fieldName = entityTypeRepoName
|
||||
entityField = "Repository"
|
||||
case params.GithubEntityTypeOrganization:
|
||||
fieldName = entityTypeOrgName
|
||||
entityField = "Organization"
|
||||
case params.GithubEntityTypeEnterprise:
|
||||
fieldName = entityTypeEnterpriseName
|
||||
entityField = "Enterprise"
|
||||
default:
|
||||
return ScaleSet{}, fmt.Errorf("invalid entityType: %v", entityType)
|
||||
}
|
||||
|
||||
q := tx
|
||||
q = q.Preload(entityField)
|
||||
if len(preload) > 0 {
|
||||
for _, item := range preload {
|
||||
q = q.Preload(item)
|
||||
}
|
||||
}
|
||||
|
||||
var scaleSet ScaleSet
|
||||
condition := fmt.Sprintf("id = ? and %s = ?", fieldName)
|
||||
err := q.Model(&ScaleSet{}).
|
||||
Where(condition, scaleSetID, entityID).
|
||||
First(&scaleSet).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ScaleSet{}, errors.Wrap(runnerErrors.ErrNotFound, "finding scale set")
|
||||
}
|
||||
return ScaleSet{}, errors.Wrap(err, "fetching scale set")
|
||||
}
|
||||
|
||||
return scaleSet, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) updateScaleSet(tx *gorm.DB, scaleSet ScaleSet, param params.UpdateScaleSetParams) (params.ScaleSet, error) {
|
||||
if param.Enabled != nil && scaleSet.Enabled != *param.Enabled {
|
||||
scaleSet.Enabled = *param.Enabled
|
||||
}
|
||||
|
||||
if param.State != nil && *param.State != scaleSet.State {
|
||||
scaleSet.State = *param.State
|
||||
}
|
||||
|
||||
if param.ExtendedState != nil && *param.ExtendedState != scaleSet.ExtendedState {
|
||||
scaleSet.ExtendedState = *param.ExtendedState
|
||||
}
|
||||
|
||||
if param.Name != "" {
|
||||
scaleSet.Name = param.Name
|
||||
}
|
||||
|
||||
if param.GitHubRunnerGroup != nil && *param.GitHubRunnerGroup != "" {
|
||||
scaleSet.GitHubRunnerGroup = *param.GitHubRunnerGroup
|
||||
}
|
||||
|
||||
if param.Flavor != "" {
|
||||
scaleSet.Flavor = param.Flavor
|
||||
}
|
||||
|
||||
if param.Image != "" {
|
||||
scaleSet.Image = param.Image
|
||||
}
|
||||
|
||||
if param.Prefix != "" {
|
||||
scaleSet.RunnerPrefix = param.Prefix
|
||||
}
|
||||
|
||||
if param.MaxRunners != nil {
|
||||
scaleSet.MaxRunners = *param.MaxRunners
|
||||
}
|
||||
|
||||
if param.MinIdleRunners != nil {
|
||||
scaleSet.MinIdleRunners = *param.MinIdleRunners
|
||||
}
|
||||
|
||||
if param.OSArch != "" {
|
||||
scaleSet.OSArch = param.OSArch
|
||||
}
|
||||
|
||||
if param.OSType != "" {
|
||||
scaleSet.OSType = param.OSType
|
||||
}
|
||||
|
||||
if param.ExtraSpecs != nil {
|
||||
scaleSet.ExtraSpecs = datatypes.JSON(param.ExtraSpecs)
|
||||
}
|
||||
|
||||
if param.RunnerBootstrapTimeout != nil && *param.RunnerBootstrapTimeout > 0 {
|
||||
scaleSet.RunnerBootstrapTimeout = *param.RunnerBootstrapTimeout
|
||||
}
|
||||
|
||||
if param.GitHubRunnerGroup != nil {
|
||||
scaleSet.GitHubRunnerGroup = *param.GitHubRunnerGroup
|
||||
}
|
||||
|
||||
if q := tx.Save(&scaleSet); q.Error != nil {
|
||||
return params.ScaleSet{}, errors.Wrap(q.Error, "saving database entry")
|
||||
}
|
||||
|
||||
return s.sqlToCommonScaleSet(scaleSet)
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) GetScaleSetByID(_ context.Context, scaleSet uint) (params.ScaleSet, error) {
|
||||
set, err := s.getScaleSetByID(s.conn, scaleSet, "Instances", "Enterprise", "Organization", "Repository")
|
||||
if err != nil {
|
||||
return params.ScaleSet{}, errors.Wrap(err, "fetching scale set by ID")
|
||||
}
|
||||
return s.sqlToCommonScaleSet(set)
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) DeleteScaleSetByID(ctx context.Context, scaleSetID uint) (err error) {
|
||||
var scaleSet params.ScaleSet
|
||||
defer func() {
|
||||
if err == nil && scaleSet.ID != 0 {
|
||||
s.sendNotify(common.ScaleSetEntityType, common.DeleteOperation, scaleSet)
|
||||
}
|
||||
}()
|
||||
err = s.conn.Transaction(func(tx *gorm.DB) error {
|
||||
dbSet, err := s.getScaleSetByID(tx, scaleSetID, "Instances")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching scale set")
|
||||
}
|
||||
|
||||
if len(dbSet.Instances) > 0 {
|
||||
return runnerErrors.NewBadRequestError("cannot delete scaleset with runners")
|
||||
}
|
||||
scaleSet, err = s.sqlToCommonScaleSet(dbSet)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "converting scale set")
|
||||
}
|
||||
|
||||
if q := tx.Unscoped().Delete(&dbSet); q.Error != nil {
|
||||
return errors.Wrap(q.Error, "deleting scale set")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "removing scale set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -428,6 +428,7 @@ func (s *sqlDatabase) migrateDB() error {
|
|||
&Instance{},
|
||||
&ControllerInfo{},
|
||||
&WorkflowJob{},
|
||||
&ScaleSet{},
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "running auto migrate")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) (params.Instance, e
|
|||
AditionalLabels: labels,
|
||||
}
|
||||
|
||||
if instance.ScaleSetFkID != nil {
|
||||
ret.ScaleSetID = *instance.ScaleSetFkID
|
||||
}
|
||||
|
||||
if instance.Job != nil {
|
||||
paramJob, err := sqlWorkflowJobToParamsJob(*instance.Job)
|
||||
if err != nil {
|
||||
|
|
@ -265,6 +269,60 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) (params.Pool, error) {
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) sqlToCommonScaleSet(scaleSet ScaleSet) (params.ScaleSet, error) {
|
||||
ret := params.ScaleSet{
|
||||
ID: scaleSet.ID,
|
||||
ScaleSetID: scaleSet.ScaleSetID,
|
||||
Name: scaleSet.Name,
|
||||
DisableUpdate: scaleSet.DisableUpdate,
|
||||
|
||||
ProviderName: scaleSet.ProviderName,
|
||||
MaxRunners: scaleSet.MaxRunners,
|
||||
MinIdleRunners: scaleSet.MinIdleRunners,
|
||||
RunnerPrefix: params.RunnerPrefix{
|
||||
Prefix: scaleSet.RunnerPrefix,
|
||||
},
|
||||
Image: scaleSet.Image,
|
||||
Flavor: scaleSet.Flavor,
|
||||
OSArch: scaleSet.OSArch,
|
||||
OSType: scaleSet.OSType,
|
||||
Enabled: scaleSet.Enabled,
|
||||
Instances: make([]params.Instance, len(scaleSet.Instances)),
|
||||
RunnerBootstrapTimeout: scaleSet.RunnerBootstrapTimeout,
|
||||
ExtraSpecs: json.RawMessage(scaleSet.ExtraSpecs),
|
||||
GitHubRunnerGroup: scaleSet.GitHubRunnerGroup,
|
||||
State: scaleSet.State,
|
||||
ExtendedState: scaleSet.ExtendedState,
|
||||
}
|
||||
|
||||
if scaleSet.RepoID != nil {
|
||||
ret.RepoID = scaleSet.RepoID.String()
|
||||
if scaleSet.Repository.Owner != "" && scaleSet.Repository.Name != "" {
|
||||
ret.RepoName = fmt.Sprintf("%s/%s", scaleSet.Repository.Owner, scaleSet.Repository.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if scaleSet.OrgID != nil && scaleSet.Organization.Name != "" {
|
||||
ret.OrgID = scaleSet.OrgID.String()
|
||||
ret.OrgName = scaleSet.Organization.Name
|
||||
}
|
||||
|
||||
if scaleSet.EnterpriseID != nil && scaleSet.Enterprise.Name != "" {
|
||||
ret.EnterpriseID = scaleSet.EnterpriseID.String()
|
||||
ret.EnterpriseName = scaleSet.Enterprise.Name
|
||||
}
|
||||
|
||||
var err error
|
||||
for idx, inst := range scaleSet.Instances {
|
||||
ret.Instances[idx], err = s.sqlToParamsInstance(inst)
|
||||
if err != nil {
|
||||
return params.ScaleSet{}, errors.Wrap(err, "converting instance")
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) sqlToCommonTags(tag Tag) params.Tag {
|
||||
return params.Tag{
|
||||
ID: tag.ID.String(),
|
||||
|
|
@ -452,6 +510,26 @@ func (s *sqlDatabase) getPoolByID(tx *gorm.DB, poolID string, preload ...string)
|
|||
return pool, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getScaleSetByID(tx *gorm.DB, scaleSetID uint, preload ...string) (ScaleSet, error) {
|
||||
var scaleSet ScaleSet
|
||||
q := tx.Model(&ScaleSet{})
|
||||
if len(preload) > 0 {
|
||||
for _, item := range preload {
|
||||
q = q.Preload(item)
|
||||
}
|
||||
}
|
||||
|
||||
q = q.Where("id = ?", scaleSetID).First(&scaleSet)
|
||||
|
||||
if q.Error != nil {
|
||||
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||
return ScaleSet{}, runnerErrors.ErrNotFound
|
||||
}
|
||||
return ScaleSet{}, errors.Wrap(q.Error, "fetching scale set from database")
|
||||
}
|
||||
return scaleSet, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) hasGithubEntity(tx *gorm.DB, entityType params.GithubEntityType, entityID string) error {
|
||||
u, err := uuid.Parse(entityID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"github.com/cloudbase/garm/params"
|
||||
)
|
||||
|
||||
type idGetter interface {
|
||||
type IDGetter interface {
|
||||
GetID() string
|
||||
}
|
||||
|
||||
|
|
@ -72,21 +72,41 @@ func WithEntityPoolFilter(ghEntity params.GithubEntity) dbCommon.PayloadFilterFu
|
|||
}
|
||||
switch ghEntity.EntityType {
|
||||
case params.GithubEntityTypeRepository:
|
||||
if pool.RepoID != ghEntity.ID {
|
||||
return false
|
||||
}
|
||||
return pool.RepoID == ghEntity.ID
|
||||
case params.GithubEntityTypeOrganization:
|
||||
if pool.OrgID != ghEntity.ID {
|
||||
return false
|
||||
}
|
||||
return pool.OrgID == ghEntity.ID
|
||||
case params.GithubEntityTypeEnterprise:
|
||||
if pool.EnterpriseID != ghEntity.ID {
|
||||
return false
|
||||
}
|
||||
return pool.EnterpriseID == ghEntity.ID
|
||||
default:
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithEntityPoolFilter returns true if the change payload is a pool that belongs to the
|
||||
// supplied Github entity. This is useful when an entity worker wants to watch for changes
|
||||
// in pools that belong to it.
|
||||
func WithEntityScaleSetFilter(ghEntity params.GithubEntity) dbCommon.PayloadFilterFunc {
|
||||
return func(payload dbCommon.ChangePayload) bool {
|
||||
switch payload.EntityType {
|
||||
case dbCommon.ScaleSetEntityType:
|
||||
scaleSet, ok := payload.Payload.(params.ScaleSet)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch ghEntity.EntityType {
|
||||
case params.GithubEntityTypeRepository:
|
||||
return scaleSet.RepoID == ghEntity.ID
|
||||
case params.GithubEntityTypeOrganization:
|
||||
return scaleSet.OrgID == ghEntity.ID
|
||||
case params.GithubEntityTypeEnterprise:
|
||||
return scaleSet.EnterpriseID == ghEntity.ID
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
@ -100,7 +120,7 @@ func WithEntityFilter(entity params.GithubEntity) dbCommon.PayloadFilterFunc {
|
|||
if params.GithubEntityType(payload.EntityType) != entity.EntityType {
|
||||
return false
|
||||
}
|
||||
var ent idGetter
|
||||
var ent IDGetter
|
||||
var ok bool
|
||||
switch payload.EntityType {
|
||||
case dbCommon.RepositoryEntityType:
|
||||
|
|
@ -210,3 +230,33 @@ func WithExcludeEntityTypeFilter(entityType dbCommon.DatabaseEntityType) dbCommo
|
|||
return payload.EntityType != entityType
|
||||
}
|
||||
}
|
||||
|
||||
// WithScaleSetFilter returns a filter function that matches a particular scale set.
|
||||
func WithScaleSetFilter(scaleset params.ScaleSet) dbCommon.PayloadFilterFunc {
|
||||
return func(payload dbCommon.ChangePayload) bool {
|
||||
if payload.EntityType != dbCommon.ScaleSetEntityType {
|
||||
return false
|
||||
}
|
||||
|
||||
ss, ok := payload.Payload.(params.ScaleSet)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return ss.ID == scaleset.ID
|
||||
}
|
||||
}
|
||||
|
||||
func WithScaleSetInstanceFilter(scaleset params.ScaleSet) dbCommon.PayloadFilterFunc {
|
||||
return func(payload dbCommon.ChangePayload) bool {
|
||||
if payload.EntityType != dbCommon.InstanceEntityType {
|
||||
return false
|
||||
}
|
||||
|
||||
instance, ok := payload.Payload.(params.Instance)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return instance.ScaleSetID == scaleset.ID
|
||||
}
|
||||
}
|
||||
|
|
|
|||
103
params/params.go
103
params/params.go
|
|
@ -45,6 +45,7 @@ type (
|
|||
WebhookEndpointType string
|
||||
GithubAuthType string
|
||||
PoolBalancerType string
|
||||
ScaleSetState string
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -128,6 +129,14 @@ func (e GithubEntityType) String() string {
|
|||
return string(e)
|
||||
}
|
||||
|
||||
const (
|
||||
ScaleSetPendingCreate ScaleSetState = "pending_create"
|
||||
ScaleSetCreated ScaleSetState = "created"
|
||||
ScaleSetError ScaleSetState = "error"
|
||||
ScaleSetPendingDelete ScaleSetState = "pending_delete"
|
||||
ScaleSetPendingForceDelete ScaleSetState = "pending_force_delete"
|
||||
)
|
||||
|
||||
type StatusMessage struct {
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
|
|
@ -179,6 +188,9 @@ type Instance struct {
|
|||
// PoolID is the ID of the garm pool to which a runner belongs.
|
||||
PoolID string `json:"pool_id,omitempty"`
|
||||
|
||||
// ScaleSetID is the ID of the scale set to which a runner belongs.
|
||||
ScaleSetID uint `json:"scale_set_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"`
|
||||
|
|
@ -403,6 +415,97 @@ func (p *Pool) HasRequiredLabels(set []string) bool {
|
|||
// used by swagger client generated code
|
||||
type Pools []Pool
|
||||
|
||||
type ScaleSet struct {
|
||||
RunnerPrefix
|
||||
|
||||
ID uint `json:"id,omitempty"`
|
||||
ScaleSetID int `json:"scale_set_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
DisableUpdate bool `json:"disable_update"`
|
||||
|
||||
State ScaleSetState `json:"state"`
|
||||
ExtendedState string `json:"extended_state,omitempty"`
|
||||
|
||||
ProviderName string `json:"provider_name,omitempty"`
|
||||
MaxRunners uint `json:"max_runners,omitempty"`
|
||||
MinIdleRunners uint `json:"min_idle_runners,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Flavor string `json:"flavor,omitempty"`
|
||||
OSType commonParams.OSType `json:"os_type,omitempty"`
|
||||
OSArch commonParams.OSArch `json:"os_arch,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Instances []Instance `json:"instances,omitempty"`
|
||||
|
||||
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout,omitempty"`
|
||||
// ExtraSpecs is an opaque raw 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. The contents of this field means
|
||||
// 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,omitempty"`
|
||||
|
||||
StatusMessages []StatusMessage `json:"status_messages"`
|
||||
|
||||
RepoID string `json:"repo_id,omitempty"`
|
||||
RepoName string `json:"repo_name,omitempty"`
|
||||
|
||||
OrgID string `json:"org_id,omitempty"`
|
||||
OrgName string `json:"org_name,omitempty"`
|
||||
|
||||
EnterpriseID string `json:"enterprise_id,omitempty"`
|
||||
EnterpriseName string `json:"enterprise_name,omitempty"`
|
||||
}
|
||||
|
||||
func (p ScaleSet) GithubEntity() (GithubEntity, error) {
|
||||
switch p.ScaleSetType() {
|
||||
case GithubEntityTypeRepository:
|
||||
return GithubEntity{
|
||||
ID: p.RepoID,
|
||||
EntityType: GithubEntityTypeRepository,
|
||||
}, nil
|
||||
case GithubEntityTypeOrganization:
|
||||
return GithubEntity{
|
||||
ID: p.OrgID,
|
||||
EntityType: GithubEntityTypeOrganization,
|
||||
}, nil
|
||||
case GithubEntityTypeEnterprise:
|
||||
return GithubEntity{
|
||||
ID: p.EnterpriseID,
|
||||
EntityType: GithubEntityTypeEnterprise,
|
||||
}, nil
|
||||
}
|
||||
return GithubEntity{}, fmt.Errorf("pool has no associated entity")
|
||||
}
|
||||
|
||||
func (p *ScaleSet) ScaleSetType() GithubEntityType {
|
||||
switch {
|
||||
case p.RepoID != "":
|
||||
return GithubEntityTypeRepository
|
||||
case p.OrgID != "":
|
||||
return GithubEntityTypeOrganization
|
||||
case p.EnterpriseID != "":
|
||||
return GithubEntityTypeEnterprise
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p ScaleSet) GetID() uint {
|
||||
return p.ID
|
||||
}
|
||||
|
||||
func (p *ScaleSet) RunnerTimeout() uint {
|
||||
if p.RunnerBootstrapTimeout == 0 {
|
||||
return appdefaults.DefaultRunnerBootstrapTimeout
|
||||
}
|
||||
return p.RunnerBootstrapTimeout
|
||||
}
|
||||
|
||||
// used by swagger client generated code
|
||||
type ScaleSets []ScaleSet
|
||||
|
||||
type Repository struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Owner string `json:"owner,omitempty"`
|
||||
|
|
|
|||
|
|
@ -533,3 +533,76 @@ func (u UpdateControllerParams) Validate() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateScaleSetParams struct {
|
||||
RunnerPrefix
|
||||
|
||||
Name string `json:"name"`
|
||||
DisableUpdate bool `json:"disable_update"`
|
||||
ScaleSetID int `json:"scale_set_id"`
|
||||
|
||||
ProviderName string `json:"provider_name,omitempty"`
|
||||
MaxRunners uint `json:"max_runners,omitempty"`
|
||||
MinIdleRunners uint `json:"min_idle_runners,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Flavor string `json:"flavor,omitempty"`
|
||||
OSType commonParams.OSType `json:"os_type,omitempty"`
|
||||
OSArch commonParams.OSArch `json:"os_arch,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout,omitempty"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (s *CreateScaleSetParams) Validate() error {
|
||||
if s.ProviderName == "" {
|
||||
return fmt.Errorf("missing provider")
|
||||
}
|
||||
|
||||
if s.MinIdleRunners > s.MaxRunners {
|
||||
return fmt.Errorf("min_idle_runners cannot be larger than max_runners")
|
||||
}
|
||||
|
||||
if s.MaxRunners == 0 {
|
||||
return fmt.Errorf("max_runners cannot be 0")
|
||||
}
|
||||
|
||||
if s.Flavor == "" {
|
||||
return fmt.Errorf("missing flavor")
|
||||
}
|
||||
|
||||
if s.Image == "" {
|
||||
return fmt.Errorf("missing image")
|
||||
}
|
||||
|
||||
if s.Name == "" {
|
||||
return fmt.Errorf("missing scale set name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateScaleSetParams struct {
|
||||
RunnerPrefix
|
||||
|
||||
Name string `json:"name,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
MaxRunners *uint `json:"max_runners,omitempty"`
|
||||
MinIdleRunners *uint `json:"min_idle_runners,omitempty"`
|
||||
RunnerBootstrapTimeout *uint `json:"runner_bootstrap_timeout,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Flavor string `json:"flavor,omitempty"`
|
||||
OSType commonParams.OSType `json:"os_type,omitempty"`
|
||||
OSArch commonParams.OSArch `json:"os_arch,omitempty"`
|
||||
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:"runner_group,omitempty"`
|
||||
State *ScaleSetState `json:"state"`
|
||||
ExtendedState *string `json:"extended_state"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue