mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
feat: add keys to organization provision daemons (#14627)
This commit is contained in:
Generated
+55
@@ -3054,6 +3054,43 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/organizations/{organization}/provisionerkeys/daemons": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Enterprise"
|
||||
],
|
||||
"summary": "List provisioner key daemons",
|
||||
"operationId": "list-provisioner-key-daemons",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Organization ID",
|
||||
"name": "organization",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerKeyDaemons"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/organizations/{organization}/provisionerkeys/{provisionerkey}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
@@ -11531,6 +11568,10 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"key_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"last_seen_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@@ -11714,6 +11755,20 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ProvisionerKeyDaemons": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"daemons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerDaemon"
|
||||
}
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerKey"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ProvisionerLogLevel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
Generated
+51
@@ -2682,6 +2682,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/organizations/{organization}/provisionerkeys/daemons": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Enterprise"],
|
||||
"summary": "List provisioner key daemons",
|
||||
"operationId": "list-provisioner-key-daemons",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Organization ID",
|
||||
"name": "organization",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerKeyDaemons"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/organizations/{organization}/provisionerkeys/{provisionerkey}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
@@ -10401,6 +10434,10 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"key_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"last_seen_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@@ -10576,6 +10613,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ProvisionerKeyDaemons": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"daemons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerDaemon"
|
||||
}
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerKey"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ProvisionerLogLevel": {
|
||||
"type": "string",
|
||||
"enum": ["debug"],
|
||||
|
||||
@@ -1491,6 +1491,11 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n
|
||||
dbTypes = append(dbTypes, database.ProvisionerType(tp))
|
||||
}
|
||||
|
||||
keyID, err := uuid.Parse(string(codersdk.ProvisionerKeyIDBuiltIn))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to parse built-in provisioner key ID: %w", err)
|
||||
}
|
||||
|
||||
//nolint:gocritic // in-memory provisioners are owned by system
|
||||
daemon, err := api.Database.UpsertProvisionerDaemon(dbauthz.AsSystemRestricted(dialCtx), database.UpsertProvisionerDaemonParams{
|
||||
Name: name,
|
||||
@@ -1501,6 +1506,7 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n
|
||||
LastSeenAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
|
||||
Version: buildinfo.Version(),
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
KeyID: keyID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to create in-memory provisioner daemon: %w", err)
|
||||
|
||||
@@ -533,6 +533,7 @@ func ProvisionerDaemon(dbDaemon database.ProvisionerDaemon) codersdk.Provisioner
|
||||
Tags: dbDaemon.Tags,
|
||||
Version: dbDaemon.Version,
|
||||
APIVersion: dbDaemon.APIVersion,
|
||||
KeyID: dbDaemon.KeyID,
|
||||
}
|
||||
for _, provisionerType := range dbDaemon.Provisioners {
|
||||
result.Provisioners = append(result.Provisioners, codersdk.ProvisionerType(provisionerType))
|
||||
@@ -540,6 +541,30 @@ func ProvisionerDaemon(dbDaemon database.ProvisionerDaemon) codersdk.Provisioner
|
||||
return result
|
||||
}
|
||||
|
||||
func RecentProvisionerDaemons(now time.Time, staleInterval time.Duration, daemons []database.ProvisionerDaemon) []codersdk.ProvisionerDaemon {
|
||||
results := []codersdk.ProvisionerDaemon{}
|
||||
|
||||
for _, daemon := range daemons {
|
||||
// Daemon never connected, skip.
|
||||
if !daemon.LastSeenAt.Valid {
|
||||
continue
|
||||
}
|
||||
// Daemon has gone away, skip.
|
||||
if now.Sub(daemon.LastSeenAt.Time) > staleInterval {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, ProvisionerDaemon(daemon))
|
||||
}
|
||||
|
||||
// Ensure stable order for display and for tests
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func SlimRole(role rbac.Role) codersdk.SlimRole {
|
||||
orgID := ""
|
||||
if role.Identifier.OrganizationID != uuid.Nil {
|
||||
|
||||
@@ -3066,6 +3066,10 @@ func (q *querier) ListProvisionerKeysByOrganization(ctx context.Context, organiz
|
||||
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.ListProvisionerKeysByOrganization)(ctx, organizationID)
|
||||
}
|
||||
|
||||
func (q *querier) ListProvisionerKeysByOrganizationExcludeReserved(ctx context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) {
|
||||
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.ListProvisionerKeysByOrganizationExcludeReserved)(ctx, organizationID)
|
||||
}
|
||||
|
||||
func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||
workspace, err := q.db.GetWorkspaceByID(ctx, workspaceID)
|
||||
if err != nil {
|
||||
|
||||
@@ -2020,6 +2020,19 @@ func (s *MethodTestSuite) TestProvisionerKeys() {
|
||||
}
|
||||
check.Args(org.ID).Asserts(pk, policy.ActionRead).Returns(pks)
|
||||
}))
|
||||
s.Run("ListProvisionerKeysByOrganizationExcludeReserved", s.Subtest(func(db database.Store, check *expects) {
|
||||
org := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
pk := dbgen.ProvisionerKey(s.T(), db, database.ProvisionerKey{OrganizationID: org.ID})
|
||||
pks := []database.ProvisionerKey{
|
||||
{
|
||||
ID: pk.ID,
|
||||
CreatedAt: pk.CreatedAt,
|
||||
OrganizationID: pk.OrganizationID,
|
||||
Name: pk.Name,
|
||||
},
|
||||
}
|
||||
check.Args(org.ID).Asserts(pk, policy.ActionRead).Returns(pks)
|
||||
}))
|
||||
s.Run("DeleteProvisionerKey", s.Subtest(func(db database.Store, check *expects) {
|
||||
org := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
pk := dbgen.ProvisionerKey(s.T(), db, database.ProvisionerKey{OrganizationID: org.ID})
|
||||
|
||||
@@ -68,6 +68,7 @@ func New() database.Store {
|
||||
notificationPreferences: make([]database.NotificationPreference, 0),
|
||||
parameterSchemas: make([]database.ParameterSchema, 0),
|
||||
provisionerDaemons: make([]database.ProvisionerDaemon, 0),
|
||||
provisionerKeys: make([]database.ProvisionerKey, 0),
|
||||
workspaceAgents: make([]database.WorkspaceAgent, 0),
|
||||
provisionerJobLogs: make([]database.ProvisionerJobLog, 0),
|
||||
workspaceResources: make([]database.WorkspaceResource, 0),
|
||||
@@ -108,6 +109,41 @@ func New() database.Store {
|
||||
|
||||
q.defaultProxyDisplayName = "Default"
|
||||
q.defaultProxyIconURL = "/emojis/1f3e1.png"
|
||||
|
||||
_, err = q.InsertProvisionerKey(context.Background(), database.InsertProvisionerKeyParams{
|
||||
ID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
OrganizationID: defaultOrg.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
HashedSecret: []byte{},
|
||||
Name: codersdk.ProvisionerKeyNameBuiltIn,
|
||||
Tags: map[string]string{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(xerrors.Errorf("failed to create built-in provisioner key: %w", err))
|
||||
}
|
||||
_, err = q.InsertProvisionerKey(context.Background(), database.InsertProvisionerKeyParams{
|
||||
ID: uuid.MustParse(codersdk.ProvisionerKeyIDUserAuth),
|
||||
OrganizationID: defaultOrg.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
HashedSecret: []byte{},
|
||||
Name: codersdk.ProvisionerKeyNameUserAuth,
|
||||
Tags: map[string]string{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(xerrors.Errorf("failed to create user-auth provisioner key: %w", err))
|
||||
}
|
||||
_, err = q.InsertProvisionerKey(context.Background(), database.InsertProvisionerKeyParams{
|
||||
ID: uuid.MustParse(codersdk.ProvisionerKeyIDPSK),
|
||||
OrganizationID: defaultOrg.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
HashedSecret: []byte{},
|
||||
Name: codersdk.ProvisionerKeyNamePSK,
|
||||
Tags: map[string]string{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(xerrors.Errorf("failed to create psk provisioner key: %w", err))
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
@@ -7582,6 +7618,25 @@ func (q *FakeQuerier) ListProvisionerKeysByOrganization(_ context.Context, organ
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) ListProvisionerKeysByOrganizationExcludeReserved(_ context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
keys := make([]database.ProvisionerKey, 0)
|
||||
for _, key := range q.provisionerKeys {
|
||||
if key.ID.String() == codersdk.ProvisionerKeyIDBuiltIn ||
|
||||
key.ID.String() == codersdk.ProvisionerKeyIDUserAuth ||
|
||||
key.ID.String() == codersdk.ProvisionerKeyIDPSK {
|
||||
continue
|
||||
}
|
||||
if key.OrganizationID == organizationID {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@@ -9311,6 +9366,7 @@ func (q *FakeQuerier) UpsertProvisionerDaemon(_ context.Context, arg database.Up
|
||||
Version: arg.Version,
|
||||
APIVersion: arg.APIVersion,
|
||||
OrganizationID: arg.OrganizationID,
|
||||
KeyID: arg.KeyID,
|
||||
}
|
||||
q.provisionerDaemons = append(q.provisionerDaemons, d)
|
||||
return d, nil
|
||||
|
||||
@@ -1922,6 +1922,13 @@ func (m metricsStore) ListProvisionerKeysByOrganization(ctx context.Context, org
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) ListProvisionerKeysByOrganizationExcludeReserved(ctx context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.ListProvisionerKeysByOrganizationExcludeReserved(ctx, organizationID)
|
||||
m.queryLatencies.WithLabelValues("ListProvisionerKeysByOrganizationExcludeReserved").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.ListWorkspaceAgentPortShares(ctx, workspaceID)
|
||||
|
||||
@@ -4045,6 +4045,21 @@ func (mr *MockStoreMockRecorder) ListProvisionerKeysByOrganization(arg0, arg1 an
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProvisionerKeysByOrganization", reflect.TypeOf((*MockStore)(nil).ListProvisionerKeysByOrganization), arg0, arg1)
|
||||
}
|
||||
|
||||
// ListProvisionerKeysByOrganizationExcludeReserved mocks base method.
|
||||
func (m *MockStore) ListProvisionerKeysByOrganizationExcludeReserved(arg0 context.Context, arg1 uuid.UUID) ([]database.ProvisionerKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListProvisionerKeysByOrganizationExcludeReserved", arg0, arg1)
|
||||
ret0, _ := ret[0].([]database.ProvisionerKey)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListProvisionerKeysByOrganizationExcludeReserved indicates an expected call of ListProvisionerKeysByOrganizationExcludeReserved.
|
||||
func (mr *MockStoreMockRecorder) ListProvisionerKeysByOrganizationExcludeReserved(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProvisionerKeysByOrganizationExcludeReserved", reflect.TypeOf((*MockStore)(nil).ListProvisionerKeysByOrganizationExcludeReserved), arg0, arg1)
|
||||
}
|
||||
|
||||
// ListWorkspaceAgentPortShares mocks base method.
|
||||
func (m *MockStore) ListWorkspaceAgentPortShares(arg0 context.Context, arg1 uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbrollup"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/provisionerd/proto"
|
||||
"github.com/coder/coder/v2/provisionersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
@@ -412,6 +413,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
OrganizationID: defaultOrg.ID,
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
|
||||
@@ -424,6 +426,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
OrganizationID: defaultOrg.ID,
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
|
||||
@@ -438,6 +441,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
OrganizationID: defaultOrg.ID,
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
|
||||
@@ -453,6 +457,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
OrganizationID: defaultOrg.ID,
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
Generated
+5
-1
@@ -832,7 +832,8 @@ CREATE TABLE provisioner_daemons (
|
||||
last_seen_at timestamp with time zone,
|
||||
version text DEFAULT ''::text NOT NULL,
|
||||
api_version text DEFAULT '1.0'::text NOT NULL,
|
||||
organization_id uuid NOT NULL
|
||||
organization_id uuid NOT NULL,
|
||||
key_id uuid NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN provisioner_daemons.api_version IS 'The API version of the provisioner daemon';
|
||||
@@ -2095,6 +2096,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_key_id_fkey FOREIGN KEY (key_id) REFERENCES provisioner_keys(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;
|
||||
|
||||
|
||||
@@ -27,6 +27,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;
|
||||
ForeignKeyProvisionerDaemonsKeyID ForeignKeyConstraint = "provisioner_daemons_key_id_fkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_key_id_fkey FOREIGN KEY (key_id) REFERENCES provisioner_keys(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;
|
||||
ForeignKeyProvisionerJobTimingsJobID ForeignKeyConstraint = "provisioner_job_timings_job_id_fkey" // ALTER TABLE ONLY provisioner_job_timings ADD CONSTRAINT provisioner_job_timings_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE provisioner_daemons DROP COLUMN key_id;
|
||||
|
||||
DELETE FROM provisioner_keys WHERE name = 'built-in';
|
||||
DELETE FROM provisioner_keys WHERE name = 'psk';
|
||||
DELETE FROM provisioner_keys WHERE name = 'user-auth';
|
||||
@@ -0,0 +1,6 @@
|
||||
INSERT INTO provisioner_keys (id, created_at, organization_id, name, hashed_secret, tags) VALUES ('00000000-0000-0000-0000-000000000001'::uuid, NOW(), (SELECT id FROM organizations WHERE is_default = true), 'built-in', ''::bytea, '{}');
|
||||
INSERT INTO provisioner_keys (id, created_at, organization_id, name, hashed_secret, tags) VALUES ('00000000-0000-0000-0000-000000000002'::uuid, NOW(), (SELECT id FROM organizations WHERE is_default = true), 'user-auth', ''::bytea, '{}');
|
||||
INSERT INTO provisioner_keys (id, created_at, organization_id, name, hashed_secret, tags) VALUES ('00000000-0000-0000-0000-000000000003'::uuid, NOW(), (SELECT id FROM organizations WHERE is_default = true), 'psk', ''::bytea, '{}');
|
||||
|
||||
ALTER TABLE provisioner_daemons ADD COLUMN key_id UUID REFERENCES provisioner_keys(id) ON DELETE CASCADE DEFAULT '00000000-0000-0000-0000-000000000001'::uuid NOT NULL;
|
||||
ALTER TABLE provisioner_daemons ALTER COLUMN key_id DROP DEFAULT;
|
||||
@@ -2311,6 +2311,7 @@ type ProvisionerDaemon struct {
|
||||
// The API version of the provisioner daemon
|
||||
APIVersion string `db:"api_version" json:"api_version"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
KeyID uuid.UUID `db:"key_id" json:"key_id"`
|
||||
}
|
||||
|
||||
type ProvisionerJob struct {
|
||||
|
||||
@@ -391,6 +391,7 @@ type sqlcQuerier interface {
|
||||
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
|
||||
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
|
||||
ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error)
|
||||
ListProvisionerKeysByOrganizationExcludeReserved(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error)
|
||||
ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error)
|
||||
// Arguments are optional with uuid.Nil to ignore.
|
||||
// - Use just 'organization_id' to get all members of an org
|
||||
|
||||
@@ -4971,7 +4971,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, organization_id
|
||||
id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version, organization_id, key_id
|
||||
FROM
|
||||
provisioner_daemons
|
||||
`
|
||||
@@ -4996,6 +4996,7 @@ func (q *sqlQuerier) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDa
|
||||
&i.Version,
|
||||
&i.APIVersion,
|
||||
&i.OrganizationID,
|
||||
&i.KeyID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -5012,7 +5013,7 @@ func (q *sqlQuerier) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDa
|
||||
|
||||
const getProvisionerDaemonsByOrganization = `-- name: GetProvisionerDaemonsByOrganization :many
|
||||
SELECT
|
||||
id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version, organization_id
|
||||
id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version, organization_id, key_id
|
||||
FROM
|
||||
provisioner_daemons
|
||||
WHERE
|
||||
@@ -5039,6 +5040,7 @@ func (q *sqlQuerier) GetProvisionerDaemonsByOrganization(ctx context.Context, or
|
||||
&i.Version,
|
||||
&i.APIVersion,
|
||||
&i.OrganizationID,
|
||||
&i.KeyID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -5084,7 +5086,8 @@ INSERT INTO
|
||||
last_seen_at,
|
||||
"version",
|
||||
organization_id,
|
||||
api_version
|
||||
api_version,
|
||||
key_id
|
||||
)
|
||||
VALUES (
|
||||
gen_random_uuid(),
|
||||
@@ -5095,15 +5098,17 @@ VALUES (
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8
|
||||
$8,
|
||||
$9
|
||||
) ON CONFLICT("organization_id", "name", LOWER(COALESCE(tags ->> 'owner'::text, ''::text))) DO UPDATE SET
|
||||
provisioners = $3,
|
||||
tags = $4,
|
||||
last_seen_at = $5,
|
||||
"version" = $6,
|
||||
api_version = $8,
|
||||
organization_id = $7
|
||||
RETURNING id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version, organization_id
|
||||
organization_id = $7,
|
||||
key_id = $9
|
||||
RETURNING id, created_at, name, provisioners, replica_id, tags, last_seen_at, version, api_version, organization_id, key_id
|
||||
`
|
||||
|
||||
type UpsertProvisionerDaemonParams struct {
|
||||
@@ -5115,6 +5120,7 @@ type UpsertProvisionerDaemonParams struct {
|
||||
Version string `db:"version" json:"version"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
APIVersion string `db:"api_version" json:"api_version"`
|
||||
KeyID uuid.UUID `db:"key_id" json:"key_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error) {
|
||||
@@ -5127,6 +5133,7 @@ func (q *sqlQuerier) UpsertProvisionerDaemon(ctx context.Context, arg UpsertProv
|
||||
arg.Version,
|
||||
arg.OrganizationID,
|
||||
arg.APIVersion,
|
||||
arg.KeyID,
|
||||
)
|
||||
var i ProvisionerDaemon
|
||||
err := row.Scan(
|
||||
@@ -5140,6 +5147,7 @@ func (q *sqlQuerier) UpsertProvisionerDaemon(ctx context.Context, arg UpsertProv
|
||||
&i.Version,
|
||||
&i.APIVersion,
|
||||
&i.OrganizationID,
|
||||
&i.KeyID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -6021,6 +6029,54 @@ func (q *sqlQuerier) ListProvisionerKeysByOrganization(ctx context.Context, orga
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listProvisionerKeysByOrganizationExcludeReserved = `-- name: ListProvisionerKeysByOrganizationExcludeReserved :many
|
||||
SELECT
|
||||
id, created_at, organization_id, name, hashed_secret, tags
|
||||
FROM
|
||||
provisioner_keys
|
||||
WHERE
|
||||
organization_id = $1
|
||||
AND
|
||||
-- exclude reserved built-in key
|
||||
id != '00000000-0000-0000-0000-000000000001'::uuid
|
||||
AND
|
||||
-- exclude reserved user-auth key
|
||||
id != '00000000-0000-0000-0000-000000000002'::uuid
|
||||
AND
|
||||
-- exclude reserved psk key
|
||||
id != '00000000-0000-0000-0000-000000000003'::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) ListProvisionerKeysByOrganizationExcludeReserved(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listProvisionerKeysByOrganizationExcludeReserved, organizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ProvisionerKey
|
||||
for rows.Next() {
|
||||
var i ProvisionerKey
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.OrganizationID,
|
||||
&i.Name,
|
||||
&i.HashedSecret,
|
||||
&i.Tags,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWorkspaceProxies = `-- name: GetWorkspaceProxies :many
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
|
||||
@@ -33,7 +33,8 @@ INSERT INTO
|
||||
last_seen_at,
|
||||
"version",
|
||||
organization_id,
|
||||
api_version
|
||||
api_version,
|
||||
key_id
|
||||
)
|
||||
VALUES (
|
||||
gen_random_uuid(),
|
||||
@@ -44,14 +45,16 @@ VALUES (
|
||||
@last_seen_at,
|
||||
@version,
|
||||
@organization_id,
|
||||
@api_version
|
||||
@api_version,
|
||||
@key_id
|
||||
) ON CONFLICT("organization_id", "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,
|
||||
organization_id = @organization_id
|
||||
organization_id = @organization_id,
|
||||
key_id = @key_id
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateProvisionerDaemonLastSeenAt :exec
|
||||
|
||||
@@ -37,6 +37,23 @@ WHERE
|
||||
AND
|
||||
lower(name) = lower(@name);
|
||||
|
||||
-- name: ListProvisionerKeysByOrganizationExcludeReserved :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
provisioner_keys
|
||||
WHERE
|
||||
organization_id = $1
|
||||
AND
|
||||
-- exclude reserved built-in key
|
||||
id != '00000000-0000-0000-0000-000000000001'::uuid
|
||||
AND
|
||||
-- exclude reserved user-auth key
|
||||
id != '00000000-0000-0000-0000-000000000002'::uuid
|
||||
AND
|
||||
-- exclude reserved psk key
|
||||
id != '00000000-0000-0000-0000-000000000003'::uuid;
|
||||
|
||||
-- name: ListProvisionerKeysByOrganization :many
|
||||
SELECT
|
||||
*
|
||||
|
||||
@@ -2,7 +2,6 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
@@ -80,23 +79,10 @@ func (r *ProvisionerDaemonsReport) Run(ctx context.Context, opts *ProvisionerDae
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure stable order for display and for tests
|
||||
sort.Slice(daemons, func(i, j int) bool {
|
||||
return daemons[i].Name < daemons[j].Name
|
||||
})
|
||||
|
||||
for _, daemon := range daemons {
|
||||
// Daemon never connected, skip.
|
||||
if !daemon.LastSeenAt.Valid {
|
||||
continue
|
||||
}
|
||||
// Daemon has gone away, skip.
|
||||
if now.Sub(daemon.LastSeenAt.Time) > (opts.StaleInterval) {
|
||||
continue
|
||||
}
|
||||
|
||||
recentDaemons := db2sdk.RecentProvisionerDaemons(now, opts.StaleInterval, daemons)
|
||||
for _, daemon := range recentDaemons {
|
||||
it := healthsdk.ProvisionerDaemonsReportItem{
|
||||
ProvisionerDaemon: db2sdk.ProvisionerDaemon(daemon),
|
||||
ProvisionerDaemon: daemon,
|
||||
Warnings: make([]health.Message, 0),
|
||||
}
|
||||
|
||||
|
||||
@@ -1956,6 +1956,7 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi
|
||||
Version: buildinfo.Version(),
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
OrganizationID: defOrg.ID,
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
type ProvisionerDaemon struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
|
||||
KeyID uuid.UUID `json:"key_id" format:"uuid"`
|
||||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||
LastSeenAt NullTime `json:"last_seen_at,omitempty" format:"date-time"`
|
||||
Name string `json:"name"`
|
||||
@@ -282,6 +283,31 @@ type ProvisionerKey struct {
|
||||
// HashedSecret - never include the access token in the API response
|
||||
}
|
||||
|
||||
type ProvisionerKeyDaemons struct {
|
||||
Key ProvisionerKey `json:"key"`
|
||||
Daemons []ProvisionerDaemon `json:"daemons"`
|
||||
}
|
||||
|
||||
const (
|
||||
ProvisionerKeyIDBuiltIn = "00000000-0000-0000-0000-000000000001"
|
||||
ProvisionerKeyIDUserAuth = "00000000-0000-0000-0000-000000000002"
|
||||
ProvisionerKeyIDPSK = "00000000-0000-0000-0000-000000000003"
|
||||
)
|
||||
|
||||
const (
|
||||
ProvisionerKeyNameBuiltIn = "built-in"
|
||||
ProvisionerKeyNameUserAuth = "user-auth"
|
||||
ProvisionerKeyNamePSK = "psk"
|
||||
)
|
||||
|
||||
func ReservedProvisionerKeyNames() []string {
|
||||
return []string{
|
||||
ProvisionerKeyNameBuiltIn,
|
||||
ProvisionerKeyNameUserAuth,
|
||||
ProvisionerKeyNamePSK,
|
||||
}
|
||||
}
|
||||
|
||||
type CreateProvisionerKeyRequest struct {
|
||||
Name string `json:"name"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
@@ -327,6 +353,24 @@ func (c *Client) ListProvisionerKeys(ctx context.Context, organizationID uuid.UU
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// ListProvisionerKeyDaemons lists all provisioner keys with their associated daemons for an organization.
|
||||
func (c *Client) ListProvisionerKeyDaemons(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKeyDaemons, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/v2/organizations/%s/provisionerkeys/daemons", organizationID.String()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
var resp []ProvisionerKeyDaemons
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// DeleteProvisionerKey deletes a provisioner key.
|
||||
func (c *Client) DeleteProvisionerKey(ctx context.Context, organizationID uuid.UUID, name string) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete,
|
||||
|
||||
Generated
+1
@@ -290,6 +290,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
|
||||
Generated
+94
@@ -1493,6 +1493,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
@@ -1522,6 +1523,7 @@ Status Code **200**
|
||||
| `» api_version` | string | false | | |
|
||||
| `» created_at` | string(date-time) | false | | |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
| `» key_id` | string(uuid) | false | | |
|
||||
| `» last_seen_at` | string(date-time) | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| `» organization_id` | string(uuid) | false | | |
|
||||
@@ -1655,6 +1657,98 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/provis
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## List provisioner key daemons
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisionerkeys/daemons \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /organizations/{organization}/provisionerkeys/daemons`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| -------------- | ---- | ------ | -------- | --------------- |
|
||||
| `organization` | path | string | true | Organization ID |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"daemons": [
|
||||
{
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"provisioners": ["string"],
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"version": "string"
|
||||
}
|
||||
],
|
||||
"key": {
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"organization": "452c1a86-a0af-475b-b03f-724878b0f387",
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------- |
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.ProvisionerKeyDaemons](schemas.md#codersdkprovisionerkeydaemons) |
|
||||
|
||||
<h3 id="list-provisioner-key-daemons-responseschema">Response Schema</h3>
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------------- | ------------------------------------------------------------ | -------- | ------------ | ----------- |
|
||||
| `[array item]` | array | false | | |
|
||||
| `» daemons` | array | false | | |
|
||||
| `»» api_version` | string | false | | |
|
||||
| `»» created_at` | string(date-time) | false | | |
|
||||
| `»» id` | string(uuid) | false | | |
|
||||
| `»» key_id` | string(uuid) | false | | |
|
||||
| `»» last_seen_at` | string(date-time) | false | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» organization_id` | string(uuid) | false | | |
|
||||
| `»» provisioners` | array | false | | |
|
||||
| `»» tags` | object | false | | |
|
||||
| `»»» [any property]` | string | false | | |
|
||||
| `»» version` | string | false | | |
|
||||
| `» key` | [codersdk.ProvisionerKey](schemas.md#codersdkprovisionerkey) | false | | |
|
||||
| `»» created_at` | string(date-time) | false | | |
|
||||
| `»» id` | string(uuid) | false | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» organization` | string(uuid) | false | | |
|
||||
| `»» tags` | object | false | | |
|
||||
| `»»» [any property]` | string | false | | |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Delete provisioner key
|
||||
|
||||
### Code samples
|
||||
|
||||
Generated
+46
@@ -3988,6 +3988,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
@@ -4007,6 +4008,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `api_version` | string | false | | |
|
||||
| `created_at` | string | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `key_id` | string | false | | |
|
||||
| `last_seen_at` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `organization_id` | string | false | | |
|
||||
@@ -4149,6 +4151,47 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `tags` | object | false | | |
|
||||
| » `[any property]` | string | false | | |
|
||||
|
||||
## codersdk.ProvisionerKeyDaemons
|
||||
|
||||
```json
|
||||
{
|
||||
"daemons": [
|
||||
{
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"provisioners": ["string"],
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"version": "string"
|
||||
}
|
||||
],
|
||||
"key": {
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"organization": "452c1a86-a0af-475b-b03f-724878b0f387",
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| --------- | ----------------------------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `daemons` | array of [codersdk.ProvisionerDaemon](#codersdkprovisionerdaemon) | false | | |
|
||||
| `key` | [codersdk.ProvisionerKey](#codersdkprovisionerkey) | false | | |
|
||||
|
||||
## codersdk.ProvisionerLogLevel
|
||||
|
||||
```json
|
||||
@@ -8585,6 +8628,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
@@ -8704,6 +8748,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
@@ -8758,6 +8803,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
|
||||
@@ -331,6 +331,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
)
|
||||
r.Get("/", api.provisionerKeys)
|
||||
r.Post("/", api.postProvisionerKey)
|
||||
r.Get("/daemons", api.provisionerKeyDaemons)
|
||||
r.Route("/{provisionerkey}", func(r chi.Router) {
|
||||
r.Use(
|
||||
httpmw.ExtractProvisionerKeyParam(options.Database),
|
||||
|
||||
@@ -82,56 +82,77 @@ type provisionerDaemonAuth struct {
|
||||
|
||||
// authorize returns mutated tags if the given HTTP request is authorized to access the provisioner daemon
|
||||
// protobuf API, and returns nil, err otherwise.
|
||||
func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags map[string]string) (map[string]string, error) {
|
||||
func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags map[string]string) (uuid.UUID, map[string]string, error) {
|
||||
ctx := r.Context()
|
||||
apiKey, apiKeyOK := httpmw.APIKeyOptional(r)
|
||||
pk, pkOK := httpmw.ProvisionerKeyAuthOptional(r)
|
||||
provAuth := httpmw.ProvisionerDaemonAuthenticated(r)
|
||||
if !provAuth && !apiKeyOK {
|
||||
return nil, xerrors.New("no API key or provisioner key provided")
|
||||
return uuid.Nil, nil, xerrors.New("no API key or provisioner key provided")
|
||||
}
|
||||
if apiKeyOK && pkOK {
|
||||
return nil, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.")
|
||||
return uuid.Nil, nil, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.")
|
||||
}
|
||||
|
||||
// Provisioner Key Auth
|
||||
if pkOK {
|
||||
if pk.OrganizationID != orgID {
|
||||
return nil, xerrors.New("provisioner key unauthorized")
|
||||
return uuid.Nil, nil, xerrors.New("provisioner key unauthorized")
|
||||
}
|
||||
if tags != nil && !maps.Equal(tags, map[string]string{}) {
|
||||
return nil, xerrors.New("tags are not allowed when using a provisioner key")
|
||||
return uuid.Nil, nil, xerrors.New("tags are not allowed when using a provisioner key")
|
||||
}
|
||||
|
||||
// If using provisioner key / PSK auth, the daemon is, by definition, scoped to the organization.
|
||||
// Use the provisioner key tags here.
|
||||
tags = provisionersdk.MutateTags(uuid.Nil, pk.Tags)
|
||||
return tags, nil
|
||||
return pk.ID, tags, nil
|
||||
}
|
||||
|
||||
// User Auth
|
||||
tags = provisionersdk.MutateTags(apiKey.UserID, tags)
|
||||
if tags[provisionersdk.TagScope] == provisionersdk.ScopeUser {
|
||||
// Any authenticated user can create provisioner daemons scoped
|
||||
// for jobs that they own,
|
||||
return tags, nil
|
||||
}
|
||||
ua := httpmw.UserAuthorization(r)
|
||||
err := p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(orgID))
|
||||
if err != nil {
|
||||
if !provAuth {
|
||||
return nil, xerrors.New("user unauthorized")
|
||||
if apiKeyOK {
|
||||
userKey, err := uuid.Parse(codersdk.ProvisionerKeyIDUserAuth)
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, xerrors.Errorf("parse user provisioner key id: %w", err)
|
||||
}
|
||||
|
||||
// Allow fallback to PSK auth if the user is not allowed to create provisioner daemons.
|
||||
// This is to preserve backwards compatibility with existing user provisioner daemons.
|
||||
// If using PSK auth, the daemon is, by definition, scoped to the organization.
|
||||
tags = provisionersdk.MutateTags(uuid.Nil, tags)
|
||||
return tags, nil
|
||||
tags = provisionersdk.MutateTags(apiKey.UserID, tags)
|
||||
if tags[provisionersdk.TagScope] == provisionersdk.ScopeUser {
|
||||
// Any authenticated user can create provisioner daemons scoped
|
||||
// for jobs that they own,
|
||||
return userKey, tags, nil
|
||||
}
|
||||
ua := httpmw.UserAuthorization(r)
|
||||
err = p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(orgID))
|
||||
if err != nil {
|
||||
if !provAuth {
|
||||
return uuid.Nil, nil, xerrors.New("user unauthorized")
|
||||
}
|
||||
|
||||
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, xerrors.Errorf("parse psk provisioner key id: %w", err)
|
||||
}
|
||||
|
||||
// Allow fallback to PSK auth if the user is not allowed to create provisioner daemons.
|
||||
// This is to preserve backwards compatibility with existing user provisioner daemons.
|
||||
// If using PSK auth, the daemon is, by definition, scoped to the organization.
|
||||
tags = provisionersdk.MutateTags(uuid.Nil, tags)
|
||||
|
||||
return pskKey, tags, nil
|
||||
}
|
||||
|
||||
return userKey, tags, nil
|
||||
}
|
||||
|
||||
// User is allowed to create provisioner daemons
|
||||
return tags, nil
|
||||
// PSK Auth
|
||||
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, xerrors.Errorf("parse psk provisioner key id: %w", err)
|
||||
}
|
||||
|
||||
tags = provisionersdk.MutateTags(uuid.Nil, tags)
|
||||
return pskKey, tags, nil
|
||||
}
|
||||
|
||||
// Serves the provisioner daemon protobuf API over a WebSocket.
|
||||
@@ -194,7 +215,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
|
||||
api.Logger.Warn(ctx, "unnamed provisioner daemon")
|
||||
}
|
||||
|
||||
tags, err := api.provisionerDaemonAuth.authorize(r, organization.ID, tags)
|
||||
keyID, tags, err := api.provisionerDaemonAuth.authorize(r, organization.ID, tags)
|
||||
if err != nil {
|
||||
api.Logger.Warn(ctx, "unauthorized provisioner daemon serve request", slog.F("tags", tags), slog.Error(err))
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden,
|
||||
@@ -267,6 +288,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
|
||||
Version: versionHdrVal,
|
||||
APIVersion: apiVersion,
|
||||
OrganizationID: organization.ID,
|
||||
KeyID: keyID,
|
||||
})
|
||||
if err != nil {
|
||||
if !xerrors.Is(err, context.Canceled) {
|
||||
|
||||
@@ -743,6 +743,7 @@ func TestGetProvisionerDaemons(t *testing.T) {
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
ProvisionerDaemonPSK: "provisionersftw",
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureExternalProvisionerDaemons: 1,
|
||||
@@ -752,6 +753,16 @@ func TestGetProvisionerDaemons(t *testing.T) {
|
||||
})
|
||||
org := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{})
|
||||
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, org.ID, rbac.ScopedRoleOrgAdmin(org.ID))
|
||||
|
||||
res, err := orgAdmin.CreateProvisionerKey(context.Background(), org.ID, codersdk.CreateProvisionerKeyRequest{
|
||||
Name: "my-key",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
keys, err := orgAdmin.ListProvisionerKeys(context.Background(), org.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, 1)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
daemonName := testutil.MustRandString(t, 63)
|
||||
@@ -762,17 +773,32 @@ func TestGetProvisionerDaemons(t *testing.T) {
|
||||
Provisioners: []codersdk.ProvisionerType{
|
||||
codersdk.ProvisionerTypeEcho,
|
||||
},
|
||||
Tags: map[string]string{},
|
||||
Tags: map[string]string{},
|
||||
ProvisionerKey: res.Key,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
srv.DRPCConn().Close()
|
||||
|
||||
daemons, err := orgAdmin.OrganizationProvisionerDaemons(ctx, org.ID)
|
||||
require.NoError(t, err)
|
||||
if assert.Len(t, daemons, 1) {
|
||||
assert.Equal(t, daemonName, daemons[0].Name)
|
||||
assert.Equal(t, buildinfo.Version(), daemons[0].Version)
|
||||
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
|
||||
}
|
||||
require.Len(t, daemons, 1)
|
||||
|
||||
assert.Equal(t, daemonName, daemons[0].Name)
|
||||
assert.Equal(t, buildinfo.Version(), daemons[0].Version)
|
||||
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
|
||||
assert.Equal(t, keys[0].ID, daemons[0].KeyID)
|
||||
|
||||
pkDaemons, err := orgAdmin.ListProvisionerKeyDaemons(ctx, org.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, pkDaemons, 1)
|
||||
require.Len(t, pkDaemons[0].Daemons, 1)
|
||||
assert.Equal(t, keys[0].ID, pkDaemons[0].Key.ID)
|
||||
assert.Equal(t, keys[0].Name, pkDaemons[0].Key.Name)
|
||||
|
||||
assert.Equal(t, daemonName, pkDaemons[0].Daemons[0].Name)
|
||||
assert.Equal(t, buildinfo.Version(), pkDaemons[0].Daemons[0].Version)
|
||||
assert.Equal(t, proto.CurrentVersion.String(), pkDaemons[0].Daemons[0].APIVersion)
|
||||
assert.Equal(t, keys[0].ID, pkDaemons[0].Daemons[0].KeyID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@ package coderd
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/provisionerdserver"
|
||||
"github.com/coder/coder/v2/coderd/provisionerkey"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
@@ -54,6 +59,21 @@ func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if slices.ContainsFunc(codersdk.ReservedProvisionerKeyNames(), func(s string) bool {
|
||||
return strings.EqualFold(req.Name, s)
|
||||
}) {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("Name cannot be reserved name '%s'", req.Name),
|
||||
Validations: []codersdk.ValidationError{
|
||||
{
|
||||
Field: "name",
|
||||
Detail: fmt.Sprintf("Name cannot be reserved name '%s'", req.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
params, token, err := provisionerkey.New(organization.ID, req.Name, req.Tags)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
@@ -89,7 +109,7 @@ func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
organization := httpmw.OrganizationParam(r)
|
||||
|
||||
pks, err := api.Database.ListProvisionerKeysByOrganization(ctx, organization.ID)
|
||||
pks, err := api.Database.ListProvisionerKeysByOrganizationExcludeReserved(ctx, organization.ID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
@@ -98,6 +118,54 @@ func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertProvisionerKeys(pks))
|
||||
}
|
||||
|
||||
// @Summary List provisioner key daemons
|
||||
// @ID list-provisioner-key-daemons
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param organization path string true "Organization ID"
|
||||
// @Success 200 {object} []codersdk.ProvisionerKeyDaemons
|
||||
// @Router /organizations/{organization}/provisionerkeys/daemons [get]
|
||||
func (api *API) provisionerKeyDaemons(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
organization := httpmw.OrganizationParam(r)
|
||||
|
||||
pks, err := api.Database.ListProvisionerKeysByOrganization(ctx, organization.ID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
sdkKeys := convertProvisionerKeys(pks)
|
||||
|
||||
daemons, err := api.Database.GetProvisionerDaemonsByOrganization(ctx, organization.ID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
// provisionerdserver.DefaultHeartbeatInterval*3 matches the healthcheck report staleInterval.
|
||||
recentDaemons := db2sdk.RecentProvisionerDaemons(time.Now(), provisionerdserver.DefaultHeartbeatInterval*3, daemons)
|
||||
|
||||
pkDaemons := []codersdk.ProvisionerKeyDaemons{}
|
||||
for _, key := range sdkKeys {
|
||||
// currently we exclude user-auth from this list
|
||||
if key.ID.String() == codersdk.ProvisionerKeyIDUserAuth {
|
||||
continue
|
||||
}
|
||||
daemons := []codersdk.ProvisionerDaemon{}
|
||||
for _, daemon := range recentDaemons {
|
||||
if daemon.KeyID == key.ID {
|
||||
daemons = append(daemons, daemon)
|
||||
}
|
||||
}
|
||||
pkDaemons = append(pkDaemons, codersdk.ProvisionerKeyDaemons{
|
||||
Key: key,
|
||||
Daemons: daemons,
|
||||
})
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, pkDaemons)
|
||||
}
|
||||
|
||||
// @Summary Delete provisioner key
|
||||
// @ID delete-provisioner-key
|
||||
// @Security CoderSessionToken
|
||||
@@ -108,24 +176,18 @@ func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) {
|
||||
// @Router /organizations/{organization}/provisionerkeys/{provisionerkey} [delete]
|
||||
func (api *API) deleteProvisionerKey(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
organization := httpmw.OrganizationParam(r)
|
||||
provisionerKey := httpmw.ProvisionerKeyParam(r)
|
||||
|
||||
pk, err := api.Database.GetProvisionerKeyByName(ctx, database.GetProvisionerKeyByNameParams{
|
||||
OrganizationID: organization.ID,
|
||||
Name: provisionerKey.Name,
|
||||
})
|
||||
if err != nil {
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.InternalServerError(rw, err)
|
||||
if provisionerKey.ID.String() == codersdk.ProvisionerKeyIDBuiltIn ||
|
||||
provisionerKey.ID.String() == codersdk.ProvisionerKeyIDUserAuth ||
|
||||
provisionerKey.ID.String() == codersdk.ProvisionerKeyIDPSK {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("Cannot delete reserved '%s' provisioner key", provisionerKey.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = api.Database.DeleteProvisionerKey(ctx, pk.ID)
|
||||
err := api.Database.DeleteProvisionerKey(ctx, provisionerKey.ID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
|
||||
@@ -64,6 +64,20 @@ func TestProvisionerKeys(t *testing.T) {
|
||||
err = outsideOrgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, "key")
|
||||
require.ErrorContains(t, err, "Resource not found")
|
||||
|
||||
// org admin cannot create reserved provisioner keys
|
||||
_, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||
Name: codersdk.ProvisionerKeyNameBuiltIn,
|
||||
})
|
||||
require.ErrorContains(t, err, "reserved")
|
||||
_, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||
Name: codersdk.ProvisionerKeyNameUserAuth,
|
||||
})
|
||||
require.ErrorContains(t, err, "reserved")
|
||||
_, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||
Name: codersdk.ProvisionerKeyNamePSK,
|
||||
})
|
||||
require.ErrorContains(t, err, "reserved")
|
||||
|
||||
// org admin can list provisioner keys and get an empty list
|
||||
keys, err := orgAdmin.ListProvisionerKeys(ctx, owner.OrganizationID)
|
||||
require.NoError(t, err, "org admin list provisioner keys")
|
||||
@@ -111,4 +125,12 @@ func TestProvisionerKeys(t *testing.T) {
|
||||
// org admin cannot delete a provisioner key that doesn't exist
|
||||
err = orgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, "key")
|
||||
require.ErrorContains(t, err, "Resource not found")
|
||||
|
||||
// org admin cannot delete reserved provisioner keys
|
||||
err = orgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, codersdk.ProvisionerKeyNameBuiltIn)
|
||||
require.ErrorContains(t, err, "reserved")
|
||||
err = orgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, codersdk.ProvisionerKeyNameUserAuth)
|
||||
require.ErrorContains(t, err, "reserved")
|
||||
err = orgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, codersdk.ProvisionerKeyNamePSK)
|
||||
require.ErrorContains(t, err, "reserved")
|
||||
}
|
||||
|
||||
Generated
+7
@@ -1016,6 +1016,7 @@ export interface ProvisionerConfig {
|
||||
export interface ProvisionerDaemon {
|
||||
readonly id: string;
|
||||
readonly organization_id: string;
|
||||
readonly key_id: string;
|
||||
readonly created_at: string;
|
||||
readonly last_seen_at?: string;
|
||||
readonly name: string;
|
||||
@@ -1061,6 +1062,12 @@ export interface ProvisionerKey {
|
||||
readonly tags: Record<string, string>;
|
||||
}
|
||||
|
||||
// From codersdk/provisionerdaemons.go
|
||||
export interface ProvisionerKeyDaemons {
|
||||
readonly key: ProvisionerKey;
|
||||
readonly daemons: Readonly<Array<ProvisionerDaemon>>;
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
export interface ProvisionerTiming {
|
||||
readonly job_id: string;
|
||||
|
||||
@@ -567,6 +567,14 @@ export const MockOrganizationMember2: TypesGen.OrganizationMemberWithUserData =
|
||||
roles: [],
|
||||
};
|
||||
|
||||
export const MockProvisionerKey: TypesGen.ProvisionerKey = {
|
||||
id: "test-provisioner-key",
|
||||
organization: MockOrganization.id,
|
||||
created_at: "2022-05-17T17:39:01.382927298Z",
|
||||
name: "test-name",
|
||||
tags: { scope: "organization" },
|
||||
};
|
||||
|
||||
export const MockProvisioner: TypesGen.ProvisionerDaemon = {
|
||||
created_at: "2022-05-17T17:39:01.382927298Z",
|
||||
id: "test-provisioner",
|
||||
@@ -576,6 +584,7 @@ export const MockProvisioner: TypesGen.ProvisionerDaemon = {
|
||||
tags: { scope: "organization" },
|
||||
version: MockBuildInfo.version,
|
||||
api_version: MockBuildInfo.provisioner_api_version,
|
||||
key_id: MockProvisionerKey.id,
|
||||
};
|
||||
|
||||
export const MockUserProvisioner: TypesGen.ProvisionerDaemon = {
|
||||
@@ -3528,6 +3537,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
provisioner_daemon: {
|
||||
id: "e455b582-ac04-4323-9ad6-ab71301fa006",
|
||||
organization_id: MockOrganization.id,
|
||||
key_id: MockProvisionerKey.id,
|
||||
created_at: "2024-01-04T15:53:03.21563Z",
|
||||
last_seen_at: "2024-01-04T16:05:03.967551Z",
|
||||
name: "ok",
|
||||
@@ -3549,6 +3559,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
provisioner_daemon: {
|
||||
id: "00000000-0000-0000-000000000000",
|
||||
organization_id: MockOrganization.id,
|
||||
key_id: MockProvisionerKey.id,
|
||||
created_at: "2024-01-04T15:53:03.21563Z",
|
||||
last_seen_at: "2024-01-04T16:05:03.967551Z",
|
||||
name: "user-scoped",
|
||||
@@ -3570,6 +3581,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
provisioner_daemon: {
|
||||
id: "e455b582-ac04-4323-9ad6-ab71301fa006",
|
||||
organization_id: MockOrganization.id,
|
||||
key_id: MockProvisionerKey.id,
|
||||
created_at: "2024-01-04T15:53:03.21563Z",
|
||||
last_seen_at: "2024-01-04T16:05:03.967551Z",
|
||||
name: "unhappy",
|
||||
@@ -3726,6 +3738,7 @@ export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = {
|
||||
provisioner_daemon: {
|
||||
id: "e455b582-ac04-4323-9ad6-ab71301fa006",
|
||||
organization_id: MockOrganization.id,
|
||||
key_id: MockProvisionerKey.id,
|
||||
created_at: "2024-01-04T15:53:03.21563Z",
|
||||
last_seen_at: "2024-01-04T16:05:03.967551Z",
|
||||
name: "vvuurrkk-2",
|
||||
|
||||
Reference in New Issue
Block a user