diff --git a/coderd/coderd.go b/coderd/coderd.go index 04fa372879..840d369b27 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1239,10 +1239,18 @@ func (api *API) CreateInMemoryProvisionerDaemon(dialCtx context.Context, name st } }() + // All in memory provisioners will be part of the default org for now. + //nolint:gocritic // in-memory provisioners are owned by system + defaultOrg, err := api.Database.GetDefaultOrganization(dbauthz.AsSystemRestricted(dialCtx)) + if err != nil { + return nil, xerrors.Errorf("unable to fetch default org for in memory provisioner: %w", err) + } + //nolint:gocritic // in-memory provisioners are owned by system daemon, err := api.Database.UpsertProvisionerDaemon(dbauthz.AsSystemRestricted(dialCtx), database.UpsertProvisionerDaemonParams{ - Name: name, - CreatedAt: dbtime.Now(), + Name: name, + OrganizationID: defaultOrg.ID, + CreatedAt: dbtime.Now(), Provisioners: []database.ProvisionerType{ database.ProvisionerTypeEcho, database.ProvisionerTypeTerraform, }, diff --git a/coderd/database/dbpurge/dbpurge_test.go b/coderd/database/dbpurge/dbpurge_test.go index d253863e39..da71852786 100644 --- a/coderd/database/dbpurge/dbpurge_test.go +++ b/coderd/database/dbpurge/dbpurge_test.go @@ -203,6 +203,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { t.Parallel() db, _ := dbtestutil.NewDB(t) + defaultOrg := dbgen.Organization(t, db, database.Organization{}) logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) @@ -213,24 +214,26 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { // given _, err := db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{ // Provisioner daemon created 14 days ago, and checked in just before 7 days deadline. - 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)}, - Version: "1.0.0", - APIVersion: proto.CurrentVersion.String(), + 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)}, + Version: "1.0.0", + APIVersion: proto.CurrentVersion.String(), + OrganizationID: defaultOrg.ID, }) require.NoError(t, err) _, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{ // Provisioner daemon created 8 days ago, and checked in last time an hour after creation. - 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)}, - Version: "1.0.0", - APIVersion: proto.CurrentVersion.String(), + 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)}, + Version: "1.0.0", + APIVersion: proto.CurrentVersion.String(), + OrganizationID: defaultOrg.ID, }) require.NoError(t, err) _, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{ @@ -241,9 +244,10 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { provisionersdk.TagScope: provisionersdk.ScopeUser, provisionersdk.TagOwner: uuid.NewString(), }, - CreatedAt: now.Add(-9 * 24 * time.Hour), - Version: "1.0.0", - APIVersion: proto.CurrentVersion.String(), + CreatedAt: now.Add(-9 * 24 * time.Hour), + Version: "1.0.0", + APIVersion: proto.CurrentVersion.String(), + OrganizationID: defaultOrg.ID, }) require.NoError(t, err) _, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{ @@ -254,10 +258,11 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { 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)}, - Version: "1.0.0", - APIVersion: proto.CurrentVersion.String(), + CreatedAt: now.Add(-6 * 24 * time.Hour), + LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-6 * 24 * time.Hour)}, + Version: "1.0.0", + APIVersion: proto.CurrentVersion.String(), + OrganizationID: defaultOrg.ID, }) require.NoError(t, err) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 81a6b77df8..55a349dc48 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -614,7 +614,8 @@ CREATE TABLE provisioner_daemons ( tags jsonb DEFAULT '{}'::jsonb NOT NULL, last_seen_at timestamp with time zone, version text DEFAULT ''::text NOT NULL, - api_version text DEFAULT '1.0'::text NOT NULL + api_version text DEFAULT '1.0'::text NOT NULL, + organization_id uuid NOT NULL ); COMMENT ON COLUMN provisioner_daemons.api_version IS 'The API version of the provisioner daemon'; @@ -1688,6 +1689,9 @@ ALTER TABLE ONLY organization_members ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; +ALTER TABLE ONLY provisioner_daemons + ADD CONSTRAINT provisioner_daemons_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index ad9ef76cbb..c808f4ecf6 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -23,6 +23,7 @@ const ( ForeignKeyOrganizationMembersOrganizationIDUUID ForeignKeyConstraint = "organization_members_organization_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyOrganizationMembersUserIDUUID ForeignKeyConstraint = "organization_members_user_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyParameterSchemasJobID ForeignKeyConstraint = "parameter_schemas_job_id_fkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; + ForeignKeyProvisionerDaemonsOrganizationID ForeignKeyConstraint = "provisioner_daemons_organization_id_fkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyProvisionerJobLogsJobID ForeignKeyConstraint = "provisioner_job_logs_job_id_fkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ForeignKeyProvisionerJobsOrganizationID ForeignKeyConstraint = "provisioner_jobs_organization_id_fkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyTailnetAgentsCoordinatorID ForeignKeyConstraint = "tailnet_agents_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000198_org_provisioners.down.sql b/coderd/database/migrations/000198_org_provisioners.down.sql new file mode 100644 index 0000000000..956cfc1478 --- /dev/null +++ b/coderd/database/migrations/000198_org_provisioners.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE provisioner_daemons + DROP COLUMN organization_id; diff --git a/coderd/database/migrations/000198_org_provisioners.up.sql b/coderd/database/migrations/000198_org_provisioners.up.sql new file mode 100644 index 0000000000..bd415045d3 --- /dev/null +++ b/coderd/database/migrations/000198_org_provisioners.up.sql @@ -0,0 +1,14 @@ +-- At the time of this migration, only 1 org is expected in a deployment. +-- In the future when multi-org is more common, there might be a use case +-- to allow a provisioner to be associated with multiple orgs. +ALTER TABLE provisioner_daemons + ADD COLUMN organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE; + +UPDATE + provisioner_daemons +SET + -- Default to the first org + organization_id = (SELECT id FROM organizations WHERE is_default = true LIMIT 1 ); + +ALTER TABLE provisioner_daemons + ALTER COLUMN organization_id SET NOT NULL; diff --git a/coderd/database/models.go b/coderd/database/models.go index 6047ea619a..d66ee21c31 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1966,7 +1966,8 @@ type ProvisionerDaemon struct { LastSeenAt sql.NullTime `db:"last_seen_at" json:"last_seen_at"` Version string `db:"version" json:"version"` // The API version of the provisioner daemon - APIVersion string `db:"api_version" json:"api_version"` + APIVersion string `db:"api_version" json:"api_version"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` } type ProvisionerJob struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e3123cb5f2..cab4863beb 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3686,7 +3686,7 @@ func (q *sqlQuerier) DeleteOldProvisionerDaemons(ctx context.Context) error { const getProvisionerDaemons = `-- name: GetProvisionerDaemons :many SELECT - id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version + id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version, organization_id FROM provisioner_daemons ` @@ -3710,6 +3710,7 @@ func (q *sqlQuerier) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDa &i.LastSeenAt, &i.Version, &i.APIVersion, + &i.OrganizationID, ); err != nil { return nil, err } @@ -3754,6 +3755,7 @@ INSERT INTO tags, last_seen_at, "version", + organization_id, api_version ) VALUES ( @@ -3764,27 +3766,30 @@ VALUES ( $4, $5, $6, - $7 + $7, + $8 ) ON CONFLICT("name", LOWER(COALESCE(tags ->> 'owner'::text, ''::text))) DO UPDATE SET provisioners = $3, tags = $4, last_seen_at = $5, "version" = $6, - api_version = $7 + api_version = $8, + organization_id = $7 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, api_version +RETURNING id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version, organization_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"` - APIVersion string `db:"api_version" json:"api_version"` + 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"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + APIVersion string `db:"api_version" json:"api_version"` } func (q *sqlQuerier) UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error) { @@ -3795,6 +3800,7 @@ func (q *sqlQuerier) UpsertProvisionerDaemon(ctx context.Context, arg UpsertProv arg.Tags, arg.LastSeenAt, arg.Version, + arg.OrganizationID, arg.APIVersion, ) var i ProvisionerDaemon @@ -3808,6 +3814,7 @@ func (q *sqlQuerier) UpsertProvisionerDaemon(ctx context.Context, arg UpsertProv &i.LastSeenAt, &i.Version, &i.APIVersion, + &i.OrganizationID, ) return i, err } diff --git a/coderd/database/queries/provisionerdaemons.sql b/coderd/database/queries/provisionerdaemons.sql index 47c92f7997..c8b04eddc3 100644 --- a/coderd/database/queries/provisionerdaemons.sql +++ b/coderd/database/queries/provisionerdaemons.sql @@ -24,6 +24,7 @@ INSERT INTO tags, last_seen_at, "version", + organization_id, api_version ) VALUES ( @@ -34,13 +35,15 @@ VALUES ( @tags, @last_seen_at, @version, + @organization_id, @api_version ) ON CONFLICT("name", LOWER(COALESCE(tags ->> 'owner'::text, ''::text))) DO UPDATE SET provisioners = @provisioners, tags = @tags, last_seen_at = @last_seen_at, "version" = @version, - api_version = @api_version + api_version = @api_version, + organization_id = @organization_id WHERE -- Only ones with the same tags are allowed clobber provisioner_daemons.tags <@ @tags :: jsonb diff --git a/enterprise/coderd/provisionerdaemons.go b/enterprise/coderd/provisionerdaemons.go index 7c89722974..38d48bec62 100644 --- a/enterprise/coderd/provisionerdaemons.go +++ b/enterprise/coderd/provisionerdaemons.go @@ -134,6 +134,7 @@ func (p *provisionerDaemonAuth) authorize(r *http.Request, tags map[string]strin // @Router /organizations/{organization}/provisionerdaemons/serve [get] func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() + organization := httpmw.OrganizationParam(r) tags := map[string]string{} if r.URL.Query().Has("tag") { @@ -246,13 +247,14 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) // Create the daemon in the database. now := dbtime.Now() daemon, err := api.Database.UpsertProvisionerDaemon(authCtx, database.UpsertProvisionerDaemonParams{ - Name: name, - Provisioners: provisioners, - Tags: tags, - CreatedAt: now, - LastSeenAt: sql.NullTime{Time: now, Valid: true}, - Version: versionHdrVal, - APIVersion: apiVersion, + Name: name, + Provisioners: provisioners, + Tags: tags, + CreatedAt: now, + LastSeenAt: sql.NullTime{Time: now, Valid: true}, + Version: versionHdrVal, + APIVersion: apiVersion, + OrganizationID: organization.ID, }) if err != nil { if !xerrors.Is(err, context.Canceled) {