garm/runner/enterprises.go
Gabriel Adrian Samfira 0074af9370
Move some defaults and types from config
The params package should not depend on config. The params packages
should be consumable by external applications that wish to interact with
garm, and it makes no sense to pull in the config package just for some
constants. As such, the following changes have been made:

  * Moved some types from config to params
  * Moved defaults in a new leaf package called appdefaults

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2023-03-14 14:15:10 +02:00

336 lines
10 KiB
Go

package runner
import (
"context"
"fmt"
"log"
"strings"
"github.com/cloudbase/garm/auth"
runnerErrors "github.com/cloudbase/garm/errors"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
"github.com/cloudbase/garm/util/appdefaults"
"github.com/pkg/errors"
)
func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterpriseParams) (enterprise params.Enterprise, err error) {
if !auth.IsAdmin(ctx) {
return enterprise, runnerErrors.ErrUnauthorized
}
err = param.Validate()
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "validating params")
}
creds, ok := r.credentials[param.CredentialsName]
if !ok {
return params.Enterprise{}, runnerErrors.NewBadRequestError("credentials %s not defined", param.CredentialsName)
}
_, err = r.store.GetEnterprise(ctx, param.Name)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
} else {
return params.Enterprise{}, runnerErrors.NewConflictError("enterprise %s already exists", param.Name)
}
enterprise, err = r.store.CreateEnterprise(ctx, param.Name, creds.Name, param.WebhookSecret)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
}
defer func() {
if err != nil {
if deleteErr := r.store.DeleteEnterprise(ctx, enterprise.ID); deleteErr != nil {
log.Printf("failed to delete enterprise: %s", deleteErr)
}
}
}()
var poolMgr common.PoolManager
poolMgr, err = r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise pool manager")
}
if err := poolMgr.Start(); err != nil {
if deleteErr := r.poolManagerCtrl.DeleteEnterprisePoolManager(enterprise); deleteErr != nil {
log.Printf("failed to cleanup pool manager for enterprise %s", enterprise.ID)
}
return params.Enterprise{}, errors.Wrap(err, "starting enterprise pool manager")
}
return enterprise, nil
}
func (r *Runner) ListEnterprises(ctx context.Context) ([]params.Enterprise, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
enterprises, err := r.store.ListEnterprises(ctx)
if err != nil {
return nil, errors.Wrap(err, "listing enterprises")
}
var allEnterprises []params.Enterprise
for _, enterprise := range enterprises {
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
enterprise.PoolManagerStatus.IsRunning = false
enterprise.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
} else {
enterprise.PoolManagerStatus = poolMgr.Status()
}
allEnterprises = append(allEnterprises, enterprise)
}
return allEnterprises, nil
}
func (r *Runner) GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error) {
if !auth.IsAdmin(ctx) {
return params.Enterprise{}, runnerErrors.ErrUnauthorized
}
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
enterprise.PoolManagerStatus.IsRunning = false
enterprise.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
}
enterprise.PoolManagerStatus = poolMgr.Status()
return enterprise, nil
}
func (r *Runner) DeleteEnterprise(ctx context.Context, enterpriseID string) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise")
}
pools, err := r.store.ListEnterprisePools(ctx, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise pools")
}
if len(pools) > 0 {
poolIds := []string{}
for _, pool := range pools {
poolIds = append(poolIds, pool.ID)
}
return runnerErrors.NewBadRequestError("enterprise has pools defined (%s)", strings.Join(poolIds, ", "))
}
if err := r.poolManagerCtrl.DeleteEnterprisePoolManager(enterprise); err != nil {
return errors.Wrap(err, "deleting enterprise pool manager")
}
if err := r.store.DeleteEnterprise(ctx, enterpriseID); err != nil {
return errors.Wrapf(err, "removing enterprise %s", enterpriseID)
}
return nil
}
func (r *Runner) UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateRepositoryParams) (params.Enterprise, error) {
if !auth.IsAdmin(ctx) {
return params.Enterprise{}, runnerErrors.ErrUnauthorized
}
r.mux.Lock()
defer r.mux.Unlock()
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
if param.CredentialsName != "" {
// Check that credentials are set before saving to db
if _, ok := r.credentials[param.CredentialsName]; !ok {
return params.Enterprise{}, runnerErrors.NewBadRequestError("invalid credentials (%s) for enterprise %s", param.CredentialsName, enterprise.Name)
}
}
enterprise, err = r.store.UpdateEnterprise(ctx, enterpriseID, param)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
}
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
newState := params.UpdatePoolStateParams{
WebhookSecret: enterprise.WebhookSecret,
}
// stop the pool mgr
if err := poolMgr.RefreshState(newState); err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise pool manager")
}
} else {
if _, err := r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store); err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise pool manager")
}
}
return enterprise, nil
}
func (r *Runner) CreateEnterprisePool(ctx context.Context, enterpriseID string, param params.CreatePoolParams) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
r.mux.Lock()
defer r.mux.Unlock()
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching enterprise")
}
if _, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise); err != nil {
return params.Pool{}, runnerErrors.ErrNotFound
}
createPoolParams, err := r.appendTagsToCreatePoolParams(param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool params")
}
if param.RunnerBootstrapTimeout == 0 {
param.RunnerBootstrapTimeout = appdefaults.DefaultRunnerBootstrapTimeout
}
pool, err := r.store.CreateEnterprisePool(ctx, enterpriseID, createPoolParams)
if err != nil {
return params.Pool{}, errors.Wrap(err, "creating pool")
}
return pool, nil
}
func (r *Runner) GetEnterprisePoolByID(ctx context.Context, enterpriseID, poolID string) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
pool, err := r.store.GetEnterprisePool(ctx, enterpriseID, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return pool, nil
}
func (r *Runner) DeleteEnterprisePool(ctx context.Context, enterpriseID, poolID string) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
// TODO: dedup instance count verification
pool, err := r.store.GetEnterprisePool(ctx, enterpriseID, poolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
}
instances, err := r.store.ListPoolInstances(ctx, pool.ID)
if err != nil {
return errors.Wrap(err, "fetching instances")
}
// TODO: implement a count function
if len(instances) > 0 {
runnerIDs := []string{}
for _, run := range instances {
runnerIDs = append(runnerIDs, run.ID)
}
return runnerErrors.NewBadRequestError("pool has runners: %s", strings.Join(runnerIDs, ", "))
}
if err := r.store.DeleteEnterprisePool(ctx, enterpriseID, poolID); err != nil {
return errors.Wrap(err, "deleting pool")
}
return nil
}
func (r *Runner) ListEnterprisePools(ctx context.Context, enterpriseID string) ([]params.Pool, error) {
if !auth.IsAdmin(ctx) {
return []params.Pool{}, runnerErrors.ErrUnauthorized
}
pools, err := r.store.ListEnterprisePools(ctx, enterpriseID)
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
}
return pools, nil
}
func (r *Runner) UpdateEnterprisePool(ctx context.Context, enterpriseID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
pool, err := r.store.GetEnterprisePool(ctx, enterpriseID, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
maxRunners := pool.MaxRunners
minIdleRunners := pool.MinIdleRunners
if param.MaxRunners != nil {
maxRunners = *param.MaxRunners
}
if param.MinIdleRunners != nil {
minIdleRunners = *param.MinIdleRunners
}
if minIdleRunners > maxRunners {
return params.Pool{}, runnerErrors.NewBadRequestError("min_idle_runners cannot be larger than max_runners")
}
newPool, err := r.store.UpdateEnterprisePool(ctx, enterpriseID, poolID, param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "updating pool")
}
return newPool, nil
}
func (r *Runner) ListEnterpriseInstances(ctx context.Context, enterpriseID string) ([]params.Instance, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
instances, err := r.store.ListEnterpriseInstances(ctx, enterpriseID)
if err != nil {
return []params.Instance{}, errors.Wrap(err, "fetching instances")
}
return instances, nil
}
func (r *Runner) findEnterprisePoolManager(name string) (common.PoolManager, error) {
r.mux.Lock()
defer r.mux.Unlock()
enterprise, err := r.store.GetEnterprise(r.ctx, name)
if err != nil {
return nil, errors.Wrap(err, "fetching enterprise")
}
poolManager, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
return nil, errors.Wrap(err, "fetching pool manager for enterprise")
}
return poolManager, nil
}