Add job tracking
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
parent
8abf94ef85
commit
fbffd8157b
34 changed files with 864 additions and 1081 deletions
|
|
@ -18,8 +18,8 @@ import (
|
|||
runnerErrors "github.com/cloudbase/garm/errors"
|
||||
"github.com/cloudbase/garm/params"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
|
|||
return params.ControllerInfo{}, runnerErrors.NewConflictError("controller already initialized")
|
||||
}
|
||||
|
||||
newID, err := uuid.NewV4()
|
||||
newID, err := uuid.NewUUID()
|
||||
if err != nil {
|
||||
return params.ControllerInfo{}, errors.Wrap(err, "generating UUID")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,5 +69,6 @@ func (s *CtrlTestSuite) TestInitControllerAlreadyInitialized() {
|
|||
}
|
||||
|
||||
func TestCtrlTestSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, new(CtrlTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import (
|
|||
"github.com/cloudbase/garm/params"
|
||||
"github.com/cloudbase/garm/util"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -224,15 +224,15 @@ func (s *sqlDatabase) UpdateEnterprisePool(ctx context.Context, enterpriseID, po
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) FindEnterprisePoolByTags(ctx context.Context, enterpriseID string, tags []string) (params.Pool, error) {
|
||||
pool, err := s.findPoolByTags(enterpriseID, "enterprise_id", tags)
|
||||
pool, err := s.findPoolByTags(enterpriseID, params.EnterprisePool, tags)
|
||||
if err != nil {
|
||||
return params.Pool{}, errors.Wrap(err, "fetching pool")
|
||||
}
|
||||
return pool, nil
|
||||
return pool[0], nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) ListEnterprisePools(ctx context.Context, enterpriseID string) ([]params.Pool, error) {
|
||||
pools, err := s.getEnterprisePools(ctx, enterpriseID, "Tags", "Enterprise")
|
||||
pools, err := s.listEntityPools(ctx, params.EnterprisePool, enterpriseID, "Tags", "Instances")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching pools")
|
||||
}
|
||||
|
|
@ -246,7 +246,7 @@ func (s *sqlDatabase) ListEnterprisePools(ctx context.Context, enterpriseID stri
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) ListEnterpriseInstances(ctx context.Context, enterpriseID string) ([]params.Instance, error) {
|
||||
pools, err := s.getEnterprisePools(ctx, enterpriseID, "Instances")
|
||||
pools, err := s.listEntityPools(ctx, params.EnterprisePool, enterpriseID, "Instances", "Tags")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching enterprise")
|
||||
}
|
||||
|
|
@ -274,7 +274,7 @@ func (s *sqlDatabase) getEnterprise(ctx context.Context, name string) (Enterpris
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) getEnterpriseByID(ctx context.Context, id string, preload ...string) (Enterprise, error) {
|
||||
u, err := uuid.FromString(id)
|
||||
u, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return Enterprise{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
|
@ -315,28 +315,3 @@ func (s *sqlDatabase) getEnterprisePoolByUniqueFields(ctx context.Context, enter
|
|||
|
||||
return pool[0], nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getEnterprisePools(ctx context.Context, enterpriseID string, preload ...string) ([]Pool, error) {
|
||||
_, err := s.getEnterpriseByID(ctx, enterpriseID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching enterprise")
|
||||
}
|
||||
|
||||
q := s.conn
|
||||
if len(preload) > 0 {
|
||||
for _, item := range preload {
|
||||
q = q.Preload(item)
|
||||
}
|
||||
}
|
||||
|
||||
var pools []Pool
|
||||
err = q.Model(&Pool{}).Where("enterprise_id = ?", enterpriseID).
|
||||
Omit("extra_specs").
|
||||
Find(&pools).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching pool")
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -671,7 +671,7 @@ func (s *EnterpriseTestSuite) TestListEnterprisePoolsInvalidEnterpriseID() {
|
|||
_, err := s.Store.ListEnterprisePools(context.Background(), "dummy-enterprise-id")
|
||||
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal("fetching pools: fetching enterprise: parsing id: invalid request", err.Error())
|
||||
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
|
||||
}
|
||||
|
||||
func (s *EnterpriseTestSuite) TestGetEnterprisePool() {
|
||||
|
|
@ -785,7 +785,7 @@ func (s *EnterpriseTestSuite) TestListEnterpriseInstancesInvalidEnterpriseID() {
|
|||
_, err := s.Store.ListEnterpriseInstances(context.Background(), "dummy-enterprise-id")
|
||||
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal("fetching enterprise: fetching enterprise: parsing id: invalid request", err.Error())
|
||||
s.Require().Equal("fetching enterprise: parsing id: invalid request", err.Error())
|
||||
}
|
||||
|
||||
func (s *EnterpriseTestSuite) TestUpdateEnterprisePool() {
|
||||
|
|
@ -811,5 +811,6 @@ func (s *EnterpriseTestSuite) TestUpdateEnterprisePoolInvalidEnterpriseID() {
|
|||
}
|
||||
|
||||
func TestEnterpriseTestSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, new(EnterpriseTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import (
|
|||
runnerErrors "github.com/cloudbase/garm/errors"
|
||||
"github.com/cloudbase/garm/params"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
|
@ -52,7 +52,7 @@ func (s *sqlDatabase) CreateInstance(ctx context.Context, poolID string, param p
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) getInstanceByID(ctx context.Context, instanceID string) (Instance, error) {
|
||||
u, err := uuid.FromString(instanceID)
|
||||
u, err := uuid.Parse(instanceID)
|
||||
if err != nil {
|
||||
return Instance{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
|
@ -248,7 +248,7 @@ func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceID string, par
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) ListPoolInstances(ctx context.Context, poolID string) ([]params.Instance, error) {
|
||||
u, err := uuid.FromString(poolID)
|
||||
u, err := uuid.Parse(poolID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -552,5 +552,6 @@ func (s *InstancesTestSuite) TestPoolInstanceCountDBCountErr() {
|
|||
}
|
||||
|
||||
func TestInstTestSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, new(InstancesTestSuite))
|
||||
}
|
||||
|
|
|
|||
286
database/sql/jobs.go
Normal file
286
database/sql/jobs.go
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/cloudbase/garm/database/common"
|
||||
runnerErrors "github.com/cloudbase/garm/errors"
|
||||
"github.com/cloudbase/garm/params"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
var _ common.JobsStore = &sqlDatabase{}
|
||||
|
||||
func sqlWorkflowJobToParamsJob(job WorkflowJob) (params.Job, error) {
|
||||
labels := []string{}
|
||||
if job.Labels != nil {
|
||||
if err := json.Unmarshal(job.Labels, &labels); err != nil {
|
||||
return params.Job{}, errors.Wrap(err, "unmarshaling labels")
|
||||
}
|
||||
}
|
||||
return params.Job{
|
||||
ID: job.ID,
|
||||
RunID: job.RunID,
|
||||
Action: job.Action,
|
||||
Status: job.Status,
|
||||
Name: job.Name,
|
||||
Conclusion: job.Conclusion,
|
||||
StartedAt: job.StartedAt,
|
||||
CompletedAt: job.CompletedAt,
|
||||
GithubRunnerID: job.GithubRunnerID,
|
||||
RunnerName: job.RunnerName,
|
||||
RunnerGroupID: job.RunnerGroupID,
|
||||
RunnerGroupName: job.RunnerGroupName,
|
||||
RepositoryName: job.RepositoryName,
|
||||
RepositoryOwner: job.RepositoryOwner,
|
||||
RepoID: job.RepoID,
|
||||
OrgID: job.OrgID,
|
||||
EnterpriseID: job.EnterpriseID,
|
||||
Labels: labels,
|
||||
CreatedAt: job.CreatedAt,
|
||||
UpdatedAt: job.UpdatedAt,
|
||||
LockedBy: job.LockedBy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func paramsJobToWorkflowJob(job params.Job) (WorkflowJob, error) {
|
||||
asJson, err := json.Marshal(job.Labels)
|
||||
if err != nil {
|
||||
return WorkflowJob{}, errors.Wrap(err, "marshaling labels")
|
||||
}
|
||||
return WorkflowJob{
|
||||
ID: job.ID,
|
||||
RunID: job.RunID,
|
||||
Action: job.Action,
|
||||
Status: job.Status,
|
||||
Name: job.Name,
|
||||
Conclusion: job.Conclusion,
|
||||
StartedAt: job.StartedAt,
|
||||
CompletedAt: job.CompletedAt,
|
||||
GithubRunnerID: job.GithubRunnerID,
|
||||
RunnerName: job.RunnerName,
|
||||
RunnerGroupID: job.RunnerGroupID,
|
||||
RunnerGroupName: job.RunnerGroupName,
|
||||
RepositoryName: job.RepositoryName,
|
||||
RepositoryOwner: job.RepositoryOwner,
|
||||
RepoID: job.RepoID,
|
||||
OrgID: job.OrgID,
|
||||
EnterpriseID: job.EnterpriseID,
|
||||
Labels: asJson,
|
||||
LockedBy: job.LockedBy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) DeleteJob(ctx context.Context, jobID int64) error {
|
||||
q := s.conn.Delete(&WorkflowJob{}, jobID)
|
||||
if q.Error != nil {
|
||||
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(q.Error, "deleting job")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) LockJob(ctx context.Context, jobID int64, entityID string) error {
|
||||
entityUUID, err := uuid.Parse(entityID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parsing entity id")
|
||||
}
|
||||
var workflowJob WorkflowJob
|
||||
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", jobID).First(&workflowJob)
|
||||
|
||||
if q.Error != nil {
|
||||
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||
return runnerErrors.ErrNotFound
|
||||
}
|
||||
return errors.Wrap(q.Error, "fetching job")
|
||||
}
|
||||
|
||||
if workflowJob.LockedBy != uuid.Nil {
|
||||
return runnerErrors.NewConflictError("job is locked by another entity %s", workflowJob.LockedBy.String())
|
||||
}
|
||||
|
||||
workflowJob.LockedBy = entityUUID
|
||||
|
||||
if err := s.conn.Save(&workflowJob).Error; err != nil {
|
||||
return errors.Wrap(err, "saving job")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) UnlockJob(ctx context.Context, jobID int64, entityID string) error {
|
||||
var workflowJob WorkflowJob
|
||||
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", jobID).First(&workflowJob)
|
||||
|
||||
if q.Error != nil {
|
||||
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||
return runnerErrors.ErrNotFound
|
||||
}
|
||||
return errors.Wrap(q.Error, "fetching job")
|
||||
}
|
||||
|
||||
if workflowJob.LockedBy == uuid.Nil {
|
||||
// Job is already unlocked.
|
||||
return nil
|
||||
}
|
||||
|
||||
if workflowJob.LockedBy != uuid.Nil && workflowJob.LockedBy.String() != entityID {
|
||||
return runnerErrors.NewConflictError("job is locked by another entity %s", workflowJob.LockedBy.String())
|
||||
}
|
||||
|
||||
workflowJob.LockedBy = uuid.Nil
|
||||
|
||||
if err := s.conn.Save(&workflowJob).Error; err != nil {
|
||||
return errors.Wrap(err, "saving job")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) CreateOrUpdateJob(ctx context.Context, job params.Job) (params.Job, error) {
|
||||
var workflowJob WorkflowJob
|
||||
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", job.ID).First(&workflowJob)
|
||||
|
||||
if q.Error != nil {
|
||||
if !errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||
return params.Job{}, errors.Wrap(q.Error, "fetching job")
|
||||
}
|
||||
}
|
||||
|
||||
if workflowJob.ID != 0 {
|
||||
// Update workflowJob with values from job.
|
||||
workflowJob.Status = job.Status
|
||||
workflowJob.Action = job.Action
|
||||
workflowJob.Conclusion = job.Conclusion
|
||||
workflowJob.StartedAt = job.StartedAt
|
||||
workflowJob.CompletedAt = job.CompletedAt
|
||||
workflowJob.GithubRunnerID = job.GithubRunnerID
|
||||
workflowJob.RunnerGroupID = job.RunnerGroupID
|
||||
workflowJob.RunnerGroupName = job.RunnerGroupName
|
||||
|
||||
if job.LockedBy != uuid.Nil {
|
||||
workflowJob.LockedBy = job.LockedBy
|
||||
}
|
||||
|
||||
if job.RunnerName != "" {
|
||||
workflowJob.RunnerName = job.RunnerName
|
||||
}
|
||||
|
||||
if job.RepoID != uuid.Nil {
|
||||
workflowJob.RepoID = job.RepoID
|
||||
}
|
||||
|
||||
if job.OrgID != uuid.Nil {
|
||||
workflowJob.OrgID = job.OrgID
|
||||
}
|
||||
|
||||
if job.EnterpriseID != uuid.Nil {
|
||||
workflowJob.EnterpriseID = job.EnterpriseID
|
||||
}
|
||||
if err := s.conn.Save(&workflowJob).Error; err != nil {
|
||||
return params.Job{}, errors.Wrap(err, "saving job")
|
||||
}
|
||||
} else {
|
||||
workflowJob, err := paramsJobToWorkflowJob(job)
|
||||
if err != nil {
|
||||
return params.Job{}, errors.Wrap(err, "converting job")
|
||||
}
|
||||
if err := s.conn.Create(&workflowJob).Error; err != nil {
|
||||
return params.Job{}, errors.Wrap(err, "creating job")
|
||||
}
|
||||
}
|
||||
|
||||
return sqlWorkflowJobToParamsJob(workflowJob)
|
||||
}
|
||||
|
||||
// ListJobsByStatus lists all jobs for a given status.
|
||||
func (s *sqlDatabase) ListJobsByStatus(ctx context.Context, status params.JobStatus) ([]params.Job, error) {
|
||||
var jobs []WorkflowJob
|
||||
query := s.conn.Model(&WorkflowJob{}).Where("status = ?", status)
|
||||
|
||||
if err := query.Find(&jobs); err.Error != nil {
|
||||
return nil, err.Error
|
||||
}
|
||||
|
||||
ret := make([]params.Job, len(jobs))
|
||||
for idx, job := range jobs {
|
||||
jobParam, err := sqlWorkflowJobToParamsJob(job)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "converting job")
|
||||
}
|
||||
ret[idx] = jobParam
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// ListEntityJobsByStatus lists all jobs for a given entity type and id.
|
||||
func (s *sqlDatabase) ListEntityJobsByStatus(ctx context.Context, entityType params.PoolType, entityID string, status params.JobStatus) ([]params.Job, error) {
|
||||
u, err := uuid.Parse(entityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var jobs []WorkflowJob
|
||||
query := s.conn.Model(&WorkflowJob{}).Where("status = ?", status)
|
||||
|
||||
switch entityType {
|
||||
case params.OrganizationPool:
|
||||
query = query.Where("org_id = ?", u)
|
||||
case params.RepositoryPool:
|
||||
query = query.Where("repo_id = ?", u)
|
||||
case params.EnterprisePool:
|
||||
query = query.Where("enterprise_id = ?", u)
|
||||
}
|
||||
|
||||
if err := query.Find(&jobs); err.Error != nil {
|
||||
if errors.Is(err.Error, gorm.ErrRecordNotFound) {
|
||||
return []params.Job{}, nil
|
||||
}
|
||||
return nil, err.Error
|
||||
}
|
||||
|
||||
ret := make([]params.Job, len(jobs))
|
||||
for idx, job := range jobs {
|
||||
jobParam, err := sqlWorkflowJobToParamsJob(job)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "converting job")
|
||||
}
|
||||
ret[idx] = jobParam
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetJobByID gets a job by id.
|
||||
func (s *sqlDatabase) GetJobByID(ctx context.Context, jobID int64) (params.Job, error) {
|
||||
var job WorkflowJob
|
||||
query := s.conn.Model(&WorkflowJob{}).Where("id = ?", jobID)
|
||||
|
||||
if err := query.First(&job); err.Error != nil {
|
||||
if errors.Is(err.Error, gorm.ErrRecordNotFound) {
|
||||
return params.Job{}, runnerErrors.ErrNotFound
|
||||
}
|
||||
return params.Job{}, err.Error
|
||||
}
|
||||
|
||||
return sqlWorkflowJobToParamsJob(job)
|
||||
}
|
||||
|
||||
// DeleteCompletedJobs deletes all completed jobs.
|
||||
func (s *sqlDatabase) DeleteCompletedJobs(ctx context.Context) error {
|
||||
query := s.conn.Model(&WorkflowJob{}).Where("status = ?", params.JobStatusCompleted)
|
||||
|
||||
if err := query.Unscoped().Delete(&WorkflowJob{}); err.Error != nil {
|
||||
if errors.Is(err.Error, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -20,8 +20,8 @@ import (
|
|||
"github.com/cloudbase/garm/params"
|
||||
"github.com/cloudbase/garm/runner/providers/common"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -38,7 +38,7 @@ func (b *Base) BeforeCreate(tx *gorm.DB) error {
|
|||
if b.ID != emptyId {
|
||||
return nil
|
||||
}
|
||||
newID, err := uuid.NewV4()
|
||||
newID, err := uuid.NewUUID()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generating id")
|
||||
}
|
||||
|
|
@ -176,3 +176,59 @@ type ControllerInfo struct {
|
|||
|
||||
ControllerID uuid.UUID
|
||||
}
|
||||
|
||||
type WorkflowJob struct {
|
||||
// ID is the ID of the job.
|
||||
ID int64 `gorm:"index"`
|
||||
// RunID is the ID of the workflow run. A run may have multiple jobs.
|
||||
RunID int64
|
||||
// Action is the specific activity that triggered the event.
|
||||
Action string `gorm:"type:varchar(254);index"`
|
||||
// Conclusion is the outcome of the job.
|
||||
// Possible values: "success", "failure", "neutral", "cancelled", "skipped",
|
||||
// "timed_out", "action_required"
|
||||
Conclusion string
|
||||
// Status is the phase of the lifecycle that the job is currently in.
|
||||
// "queued", "in_progress" and "completed".
|
||||
Status string
|
||||
// Name is the name if the job that was triggered.
|
||||
Name string
|
||||
|
||||
StartedAt time.Time
|
||||
CompletedAt time.Time
|
||||
|
||||
GithubRunnerID int64
|
||||
RunnerName string
|
||||
RunnerGroupID int64
|
||||
RunnerGroupName string
|
||||
|
||||
// repository in which the job was triggered.
|
||||
RepositoryName string
|
||||
RepositoryOwner string
|
||||
|
||||
Labels datatypes.JSON
|
||||
|
||||
// The entity that received the hook.
|
||||
//
|
||||
// Webhooks may be configured on the repo, the org and/or the enterprise.
|
||||
// If we only configure a repo to use garm, we'll only ever receive a
|
||||
// webhook from the repo. But if we configure the parent org of the repo and
|
||||
// the parent enterprise of the org to use garm, a webhook will be sent for each
|
||||
// entity type, in response to one workflow event. Thus, we will get 3 webhooks
|
||||
// with the same run_id and job id. Record all involved entities in the same job
|
||||
// if we have them configured in garm.
|
||||
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"`
|
||||
|
||||
LockedBy uuid.UUID
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import (
|
|||
"github.com/cloudbase/garm/params"
|
||||
"github.com/cloudbase/garm/util"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -212,7 +212,7 @@ func (s *sqlDatabase) CreateOrganizationPool(ctx context.Context, orgId string,
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) ListOrgPools(ctx context.Context, orgID string) ([]params.Pool, error) {
|
||||
pools, err := s.getOrgPools(ctx, orgID, "Tags")
|
||||
pools, err := s.listEntityPools(ctx, params.OrganizationPool, orgID, "Tags", "Instances")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching pools")
|
||||
}
|
||||
|
|
@ -246,15 +246,15 @@ func (s *sqlDatabase) DeleteOrganizationPool(ctx context.Context, orgID, poolID
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) FindOrganizationPoolByTags(ctx context.Context, orgID string, tags []string) (params.Pool, error) {
|
||||
pool, err := s.findPoolByTags(orgID, "org_id", tags)
|
||||
pool, err := s.findPoolByTags(orgID, params.OrganizationPool, tags)
|
||||
if err != nil {
|
||||
return params.Pool{}, errors.Wrap(err, "fetching pool")
|
||||
}
|
||||
return pool, nil
|
||||
return pool[0], nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) ListOrgInstances(ctx context.Context, orgID string) ([]params.Instance, error) {
|
||||
pools, err := s.getOrgPools(ctx, orgID, "Instances")
|
||||
pools, err := s.listEntityPools(ctx, params.OrganizationPool, orgID, "Tags", "Instances")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching org")
|
||||
}
|
||||
|
|
@ -277,7 +277,7 @@ func (s *sqlDatabase) UpdateOrganizationPool(ctx context.Context, orgID, poolID
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) getPoolByID(ctx context.Context, poolID string, preload ...string) (Pool, error) {
|
||||
u, err := uuid.FromString(poolID)
|
||||
u, err := uuid.Parse(poolID)
|
||||
if err != nil {
|
||||
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
|
@ -300,34 +300,8 @@ func (s *sqlDatabase) getPoolByID(ctx context.Context, poolID string, preload ..
|
|||
return pool, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getOrgPools(ctx context.Context, orgID string, preload ...string) ([]Pool, error) {
|
||||
_, err := s.getOrgByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching org")
|
||||
}
|
||||
|
||||
q := s.conn
|
||||
if len(preload) > 0 {
|
||||
for _, item := range preload {
|
||||
q = q.Preload(item)
|
||||
}
|
||||
}
|
||||
|
||||
var pools []Pool
|
||||
err = q.Model(&Pool{}).
|
||||
Where("org_id = ?", orgID).
|
||||
Omit("extra_specs").
|
||||
Find(&pools).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching pool")
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getOrgByID(ctx context.Context, id string, preload ...string) (Organization, error) {
|
||||
u, err := uuid.FromString(id)
|
||||
u, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return Organization{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -670,7 +670,7 @@ func (s *OrgTestSuite) TestListOrgPoolsInvalidOrgID() {
|
|||
_, err := s.Store.ListOrgPools(context.Background(), "dummy-org-id")
|
||||
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal("fetching pools: fetching org: parsing id: invalid request", err.Error())
|
||||
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
|
||||
}
|
||||
|
||||
func (s *OrgTestSuite) TestGetOrganizationPool() {
|
||||
|
|
@ -784,7 +784,7 @@ func (s *OrgTestSuite) TestListOrgInstancesInvalidOrgID() {
|
|||
_, err := s.Store.ListOrgInstances(context.Background(), "dummy-org-id")
|
||||
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal("fetching org: fetching org: parsing id: invalid request", err.Error())
|
||||
s.Require().Equal("fetching org: parsing id: invalid request", err.Error())
|
||||
}
|
||||
|
||||
func (s *OrgTestSuite) TestUpdateOrganizationPool() {
|
||||
|
|
@ -810,5 +810,6 @@ func (s *OrgTestSuite) TestUpdateOrganizationPoolInvalidOrgID() {
|
|||
}
|
||||
|
||||
func TestOrgTestSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, new(OrgTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import (
|
|||
runnerErrors "github.com/cloudbase/garm/errors"
|
||||
"github.com/cloudbase/garm/params"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ func (s *sqlDatabase) getEntityPool(ctx context.Context, entityType params.PoolT
|
|||
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing entity id")
|
||||
}
|
||||
|
||||
u, err := uuid.FromString(poolID)
|
||||
u, err := uuid.Parse(poolID)
|
||||
if err != nil {
|
||||
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
|
@ -112,3 +112,109 @@ func (s *sqlDatabase) getEntityPool(ctx context.Context, entityType params.PoolT
|
|||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) listEntityPools(ctx context.Context, entityType params.PoolType, entityID string, preload ...string) ([]Pool, error) {
|
||||
if _, err := uuid.Parse(entityID); err != nil {
|
||||
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
||||
q := s.conn
|
||||
if len(preload) > 0 {
|
||||
for _, item := range preload {
|
||||
q = q.Preload(item)
|
||||
}
|
||||
}
|
||||
|
||||
var fieldName string
|
||||
switch entityType {
|
||||
case params.RepositoryPool:
|
||||
fieldName = "repo_id"
|
||||
case params.OrganizationPool:
|
||||
fieldName = "org_id"
|
||||
case params.EnterprisePool:
|
||||
fieldName = "enterprise_id"
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid entityType: %v", entityType)
|
||||
}
|
||||
|
||||
var pools []Pool
|
||||
condition := fmt.Sprintf("%s = ?", fieldName)
|
||||
err := q.Model(&Pool{}).
|
||||
Where(condition, entityID).
|
||||
Omit("extra_specs").
|
||||
Find(&pools).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []Pool{}, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "fetching pool")
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) findPoolByTags(id string, poolType params.PoolType, tags []string) ([]params.Pool, error) {
|
||||
if len(tags) == 0 {
|
||||
return nil, runnerErrors.NewBadRequestError("missing tags")
|
||||
}
|
||||
u, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
||||
var fieldName string
|
||||
switch poolType {
|
||||
case params.RepositoryPool:
|
||||
fieldName = "repo_id"
|
||||
case params.OrganizationPool:
|
||||
fieldName = "org_id"
|
||||
case params.EnterprisePool:
|
||||
fieldName = "enterprise_id"
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid poolType: %v", poolType)
|
||||
}
|
||||
|
||||
var pools []Pool
|
||||
where := fmt.Sprintf("tags.name in ? and %s = ? and enabled = true", fieldName)
|
||||
q := s.conn.Joins("JOIN pool_tags on pool_tags.pool_id=pools.id").
|
||||
Joins("JOIN tags on tags.id=pool_tags.tag_id").
|
||||
Group("pools.id").
|
||||
Preload("Tags").
|
||||
Having("count(1) = ?", len(tags)).
|
||||
Where(where, tags, u).Find(&pools)
|
||||
|
||||
if q.Error != nil {
|
||||
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||
return nil, runnerErrors.ErrNotFound
|
||||
}
|
||||
return nil, errors.Wrap(q.Error, "fetching pool")
|
||||
}
|
||||
|
||||
if len(pools) == 0 {
|
||||
return nil, runnerErrors.ErrNotFound
|
||||
}
|
||||
|
||||
ret := make([]params.Pool, len(pools))
|
||||
for idx, val := range pools {
|
||||
ret[idx] = s.sqlToCommonPool(val)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) FindPoolsMatchingAllTags(ctx context.Context, entityType params.PoolType, entityID string, tags []string) ([]params.Pool, error) {
|
||||
if len(tags) == 0 {
|
||||
return nil, runnerErrors.NewBadRequestError("missing tags")
|
||||
}
|
||||
|
||||
pools, err := s.findPoolByTags(entityID, entityType, tags)
|
||||
if err != nil {
|
||||
if errors.Is(err, runnerErrors.ErrNotFound) {
|
||||
return []params.Pool{}, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "fetching pools")
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,5 +186,6 @@ func (s *PoolsTestSuite) TestDeletePoolByIDDBRemoveErr() {
|
|||
}
|
||||
|
||||
func TestPoolsTestSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, new(PoolsTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import (
|
|||
"github.com/cloudbase/garm/params"
|
||||
"github.com/cloudbase/garm/util"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -212,7 +212,7 @@ func (s *sqlDatabase) CreateRepositoryPool(ctx context.Context, repoId string, p
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) ListRepoPools(ctx context.Context, repoID string) ([]params.Pool, error) {
|
||||
pools, err := s.getRepoPools(ctx, repoID, "Tags")
|
||||
pools, err := s.listEntityPools(ctx, params.RepositoryPool, repoID, "Tags", "Instances")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching pools")
|
||||
}
|
||||
|
|
@ -246,15 +246,15 @@ func (s *sqlDatabase) DeleteRepositoryPool(ctx context.Context, repoID, poolID s
|
|||
}
|
||||
|
||||
func (s *sqlDatabase) FindRepositoryPoolByTags(ctx context.Context, repoID string, tags []string) (params.Pool, error) {
|
||||
pool, err := s.findPoolByTags(repoID, "repo_id", tags)
|
||||
pool, err := s.findPoolByTags(repoID, params.RepositoryPool, tags)
|
||||
if err != nil {
|
||||
return params.Pool{}, errors.Wrap(err, "fetching pool")
|
||||
}
|
||||
return pool, nil
|
||||
return pool[0], nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) ListRepoInstances(ctx context.Context, repoID string) ([]params.Instance, error) {
|
||||
pools, err := s.getRepoPools(ctx, repoID, "Instances")
|
||||
pools, err := s.listEntityPools(ctx, params.RepositoryPool, repoID, "Tags", "Instances")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching repo")
|
||||
}
|
||||
|
|
@ -294,38 +294,6 @@ func (s *sqlDatabase) getRepo(ctx context.Context, owner, name string) (Reposito
|
|||
return repo, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) findPoolByTags(id, poolType string, tags []string) (params.Pool, error) {
|
||||
if len(tags) == 0 {
|
||||
return params.Pool{}, runnerErrors.NewBadRequestError("missing tags")
|
||||
}
|
||||
u, err := uuid.FromString(id)
|
||||
if err != nil {
|
||||
return params.Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
||||
var pools []Pool
|
||||
where := fmt.Sprintf("tags.name in ? and %s = ? and enabled = true", poolType)
|
||||
q := s.conn.Joins("JOIN pool_tags on pool_tags.pool_id=pools.id").
|
||||
Joins("JOIN tags on tags.id=pool_tags.tag_id").
|
||||
Group("pools.id").
|
||||
Preload("Tags").
|
||||
Having("count(1) = ?", len(tags)).
|
||||
Where(where, tags, u).Find(&pools)
|
||||
|
||||
if q.Error != nil {
|
||||
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||
return params.Pool{}, runnerErrors.ErrNotFound
|
||||
}
|
||||
return params.Pool{}, errors.Wrap(q.Error, "fetching pool")
|
||||
}
|
||||
|
||||
if len(pools) == 0 {
|
||||
return params.Pool{}, runnerErrors.ErrNotFound
|
||||
}
|
||||
|
||||
return s.sqlToCommonPool(pools[0]), nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getRepoPoolByUniqueFields(ctx context.Context, repoID string, provider, image, flavor string) (Pool, error) {
|
||||
repo, err := s.getRepoByID(ctx, repoID)
|
||||
if err != nil {
|
||||
|
|
@ -345,32 +313,8 @@ func (s *sqlDatabase) getRepoPoolByUniqueFields(ctx context.Context, repoID stri
|
|||
return pool[0], nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getRepoPools(ctx context.Context, repoID string, preload ...string) ([]Pool, error) {
|
||||
_, err := s.getRepoByID(ctx, repoID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching repo")
|
||||
}
|
||||
|
||||
q := s.conn
|
||||
if len(preload) > 0 {
|
||||
for _, item := range preload {
|
||||
q = q.Preload(item)
|
||||
}
|
||||
}
|
||||
|
||||
var pools []Pool
|
||||
err = q.Model(&Pool{}).Where("repo_id = ?", repoID).
|
||||
Omit("extra_specs").
|
||||
Find(&pools).Error
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching pool")
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
func (s *sqlDatabase) getRepoByID(ctx context.Context, id string, preload ...string) (Repository, error) {
|
||||
u, err := uuid.FromString(id)
|
||||
u, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return Repository{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -707,7 +707,7 @@ func (s *RepoTestSuite) TestListRepoPoolsInvalidRepoID() {
|
|||
_, err := s.Store.ListRepoPools(context.Background(), "dummy-repo-id")
|
||||
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal("fetching pools: fetching repo: parsing id: invalid request", err.Error())
|
||||
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
|
||||
}
|
||||
|
||||
func (s *RepoTestSuite) TestGetRepositoryPool() {
|
||||
|
|
@ -820,7 +820,7 @@ func (s *RepoTestSuite) TestListRepoInstancesInvalidRepoID() {
|
|||
_, err := s.Store.ListRepoInstances(context.Background(), "dummy-repo-id")
|
||||
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal("fetching repo: fetching repo: parsing id: invalid request", err.Error())
|
||||
s.Require().Equal("fetching repo: parsing id: invalid request", err.Error())
|
||||
}
|
||||
|
||||
func (s *RepoTestSuite) TestUpdateRepositoryPool() {
|
||||
|
|
@ -846,5 +846,6 @@ func (s *RepoTestSuite) TestUpdateRepositoryPoolInvalidRepoID() {
|
|||
}
|
||||
|
||||
func TestRepoTestSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, new(RepoTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ func (s *sqlDatabase) migrateDB() error {
|
|||
&Instance{},
|
||||
&ControllerInfo{},
|
||||
&User{},
|
||||
&WorkflowJob{},
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "running auto migrate")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import (
|
|||
"github.com/cloudbase/garm/params"
|
||||
"github.com/cloudbase/garm/util"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue