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
|
|
@ -86,6 +86,7 @@ type PoolStore interface {
|
||||||
|
|
||||||
PoolInstanceCount(ctx context.Context, poolID string) (int64, error)
|
PoolInstanceCount(ctx context.Context, poolID string) (int64, error)
|
||||||
GetPoolInstanceByName(ctx context.Context, poolID string, instanceName string) (params.Instance, error)
|
GetPoolInstanceByName(ctx context.Context, poolID string, instanceName string) (params.Instance, error)
|
||||||
|
FindPoolsMatchingAllTags(ctx context.Context, entityType params.PoolType, entityID string, tags []string) ([]params.Pool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserStore interface {
|
type UserStore interface {
|
||||||
|
|
@ -111,6 +112,19 @@ type InstanceStore interface {
|
||||||
ListInstanceEvents(ctx context.Context, instanceID string, eventType params.EventType, eventLevel params.EventLevel) ([]params.StatusMessage, error)
|
ListInstanceEvents(ctx context.Context, instanceID string, eventType params.EventType, eventLevel params.EventLevel) ([]params.StatusMessage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JobsStore interface {
|
||||||
|
CreateOrUpdateJob(ctx context.Context, job params.Job) (params.Job, error)
|
||||||
|
ListEntityJobsByStatus(ctx context.Context, entityType params.PoolType, entityID string, status params.JobStatus) ([]params.Job, error)
|
||||||
|
ListJobsByStatus(ctx context.Context, status params.JobStatus) ([]params.Job, error)
|
||||||
|
|
||||||
|
GetJobByID(ctx context.Context, jobID int64) (params.Job, error)
|
||||||
|
DeleteJob(ctx context.Context, jobID int64) error
|
||||||
|
UnlockJob(ctx context.Context, jobID int64, entityID string) error
|
||||||
|
LockJob(ctx context.Context, jobID int64, entityID string) error
|
||||||
|
|
||||||
|
DeleteCompletedJobs(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate mockery --name=Store
|
//go:generate mockery --name=Store
|
||||||
type Store interface {
|
type Store interface {
|
||||||
RepoStore
|
RepoStore
|
||||||
|
|
@ -119,6 +133,7 @@ type Store interface {
|
||||||
PoolStore
|
PoolStore
|
||||||
UserStore
|
UserStore
|
||||||
InstanceStore
|
InstanceStore
|
||||||
|
JobsStore
|
||||||
|
|
||||||
ControllerInfo() (params.ControllerInfo, error)
|
ControllerInfo() (params.ControllerInfo, error)
|
||||||
InitController() (params.ControllerInfo, error)
|
InitController() (params.ControllerInfo, error)
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import (
|
||||||
runnerErrors "github.com/cloudbase/garm/errors"
|
runnerErrors "github.com/cloudbase/garm/errors"
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
|
||||||
return params.ControllerInfo{}, runnerErrors.NewConflictError("controller already initialized")
|
return params.ControllerInfo{}, runnerErrors.NewConflictError("controller already initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
newID, err := uuid.NewV4()
|
newID, err := uuid.NewUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return params.ControllerInfo{}, errors.Wrap(err, "generating UUID")
|
return params.ControllerInfo{}, errors.Wrap(err, "generating UUID")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,5 +69,6 @@ func (s *CtrlTestSuite) TestInitControllerAlreadyInitialized() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCtrlTestSuite(t *testing.T) {
|
func TestCtrlTestSuite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
suite.Run(t, new(CtrlTestSuite))
|
suite.Run(t, new(CtrlTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
"github.com/cloudbase/garm/util"
|
"github.com/cloudbase/garm/util"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
"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) {
|
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 {
|
if err != nil {
|
||||||
return params.Pool{}, errors.Wrap(err, "fetching pool")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "fetching pools")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "fetching enterprise")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return Enterprise{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
return Enterprise{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||||
}
|
}
|
||||||
|
|
@ -315,28 +315,3 @@ func (s *sqlDatabase) getEnterprisePoolByUniqueFields(ctx context.Context, enter
|
||||||
|
|
||||||
return pool[0], nil
|
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")
|
_, err := s.Store.ListEnterprisePools(context.Background(), "dummy-enterprise-id")
|
||||||
|
|
||||||
s.Require().NotNil(err)
|
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() {
|
func (s *EnterpriseTestSuite) TestGetEnterprisePool() {
|
||||||
|
|
@ -785,7 +785,7 @@ func (s *EnterpriseTestSuite) TestListEnterpriseInstancesInvalidEnterpriseID() {
|
||||||
_, err := s.Store.ListEnterpriseInstances(context.Background(), "dummy-enterprise-id")
|
_, err := s.Store.ListEnterpriseInstances(context.Background(), "dummy-enterprise-id")
|
||||||
|
|
||||||
s.Require().NotNil(err)
|
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() {
|
func (s *EnterpriseTestSuite) TestUpdateEnterprisePool() {
|
||||||
|
|
@ -811,5 +811,6 @@ func (s *EnterpriseTestSuite) TestUpdateEnterprisePoolInvalidEnterpriseID() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnterpriseTestSuite(t *testing.T) {
|
func TestEnterpriseTestSuite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
suite.Run(t, new(EnterpriseTestSuite))
|
suite.Run(t, new(EnterpriseTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import (
|
||||||
runnerErrors "github.com/cloudbase/garm/errors"
|
runnerErrors "github.com/cloudbase/garm/errors"
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"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) {
|
func (s *sqlDatabase) getInstanceByID(ctx context.Context, instanceID string) (Instance, error) {
|
||||||
u, err := uuid.FromString(instanceID)
|
u, err := uuid.Parse(instanceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Instance{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -552,5 +552,6 @@ func (s *InstancesTestSuite) TestPoolInstanceCountDBCountErr() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstTestSuite(t *testing.T) {
|
func TestInstTestSuite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
suite.Run(t, new(InstancesTestSuite))
|
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/params"
|
||||||
"github.com/cloudbase/garm/runner/providers/common"
|
"github.com/cloudbase/garm/runner/providers/common"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
@ -38,7 +38,7 @@ func (b *Base) BeforeCreate(tx *gorm.DB) error {
|
||||||
if b.ID != emptyId {
|
if b.ID != emptyId {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
newID, err := uuid.NewV4()
|
newID, err := uuid.NewUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "generating id")
|
return errors.Wrap(err, "generating id")
|
||||||
}
|
}
|
||||||
|
|
@ -176,3 +176,59 @@ type ControllerInfo struct {
|
||||||
|
|
||||||
ControllerID uuid.UUID
|
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/params"
|
||||||
"github.com/cloudbase/garm/util"
|
"github.com/cloudbase/garm/util"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
"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) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "fetching pools")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return params.Pool{}, errors.Wrap(err, "fetching pool")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "fetching org")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
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
|
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) {
|
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 {
|
if err != nil {
|
||||||
return Organization{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
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")
|
_, err := s.Store.ListOrgPools(context.Background(), "dummy-org-id")
|
||||||
|
|
||||||
s.Require().NotNil(err)
|
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() {
|
func (s *OrgTestSuite) TestGetOrganizationPool() {
|
||||||
|
|
@ -784,7 +784,7 @@ func (s *OrgTestSuite) TestListOrgInstancesInvalidOrgID() {
|
||||||
_, err := s.Store.ListOrgInstances(context.Background(), "dummy-org-id")
|
_, err := s.Store.ListOrgInstances(context.Background(), "dummy-org-id")
|
||||||
|
|
||||||
s.Require().NotNil(err)
|
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() {
|
func (s *OrgTestSuite) TestUpdateOrganizationPool() {
|
||||||
|
|
@ -810,5 +810,6 @@ func (s *OrgTestSuite) TestUpdateOrganizationPoolInvalidOrgID() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrgTestSuite(t *testing.T) {
|
func TestOrgTestSuite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
suite.Run(t, new(OrgTestSuite))
|
suite.Run(t, new(OrgTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import (
|
||||||
runnerErrors "github.com/cloudbase/garm/errors"
|
runnerErrors "github.com/cloudbase/garm/errors"
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/gorm"
|
"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")
|
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing entity id")
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := uuid.FromString(poolID)
|
u, err := uuid.Parse(poolID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
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
|
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) {
|
func TestPoolsTestSuite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
suite.Run(t, new(PoolsTestSuite))
|
suite.Run(t, new(PoolsTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import (
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
"github.com/cloudbase/garm/util"
|
"github.com/cloudbase/garm/util"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
"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) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "fetching pools")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return params.Pool{}, errors.Wrap(err, "fetching pool")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "fetching repo")
|
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
|
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) {
|
func (s *sqlDatabase) getRepoPoolByUniqueFields(ctx context.Context, repoID string, provider, image, flavor string) (Pool, error) {
|
||||||
repo, err := s.getRepoByID(ctx, repoID)
|
repo, err := s.getRepoByID(ctx, repoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -345,32 +313,8 @@ func (s *sqlDatabase) getRepoPoolByUniqueFields(ctx context.Context, repoID stri
|
||||||
return pool[0], nil
|
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) {
|
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 {
|
if err != nil {
|
||||||
return Repository{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
|
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")
|
_, err := s.Store.ListRepoPools(context.Background(), "dummy-repo-id")
|
||||||
|
|
||||||
s.Require().NotNil(err)
|
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() {
|
func (s *RepoTestSuite) TestGetRepositoryPool() {
|
||||||
|
|
@ -820,7 +820,7 @@ func (s *RepoTestSuite) TestListRepoInstancesInvalidRepoID() {
|
||||||
_, err := s.Store.ListRepoInstances(context.Background(), "dummy-repo-id")
|
_, err := s.Store.ListRepoInstances(context.Background(), "dummy-repo-id")
|
||||||
|
|
||||||
s.Require().NotNil(err)
|
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() {
|
func (s *RepoTestSuite) TestUpdateRepositoryPool() {
|
||||||
|
|
@ -846,5 +846,6 @@ func (s *RepoTestSuite) TestUpdateRepositoryPoolInvalidRepoID() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoTestSuite(t *testing.T) {
|
func TestRepoTestSuite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
suite.Run(t, new(RepoTestSuite))
|
suite.Run(t, new(RepoTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ func (s *sqlDatabase) migrateDB() error {
|
||||||
&Instance{},
|
&Instance{},
|
||||||
&ControllerInfo{},
|
&ControllerInfo{},
|
||||||
&User{},
|
&User{},
|
||||||
|
&WorkflowJob{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return errors.Wrap(err, "running auto migrate")
|
return errors.Wrap(err, "running auto migrate")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import (
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
"github.com/cloudbase/garm/util"
|
"github.com/cloudbase/garm/util"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -23,7 +23,6 @@ require (
|
||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.14.0
|
github.com/prometheus/client_golang v1.14.0
|
||||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
|
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
|
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -293,8 +293,6 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
|
|
||||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
|
||||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/cloudbase/garm/util/appdefaults"
|
"github.com/cloudbase/garm/util/appdefaults"
|
||||||
|
|
||||||
"github.com/google/go-github/v53/github"
|
"github.com/google/go-github/v53/github"
|
||||||
uuid "github.com/satori/go.uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -33,6 +33,7 @@ type (
|
||||||
OSType string
|
OSType string
|
||||||
OSArch string
|
OSArch string
|
||||||
ProviderType string
|
ProviderType string
|
||||||
|
JobStatus string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -42,6 +43,12 @@ const (
|
||||||
ExternalProvider ProviderType = "external"
|
ExternalProvider ProviderType = "external"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
JobStatusQueued JobStatus = "queued"
|
||||||
|
JobStatusInProgress JobStatus = "in_progress"
|
||||||
|
JobStatusCompleted JobStatus = "completed"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RepositoryPool PoolType = "repository"
|
RepositoryPool PoolType = "repository"
|
||||||
OrganizationPool PoolType = "organization"
|
OrganizationPool PoolType = "organization"
|
||||||
|
|
@ -417,3 +424,53 @@ func (p RunnerPrefix) GetRunnerPrefix() string {
|
||||||
}
|
}
|
||||||
return p.Prefix
|
return p.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
// ID is the ID of the job.
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
// 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 `json:"run_id"`
|
||||||
|
// Conclusion is the outcome of the job.
|
||||||
|
// Possible values: "success", "failure", "neutral", "cancelled", "skipped",
|
||||||
|
// "timed_out", "action_required"
|
||||||
|
Conclusion string `json:"conclusion"`
|
||||||
|
// Status is the phase of the lifecycle that the job is currently in.
|
||||||
|
// "queued", "in_progress" and "completed".
|
||||||
|
Status string `json:"status"`
|
||||||
|
// Name is the name if the job that was triggered.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
StartedAt time.Time
|
||||||
|
CompletedAt time.Time
|
||||||
|
|
||||||
|
GithubRunnerID int64 `json:"runner_id"`
|
||||||
|
RunnerName string `json:"runner_name"`
|
||||||
|
RunnerGroupID int64 `json:"runner_group_id"`
|
||||||
|
RunnerGroupName string `json:"runner_group_name"`
|
||||||
|
|
||||||
|
// repository in which the job was triggered.
|
||||||
|
RepositoryName string
|
||||||
|
RepositoryOwner string
|
||||||
|
|
||||||
|
Labels []string
|
||||||
|
|
||||||
|
// 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 `json:"repo_id,omitempty"`
|
||||||
|
OrgID uuid.UUID `json:"org_id,omitempty"`
|
||||||
|
EnterpriseID uuid.UUID `json:"enterprise_id,omitempty"`
|
||||||
|
|
||||||
|
LockedBy uuid.UUID
|
||||||
|
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,14 @@ type enterprise struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *enterprise) GithubCLI() common.GithubClient {
|
||||||
|
return r.ghcli
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *enterprise) PoolType() params.PoolType {
|
||||||
|
return params.EnterprisePool
|
||||||
|
}
|
||||||
|
|
||||||
func (r *enterprise) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
|
func (r *enterprise) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
|
||||||
if err := r.ValidateOwner(job); err != nil {
|
if err := r.ValidateOwner(job); err != nil {
|
||||||
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
|
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package pool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
|
"github.com/cloudbase/garm/runner/common"
|
||||||
|
|
||||||
"github.com/google/go-github/v53/github"
|
"github.com/google/go-github/v53/github"
|
||||||
)
|
)
|
||||||
|
|
@ -28,6 +29,8 @@ type poolHelper interface {
|
||||||
RemoveGithubRunner(runnerID int64) (*github.Response, error)
|
RemoveGithubRunner(runnerID int64) (*github.Response, error)
|
||||||
FetchTools() ([]*github.RunnerApplicationDownload, error)
|
FetchTools() ([]*github.RunnerApplicationDownload, error)
|
||||||
|
|
||||||
|
GithubCLI() common.GithubClient
|
||||||
|
|
||||||
FetchDbInstances() ([]params.Instance, error)
|
FetchDbInstances() ([]params.Instance, error)
|
||||||
ListPools() ([]params.Pool, error)
|
ListPools() ([]params.Pool, error)
|
||||||
GithubURL() string
|
GithubURL() string
|
||||||
|
|
@ -41,4 +44,5 @@ type poolHelper interface {
|
||||||
UpdateState(param params.UpdatePoolStateParams) error
|
UpdateState(param params.UpdatePoolStateParams) error
|
||||||
WebhookSecret() string
|
WebhookSecret() string
|
||||||
ID() string
|
ID() string
|
||||||
|
PoolType() params.PoolType
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,14 @@ type organization struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *organization) GithubCLI() common.GithubClient {
|
||||||
|
return r.ghcli
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *organization) PoolType() params.PoolType {
|
||||||
|
return params.OrganizationPool
|
||||||
|
}
|
||||||
|
|
||||||
func (r *organization) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
|
func (r *organization) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
|
||||||
if err := r.ValidateOwner(job); err != nil {
|
if err := r.ValidateOwner(job); err != nil {
|
||||||
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
|
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/cloudbase/garm/util"
|
"github.com/cloudbase/garm/util"
|
||||||
|
|
||||||
"github.com/google/go-github/v53/github"
|
"github.com/google/go-github/v53/github"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
@ -96,62 +97,63 @@ type basePoolManager struct {
|
||||||
keyMux *keyMutex
|
keyMux *keyMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) (err error) {
|
func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
|
||||||
if err := r.helper.ValidateOwner(job); err != nil {
|
if err := r.helper.ValidateOwner(job); err != nil {
|
||||||
return errors.Wrap(err, "validating owner")
|
return errors.Wrap(err, "validating owner")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jobParams params.Job
|
||||||
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil && errors.Is(err, runnerErrors.ErrUnauthorized) {
|
// we're updating the job in the database, regardless of whether it was successful or not.
|
||||||
r.setPoolRunningState(false, fmt.Sprintf("failed to handle job: %q", err))
|
// or if it was meant for this pool or not. Github will send the same job data to all hierarchies
|
||||||
|
// that have been configured to work with garm. Updating the job at all levels should yield the same
|
||||||
|
// outcome in the db.
|
||||||
|
if jobParams.ID != 0 {
|
||||||
|
if _, jobErr := r.store.CreateOrUpdateJob(r.ctx, jobParams); jobErr != nil {
|
||||||
|
log.Printf("failed to update job %d: %s", jobParams.ID, jobErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
switch job.Action {
|
switch job.Action {
|
||||||
case "queued":
|
case "queued":
|
||||||
// Create instance in database and set it to pending create.
|
// Record the job in the database. Queued jobs will be picked up by the consumeQueuedJobs() method
|
||||||
// If we already have an idle runner around, that runner will pick up the job
|
// when reconciling.
|
||||||
// and trigger an "in_progress" update from github (see bellow), which in turn will set the
|
jobParams, err = r.paramsWorkflowJobToParamsJob(job)
|
||||||
// runner state of the instance to "active". The ensureMinIdleRunners() function will
|
if err != nil {
|
||||||
// exclude that runner from available runners and attempt to ensure
|
return errors.Wrap(err, "converting job to params")
|
||||||
// the needed number of runners.
|
|
||||||
if err := r.acquireNewInstance(job); err != nil {
|
|
||||||
r.log("failed to add instance: %s", err)
|
|
||||||
}
|
}
|
||||||
case "completed":
|
case "completed":
|
||||||
// ignore the error here. A completed job may not have a runner name set
|
jobParams, err = r.paramsWorkflowJobToParamsJob(job)
|
||||||
// if it was never assigned to a runner, and was canceled.
|
|
||||||
runnerInfo, err := r.getRunnerDetailsFromJob(job)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, runnerErrors.ErrUnauthorized) {
|
if errors.Is(err, runnerErrors.ErrNotFound) {
|
||||||
// Unassigned jobs will have an empty runner_name.
|
// Unassigned jobs will have an empty runner_name.
|
||||||
// We also need to ignore not found errors, as we may get a webhook regarding
|
// We also need to ignore not found errors, as we may get a webhook regarding
|
||||||
// a workflow that is handled by a runner at a different hierarchy level.
|
// a workflow that is handled by a runner at a different hierarchy level.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Wrap(err, "updating runner")
|
return errors.Wrap(err, "converting job to params")
|
||||||
}
|
}
|
||||||
|
|
||||||
// update instance workload state.
|
// update instance workload state.
|
||||||
if err := r.setInstanceRunnerStatus(runnerInfo.Name, providerCommon.RunnerTerminated); err != nil {
|
if _, err := r.setInstanceRunnerStatus(jobParams.RunnerName, providerCommon.RunnerTerminated); err != nil {
|
||||||
if errors.Is(err, runnerErrors.ErrNotFound) {
|
if errors.Is(err, runnerErrors.ErrNotFound) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
r.log("failed to update runner %s status: %s", util.SanitizeLogEntry(runnerInfo.Name), err)
|
r.log("failed to update runner %s status: %s", util.SanitizeLogEntry(jobParams.RunnerName), err)
|
||||||
return errors.Wrap(err, "updating runner")
|
return errors.Wrap(err, "updating runner")
|
||||||
}
|
}
|
||||||
r.log("marking instance %s as pending_delete", util.SanitizeLogEntry(runnerInfo.Name))
|
r.log("marking instance %s as pending_delete", util.SanitizeLogEntry(jobParams.RunnerName))
|
||||||
if err := r.setInstanceStatus(runnerInfo.Name, providerCommon.InstancePendingDelete, nil); err != nil {
|
if _, err := r.setInstanceStatus(jobParams.RunnerName, providerCommon.InstancePendingDelete, nil); err != nil {
|
||||||
if errors.Is(err, runnerErrors.ErrNotFound) {
|
if errors.Is(err, runnerErrors.ErrNotFound) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
r.log("failed to update runner %s status: %s", util.SanitizeLogEntry(runnerInfo.Name), err)
|
r.log("failed to update runner %s status: %s", util.SanitizeLogEntry(jobParams.RunnerName), err)
|
||||||
return errors.Wrap(err, "updating runner")
|
return errors.Wrap(err, "updating runner")
|
||||||
}
|
}
|
||||||
case "in_progress":
|
case "in_progress":
|
||||||
// in_progress jobs must have a runner name/ID assigned. Sometimes github will send a hook without
|
jobParams, err = r.paramsWorkflowJobToParamsJob(job)
|
||||||
// a runner set. In such cases, we attemt to fetch it from the API.
|
|
||||||
runnerInfo, err := r.getRunnerDetailsFromJob(job)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, runnerErrors.ErrNotFound) {
|
if errors.Is(err, runnerErrors.ErrNotFound) {
|
||||||
// This is most likely a runner we're not managing. If we define a repo from within an org
|
// This is most likely a runner we're not managing. If we define a repo from within an org
|
||||||
|
|
@ -161,17 +163,28 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) (err error)
|
||||||
// that we are not responsible for that runner, and we should ignore it.
|
// that we are not responsible for that runner, and we should ignore it.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Wrap(err, "determining runner name")
|
return errors.Wrap(err, "converting job to params")
|
||||||
}
|
}
|
||||||
|
|
||||||
// update instance workload state.
|
// update instance workload state.
|
||||||
if err := r.setInstanceRunnerStatus(runnerInfo.Name, providerCommon.RunnerActive); err != nil {
|
instance, err := r.setInstanceRunnerStatus(jobParams.RunnerName, providerCommon.RunnerActive)
|
||||||
|
if err != nil {
|
||||||
if errors.Is(err, runnerErrors.ErrNotFound) {
|
if errors.Is(err, runnerErrors.ErrNotFound) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
r.log("failed to update runner %s status: %s", util.SanitizeLogEntry(runnerInfo.Name), err)
|
r.log("failed to update runner %s status: %s", util.SanitizeLogEntry(jobParams.RunnerName), err)
|
||||||
return errors.Wrap(err, "updating runner")
|
return errors.Wrap(err, "updating runner")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A runner has picked up the job, and is now running it. It may need to be replaced if the pool has
|
||||||
|
// a minimum number of idle runners configured.
|
||||||
|
pool, err := r.store.GetPoolByID(r.ctx, instance.PoolID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting pool")
|
||||||
|
}
|
||||||
|
if err := r.ensureIdleRunnersForOnePool(pool); err != nil {
|
||||||
|
log.Printf("error ensuring idle runners for pool %s: %s", pool.ID, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -329,7 +342,7 @@ func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runne
|
||||||
|
|
||||||
if ok := runnerNames[instance.Name]; !ok {
|
if ok := runnerNames[instance.Name]; !ok {
|
||||||
// Set pending_delete on DB field. Allow consolidate() to remove it.
|
// Set pending_delete on DB field. Allow consolidate() to remove it.
|
||||||
if err := r.setInstanceStatus(instance.Name, providerCommon.InstancePendingDelete, nil); err != nil {
|
if _, err := r.setInstanceStatus(instance.Name, providerCommon.InstancePendingDelete, nil); err != nil {
|
||||||
r.log("failed to update runner %s status: %s", instance.Name, err)
|
r.log("failed to update runner %s status: %s", instance.Name, err)
|
||||||
return errors.Wrap(err, "updating runner")
|
return errors.Wrap(err, "updating runner")
|
||||||
}
|
}
|
||||||
|
|
@ -568,98 +581,42 @@ func (r *basePoolManager) fetchInstance(runnerName string) (params.Instance, err
|
||||||
return runner, nil
|
return runner, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *basePoolManager) setInstanceRunnerStatus(runnerName string, status providerCommon.RunnerStatus) error {
|
func (r *basePoolManager) setInstanceRunnerStatus(runnerName string, status providerCommon.RunnerStatus) (params.Instance, error) {
|
||||||
updateParams := params.UpdateInstanceParams{
|
updateParams := params.UpdateInstanceParams{
|
||||||
RunnerStatus: status,
|
RunnerStatus: status,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.updateInstance(runnerName, updateParams); err != nil {
|
instance, err := r.updateInstance(runnerName, updateParams)
|
||||||
return errors.Wrap(err, "updating runner state")
|
if err != nil {
|
||||||
|
return params.Instance{}, errors.Wrap(err, "updating runner state")
|
||||||
}
|
}
|
||||||
return nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *basePoolManager) updateInstance(runnerName string, update params.UpdateInstanceParams) error {
|
func (r *basePoolManager) updateInstance(runnerName string, update params.UpdateInstanceParams) (params.Instance, error) {
|
||||||
runner, err := r.fetchInstance(runnerName)
|
runner, err := r.fetchInstance(runnerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "fetching instance")
|
return params.Instance{}, errors.Wrap(err, "fetching instance")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := r.store.UpdateInstance(r.ctx, runner.ID, update); err != nil {
|
instance, err := r.store.UpdateInstance(r.ctx, runner.ID, update)
|
||||||
return errors.Wrap(err, "updating runner state")
|
if err != nil {
|
||||||
|
return params.Instance{}, errors.Wrap(err, "updating runner state")
|
||||||
}
|
}
|
||||||
return nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *basePoolManager) setInstanceStatus(runnerName string, status providerCommon.InstanceStatus, providerFault []byte) error {
|
func (r *basePoolManager) setInstanceStatus(runnerName string, status providerCommon.InstanceStatus, providerFault []byte) (params.Instance, error) {
|
||||||
updateParams := params.UpdateInstanceParams{
|
updateParams := params.UpdateInstanceParams{
|
||||||
Status: status,
|
Status: status,
|
||||||
ProviderFault: providerFault,
|
ProviderFault: providerFault,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.updateInstance(runnerName, updateParams); err != nil {
|
instance, err := r.updateInstance(runnerName, updateParams)
|
||||||
return errors.Wrap(err, "updating runner state")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *basePoolManager) acquireNewInstance(job params.WorkflowJob) error {
|
|
||||||
requestedLabels := job.WorkflowJob.Labels
|
|
||||||
if len(requestedLabels) == 0 {
|
|
||||||
// no labels were requested.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := r.helper.FindPoolByTags(requestedLabels)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, runnerErrors.ErrNotFound) {
|
return params.Instance{}, errors.Wrap(err, "updating runner state")
|
||||||
r.log("failed to find an enabled pool with required labels: %s", strings.Join(requestedLabels, ", "))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.Wrap(err, "fetching suitable pool")
|
|
||||||
}
|
}
|
||||||
r.log("adding new runner with requested tags %s in pool %s", util.SanitizeLogEntry(strings.Join(job.WorkflowJob.Labels, ", ")), util.SanitizeLogEntry(pool.ID))
|
return instance, nil
|
||||||
|
|
||||||
if !pool.Enabled {
|
|
||||||
r.log("selected pool (%s) is disabled", pool.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
poolInstances, err := r.store.PoolInstanceCount(r.ctx, pool.ID)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "fetching instances")
|
|
||||||
}
|
|
||||||
|
|
||||||
if poolInstances >= int64(pool.MaxRunners) {
|
|
||||||
r.log("max_runners (%d) reached for pool %s, skipping...", pool.MaxRunners, pool.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
instances, err := r.store.ListPoolInstances(r.ctx, pool.ID)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "fetching instances")
|
|
||||||
}
|
|
||||||
|
|
||||||
idleWorkers := 0
|
|
||||||
for _, inst := range instances {
|
|
||||||
if providerCommon.RunnerStatus(inst.RunnerStatus) == providerCommon.RunnerIdle &&
|
|
||||||
providerCommon.InstanceStatus(inst.Status) == providerCommon.InstanceRunning {
|
|
||||||
idleWorkers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip creating a new runner if we have at least one idle runner and the minimum is already satisfied.
|
|
||||||
// This should work even for pools that define a MinIdleRunner of 0.
|
|
||||||
if int64(idleWorkers) > 0 && int64(idleWorkers) >= int64(pool.MinIdleRunners) {
|
|
||||||
r.log("we have enough min_idle_runners (%d) for pool %s, skipping...", pool.MinIdleRunners, pool.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.AddRunner(r.ctx, pool.ID); err != nil {
|
|
||||||
r.log("failed to add runner to pool %s", pool.ID)
|
|
||||||
return errors.Wrap(err, "adding runner")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *basePoolManager) AddRunner(ctx context.Context, poolID string) error {
|
func (r *basePoolManager) AddRunner(ctx context.Context, poolID string) error {
|
||||||
|
|
@ -825,6 +782,71 @@ func (r *basePoolManager) getRunnerDetailsFromJob(job params.WorkflowJob) (param
|
||||||
return runnerInfo, nil
|
return runnerInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// paramsWorkflowJobToParamsJob returns a params.Job from a params.WorkflowJob, and aditionally determines
|
||||||
|
// if the runner belongs to this pool or not. It will always return a valid params.Job, even if it errs out.
|
||||||
|
// This allows us to still update the job in the database, even if we determined that it wasn't necessarily meant
|
||||||
|
// for this pool.
|
||||||
|
// If garm manages multiple hierarchies (repos, org, enterprise) which involve the same repo, we will get a hook
|
||||||
|
// whenever a job involving our repo triggers a hook. So even if the job is picked up by a runner at the enterprise
|
||||||
|
// level, the repo and org still get a hook.
|
||||||
|
// We even get a hook if a particular job is picked up by a GitHub hosted runner. We don't know who will pick up the job
|
||||||
|
// until the "in_progress" event is sent and we can see which runner picked it up.
|
||||||
|
//
|
||||||
|
// We save the details of that job at every level, because we want to at least update the status of the job. We make
|
||||||
|
// decissions based on the status of saved jobs. A "queued" job will prompt garm to search for an appropriate pool
|
||||||
|
// and spin up a runner there if no other idle runner exists to pick it up.
|
||||||
|
func (r *basePoolManager) paramsWorkflowJobToParamsJob(job params.WorkflowJob) (params.Job, error) {
|
||||||
|
jobParams := params.Job{
|
||||||
|
ID: job.WorkflowJob.ID,
|
||||||
|
Action: job.Action,
|
||||||
|
RunID: job.WorkflowJob.RunID,
|
||||||
|
Status: job.WorkflowJob.Status,
|
||||||
|
Conclusion: job.WorkflowJob.Conclusion,
|
||||||
|
StartedAt: job.WorkflowJob.StartedAt,
|
||||||
|
CompletedAt: job.WorkflowJob.CompletedAt,
|
||||||
|
Name: job.WorkflowJob.Name,
|
||||||
|
GithubRunnerID: job.WorkflowJob.RunnerID,
|
||||||
|
RunnerGroupID: job.WorkflowJob.RunnerGroupID,
|
||||||
|
RunnerGroupName: job.WorkflowJob.RunnerGroupName,
|
||||||
|
RepositoryName: job.Repository.Name,
|
||||||
|
RepositoryOwner: job.Repository.Owner.Login,
|
||||||
|
Labels: job.WorkflowJob.Labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerName := job.WorkflowJob.RunnerName
|
||||||
|
if job.Action != "queued" && runnerName == "" {
|
||||||
|
// Runner name was not set in WorkflowJob by github. We can still attempt to fetch the info we need,
|
||||||
|
// using the workflow run ID, from the API.
|
||||||
|
// We may still get no runner name. In situations such as jobs being cancelled before a runner had the chance
|
||||||
|
// to pick up the job, the runner name is not available from the API.
|
||||||
|
runnerInfo, err := r.getRunnerDetailsFromJob(job)
|
||||||
|
if err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
|
||||||
|
return jobParams, errors.Wrap(err, "fetching runner details")
|
||||||
|
}
|
||||||
|
runnerName = runnerInfo.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
jobParams.RunnerName = runnerName
|
||||||
|
|
||||||
|
asUUID, err := uuid.Parse(r.ID())
|
||||||
|
if err != nil {
|
||||||
|
return jobParams, errors.Wrap(err, "parsing pool ID as UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.helper.PoolType() {
|
||||||
|
case params.EnterprisePool:
|
||||||
|
jobParams.EnterpriseID = asUUID
|
||||||
|
case params.RepositoryPool:
|
||||||
|
jobParams.RepoID = asUUID
|
||||||
|
case params.OrganizationPool:
|
||||||
|
jobParams.OrgID = asUUID
|
||||||
|
default:
|
||||||
|
return jobParams, errors.Errorf("unknown pool type: %s", r.helper.PoolType())
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobParams, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *basePoolManager) poolLabel(poolID string) string {
|
func (r *basePoolManager) poolLabel(poolID string) string {
|
||||||
return fmt.Sprintf("%s%s", poolIDLabelprefix, poolID)
|
return fmt.Sprintf("%s%s", poolIDLabelprefix, poolID)
|
||||||
}
|
}
|
||||||
|
|
@ -911,6 +933,26 @@ func (r *basePoolManager) scaleDownOnePool(ctx context.Context, pool params.Pool
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *basePoolManager) addRunnerToPool(pool params.Pool) error {
|
||||||
|
if !pool.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
poolInstanceCount, err := r.store.PoolInstanceCount(r.ctx, pool.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list pool instances: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if poolInstanceCount >= int64(pool.MaxRunners) {
|
||||||
|
return fmt.Errorf("max workers (%d) reached for pool %s", pool.MaxRunners, pool.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.AddRunner(r.ctx, pool.ID); err != nil {
|
||||||
|
return fmt.Errorf("failed to add new instance for pool %s: %s", pool.ID, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *basePoolManager) ensureIdleRunnersForOnePool(pool params.Pool) error {
|
func (r *basePoolManager) ensureIdleRunnersForOnePool(pool params.Pool) error {
|
||||||
if !pool.Enabled {
|
if !pool.Enabled {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -918,6 +960,7 @@ func (r *basePoolManager) ensureIdleRunnersForOnePool(pool params.Pool) error {
|
||||||
existingInstances, err := r.store.ListPoolInstances(r.ctx, pool.ID)
|
existingInstances, err := r.store.ListPoolInstances(r.ctx, pool.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to ensure minimum idle workers for pool %s: %w", pool.ID, err)
|
return fmt.Errorf("failed to ensure minimum idle workers for pool %s: %w", pool.ID, err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if uint(len(existingInstances)) >= pool.MaxRunners {
|
if uint(len(existingInstances)) >= pool.MaxRunners {
|
||||||
|
|
@ -1010,7 +1053,7 @@ func (r *basePoolManager) retryFailedInstancesForOnePool(ctx context.Context, po
|
||||||
}
|
}
|
||||||
r.log("queueing previously failed instance %s for retry", instance.Name)
|
r.log("queueing previously failed instance %s for retry", instance.Name)
|
||||||
// Set instance to pending create and wait for retry.
|
// Set instance to pending create and wait for retry.
|
||||||
if err := r.updateInstance(instance.Name, updateParams); err != nil {
|
if _, err := r.updateInstance(instance.Name, updateParams); err != nil {
|
||||||
r.log("failed to update runner %s status: %s", instance.Name, err)
|
r.log("failed to update runner %s status: %s", instance.Name, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -1131,7 +1174,7 @@ func (r *basePoolManager) deletePendingInstances() error {
|
||||||
|
|
||||||
// Set the status to deleting before launching the goroutine that removes
|
// Set the status to deleting before launching the goroutine that removes
|
||||||
// the runner from the provider (which can take a long time).
|
// the runner from the provider (which can take a long time).
|
||||||
if err := r.setInstanceStatus(instance.Name, providerCommon.InstanceDeleting, nil); err != nil {
|
if _, err := r.setInstanceStatus(instance.Name, providerCommon.InstanceDeleting, nil); err != nil {
|
||||||
r.log("failed to update runner %s status: %q", instance.Name, err)
|
r.log("failed to update runner %s status: %q", instance.Name, err)
|
||||||
r.keyMux.Unlock(instance.Name, false)
|
r.keyMux.Unlock(instance.Name, false)
|
||||||
continue
|
continue
|
||||||
|
|
@ -1147,7 +1190,7 @@ func (r *basePoolManager) deletePendingInstances() error {
|
||||||
r.log("failed to remove instance %s: %s", instance.Name, err)
|
r.log("failed to remove instance %s: %s", instance.Name, err)
|
||||||
// failed to remove from provider. Set the status back to pending_delete, which
|
// failed to remove from provider. Set the status back to pending_delete, which
|
||||||
// will retry the operation.
|
// will retry the operation.
|
||||||
if err := r.setInstanceStatus(instance.Name, providerCommon.InstancePendingDelete, nil); err != nil {
|
if _, err := r.setInstanceStatus(instance.Name, providerCommon.InstancePendingDelete, nil); err != nil {
|
||||||
r.log("failed to update runner %s status: %s", instance.Name, err)
|
r.log("failed to update runner %s status: %s", instance.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1192,7 +1235,7 @@ func (r *basePoolManager) addPendingInstances() error {
|
||||||
|
|
||||||
// Set the instance to "creating" before launching the goroutine. This will ensure that addPendingInstances()
|
// Set the instance to "creating" before launching the goroutine. This will ensure that addPendingInstances()
|
||||||
// won't attempt to create the runner a second time.
|
// won't attempt to create the runner a second time.
|
||||||
if err := r.setInstanceStatus(instance.Name, providerCommon.InstanceCreating, nil); err != nil {
|
if _, err := r.setInstanceStatus(instance.Name, providerCommon.InstanceCreating, nil); err != nil {
|
||||||
r.log("failed to update runner %s status: %s", instance.Name, err)
|
r.log("failed to update runner %s status: %s", instance.Name, err)
|
||||||
r.keyMux.Unlock(instance.Name, false)
|
r.keyMux.Unlock(instance.Name, false)
|
||||||
// We failed to transition the instance to Creating. This means that garm will retry to create this instance
|
// We failed to transition the instance to Creating. This means that garm will retry to create this instance
|
||||||
|
|
@ -1206,7 +1249,7 @@ func (r *basePoolManager) addPendingInstances() error {
|
||||||
if err := r.addInstanceToProvider(instance); err != nil {
|
if err := r.addInstanceToProvider(instance); err != nil {
|
||||||
r.log("failed to add instance to provider: %s", err)
|
r.log("failed to add instance to provider: %s", err)
|
||||||
errAsBytes := []byte(err.Error())
|
errAsBytes := []byte(err.Error())
|
||||||
if err := r.setInstanceStatus(instance.Name, providerCommon.InstanceError, errAsBytes); err != nil {
|
if _, err := r.setInstanceStatus(instance.Name, providerCommon.InstanceError, errAsBytes); err != nil {
|
||||||
r.log("failed to update runner %s status: %s", instance.Name, err)
|
r.log("failed to update runner %s status: %s", instance.Name, err)
|
||||||
}
|
}
|
||||||
r.log("failed to create instance in provider: %s", err)
|
r.log("failed to create instance in provider: %s", err)
|
||||||
|
|
@ -1275,6 +1318,7 @@ func (r *basePoolManager) Start() error {
|
||||||
go r.startLoopForFunction(r.ensureMinIdleRunners, common.PoolConsilitationInterval, "consolidate[ensure_min_idle]", false)
|
go r.startLoopForFunction(r.ensureMinIdleRunners, common.PoolConsilitationInterval, "consolidate[ensure_min_idle]", false)
|
||||||
go r.startLoopForFunction(r.retryFailedInstances, common.PoolConsilitationInterval, "consolidate[retry_failed]", false)
|
go r.startLoopForFunction(r.retryFailedInstances, common.PoolConsilitationInterval, "consolidate[retry_failed]", false)
|
||||||
go r.startLoopForFunction(r.updateTools, common.PoolToolUpdateInterval, "update_tools", true)
|
go r.startLoopForFunction(r.updateTools, common.PoolToolUpdateInterval, "update_tools", true)
|
||||||
|
go r.startLoopForFunction(r.consumeQueuedJobs, common.PoolConsilitationInterval, "job_queue_consumer", false)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1331,9 +1375,132 @@ func (r *basePoolManager) ForceDeleteRunner(runner params.Instance) error {
|
||||||
}
|
}
|
||||||
r.log("setting instance status for: %v", runner.Name)
|
r.log("setting instance status for: %v", runner.Name)
|
||||||
|
|
||||||
if err := r.setInstanceStatus(runner.Name, providerCommon.InstancePendingDelete, nil); err != nil {
|
if _, err := r.setInstanceStatus(runner.Name, providerCommon.InstancePendingDelete, nil); err != nil {
|
||||||
r.log("failed to update runner %s status: %s", runner.Name, err)
|
r.log("failed to update runner %s status: %s", runner.Name, err)
|
||||||
return errors.Wrap(err, "updating runner")
|
return errors.Wrap(err, "updating runner")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// consumeQueuedJobs qull pull all the known jobs from the database and attempt to create a new
|
||||||
|
// runner in one of the pools it manages if it matches the requested labels.
|
||||||
|
// This is a best effort attempt to consume queued jobs. We do not have any real way to know which
|
||||||
|
// runner from which pool will pick up a job we react to here. For example, the same job may be received
|
||||||
|
// by an enterprise manager, an org manager AND a repo manager. If an idle runner from another pool
|
||||||
|
// picks up the job after we created a runner in this pool, we will have an extra runner that may or may not
|
||||||
|
// have a job waiting for it.
|
||||||
|
// This is not a huge problem, as we have scale down logic which should remove any idle runners that have not
|
||||||
|
// picked up a job within a certain time frame. Also, the logic here should ensure that eventually, all known
|
||||||
|
// queued jobs will be consumed sooner or later.
|
||||||
|
//
|
||||||
|
// NOTE: jobs that were created while the garm instance was down, will be unknown to garm itself and will linger
|
||||||
|
// in queued state if the pools defined in garm have a minimum idle runner value set to 0. Simply put, garm won't
|
||||||
|
// know about the queued jobs that we didn't get a webhook for. Listing all jobs on startup is not feasible, as
|
||||||
|
// an enterprise may have thousands of repos and thousands of jobs in queued state. To fetch all jobs for an
|
||||||
|
// enterprise, we'd have to list all repos, and for each repo list all jobs currently in queued state. This is
|
||||||
|
// not desirable by any measure.
|
||||||
|
func (r *basePoolManager) consumeQueuedJobs() error {
|
||||||
|
queued, err := r.store.ListEntityJobsByStatus(r.ctx, r.helper.PoolType(), r.helper.ID(), params.JobStatusQueued)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "listing queued jobs")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("found %d queued jobs", len(queued))
|
||||||
|
for _, job := range queued {
|
||||||
|
if job.LockedBy != uuid.Nil && job.LockedBy.String() != r.ID() {
|
||||||
|
// Job was handled by us or another entity.
|
||||||
|
log.Printf("[Pool mgr ID %s] job %d is locked by %s", r.ID(), job.ID, job.LockedBy.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(job.CreatedAt) < time.Second*15 {
|
||||||
|
// give the idle runners a chance to pick up the job.
|
||||||
|
log.Printf("job %d was created less than 15 seconds ago. Skipping", job.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(job.CreatedAt) >= time.Minute*5 {
|
||||||
|
// Job has been in queued state for 30 minutes or more. Check if it was consumed by another runner.
|
||||||
|
workflow, ghResp, err := r.helper.GithubCLI().GetWorkflowJobByID(r.ctx, job.RepositoryOwner, job.RepositoryName, job.ID)
|
||||||
|
if err != nil {
|
||||||
|
if ghResp != nil {
|
||||||
|
switch ghResp.StatusCode {
|
||||||
|
case http.StatusNotFound:
|
||||||
|
// Job does not exist in github. Remove it from the database.
|
||||||
|
if err := r.store.DeleteJob(r.ctx, job.ID); err != nil {
|
||||||
|
return errors.Wrap(err, "deleting job")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("failed to fetch job information from github: %q (status code: %d)", err, ghResp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("error fetching workflow info: %q", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if workflow.GetStatus() != "queued" {
|
||||||
|
log.Printf("job is no longer in queued state on github. New status is: %s", workflow.GetStatus())
|
||||||
|
job.Action = workflow.GetStatus()
|
||||||
|
job.Status = workflow.GetStatus()
|
||||||
|
job.Conclusion = workflow.GetConclusion()
|
||||||
|
if workflow.RunnerName != nil {
|
||||||
|
job.RunnerName = *workflow.RunnerName
|
||||||
|
}
|
||||||
|
if workflow.RunnerID != nil {
|
||||||
|
job.GithubRunnerID = *workflow.RunnerID
|
||||||
|
}
|
||||||
|
if workflow.RunnerGroupName != nil {
|
||||||
|
job.RunnerGroupName = *workflow.RunnerGroupName
|
||||||
|
}
|
||||||
|
if workflow.RunnerGroupID != nil {
|
||||||
|
job.RunnerGroupID = *workflow.RunnerGroupID
|
||||||
|
}
|
||||||
|
if _, err := r.store.CreateOrUpdateJob(r.ctx, job); err != nil {
|
||||||
|
log.Printf("failed to update job status: %q", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Job is still queued in our db and in github. Unlock it and try again.
|
||||||
|
if err := r.store.UnlockJob(r.ctx, job.ID, r.ID()); err != nil {
|
||||||
|
// TODO: Implament a cache? Should we return here?
|
||||||
|
log.Printf("failed to unlock job: %q", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
potentialPools, err := r.store.FindPoolsMatchingAllTags(r.ctx, r.helper.PoolType(), r.helper.ID(), job.Labels)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[Pool mgr ID %s] error finding pools matching labels: %s", r.ID(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(potentialPools) == 0 {
|
||||||
|
log.Printf("[Pool mgr ID %s] could not find pool with labels %s", r.ID(), strings.Join(job.Labels, ","))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerCreated := false
|
||||||
|
if err := r.store.LockJob(r.ctx, job.ID, r.ID()); err != nil {
|
||||||
|
log.Printf("[Pool mgr ID %s] could not lock job %d: %s", r.ID(), job.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, pool := range potentialPools {
|
||||||
|
log.Printf("attempting to create a runner in pool %s for job %d", pool.ID, job.ID)
|
||||||
|
if err := r.addRunnerToPool(pool); err != nil {
|
||||||
|
log.Printf("could not add runner to pool %s: %s", pool.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("a new runner was added to pool %s as a response to queued job %d", pool.ID, job.ID)
|
||||||
|
runnerCreated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !runnerCreated {
|
||||||
|
log.Printf("could not create a runner for job %d; unlocking", job.ID)
|
||||||
|
if err := r.store.UnlockJob(r.ctx, job.ID, r.ID()); err != nil {
|
||||||
|
return errors.Wrap(err, "unlocking job")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,14 @@ type repository struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *repository) GithubCLI() common.GithubClient {
|
||||||
|
return r.ghcli
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repository) PoolType() params.PoolType {
|
||||||
|
return params.RepositoryPool
|
||||||
|
}
|
||||||
|
|
||||||
func (r *repository) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
|
func (r *repository) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
|
||||||
if err := r.ValidateOwner(job); err != nil {
|
if err := r.ValidateOwner(job); err != nil {
|
||||||
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
|
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,10 @@ import (
|
||||||
"github.com/cloudbase/garm/util"
|
"github.com/cloudbase/garm/util"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/juju/clock"
|
"github.com/juju/clock"
|
||||||
"github.com/juju/retry"
|
"github.com/juju/retry"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) {
|
func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) {
|
||||||
|
|
|
||||||
21
vendor/github.com/satori/go.uuid/.travis.yml
generated
vendored
21
vendor/github.com/satori/go.uuid/.travis.yml
generated
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
go:
|
|
||||||
- 1.6.x
|
|
||||||
- 1.7.x
|
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- tip
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
fast_finish: true
|
|
||||||
before_install:
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
- go get golang.org/x/tools/cmd/cover
|
|
||||||
script:
|
|
||||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
20
vendor/github.com/satori/go.uuid/LICENSE
generated
vendored
20
vendor/github.com/satori/go.uuid/LICENSE
generated
vendored
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
75
vendor/github.com/satori/go.uuid/README.md
generated
vendored
75
vendor/github.com/satori/go.uuid/README.md
generated
vendored
|
|
@ -1,75 +0,0 @@
|
||||||
# UUID package for Go language
|
|
||||||
|
|
||||||
[](https://travis-ci.org/satori/go.uuid)
|
|
||||||
[](https://coveralls.io/github/satori/go.uuid)
|
|
||||||
[](http://godoc.org/github.com/satori/go.uuid)
|
|
||||||
|
|
||||||
This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs.
|
|
||||||
|
|
||||||
With 100% test coverage and benchmarks out of box.
|
|
||||||
|
|
||||||
Supported versions:
|
|
||||||
* Version 1, based on timestamp and MAC address (RFC 4122)
|
|
||||||
* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1)
|
|
||||||
* Version 3, based on MD5 hashing (RFC 4122)
|
|
||||||
* Version 4, based on random numbers (RFC 4122)
|
|
||||||
* Version 5, based on SHA-1 hashing (RFC 4122)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Use the `go` command:
|
|
||||||
|
|
||||||
$ go get github.com/satori/go.uuid
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
UUID package tested against Go >= 1.6.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Creating UUID Version 4
|
|
||||||
// panic on error
|
|
||||||
u1 := uuid.Must(uuid.NewV4())
|
|
||||||
fmt.Printf("UUIDv4: %s\n", u1)
|
|
||||||
|
|
||||||
// or error handling
|
|
||||||
u2, err := uuid.NewV4()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Something went wrong: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("UUIDv4: %s\n", u2)
|
|
||||||
|
|
||||||
// Parsing UUID from string input
|
|
||||||
u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Something went wrong: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully parsed: %s", u2)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project.
|
|
||||||
|
|
||||||
## Links
|
|
||||||
* [RFC 4122](http://tools.ietf.org/html/rfc4122)
|
|
||||||
* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01)
|
|
||||||
|
|
||||||
## Copyright
|
|
||||||
|
|
||||||
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>.
|
|
||||||
|
|
||||||
UUID package released under MIT License.
|
|
||||||
See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details.
|
|
||||||
206
vendor/github.com/satori/go.uuid/codec.go
generated
vendored
206
vendor/github.com/satori/go.uuid/codec.go
generated
vendored
|
|
@ -1,206 +0,0 @@
|
||||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
// a copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
// permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
// the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be
|
|
||||||
// included in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FromBytes returns UUID converted from raw byte slice input.
|
|
||||||
// It will return error if the slice isn't 16 bytes long.
|
|
||||||
func FromBytes(input []byte) (u UUID, err error) {
|
|
||||||
err = u.UnmarshalBinary(input)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromBytesOrNil returns UUID converted from raw byte slice input.
|
|
||||||
// Same behavior as FromBytes, but returns a Nil UUID on error.
|
|
||||||
func FromBytesOrNil(input []byte) UUID {
|
|
||||||
uuid, err := FromBytes(input)
|
|
||||||
if err != nil {
|
|
||||||
return Nil
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromString returns UUID parsed from string input.
|
|
||||||
// Input is expected in a form accepted by UnmarshalText.
|
|
||||||
func FromString(input string) (u UUID, err error) {
|
|
||||||
err = u.UnmarshalText([]byte(input))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromStringOrNil returns UUID parsed from string input.
|
|
||||||
// Same behavior as FromString, but returns a Nil UUID on error.
|
|
||||||
func FromStringOrNil(input string) UUID {
|
|
||||||
uuid, err := FromString(input)
|
|
||||||
if err != nil {
|
|
||||||
return Nil
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements the encoding.TextMarshaler interface.
|
|
||||||
// The encoding is the same as returned by String.
|
|
||||||
func (u UUID) MarshalText() (text []byte, err error) {
|
|
||||||
text = []byte(u.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
|
||||||
// Following formats are supported:
|
|
||||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
||||||
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
|
|
||||||
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
|
||||||
// "6ba7b8109dad11d180b400c04fd430c8"
|
|
||||||
// ABNF for supported UUID text representation follows:
|
|
||||||
// uuid := canonical | hashlike | braced | urn
|
|
||||||
// plain := canonical | hashlike
|
|
||||||
// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct
|
|
||||||
// hashlike := 12hexoct
|
|
||||||
// braced := '{' plain '}'
|
|
||||||
// urn := URN ':' UUID-NID ':' plain
|
|
||||||
// URN := 'urn'
|
|
||||||
// UUID-NID := 'uuid'
|
|
||||||
// 12hexoct := 6hexoct 6hexoct
|
|
||||||
// 6hexoct := 4hexoct 2hexoct
|
|
||||||
// 4hexoct := 2hexoct 2hexoct
|
|
||||||
// 2hexoct := hexoct hexoct
|
|
||||||
// hexoct := hexdig hexdig
|
|
||||||
// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
|
|
||||||
// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
|
|
||||||
// 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
|
|
||||||
func (u *UUID) UnmarshalText(text []byte) (err error) {
|
|
||||||
switch len(text) {
|
|
||||||
case 32:
|
|
||||||
return u.decodeHashLike(text)
|
|
||||||
case 36:
|
|
||||||
return u.decodeCanonical(text)
|
|
||||||
case 38:
|
|
||||||
return u.decodeBraced(text)
|
|
||||||
case 41:
|
|
||||||
fallthrough
|
|
||||||
case 45:
|
|
||||||
return u.decodeURN(text)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("uuid: incorrect UUID length: %s", text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeCanonical decodes UUID string in format
|
|
||||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
|
|
||||||
func (u *UUID) decodeCanonical(t []byte) (err error) {
|
|
||||||
if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' {
|
|
||||||
return fmt.Errorf("uuid: incorrect UUID format %s", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
src := t[:]
|
|
||||||
dst := u[:]
|
|
||||||
|
|
||||||
for i, byteGroup := range byteGroups {
|
|
||||||
if i > 0 {
|
|
||||||
src = src[1:] // skip dash
|
|
||||||
}
|
|
||||||
_, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
src = src[byteGroup:]
|
|
||||||
dst = dst[byteGroup/2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeHashLike decodes UUID string in format
|
|
||||||
// "6ba7b8109dad11d180b400c04fd430c8".
|
|
||||||
func (u *UUID) decodeHashLike(t []byte) (err error) {
|
|
||||||
src := t[:]
|
|
||||||
dst := u[:]
|
|
||||||
|
|
||||||
if _, err = hex.Decode(dst, src); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeBraced decodes UUID string in format
|
|
||||||
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format
|
|
||||||
// "{6ba7b8109dad11d180b400c04fd430c8}".
|
|
||||||
func (u *UUID) decodeBraced(t []byte) (err error) {
|
|
||||||
l := len(t)
|
|
||||||
|
|
||||||
if t[0] != '{' || t[l-1] != '}' {
|
|
||||||
return fmt.Errorf("uuid: incorrect UUID format %s", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.decodePlain(t[1 : l-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeURN decodes UUID string in format
|
|
||||||
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format
|
|
||||||
// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8".
|
|
||||||
func (u *UUID) decodeURN(t []byte) (err error) {
|
|
||||||
total := len(t)
|
|
||||||
|
|
||||||
urn_uuid_prefix := t[:9]
|
|
||||||
|
|
||||||
if !bytes.Equal(urn_uuid_prefix, urnPrefix) {
|
|
||||||
return fmt.Errorf("uuid: incorrect UUID format: %s", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.decodePlain(t[9:total])
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodePlain decodes UUID string in canonical format
|
|
||||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format
|
|
||||||
// "6ba7b8109dad11d180b400c04fd430c8".
|
|
||||||
func (u *UUID) decodePlain(t []byte) (err error) {
|
|
||||||
switch len(t) {
|
|
||||||
case 32:
|
|
||||||
return u.decodeHashLike(t)
|
|
||||||
case 36:
|
|
||||||
return u.decodeCanonical(t)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("uuid: incorrrect UUID length: %s", t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
|
||||||
func (u UUID) MarshalBinary() (data []byte, err error) {
|
|
||||||
data = u.Bytes()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
|
||||||
// It will return error if the slice isn't 16 bytes long.
|
|
||||||
func (u *UUID) UnmarshalBinary(data []byte) (err error) {
|
|
||||||
if len(data) != Size {
|
|
||||||
err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
copy(u[:], data)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
265
vendor/github.com/satori/go.uuid/generator.go
generated
vendored
265
vendor/github.com/satori/go.uuid/generator.go
generated
vendored
|
|
@ -1,265 +0,0 @@
|
||||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
// a copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
// permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
// the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be
|
|
||||||
// included in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Difference in 100-nanosecond intervals between
|
|
||||||
// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
|
|
||||||
const epochStart = 122192928000000000
|
|
||||||
|
|
||||||
type epochFunc func() time.Time
|
|
||||||
type hwAddrFunc func() (net.HardwareAddr, error)
|
|
||||||
|
|
||||||
var (
|
|
||||||
global = newRFC4122Generator()
|
|
||||||
|
|
||||||
posixUID = uint32(os.Getuid())
|
|
||||||
posixGID = uint32(os.Getgid())
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewV1 returns UUID based on current timestamp and MAC address.
|
|
||||||
func NewV1() (UUID, error) {
|
|
||||||
return global.NewV1()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV2 returns DCE Security UUID based on POSIX UID/GID.
|
|
||||||
func NewV2(domain byte) (UUID, error) {
|
|
||||||
return global.NewV2(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
|
|
||||||
func NewV3(ns UUID, name string) UUID {
|
|
||||||
return global.NewV3(ns, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV4 returns random generated UUID.
|
|
||||||
func NewV4() (UUID, error) {
|
|
||||||
return global.NewV4()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
|
|
||||||
func NewV5(ns UUID, name string) UUID {
|
|
||||||
return global.NewV5(ns, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generator provides interface for generating UUIDs.
|
|
||||||
type Generator interface {
|
|
||||||
NewV1() (UUID, error)
|
|
||||||
NewV2(domain byte) (UUID, error)
|
|
||||||
NewV3(ns UUID, name string) UUID
|
|
||||||
NewV4() (UUID, error)
|
|
||||||
NewV5(ns UUID, name string) UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default generator implementation.
|
|
||||||
type rfc4122Generator struct {
|
|
||||||
clockSequenceOnce sync.Once
|
|
||||||
hardwareAddrOnce sync.Once
|
|
||||||
storageMutex sync.Mutex
|
|
||||||
|
|
||||||
rand io.Reader
|
|
||||||
|
|
||||||
epochFunc epochFunc
|
|
||||||
hwAddrFunc hwAddrFunc
|
|
||||||
lastTime uint64
|
|
||||||
clockSequence uint16
|
|
||||||
hardwareAddr [6]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRFC4122Generator() Generator {
|
|
||||||
return &rfc4122Generator{
|
|
||||||
epochFunc: time.Now,
|
|
||||||
hwAddrFunc: defaultHWAddrFunc,
|
|
||||||
rand: rand.Reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV1 returns UUID based on current timestamp and MAC address.
|
|
||||||
func (g *rfc4122Generator) NewV1() (UUID, error) {
|
|
||||||
u := UUID{}
|
|
||||||
|
|
||||||
timeNow, clockSeq, err := g.getClockSequence()
|
|
||||||
if err != nil {
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
|
|
||||||
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
|
|
||||||
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
|
|
||||||
binary.BigEndian.PutUint16(u[8:], clockSeq)
|
|
||||||
|
|
||||||
hardwareAddr, err := g.getHardwareAddr()
|
|
||||||
if err != nil {
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
copy(u[10:], hardwareAddr)
|
|
||||||
|
|
||||||
u.SetVersion(V1)
|
|
||||||
u.SetVariant(VariantRFC4122)
|
|
||||||
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV2 returns DCE Security UUID based on POSIX UID/GID.
|
|
||||||
func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) {
|
|
||||||
u, err := g.NewV1()
|
|
||||||
if err != nil {
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch domain {
|
|
||||||
case DomainPerson:
|
|
||||||
binary.BigEndian.PutUint32(u[:], posixUID)
|
|
||||||
case DomainGroup:
|
|
||||||
binary.BigEndian.PutUint32(u[:], posixGID)
|
|
||||||
}
|
|
||||||
|
|
||||||
u[9] = domain
|
|
||||||
|
|
||||||
u.SetVersion(V2)
|
|
||||||
u.SetVariant(VariantRFC4122)
|
|
||||||
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
|
|
||||||
func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID {
|
|
||||||
u := newFromHash(md5.New(), ns, name)
|
|
||||||
u.SetVersion(V3)
|
|
||||||
u.SetVariant(VariantRFC4122)
|
|
||||||
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV4 returns random generated UUID.
|
|
||||||
func (g *rfc4122Generator) NewV4() (UUID, error) {
|
|
||||||
u := UUID{}
|
|
||||||
if _, err := io.ReadFull(g.rand, u[:]); err != nil {
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
u.SetVersion(V4)
|
|
||||||
u.SetVariant(VariantRFC4122)
|
|
||||||
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
|
|
||||||
func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID {
|
|
||||||
u := newFromHash(sha1.New(), ns, name)
|
|
||||||
u.SetVersion(V5)
|
|
||||||
u.SetVariant(VariantRFC4122)
|
|
||||||
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns epoch and clock sequence.
|
|
||||||
func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) {
|
|
||||||
var err error
|
|
||||||
g.clockSequenceOnce.Do(func() {
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
if _, err = io.ReadFull(g.rand, buf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.clockSequence = binary.BigEndian.Uint16(buf)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
g.storageMutex.Lock()
|
|
||||||
defer g.storageMutex.Unlock()
|
|
||||||
|
|
||||||
timeNow := g.getEpoch()
|
|
||||||
// Clock didn't change since last UUID generation.
|
|
||||||
// Should increase clock sequence.
|
|
||||||
if timeNow <= g.lastTime {
|
|
||||||
g.clockSequence++
|
|
||||||
}
|
|
||||||
g.lastTime = timeNow
|
|
||||||
|
|
||||||
return timeNow, g.clockSequence, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns hardware address.
|
|
||||||
func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) {
|
|
||||||
var err error
|
|
||||||
g.hardwareAddrOnce.Do(func() {
|
|
||||||
if hwAddr, err := g.hwAddrFunc(); err == nil {
|
|
||||||
copy(g.hardwareAddr[:], hwAddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize hardwareAddr randomly in case
|
|
||||||
// of real network interfaces absence.
|
|
||||||
if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Set multicast bit as recommended by RFC 4122
|
|
||||||
g.hardwareAddr[0] |= 0x01
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, err
|
|
||||||
}
|
|
||||||
return g.hardwareAddr[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns difference in 100-nanosecond intervals between
|
|
||||||
// UUID epoch (October 15, 1582) and current time.
|
|
||||||
func (g *rfc4122Generator) getEpoch() uint64 {
|
|
||||||
return epochStart + uint64(g.epochFunc().UnixNano()/100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns UUID based on hashing of namespace UUID and name.
|
|
||||||
func newFromHash(h hash.Hash, ns UUID, name string) UUID {
|
|
||||||
u := UUID{}
|
|
||||||
h.Write(ns[:])
|
|
||||||
h.Write([]byte(name))
|
|
||||||
copy(u[:], h.Sum(nil))
|
|
||||||
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns hardware address.
|
|
||||||
func defaultHWAddrFunc() (net.HardwareAddr, error) {
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, err
|
|
||||||
}
|
|
||||||
for _, iface := range ifaces {
|
|
||||||
if len(iface.HardwareAddr) >= 6 {
|
|
||||||
return iface.HardwareAddr, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []byte{}, fmt.Errorf("uuid: no HW address found")
|
|
||||||
}
|
|
||||||
78
vendor/github.com/satori/go.uuid/sql.go
generated
vendored
78
vendor/github.com/satori/go.uuid/sql.go
generated
vendored
|
|
@ -1,78 +0,0 @@
|
||||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
// a copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
// permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
// the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be
|
|
||||||
// included in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Value implements the driver.Valuer interface.
|
|
||||||
func (u UUID) Value() (driver.Value, error) {
|
|
||||||
return u.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan implements the sql.Scanner interface.
|
|
||||||
// A 16-byte slice is handled by UnmarshalBinary, while
|
|
||||||
// a longer byte slice or a string is handled by UnmarshalText.
|
|
||||||
func (u *UUID) Scan(src interface{}) error {
|
|
||||||
switch src := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
if len(src) == Size {
|
|
||||||
return u.UnmarshalBinary(src)
|
|
||||||
}
|
|
||||||
return u.UnmarshalText(src)
|
|
||||||
|
|
||||||
case string:
|
|
||||||
return u.UnmarshalText([]byte(src))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("uuid: cannot convert %T to UUID", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NullUUID can be used with the standard sql package to represent a
|
|
||||||
// UUID value that can be NULL in the database
|
|
||||||
type NullUUID struct {
|
|
||||||
UUID UUID
|
|
||||||
Valid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements the driver.Valuer interface.
|
|
||||||
func (u NullUUID) Value() (driver.Value, error) {
|
|
||||||
if !u.Valid {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// Delegate to UUID Value function
|
|
||||||
return u.UUID.Value()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan implements the sql.Scanner interface.
|
|
||||||
func (u *NullUUID) Scan(src interface{}) error {
|
|
||||||
if src == nil {
|
|
||||||
u.UUID, u.Valid = Nil, false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delegate to UUID Scan function
|
|
||||||
u.Valid = true
|
|
||||||
return u.UUID.Scan(src)
|
|
||||||
}
|
|
||||||
161
vendor/github.com/satori/go.uuid/uuid.go
generated
vendored
161
vendor/github.com/satori/go.uuid/uuid.go
generated
vendored
|
|
@ -1,161 +0,0 @@
|
||||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
// a copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
// permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
// the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be
|
|
||||||
// included in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
// Package uuid provides implementation of Universally Unique Identifier (UUID).
|
|
||||||
// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and
|
|
||||||
// version 2 (as specified in DCE 1.1).
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Size of a UUID in bytes.
|
|
||||||
const Size = 16
|
|
||||||
|
|
||||||
// UUID representation compliant with specification
|
|
||||||
// described in RFC 4122.
|
|
||||||
type UUID [Size]byte
|
|
||||||
|
|
||||||
// UUID versions
|
|
||||||
const (
|
|
||||||
_ byte = iota
|
|
||||||
V1
|
|
||||||
V2
|
|
||||||
V3
|
|
||||||
V4
|
|
||||||
V5
|
|
||||||
)
|
|
||||||
|
|
||||||
// UUID layout variants.
|
|
||||||
const (
|
|
||||||
VariantNCS byte = iota
|
|
||||||
VariantRFC4122
|
|
||||||
VariantMicrosoft
|
|
||||||
VariantFuture
|
|
||||||
)
|
|
||||||
|
|
||||||
// UUID DCE domains.
|
|
||||||
const (
|
|
||||||
DomainPerson = iota
|
|
||||||
DomainGroup
|
|
||||||
DomainOrg
|
|
||||||
)
|
|
||||||
|
|
||||||
// String parse helpers.
|
|
||||||
var (
|
|
||||||
urnPrefix = []byte("urn:uuid:")
|
|
||||||
byteGroups = []int{8, 4, 4, 4, 12}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Nil is special form of UUID that is specified to have all
|
|
||||||
// 128 bits set to zero.
|
|
||||||
var Nil = UUID{}
|
|
||||||
|
|
||||||
// Predefined namespace UUIDs.
|
|
||||||
var (
|
|
||||||
NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Equal returns true if u1 and u2 equals, otherwise returns false.
|
|
||||||
func Equal(u1 UUID, u2 UUID) bool {
|
|
||||||
return bytes.Equal(u1[:], u2[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns algorithm version used to generate UUID.
|
|
||||||
func (u UUID) Version() byte {
|
|
||||||
return u[6] >> 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant returns UUID layout variant.
|
|
||||||
func (u UUID) Variant() byte {
|
|
||||||
switch {
|
|
||||||
case (u[8] >> 7) == 0x00:
|
|
||||||
return VariantNCS
|
|
||||||
case (u[8] >> 6) == 0x02:
|
|
||||||
return VariantRFC4122
|
|
||||||
case (u[8] >> 5) == 0x06:
|
|
||||||
return VariantMicrosoft
|
|
||||||
case (u[8] >> 5) == 0x07:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return VariantFuture
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns bytes slice representation of UUID.
|
|
||||||
func (u UUID) Bytes() []byte {
|
|
||||||
return u[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns canonical string representation of UUID:
|
|
||||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
|
|
||||||
func (u UUID) String() string {
|
|
||||||
buf := make([]byte, 36)
|
|
||||||
|
|
||||||
hex.Encode(buf[0:8], u[0:4])
|
|
||||||
buf[8] = '-'
|
|
||||||
hex.Encode(buf[9:13], u[4:6])
|
|
||||||
buf[13] = '-'
|
|
||||||
hex.Encode(buf[14:18], u[6:8])
|
|
||||||
buf[18] = '-'
|
|
||||||
hex.Encode(buf[19:23], u[8:10])
|
|
||||||
buf[23] = '-'
|
|
||||||
hex.Encode(buf[24:], u[10:])
|
|
||||||
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVersion sets version bits.
|
|
||||||
func (u *UUID) SetVersion(v byte) {
|
|
||||||
u[6] = (u[6] & 0x0f) | (v << 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVariant sets variant bits.
|
|
||||||
func (u *UUID) SetVariant(v byte) {
|
|
||||||
switch v {
|
|
||||||
case VariantNCS:
|
|
||||||
u[8] = (u[8]&(0xff>>1) | (0x00 << 7))
|
|
||||||
case VariantRFC4122:
|
|
||||||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6))
|
|
||||||
case VariantMicrosoft:
|
|
||||||
u[8] = (u[8]&(0xff>>3) | (0x06 << 5))
|
|
||||||
case VariantFuture:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
u[8] = (u[8]&(0xff>>3) | (0x07 << 5))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must is a helper that wraps a call to a function returning (UUID, error)
|
|
||||||
// and panics if the error is non-nil. It is intended for use in variable
|
|
||||||
// initializations such as
|
|
||||||
// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000"));
|
|
||||||
func Must(u UUID, err error) UUID {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
|
@ -296,9 +296,6 @@ github.com/robfig/cron/v3
|
||||||
# github.com/rogpeppe/fastuuid v1.2.0
|
# github.com/rogpeppe/fastuuid v1.2.0
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/rogpeppe/fastuuid
|
github.com/rogpeppe/fastuuid
|
||||||
# github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
|
|
||||||
## explicit
|
|
||||||
github.com/satori/go.uuid
|
|
||||||
# github.com/sirupsen/logrus v1.9.0
|
# github.com/sirupsen/logrus v1.9.0
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/sirupsen/logrus
|
github.com/sirupsen/logrus
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue