feat(coderd/database): add UpsertProvisionerDaemons query (#11178)

Co-authored-by: Marcin Tojek <marcin@coder.com>
This commit is contained in:
Cian Johnston 2023-12-13 12:31:40 +00:00 committed by GitHub
parent ef4d1b68e1
commit 4f7ae6461b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 210 additions and 98 deletions

View File

@ -22,6 +22,7 @@ import (
"github.com/coder/coder/v2/coderd/httpapi/httpapiconstraints"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/provisionersdk"
)
var _ database.Store = (*querier)(nil)
@ -2155,14 +2156,6 @@ func (q *querier) InsertOrganizationMember(ctx context.Context, arg database.Ins
return insert(q.log, q.auth, obj, q.db.InsertOrganizationMember)(ctx, arg)
}
// TODO: We need to create a ProvisionerDaemon resource type
func (q *querier) InsertProvisionerDaemon(ctx context.Context, arg database.InsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
// if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
// return database.ProvisionerDaemon{}, err
// }
return q.db.InsertProvisionerDaemon(ctx, arg)
}
// TODO: We need to create a ProvisionerJob resource type
func (q *querier) InsertProvisionerJob(ctx context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
// if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
@ -3063,6 +3056,17 @@ func (q *querier) UpsertOAuthSigningKey(ctx context.Context, value string) error
return q.db.UpsertOAuthSigningKey(ctx, value)
}
func (q *querier) UpsertProvisionerDaemon(ctx context.Context, arg database.UpsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
res := rbac.ResourceProvisionerDaemon.All()
if arg.Tags[provisionersdk.TagScope] == provisionersdk.ScopeUser {
res.Owner = arg.Tags[provisionersdk.TagOwner]
}
if err := q.authorizeContext(ctx, rbac.ActionCreate, res); err != nil {
return database.ProvisionerDaemon{}, err
}
return q.db.UpsertProvisionerDaemon(ctx, arg)
}
func (q *querier) UpsertServiceBanner(ctx context.Context, value string) error {
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil {
return err

View File

@ -22,6 +22,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/testutil"
)
@ -1370,8 +1371,10 @@ func (s *MethodTestSuite) TestWorkspace() {
func (s *MethodTestSuite) TestExtraMethods() {
s.Run("GetProvisionerDaemons", s.Subtest(func(db database.Store, check *expects) {
d, err := db.InsertProvisionerDaemon(context.Background(), database.InsertProvisionerDaemonParams{
ID: uuid.New(),
d, err := db.UpsertProvisionerDaemon(context.Background(), database.UpsertProvisionerDaemonParams{
Tags: database.StringMap(map[string]string{
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
}),
})
s.NoError(err, "insert provisioner daemon")
check.Args().Asserts(d, rbac.ActionRead)
@ -1650,11 +1653,19 @@ func (s *MethodTestSuite) TestSystemFunctions() {
JobID: j.ID,
}).Asserts( /*rbac.ResourceSystem, rbac.ActionCreate*/ )
}))
s.Run("InsertProvisionerDaemon", s.Subtest(func(db database.Store, check *expects) {
// TODO: we need to create a ProvisionerDaemon resource
check.Args(database.InsertProvisionerDaemonParams{
ID: uuid.New(),
}).Asserts( /*rbac.ResourceSystem, rbac.ActionCreate*/ )
s.Run("UpsertProvisionerDaemon", s.Subtest(func(db database.Store, check *expects) {
pd := rbac.ResourceProvisionerDaemon.All()
check.Args(database.UpsertProvisionerDaemonParams{
Tags: database.StringMap(map[string]string{
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
}),
}).Asserts(pd, rbac.ActionCreate)
check.Args(database.UpsertProvisionerDaemonParams{
Tags: database.StringMap(map[string]string{
provisionersdk.TagScope: provisionersdk.ScopeUser,
provisionersdk.TagOwner: "11111111-1111-1111-1111-111111111111",
}),
}).Asserts(pd.WithOwner("11111111-1111-1111-1111-111111111111"), rbac.ActionCreate)
}))
s.Run("InsertTemplateVersionParameter", s.Subtest(func(db database.Store, check *expects) {
v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{})

View File

@ -26,6 +26,7 @@ import (
"github.com/coder/coder/v2/coderd/rbac/regosql"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/provisionersdk"
)
var validProxyByHostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
@ -4936,25 +4937,6 @@ func (q *FakeQuerier) InsertOrganizationMember(_ context.Context, arg database.I
return organizationMember, nil
}
func (q *FakeQuerier) InsertProvisionerDaemon(_ context.Context, arg database.InsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
if err := validateDatabaseType(arg); err != nil {
return database.ProvisionerDaemon{}, err
}
q.mutex.Lock()
defer q.mutex.Unlock()
daemon := database.ProvisionerDaemon{
ID: arg.ID,
Name: arg.Name,
Provisioners: arg.Provisioners,
Tags: arg.Tags,
LastSeenAt: arg.LastSeenAt,
}
q.provisionerDaemons = append(q.provisionerDaemons, daemon)
return daemon, nil
}
func (q *FakeQuerier) InsertProvisionerJob(_ context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
if err := validateDatabaseType(arg); err != nil {
return database.ProvisionerJob{}, err
@ -6961,6 +6943,43 @@ func (q *FakeQuerier) UpsertOAuthSigningKey(_ context.Context, value string) err
return nil
}
func (q *FakeQuerier) UpsertProvisionerDaemon(_ context.Context, arg database.UpsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
err := validateDatabaseType(arg)
if err != nil {
return database.ProvisionerDaemon{}, err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for _, d := range q.provisionerDaemons {
if d.Name == arg.Name {
if d.Tags[provisionersdk.TagScope] == provisionersdk.ScopeOrganization && arg.Tags[provisionersdk.TagOwner] != "" {
continue
}
if d.Tags[provisionersdk.TagScope] == provisionersdk.ScopeUser && arg.Tags[provisionersdk.TagOwner] != d.Tags[provisionersdk.TagOwner] {
continue
}
d.Provisioners = arg.Provisioners
d.Tags = arg.Tags
d.Version = arg.Version
d.LastSeenAt = arg.LastSeenAt
return d, nil
}
}
d := database.ProvisionerDaemon{
ID: uuid.New(),
CreatedAt: arg.CreatedAt,
Name: arg.Name,
Provisioners: arg.Provisioners,
Tags: arg.Tags,
ReplicaID: uuid.NullUUID{},
LastSeenAt: arg.LastSeenAt,
Version: arg.Version,
}
q.provisionerDaemons = append(q.provisionerDaemons, d)
return d, nil
}
func (q *FakeQuerier) UpsertServiceBanner(_ context.Context, data string) error {
q.mutex.RLock()
defer q.mutex.RUnlock()

View File

@ -1348,13 +1348,6 @@ func (m metricsStore) InsertOrganizationMember(ctx context.Context, arg database
return member, err
}
func (m metricsStore) InsertProvisionerDaemon(ctx context.Context, arg database.InsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
start := time.Now()
daemon, err := m.s.InsertProvisionerDaemon(ctx, arg)
m.queryLatencies.WithLabelValues("InsertProvisionerDaemon").Observe(time.Since(start).Seconds())
return daemon, err
}
func (m metricsStore) InsertProvisionerJob(ctx context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
start := time.Now()
job, err := m.s.InsertProvisionerJob(ctx, arg)
@ -1950,6 +1943,13 @@ func (m metricsStore) UpsertOAuthSigningKey(ctx context.Context, value string) e
return r0
}
func (m metricsStore) UpsertProvisionerDaemon(ctx context.Context, arg database.UpsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
start := time.Now()
r0, r1 := m.s.UpsertProvisionerDaemon(ctx, arg)
m.queryLatencies.WithLabelValues("UpsertProvisionerDaemon").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) UpsertServiceBanner(ctx context.Context, value string) error {
start := time.Now()
r0 := m.s.UpsertServiceBanner(ctx, value)

View File

@ -2833,21 +2833,6 @@ func (mr *MockStoreMockRecorder) InsertOrganizationMember(arg0, arg1 interface{}
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOrganizationMember", reflect.TypeOf((*MockStore)(nil).InsertOrganizationMember), arg0, arg1)
}
// InsertProvisionerDaemon mocks base method.
func (m *MockStore) InsertProvisionerDaemon(arg0 context.Context, arg1 database.InsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertProvisionerDaemon", arg0, arg1)
ret0, _ := ret[0].(database.ProvisionerDaemon)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertProvisionerDaemon indicates an expected call of InsertProvisionerDaemon.
func (mr *MockStoreMockRecorder) InsertProvisionerDaemon(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerDaemon", reflect.TypeOf((*MockStore)(nil).InsertProvisionerDaemon), arg0, arg1)
}
// InsertProvisionerJob mocks base method.
func (m *MockStore) InsertProvisionerJob(arg0 context.Context, arg1 database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
m.ctrl.T.Helper()
@ -4089,6 +4074,21 @@ func (mr *MockStoreMockRecorder) UpsertOAuthSigningKey(arg0, arg1 interface{}) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertOAuthSigningKey", reflect.TypeOf((*MockStore)(nil).UpsertOAuthSigningKey), arg0, arg1)
}
// UpsertProvisionerDaemon mocks base method.
func (m *MockStore) UpsertProvisionerDaemon(arg0 context.Context, arg1 database.UpsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertProvisionerDaemon", arg0, arg1)
ret0, _ := ret[0].(database.ProvisionerDaemon)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpsertProvisionerDaemon indicates an expected call of UpsertProvisionerDaemon.
func (mr *MockStoreMockRecorder) UpsertProvisionerDaemon(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertProvisionerDaemon", reflect.TypeOf((*MockStore)(nil).UpsertProvisionerDaemon), arg0, arg1)
}
// UpsertServiceBanner mocks base method.
func (m *MockStore) UpsertServiceBanner(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()

View File

@ -19,6 +19,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbpurge"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/testutil"
)
@ -209,39 +210,45 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
now := dbtime.Now()
// given
_, err := db.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{
_, err := db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
// Provisioner daemon created 14 days ago, and checked in just before 7 days deadline.
ID: uuid.New(),
Name: "external-0",
Provisioners: []database.ProvisionerType{"echo"},
Tags: database.StringMap{provisionersdk.TagScope: provisionersdk.ScopeOrganization},
CreatedAt: now.Add(-14 * 24 * time.Hour),
LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-7 * 24 * time.Hour).Add(time.Minute)},
})
require.NoError(t, err)
_, err = db.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
// Provisioner daemon created 8 days ago, and checked in last time an hour after creation.
ID: uuid.New(),
Name: "external-1",
Provisioners: []database.ProvisionerType{"echo"},
Tags: database.StringMap{provisionersdk.TagScope: provisionersdk.ScopeOrganization},
CreatedAt: now.Add(-8 * 24 * time.Hour),
LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-8 * 24 * time.Hour).Add(time.Hour)},
})
require.NoError(t, err)
_, err = db.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
// Provisioner daemon created 9 days ago, and never checked in.
ID: uuid.New(),
Name: "external-2",
Name: "alice-provisioner",
Provisioners: []database.ProvisionerType{"echo"},
CreatedAt: now.Add(-9 * 24 * time.Hour),
Tags: database.StringMap{
provisionersdk.TagScope: provisionersdk.ScopeUser,
provisionersdk.TagOwner: uuid.NewString(),
},
CreatedAt: now.Add(-9 * 24 * time.Hour),
})
require.NoError(t, err)
_, err = db.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
// Provisioner daemon created 6 days ago, and never checked in.
ID: uuid.New(),
Name: "external-3",
Name: "bob-provisioner",
Provisioners: []database.ProvisionerType{"echo"},
CreatedAt: now.Add(-6 * 24 * time.Hour),
LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-6 * 24 * time.Hour)},
Tags: database.StringMap{
provisionersdk.TagScope: provisionersdk.ScopeUser,
provisionersdk.TagOwner: uuid.NewString(),
},
CreatedAt: now.Add(-6 * 24 * time.Hour),
LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-6 * 24 * time.Hour)},
})
require.NoError(t, err)
@ -257,8 +264,8 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
}
return containsProvisionerDaemon(daemons, "external-0") &&
!containsProvisionerDaemon(daemons, "external-1") &&
!containsProvisionerDaemon(daemons, "external-2") &&
containsProvisionerDaemon(daemons, "external-3")
!containsProvisionerDaemon(daemons, "alice-provisioner") &&
containsProvisionerDaemon(daemons, "bob-provisioner")
}, testutil.WaitShort, testutil.IntervalFast)
}

View File

@ -1283,9 +1283,6 @@ ALTER TABLE ONLY parameter_values
ALTER TABLE ONLY parameter_values
ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name);
ALTER TABLE ONLY provisioner_daemons
ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name);
ALTER TABLE ONLY provisioner_daemons
ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id);
@ -1415,6 +1412,10 @@ CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name);
CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name));
CREATE UNIQUE INDEX idx_provisioner_daemons_name_owner_key ON provisioner_daemons USING btree (name, lower((tags ->> 'owner'::text)));
COMMENT ON INDEX idx_provisioner_daemons_name_owner_key IS 'Relax uniqueness constraint for provisioner daemon names';
CREATE INDEX idx_tailnet_agents_coordinator ON tailnet_agents USING btree (coordinator_id);
CREATE INDEX idx_tailnet_clients_coordinator ON tailnet_clients USING btree (coordinator_id);

View File

@ -0,0 +1,4 @@
DROP INDEX IF EXISTS idx_provisioner_daemons_name_owner_key;
ALTER TABLE ONLY provisioner_daemons
ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name);

View File

@ -0,0 +1,10 @@
ALTER TABLE ONLY provisioner_daemons
DROP CONSTRAINT IF EXISTS provisioner_daemons_name_key;
CREATE UNIQUE INDEX IF NOT EXISTS idx_provisioner_daemons_name_owner_key
ON provisioner_daemons
USING btree (name, lower((tags->>'owner')::text));
COMMENT ON INDEX idx_provisioner_daemons_name_owner_key
IS 'Relax uniqueness constraint for provisioner daemon names';

View File

@ -277,7 +277,6 @@ type sqlcQuerier interface {
InsertMissingGroups(ctx context.Context, arg InsertMissingGroupsParams) ([]Group, error)
InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error)
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
InsertProvisionerDaemon(ctx context.Context, arg InsertProvisionerDaemonParams) (ProvisionerDaemon, error)
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error)
InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error)
@ -373,6 +372,7 @@ type sqlcQuerier interface {
UpsertLastUpdateCheck(ctx context.Context, value string) error
UpsertLogoURL(ctx context.Context, value string) error
UpsertOAuthSigningKey(ctx context.Context, value string) error
UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error)
UpsertServiceBanner(ctx context.Context, value string) error
UpsertTailnetAgent(ctx context.Context, arg UpsertTailnetAgentParams) (TailnetAgent, error)
UpsertTailnetClient(ctx context.Context, arg UpsertTailnetClientParams) (TailnetClient, error)

View File

@ -3056,7 +3056,7 @@ func (q *sqlQuerier) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDa
return items, nil
}
const insertProvisionerDaemon = `-- name: InsertProvisionerDaemon :one
const upsertProvisionerDaemon = `-- name: UpsertProvisionerDaemon :one
INSERT INTO
provisioner_daemons (
id,
@ -3064,29 +3064,45 @@ INSERT INTO
"name",
provisioners,
tags,
last_seen_at
last_seen_at,
"version"
)
VALUES
($1, $2, $3, $4, $5, $6) RETURNING id, created_at, name, provisioners, replica_id, tags, last_seen_at, version
VALUES (
gen_random_uuid(),
$1,
$2,
$3,
$4,
$5,
$6
) ON CONFLICT("name", lower((tags ->> 'owner'::text))) DO UPDATE SET
provisioners = $3,
tags = $4,
last_seen_at = $5,
"version" = $6
WHERE
-- Only ones with the same tags are allowed clobber
provisioner_daemons.tags <@ $4 :: jsonb
RETURNING id, created_at, name, provisioners, replica_id, tags, last_seen_at, version
`
type InsertProvisionerDaemonParams struct {
ID uuid.UUID `db:"id" json:"id"`
type UpsertProvisionerDaemonParams struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
Name string `db:"name" json:"name"`
Provisioners []ProvisionerType `db:"provisioners" json:"provisioners"`
Tags StringMap `db:"tags" json:"tags"`
LastSeenAt sql.NullTime `db:"last_seen_at" json:"last_seen_at"`
Version string `db:"version" json:"version"`
}
func (q *sqlQuerier) InsertProvisionerDaemon(ctx context.Context, arg InsertProvisionerDaemonParams) (ProvisionerDaemon, error) {
row := q.db.QueryRowContext(ctx, insertProvisionerDaemon,
arg.ID,
func (q *sqlQuerier) UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error) {
row := q.db.QueryRowContext(ctx, upsertProvisionerDaemon,
arg.CreatedAt,
arg.Name,
pq.Array(arg.Provisioners),
arg.Tags,
arg.LastSeenAt,
arg.Version,
)
var i ProvisionerDaemon
err := row.Scan(

View File

@ -4,19 +4,6 @@ SELECT
FROM
provisioner_daemons;
-- name: InsertProvisionerDaemon :one
INSERT INTO
provisioner_daemons (
id,
created_at,
"name",
provisioners,
tags,
last_seen_at
)
VALUES
($1, $2, $3, $4, $5, $6) RETURNING *;
-- name: DeleteOldProvisionerDaemons :exec
-- Delete provisioner daemons that have been created at least a week ago
-- and have not connected to coderd since a week.
@ -26,3 +13,32 @@ DELETE FROM provisioner_daemons WHERE (
(created_at < (NOW() - INTERVAL '7 days') AND last_seen_at IS NULL) OR
(last_seen_at IS NOT NULL AND last_seen_at < (NOW() - INTERVAL '7 days'))
);
-- name: UpsertProvisionerDaemon :one
INSERT INTO
provisioner_daemons (
id,
created_at,
"name",
provisioners,
tags,
last_seen_at,
"version"
)
VALUES (
gen_random_uuid(),
@created_at,
@name,
@provisioners,
@tags,
@last_seen_at,
@version
) ON CONFLICT("name", lower((tags ->> 'owner'::text))) DO UPDATE SET
provisioners = @provisioners,
tags = @tags,
last_seen_at = @last_seen_at,
"version" = @version
WHERE
-- Only ones with the same tags are allowed clobber
provisioner_daemons.tags <@ @tags :: jsonb
RETURNING *;

View File

@ -27,7 +27,6 @@ const (
UniqueParameterSchemasPkey UniqueConstraint = "parameter_schemas_pkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_pkey PRIMARY KEY (id);
UniqueParameterValuesPkey UniqueConstraint = "parameter_values_pkey" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_pkey PRIMARY KEY (id);
UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name);
UniqueProvisionerDaemonsNameKey UniqueConstraint = "provisioner_daemons_name_key" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name);
UniqueProvisionerDaemonsPkey UniqueConstraint = "provisioner_daemons_pkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id);
UniqueProvisionerJobLogsPkey UniqueConstraint = "provisioner_job_logs_pkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_pkey PRIMARY KEY (id);
UniqueProvisionerJobsPkey UniqueConstraint = "provisioner_jobs_pkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id);
@ -66,6 +65,7 @@ const (
UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type);
UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name);
UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name));
UniqueIndexProvisionerDaemonsNameOwnerKey UniqueConstraint = "idx_provisioner_daemons_name_owner_key" // CREATE UNIQUE INDEX idx_provisioner_daemons_name_owner_key ON provisioner_daemons USING btree (name, lower((tags ->> 'owner'::text)));
UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false);
UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false);
UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false);

View File

@ -14,6 +14,7 @@ const (
// If the scope is "user", the "owner" is changed to the user ID.
// This is for user-scoped provisioner daemons, where users should
// own their own operations.
// Otherwise, the "owner" tag is always empty.
func MutateTags(userID uuid.UUID, tags map[string]string) map[string]string {
if tags == nil {
tags = map[string]string{}
@ -21,11 +22,13 @@ func MutateTags(userID uuid.UUID, tags map[string]string) map[string]string {
_, ok := tags[TagScope]
if !ok {
tags[TagScope] = ScopeOrganization
delete(tags, TagOwner)
}
switch tags[TagScope] {
case ScopeUser:
tags[TagOwner] = userID.String()
case ScopeOrganization:
delete(tags, TagOwner)
default:
tags[TagScope] = ScopeOrganization
}

View File

@ -54,6 +54,27 @@ func TestMutateTags(t *testing.T) {
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
},
},
{
name: "organization scope with owner",
tags: map[string]string{
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
provisionersdk.TagOwner: testUserID.String(),
},
userID: uuid.Nil,
want: map[string]string{
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
},
},
{
name: "owner tag with no other context",
tags: map[string]string{
provisionersdk.TagOwner: testUserID.String(),
},
userID: uuid.Nil,
want: map[string]string{
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
},
},
{
name: "invalid scope",
tags: map[string]string{provisionersdk.TagScope: "360noscope"},