mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: add provisioner key crud apis (#13857)
This commit is contained in:
Generated
+134
@@ -2676,6 +2676,110 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/organizations/{organization}/provisionerkeys": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Enterprise"
|
||||||
|
],
|
||||||
|
"summary": "List provisioner key",
|
||||||
|
"operationId": "list-provisioner-key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Organization ID",
|
||||||
|
"name": "organization",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/codersdk.ProvisionerKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Enterprise"
|
||||||
|
],
|
||||||
|
"summary": "Create provisioner key",
|
||||||
|
"operationId": "create-provisioner-key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Organization ID",
|
||||||
|
"name": "organization",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.CreateProvisionerKeyResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/organizations/{organization}/provisionerkeys/{provisionerkey}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Enterprise"
|
||||||
|
],
|
||||||
|
"summary": "Delete provisioner key",
|
||||||
|
"operationId": "delete-provisioner-key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Organization ID",
|
||||||
|
"name": "organization",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Provisioner key name",
|
||||||
|
"name": "provisionerkey",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/organizations/{organization}/templates": {
|
"/organizations/{organization}/templates": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -8609,6 +8713,14 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"codersdk.CreateProvisionerKeyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.CreateTemplateRequest": {
|
"codersdk.CreateTemplateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -10762,6 +10874,26 @@ const docTemplate = `{
|
|||||||
"ProvisionerJobUnknown"
|
"ProvisionerJobUnknown"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"codersdk.ProvisionerKey": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.ProvisionerLogLevel": {
|
"codersdk.ProvisionerLogLevel": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -10897,6 +11029,7 @@ const docTemplate = `{
|
|||||||
"organization",
|
"organization",
|
||||||
"organization_member",
|
"organization_member",
|
||||||
"provisioner_daemon",
|
"provisioner_daemon",
|
||||||
|
"provisioner_keys",
|
||||||
"replicas",
|
"replicas",
|
||||||
"system",
|
"system",
|
||||||
"tailnet_coordinator",
|
"tailnet_coordinator",
|
||||||
@@ -10924,6 +11057,7 @@ const docTemplate = `{
|
|||||||
"ResourceOrganization",
|
"ResourceOrganization",
|
||||||
"ResourceOrganizationMember",
|
"ResourceOrganizationMember",
|
||||||
"ResourceProvisionerDaemon",
|
"ResourceProvisionerDaemon",
|
||||||
|
"ResourceProvisionerKeys",
|
||||||
"ResourceReplicas",
|
"ResourceReplicas",
|
||||||
"ResourceSystem",
|
"ResourceSystem",
|
||||||
"ResourceTailnetCoordinator",
|
"ResourceTailnetCoordinator",
|
||||||
|
|||||||
Generated
+124
@@ -2346,6 +2346,100 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/organizations/{organization}/provisionerkeys": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": ["application/json"],
|
||||||
|
"tags": ["Enterprise"],
|
||||||
|
"summary": "List provisioner key",
|
||||||
|
"operationId": "list-provisioner-key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Organization ID",
|
||||||
|
"name": "organization",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/codersdk.ProvisionerKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": ["application/json"],
|
||||||
|
"tags": ["Enterprise"],
|
||||||
|
"summary": "Create provisioner key",
|
||||||
|
"operationId": "create-provisioner-key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Organization ID",
|
||||||
|
"name": "organization",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.CreateProvisionerKeyResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/organizations/{organization}/provisionerkeys/{provisionerkey}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": ["Enterprise"],
|
||||||
|
"summary": "Delete provisioner key",
|
||||||
|
"operationId": "delete-provisioner-key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Organization ID",
|
||||||
|
"name": "organization",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Provisioner key name",
|
||||||
|
"name": "provisionerkey",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/organizations/{organization}/templates": {
|
"/organizations/{organization}/templates": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -7661,6 +7755,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"codersdk.CreateProvisionerKeyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.CreateTemplateRequest": {
|
"codersdk.CreateTemplateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name", "template_version_id"],
|
"required": ["name", "template_version_id"],
|
||||||
@@ -9702,6 +9804,26 @@
|
|||||||
"ProvisionerJobUnknown"
|
"ProvisionerJobUnknown"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"codersdk.ProvisionerKey": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.ProvisionerLogLevel": {
|
"codersdk.ProvisionerLogLevel": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["debug"],
|
"enum": ["debug"],
|
||||||
@@ -9819,6 +9941,7 @@
|
|||||||
"organization",
|
"organization",
|
||||||
"organization_member",
|
"organization_member",
|
||||||
"provisioner_daemon",
|
"provisioner_daemon",
|
||||||
|
"provisioner_keys",
|
||||||
"replicas",
|
"replicas",
|
||||||
"system",
|
"system",
|
||||||
"tailnet_coordinator",
|
"tailnet_coordinator",
|
||||||
@@ -9846,6 +9969,7 @@
|
|||||||
"ResourceOrganization",
|
"ResourceOrganization",
|
||||||
"ResourceOrganizationMember",
|
"ResourceOrganizationMember",
|
||||||
"ResourceProvisionerDaemon",
|
"ResourceProvisionerDaemon",
|
||||||
|
"ResourceProvisionerKeys",
|
||||||
"ResourceReplicas",
|
"ResourceReplicas",
|
||||||
"ResourceSystem",
|
"ResourceSystem",
|
||||||
"ResourceTailnetCoordinator",
|
"ResourceTailnetCoordinator",
|
||||||
|
|||||||
@@ -1074,6 +1074,10 @@ func (q *querier) DeleteOrganizationMember(ctx context.Context, arg database.Del
|
|||||||
}, q.db.DeleteOrganizationMember)(ctx, arg)
|
}, q.db.DeleteOrganizationMember)(ctx, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *querier) DeleteProvisionerKey(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return deleteQ(q.log, q.auth, q.db.GetProvisionerKeyByID, q.db.DeleteProvisionerKey)(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
func (q *querier) DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error {
|
func (q *querier) DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error {
|
||||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
|
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1671,6 +1675,14 @@ func (q *querier) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt
|
|||||||
return q.db.GetProvisionerJobsCreatedAfter(ctx, createdAt)
|
return q.db.GetProvisionerJobsCreatedAfter(ctx, createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *querier) GetProvisionerKeyByID(ctx context.Context, id uuid.UUID) (database.ProvisionerKey, error) {
|
||||||
|
return fetch(q.log, q.auth, q.db.GetProvisionerKeyByID)(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *querier) GetProvisionerKeyByName(ctx context.Context, name database.GetProvisionerKeyByNameParams) (database.ProvisionerKey, error) {
|
||||||
|
return fetch(q.log, q.auth, q.db.GetProvisionerKeyByName)(ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
func (q *querier) GetProvisionerLogsAfterID(ctx context.Context, arg database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
func (q *querier) GetProvisionerLogsAfterID(ctx context.Context, arg database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
||||||
// Authorized read on job lets the actor also read the logs.
|
// Authorized read on job lets the actor also read the logs.
|
||||||
_, err := q.GetProvisionerJobByID(ctx, arg.JobID)
|
_, err := q.GetProvisionerJobByID(ctx, arg.JobID)
|
||||||
@@ -2615,6 +2627,10 @@ func (q *querier) InsertProvisionerJobLogs(ctx context.Context, arg database.Ins
|
|||||||
return q.db.InsertProvisionerJobLogs(ctx, arg)
|
return q.db.InsertProvisionerJobLogs(ctx, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *querier) InsertProvisionerKey(ctx context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
|
||||||
|
return insert(q.log, q.auth, rbac.ResourceProvisionerKeys.InOrg(arg.OrganizationID).WithID(arg.ID), q.db.InsertProvisionerKey)(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
func (q *querier) InsertReplica(ctx context.Context, arg database.InsertReplicaParams) (database.Replica, error) {
|
func (q *querier) InsertReplica(ctx context.Context, arg database.InsertReplicaParams) (database.Replica, error) {
|
||||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
|
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||||
return database.Replica{}, err
|
return database.Replica{}, err
|
||||||
@@ -2843,6 +2859,10 @@ func (q *querier) InsertWorkspaceResourceMetadata(ctx context.Context, arg datab
|
|||||||
return q.db.InsertWorkspaceResourceMetadata(ctx, arg)
|
return q.db.InsertWorkspaceResourceMetadata(ctx, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *querier) ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) {
|
||||||
|
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.ListProvisionerKeysByOrganization)(ctx, organizationID)
|
||||||
|
}
|
||||||
|
|
||||||
func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||||
workspace, err := q.db.GetWorkspaceByID(ctx, workspaceID)
|
workspace, err := q.db.GetWorkspaceByID(ctx, workspaceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -1800,6 +1801,58 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MethodTestSuite) TestProvisionerKeys() {
|
||||||
|
s.Run("InsertProvisionerKey", s.Subtest(func(db database.Store, check *expects) {
|
||||||
|
org := dbgen.Organization(s.T(), db, database.Organization{})
|
||||||
|
pk := database.ProvisionerKey{
|
||||||
|
ID: uuid.New(),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
OrganizationID: org.ID,
|
||||||
|
Name: strings.ToLower(coderdtest.RandomName(s.T())),
|
||||||
|
HashedSecret: []byte(coderdtest.RandomName(s.T())),
|
||||||
|
}
|
||||||
|
//nolint:gosimple // casting is not a simplification
|
||||||
|
check.Args(database.InsertProvisionerKeyParams{
|
||||||
|
ID: pk.ID,
|
||||||
|
CreatedAt: pk.CreatedAt,
|
||||||
|
OrganizationID: pk.OrganizationID,
|
||||||
|
Name: pk.Name,
|
||||||
|
HashedSecret: pk.HashedSecret,
|
||||||
|
}).Asserts(pk, policy.ActionCreate).Returns(pk)
|
||||||
|
}))
|
||||||
|
s.Run("GetProvisionerKeyByID", 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})
|
||||||
|
check.Args(pk.ID).Asserts(pk, policy.ActionRead).Returns(pk)
|
||||||
|
}))
|
||||||
|
s.Run("GetProvisionerKeyByName", 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})
|
||||||
|
check.Args(database.GetProvisionerKeyByNameParams{
|
||||||
|
OrganizationID: org.ID,
|
||||||
|
Name: pk.Name,
|
||||||
|
}).Asserts(pk, policy.ActionRead).Returns(pk)
|
||||||
|
}))
|
||||||
|
s.Run("ListProvisionerKeysByOrganization", 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})
|
||||||
|
check.Args(pk.ID).Asserts(pk, policy.ActionDelete).Returns()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MethodTestSuite) TestExtraMethods() {
|
func (s *MethodTestSuite) TestExtraMethods() {
|
||||||
s.Run("GetProvisionerDaemons", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("GetProvisionerDaemons", s.Subtest(func(db database.Store, check *expects) {
|
||||||
d, err := db.UpsertProvisionerDaemon(context.Background(), database.UpsertProvisionerDaemonParams{
|
d, err := db.UpsertProvisionerDaemon(context.Background(), database.UpsertProvisionerDaemonParams{
|
||||||
|
|||||||
@@ -465,6 +465,18 @@ func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig data
|
|||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProvisionerKey(t testing.TB, db database.Store, orig database.ProvisionerKey) database.ProvisionerKey {
|
||||||
|
key, err := db.InsertProvisionerKey(genCtx, database.InsertProvisionerKeyParams{
|
||||||
|
ID: takeFirst(orig.ID, uuid.New()),
|
||||||
|
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
||||||
|
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()),
|
||||||
|
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
|
||||||
|
HashedSecret: orig.HashedSecret,
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "insert provisioner key")
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
func WorkspaceApp(t testing.TB, db database.Store, orig database.WorkspaceApp) database.WorkspaceApp {
|
func WorkspaceApp(t testing.TB, db database.Store, orig database.WorkspaceApp) database.WorkspaceApp {
|
||||||
resource, err := db.InsertWorkspaceApp(genCtx, database.InsertWorkspaceAppParams{
|
resource, err := db.InsertWorkspaceApp(genCtx, database.InsertWorkspaceAppParams{
|
||||||
ID: takeFirst(orig.ID, uuid.New()),
|
ID: takeFirst(orig.ID, uuid.New()),
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ type data struct {
|
|||||||
provisionerDaemons []database.ProvisionerDaemon
|
provisionerDaemons []database.ProvisionerDaemon
|
||||||
provisionerJobLogs []database.ProvisionerJobLog
|
provisionerJobLogs []database.ProvisionerJobLog
|
||||||
provisionerJobs []database.ProvisionerJob
|
provisionerJobs []database.ProvisionerJob
|
||||||
|
provisionerKeys []database.ProvisionerKey
|
||||||
replicas []database.Replica
|
replicas []database.Replica
|
||||||
templateVersions []database.TemplateVersionTable
|
templateVersions []database.TemplateVersionTable
|
||||||
templateVersionParameters []database.TemplateVersionParameter
|
templateVersionParameters []database.TemplateVersionParameter
|
||||||
@@ -268,6 +269,13 @@ func validateDatabaseType(args interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newUniqueConstraintError(uc database.UniqueConstraint) *pq.Error {
|
||||||
|
newErr := *errUniqueConstraint
|
||||||
|
newErr.Constraint = string(uc)
|
||||||
|
|
||||||
|
return &newErr
|
||||||
|
}
|
||||||
|
|
||||||
func (*FakeQuerier) Ping(_ context.Context) (time.Duration, error) {
|
func (*FakeQuerier) Ping(_ context.Context) (time.Duration, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
@@ -1734,6 +1742,20 @@ func (q *FakeQuerier) DeleteOrganizationMember(_ context.Context, arg database.D
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *FakeQuerier) DeleteProvisionerKey(_ context.Context, id uuid.UUID) error {
|
||||||
|
q.mutex.Lock()
|
||||||
|
defer q.mutex.Unlock()
|
||||||
|
|
||||||
|
for i, key := range q.provisionerKeys {
|
||||||
|
if key.ID == id {
|
||||||
|
q.provisionerKeys = append(q.provisionerKeys[:i], q.provisionerKeys[i+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
func (q *FakeQuerier) DeleteReplicasUpdatedBefore(_ context.Context, before time.Time) error {
|
func (q *FakeQuerier) DeleteReplicasUpdatedBefore(_ context.Context, before time.Time) error {
|
||||||
q.mutex.Lock()
|
q.mutex.Lock()
|
||||||
defer q.mutex.Unlock()
|
defer q.mutex.Unlock()
|
||||||
@@ -3195,6 +3217,32 @@ func (q *FakeQuerier) GetProvisionerJobsCreatedAfter(_ context.Context, after ti
|
|||||||
return jobs, nil
|
return jobs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *FakeQuerier) GetProvisionerKeyByID(_ context.Context, id uuid.UUID) (database.ProvisionerKey, error) {
|
||||||
|
q.mutex.RLock()
|
||||||
|
defer q.mutex.RUnlock()
|
||||||
|
|
||||||
|
for _, key := range q.provisionerKeys {
|
||||||
|
if key.ID == id {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return database.ProvisionerKey{}, sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FakeQuerier) GetProvisionerKeyByName(_ context.Context, arg database.GetProvisionerKeyByNameParams) (database.ProvisionerKey, error) {
|
||||||
|
q.mutex.RLock()
|
||||||
|
defer q.mutex.RUnlock()
|
||||||
|
|
||||||
|
for _, key := range q.provisionerKeys {
|
||||||
|
if strings.EqualFold(key.Name, arg.Name) && key.OrganizationID == arg.OrganizationID {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return database.ProvisionerKey{}, sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
func (q *FakeQuerier) GetProvisionerLogsAfterID(_ context.Context, arg database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
func (q *FakeQuerier) GetProvisionerLogsAfterID(_ context.Context, arg database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
||||||
if err := validateDatabaseType(arg); err != nil {
|
if err := validateDatabaseType(arg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -6493,6 +6541,34 @@ func (q *FakeQuerier) InsertProvisionerJobLogs(_ context.Context, arg database.I
|
|||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *FakeQuerier) InsertProvisionerKey(_ context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
|
||||||
|
err := validateDatabaseType(arg)
|
||||||
|
if err != nil {
|
||||||
|
return database.ProvisionerKey{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
q.mutex.Lock()
|
||||||
|
defer q.mutex.Unlock()
|
||||||
|
|
||||||
|
for _, key := range q.provisionerKeys {
|
||||||
|
if key.ID == arg.ID || (key.OrganizationID == arg.OrganizationID && strings.EqualFold(key.Name, arg.Name)) {
|
||||||
|
return database.ProvisionerKey{}, newUniqueConstraintError(database.UniqueProvisionerKeysOrganizationIDNameIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gosimple
|
||||||
|
provisionerKey := database.ProvisionerKey{
|
||||||
|
ID: arg.ID,
|
||||||
|
CreatedAt: arg.CreatedAt,
|
||||||
|
OrganizationID: arg.OrganizationID,
|
||||||
|
Name: strings.ToLower(arg.Name),
|
||||||
|
HashedSecret: arg.HashedSecret,
|
||||||
|
}
|
||||||
|
q.provisionerKeys = append(q.provisionerKeys, provisionerKey)
|
||||||
|
|
||||||
|
return provisionerKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (q *FakeQuerier) InsertReplica(_ context.Context, arg database.InsertReplicaParams) (database.Replica, error) {
|
func (q *FakeQuerier) InsertReplica(_ context.Context, arg database.InsertReplicaParams) (database.Replica, error) {
|
||||||
if err := validateDatabaseType(arg); err != nil {
|
if err := validateDatabaseType(arg); err != nil {
|
||||||
return database.Replica{}, err
|
return database.Replica{}, err
|
||||||
@@ -7170,6 +7246,26 @@ func (q *FakeQuerier) InsertWorkspaceResourceMetadata(_ context.Context, arg dat
|
|||||||
return metadata, nil
|
return metadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *FakeQuerier) ListProvisionerKeysByOrganization(_ 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.OrganizationID == organizationID {
|
||||||
|
keys = append(keys, database.ProvisionerKey{
|
||||||
|
ID: key.ID,
|
||||||
|
CreatedAt: key.CreatedAt,
|
||||||
|
OrganizationID: key.OrganizationID,
|
||||||
|
Name: key.Name,
|
||||||
|
HashedSecret: key.HashedSecret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||||
q.mutex.Lock()
|
q.mutex.Lock()
|
||||||
defer q.mutex.Unlock()
|
defer q.mutex.Unlock()
|
||||||
|
|||||||
@@ -326,6 +326,13 @@ func (m metricsStore) DeleteOrganizationMember(ctx context.Context, arg database
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m metricsStore) DeleteProvisionerKey(ctx context.Context, id uuid.UUID) error {
|
||||||
|
start := time.Now()
|
||||||
|
r0 := m.s.DeleteProvisionerKey(ctx, id)
|
||||||
|
m.queryLatencies.WithLabelValues("DeleteProvisionerKey").Observe(time.Since(start).Seconds())
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
func (m metricsStore) DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error {
|
func (m metricsStore) DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := m.s.DeleteReplicasUpdatedBefore(ctx, updatedAt)
|
err := m.s.DeleteReplicasUpdatedBefore(ctx, updatedAt)
|
||||||
@@ -900,6 +907,20 @@ func (m metricsStore) GetProvisionerJobsCreatedAfter(ctx context.Context, create
|
|||||||
return jobs, err
|
return jobs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m metricsStore) GetProvisionerKeyByID(ctx context.Context, id uuid.UUID) (database.ProvisionerKey, error) {
|
||||||
|
start := time.Now()
|
||||||
|
r0, r1 := m.s.GetProvisionerKeyByID(ctx, id)
|
||||||
|
m.queryLatencies.WithLabelValues("GetProvisionerKeyByID").Observe(time.Since(start).Seconds())
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m metricsStore) GetProvisionerKeyByName(ctx context.Context, name database.GetProvisionerKeyByNameParams) (database.ProvisionerKey, error) {
|
||||||
|
start := time.Now()
|
||||||
|
r0, r1 := m.s.GetProvisionerKeyByName(ctx, name)
|
||||||
|
m.queryLatencies.WithLabelValues("GetProvisionerKeyByName").Observe(time.Since(start).Seconds())
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
func (m metricsStore) GetProvisionerLogsAfterID(ctx context.Context, arg database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
func (m metricsStore) GetProvisionerLogsAfterID(ctx context.Context, arg database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
logs, err := m.s.GetProvisionerLogsAfterID(ctx, arg)
|
logs, err := m.s.GetProvisionerLogsAfterID(ctx, arg)
|
||||||
@@ -1642,6 +1663,13 @@ func (m metricsStore) InsertProvisionerJobLogs(ctx context.Context, arg database
|
|||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m metricsStore) InsertProvisionerKey(ctx context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
|
||||||
|
start := time.Now()
|
||||||
|
r0, r1 := m.s.InsertProvisionerKey(ctx, arg)
|
||||||
|
m.queryLatencies.WithLabelValues("InsertProvisionerKey").Observe(time.Since(start).Seconds())
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
func (m metricsStore) InsertReplica(ctx context.Context, arg database.InsertReplicaParams) (database.Replica, error) {
|
func (m metricsStore) InsertReplica(ctx context.Context, arg database.InsertReplicaParams) (database.Replica, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
replica, err := m.s.InsertReplica(ctx, arg)
|
replica, err := m.s.InsertReplica(ctx, arg)
|
||||||
@@ -1803,6 +1831,13 @@ func (m metricsStore) InsertWorkspaceResourceMetadata(ctx context.Context, arg d
|
|||||||
return metadata, err
|
return metadata, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m metricsStore) ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) {
|
||||||
|
start := time.Now()
|
||||||
|
r0, r1 := m.s.ListProvisionerKeysByOrganization(ctx, organizationID)
|
||||||
|
m.queryLatencies.WithLabelValues("ListProvisionerKeysByOrganization").Observe(time.Since(start).Seconds())
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
func (m metricsStore) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
func (m metricsStore) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
r0, r1 := m.s.ListWorkspaceAgentPortShares(ctx, workspaceID)
|
r0, r1 := m.s.ListWorkspaceAgentPortShares(ctx, workspaceID)
|
||||||
|
|||||||
@@ -542,6 +542,20 @@ func (mr *MockStoreMockRecorder) DeleteOrganizationMember(arg0, arg1 any) *gomoc
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOrganizationMember", reflect.TypeOf((*MockStore)(nil).DeleteOrganizationMember), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOrganizationMember", reflect.TypeOf((*MockStore)(nil).DeleteOrganizationMember), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteProvisionerKey mocks base method.
|
||||||
|
func (m *MockStore) DeleteProvisionerKey(arg0 context.Context, arg1 uuid.UUID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteProvisionerKey", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteProvisionerKey indicates an expected call of DeleteProvisionerKey.
|
||||||
|
func (mr *MockStoreMockRecorder) DeleteProvisionerKey(arg0, arg1 any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProvisionerKey", reflect.TypeOf((*MockStore)(nil).DeleteProvisionerKey), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteReplicasUpdatedBefore mocks base method.
|
// DeleteReplicasUpdatedBefore mocks base method.
|
||||||
func (m *MockStore) DeleteReplicasUpdatedBefore(arg0 context.Context, arg1 time.Time) error {
|
func (m *MockStore) DeleteReplicasUpdatedBefore(arg0 context.Context, arg1 time.Time) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -1811,6 +1825,36 @@ func (mr *MockStoreMockRecorder) GetProvisionerJobsCreatedAfter(arg0, arg1 any)
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobsCreatedAfter", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobsCreatedAfter), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobsCreatedAfter", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobsCreatedAfter), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProvisionerKeyByID mocks base method.
|
||||||
|
func (m *MockStore) GetProvisionerKeyByID(arg0 context.Context, arg1 uuid.UUID) (database.ProvisionerKey, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetProvisionerKeyByID", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(database.ProvisionerKey)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProvisionerKeyByID indicates an expected call of GetProvisionerKeyByID.
|
||||||
|
func (mr *MockStoreMockRecorder) GetProvisionerKeyByID(arg0, arg1 any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerKeyByID", reflect.TypeOf((*MockStore)(nil).GetProvisionerKeyByID), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProvisionerKeyByName mocks base method.
|
||||||
|
func (m *MockStore) GetProvisionerKeyByName(arg0 context.Context, arg1 database.GetProvisionerKeyByNameParams) (database.ProvisionerKey, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetProvisionerKeyByName", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(database.ProvisionerKey)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProvisionerKeyByName indicates an expected call of GetProvisionerKeyByName.
|
||||||
|
func (mr *MockStoreMockRecorder) GetProvisionerKeyByName(arg0, arg1 any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerKeyByName", reflect.TypeOf((*MockStore)(nil).GetProvisionerKeyByName), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// GetProvisionerLogsAfterID mocks base method.
|
// GetProvisionerLogsAfterID mocks base method.
|
||||||
func (m *MockStore) GetProvisionerLogsAfterID(arg0 context.Context, arg1 database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
func (m *MockStore) GetProvisionerLogsAfterID(arg0 context.Context, arg1 database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -3441,6 +3485,21 @@ func (mr *MockStoreMockRecorder) InsertProvisionerJobLogs(arg0, arg1 any) *gomoc
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerJobLogs", reflect.TypeOf((*MockStore)(nil).InsertProvisionerJobLogs), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerJobLogs", reflect.TypeOf((*MockStore)(nil).InsertProvisionerJobLogs), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InsertProvisionerKey mocks base method.
|
||||||
|
func (m *MockStore) InsertProvisionerKey(arg0 context.Context, arg1 database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "InsertProvisionerKey", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(database.ProvisionerKey)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertProvisionerKey indicates an expected call of InsertProvisionerKey.
|
||||||
|
func (mr *MockStoreMockRecorder) InsertProvisionerKey(arg0, arg1 any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerKey", reflect.TypeOf((*MockStore)(nil).InsertProvisionerKey), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// InsertReplica mocks base method.
|
// InsertReplica mocks base method.
|
||||||
func (m *MockStore) InsertReplica(arg0 context.Context, arg1 database.InsertReplicaParams) (database.Replica, error) {
|
func (m *MockStore) InsertReplica(arg0 context.Context, arg1 database.InsertReplicaParams) (database.Replica, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -3778,6 +3837,21 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceResourceMetadata(arg0, arg1 any)
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceResourceMetadata", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceResourceMetadata), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceResourceMetadata", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceResourceMetadata), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListProvisionerKeysByOrganization mocks base method.
|
||||||
|
func (m *MockStore) ListProvisionerKeysByOrganization(arg0 context.Context, arg1 uuid.UUID) ([]database.ProvisionerKey, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ListProvisionerKeysByOrganization", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].([]database.ProvisionerKey)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListProvisionerKeysByOrganization indicates an expected call of ListProvisionerKeysByOrganization.
|
||||||
|
func (mr *MockStoreMockRecorder) ListProvisionerKeysByOrganization(arg0, arg1 any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProvisionerKeysByOrganization", reflect.TypeOf((*MockStore)(nil).ListProvisionerKeysByOrganization), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// ListWorkspaceAgentPortShares mocks base method.
|
// ListWorkspaceAgentPortShares mocks base method.
|
||||||
func (m *MockStore) ListWorkspaceAgentPortShares(arg0 context.Context, arg1 uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
func (m *MockStore) ListWorkspaceAgentPortShares(arg0 context.Context, arg1 uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
Generated
+16
@@ -749,6 +749,14 @@ END) STORED NOT NULL
|
|||||||
|
|
||||||
COMMENT ON COLUMN provisioner_jobs.job_status IS 'Computed column to track the status of the job.';
|
COMMENT ON COLUMN provisioner_jobs.job_status IS 'Computed column to track the status of the job.';
|
||||||
|
|
||||||
|
CREATE TABLE provisioner_keys (
|
||||||
|
id uuid NOT NULL,
|
||||||
|
created_at timestamp with time zone NOT NULL,
|
||||||
|
organization_id uuid NOT NULL,
|
||||||
|
name character varying(64) NOT NULL,
|
||||||
|
hashed_secret bytea NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE replicas (
|
CREATE TABLE replicas (
|
||||||
id uuid NOT NULL,
|
id uuid NOT NULL,
|
||||||
created_at timestamp with time zone NOT NULL,
|
created_at timestamp with time zone NOT NULL,
|
||||||
@@ -1584,6 +1592,9 @@ ALTER TABLE ONLY provisioner_job_logs
|
|||||||
ALTER TABLE ONLY provisioner_jobs
|
ALTER TABLE ONLY provisioner_jobs
|
||||||
ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
ALTER TABLE ONLY provisioner_keys
|
||||||
|
ADD CONSTRAINT provisioner_keys_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
ALTER TABLE ONLY site_configs
|
ALTER TABLE ONLY site_configs
|
||||||
ADD CONSTRAINT site_configs_key_key UNIQUE (key);
|
ADD CONSTRAINT site_configs_key_key UNIQUE (key);
|
||||||
|
|
||||||
@@ -1743,6 +1754,8 @@ CREATE INDEX provisioner_job_logs_id_job_id_idx ON provisioner_job_logs USING bt
|
|||||||
|
|
||||||
CREATE INDEX provisioner_jobs_started_at_idx ON provisioner_jobs USING btree (started_at) WHERE (started_at IS NULL);
|
CREATE INDEX provisioner_jobs_started_at_idx ON provisioner_jobs USING btree (started_at) WHERE (started_at IS NULL);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX provisioner_keys_organization_id_name_idx ON provisioner_keys USING btree (organization_id, lower((name)::text));
|
||||||
|
|
||||||
CREATE INDEX template_usage_stats_start_time_idx ON template_usage_stats USING btree (start_time DESC);
|
CREATE INDEX template_usage_stats_start_time_idx ON template_usage_stats USING btree (start_time DESC);
|
||||||
|
|
||||||
COMMENT ON INDEX template_usage_stats_start_time_idx IS 'Index for querying MAX(start_time).';
|
COMMENT ON INDEX template_usage_stats_start_time_idx IS 'Index for querying MAX(start_time).';
|
||||||
@@ -1867,6 +1880,9 @@ ALTER TABLE ONLY provisioner_job_logs
|
|||||||
ALTER TABLE ONLY provisioner_jobs
|
ALTER TABLE ONLY provisioner_jobs
|
||||||
ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY provisioner_keys
|
||||||
|
ADD CONSTRAINT provisioner_keys_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
ALTER TABLE ONLY tailnet_agents
|
ALTER TABLE ONLY tailnet_agents
|
||||||
ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const (
|
|||||||
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;
|
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;
|
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;
|
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;
|
||||||
|
ForeignKeyProvisionerKeysOrganizationID ForeignKeyConstraint = "provisioner_keys_organization_id_fkey" // ALTER TABLE ONLY provisioner_keys ADD CONSTRAINT provisioner_keys_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;
|
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;
|
||||||
ForeignKeyTailnetClientSubscriptionsCoordinatorID ForeignKeyConstraint = "tailnet_client_subscriptions_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
ForeignKeyTailnetClientSubscriptionsCoordinatorID ForeignKeyConstraint = "tailnet_client_subscriptions_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||||
ForeignKeyTailnetClientsCoordinatorID ForeignKeyConstraint = "tailnet_clients_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
ForeignKeyTailnetClientsCoordinatorID ForeignKeyConstraint = "tailnet_clients_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE provisioner_keys;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE provisioner_keys (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
created_at timestamptz NOT NULL,
|
||||||
|
organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE,
|
||||||
|
name varchar(64) NOT NULL,
|
||||||
|
hashed_secret bytea NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX provisioner_keys_organization_id_name_idx ON provisioner_keys USING btree (organization_id, lower(name));
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO provisioner_keys
|
||||||
|
(id, created_at, organization_id, name, hashed_secret)
|
||||||
|
VALUES
|
||||||
|
('b90547be-8870-4d68-8184-e8b2242b7c01', '2021-06-01 00:00:00', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', 'qua', '\xDEADBEEF'::bytea);
|
||||||
@@ -212,6 +212,12 @@ func (p ProvisionerDaemon) RBACObject() rbac.Object {
|
|||||||
return rbac.ResourceProvisionerDaemon.WithID(p.ID)
|
return rbac.ResourceProvisionerDaemon.WithID(p.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p ProvisionerKey) RBACObject() rbac.Object {
|
||||||
|
return rbac.ResourceProvisionerKeys.
|
||||||
|
WithID(p.ID).
|
||||||
|
InOrg(p.OrganizationID)
|
||||||
|
}
|
||||||
|
|
||||||
func (w WorkspaceProxy) RBACObject() rbac.Object {
|
func (w WorkspaceProxy) RBACObject() rbac.Object {
|
||||||
return rbac.ResourceWorkspaceProxy.
|
return rbac.ResourceWorkspaceProxy.
|
||||||
WithID(w.ID)
|
WithID(w.ID)
|
||||||
|
|||||||
@@ -2185,6 +2185,14 @@ type ProvisionerJobLog struct {
|
|||||||
ID int64 `db:"id" json:"id"`
|
ID int64 `db:"id" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProvisionerKey struct {
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
type Replica struct {
|
type Replica struct {
|
||||||
ID uuid.UUID `db:"id" json:"id"`
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ type sqlcQuerier interface {
|
|||||||
DeleteOldWorkspaceAgentStats(ctx context.Context) error
|
DeleteOldWorkspaceAgentStats(ctx context.Context) error
|
||||||
DeleteOrganization(ctx context.Context, id uuid.UUID) error
|
DeleteOrganization(ctx context.Context, id uuid.UUID) error
|
||||||
DeleteOrganizationMember(ctx context.Context, arg DeleteOrganizationMemberParams) error
|
DeleteOrganizationMember(ctx context.Context, arg DeleteOrganizationMemberParams) error
|
||||||
|
DeleteProvisionerKey(ctx context.Context, id uuid.UUID) error
|
||||||
DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error
|
DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error
|
||||||
DeleteTailnetAgent(ctx context.Context, arg DeleteTailnetAgentParams) (DeleteTailnetAgentRow, error)
|
DeleteTailnetAgent(ctx context.Context, arg DeleteTailnetAgentParams) (DeleteTailnetAgentRow, error)
|
||||||
DeleteTailnetClient(ctx context.Context, arg DeleteTailnetClientParams) (DeleteTailnetClientRow, error)
|
DeleteTailnetClient(ctx context.Context, arg DeleteTailnetClientParams) (DeleteTailnetClientRow, error)
|
||||||
@@ -184,6 +185,8 @@ type sqlcQuerier interface {
|
|||||||
GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error)
|
GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error)
|
||||||
GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error)
|
GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error)
|
||||||
GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error)
|
GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error)
|
||||||
|
GetProvisionerKeyByID(ctx context.Context, id uuid.UUID) (ProvisionerKey, error)
|
||||||
|
GetProvisionerKeyByName(ctx context.Context, arg GetProvisionerKeyByNameParams) (ProvisionerKey, error)
|
||||||
GetProvisionerLogsAfterID(ctx context.Context, arg GetProvisionerLogsAfterIDParams) ([]ProvisionerJobLog, error)
|
GetProvisionerLogsAfterID(ctx context.Context, arg GetProvisionerLogsAfterIDParams) ([]ProvisionerJobLog, error)
|
||||||
GetQuotaAllowanceForUser(ctx context.Context, userID uuid.UUID) (int64, error)
|
GetQuotaAllowanceForUser(ctx context.Context, userID uuid.UUID) (int64, error)
|
||||||
GetQuotaConsumedForUser(ctx context.Context, ownerID uuid.UUID) (int64, error)
|
GetQuotaConsumedForUser(ctx context.Context, ownerID uuid.UUID) (int64, error)
|
||||||
@@ -346,6 +349,7 @@ type sqlcQuerier interface {
|
|||||||
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
|
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
|
||||||
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
|
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
|
||||||
InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error)
|
InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error)
|
||||||
|
InsertProvisionerKey(ctx context.Context, arg InsertProvisionerKeyParams) (ProvisionerKey, error)
|
||||||
InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error)
|
InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error)
|
||||||
InsertTemplate(ctx context.Context, arg InsertTemplateParams) error
|
InsertTemplate(ctx context.Context, arg InsertTemplateParams) error
|
||||||
InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) error
|
InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) error
|
||||||
@@ -370,6 +374,7 @@ type sqlcQuerier interface {
|
|||||||
InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error)
|
InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error)
|
||||||
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
|
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
|
||||||
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
|
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
|
||||||
|
ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error)
|
||||||
ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error)
|
ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error)
|
||||||
// Arguments are optional with uuid.Nil to ignore.
|
// Arguments are optional with uuid.Nil to ignore.
|
||||||
// - Use just 'organization_id' to get all members of an org
|
// - Use just 'organization_id' to get all members of an org
|
||||||
|
|||||||
@@ -5455,6 +5455,147 @@ func (q *sqlQuerier) UpdateProvisionerJobWithCompleteByID(ctx context.Context, a
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteProvisionerKey = `-- name: DeleteProvisionerKey :exec
|
||||||
|
DELETE FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *sqlQuerier) DeleteProvisionerKey(ctx context.Context, id uuid.UUID) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteProvisionerKey, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProvisionerKeyByID = `-- name: GetProvisionerKeyByID :one
|
||||||
|
SELECT
|
||||||
|
id, created_at, organization_id, name, hashed_secret
|
||||||
|
FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *sqlQuerier) GetProvisionerKeyByID(ctx context.Context, id uuid.UUID) (ProvisionerKey, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getProvisionerKeyByID, id)
|
||||||
|
var i ProvisionerKey
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.OrganizationID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HashedSecret,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProvisionerKeyByName = `-- name: GetProvisionerKeyByName :one
|
||||||
|
SELECT
|
||||||
|
id, created_at, organization_id, name, hashed_secret
|
||||||
|
FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
organization_id = $1
|
||||||
|
AND
|
||||||
|
lower(name) = lower($2)
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetProvisionerKeyByNameParams struct {
|
||||||
|
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *sqlQuerier) GetProvisionerKeyByName(ctx context.Context, arg GetProvisionerKeyByNameParams) (ProvisionerKey, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getProvisionerKeyByName, arg.OrganizationID, arg.Name)
|
||||||
|
var i ProvisionerKey
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.OrganizationID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HashedSecret,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertProvisionerKey = `-- name: InsertProvisionerKey :one
|
||||||
|
INSERT INTO
|
||||||
|
provisioner_keys (
|
||||||
|
id,
|
||||||
|
created_at,
|
||||||
|
organization_id,
|
||||||
|
name,
|
||||||
|
hashed_secret
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, lower($5), $4) RETURNING id, created_at, organization_id, name, hashed_secret
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertProvisionerKeyParams struct {
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||||
|
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *sqlQuerier) InsertProvisionerKey(ctx context.Context, arg InsertProvisionerKeyParams) (ProvisionerKey, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, insertProvisionerKey,
|
||||||
|
arg.ID,
|
||||||
|
arg.CreatedAt,
|
||||||
|
arg.OrganizationID,
|
||||||
|
arg.HashedSecret,
|
||||||
|
arg.Name,
|
||||||
|
)
|
||||||
|
var i ProvisionerKey
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.OrganizationID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HashedSecret,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listProvisionerKeysByOrganization = `-- name: ListProvisionerKeysByOrganization :many
|
||||||
|
SELECT
|
||||||
|
id, created_at, organization_id, name, hashed_secret
|
||||||
|
FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
organization_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *sqlQuerier) ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, listProvisionerKeysByOrganization, 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,
|
||||||
|
); 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
|
const getWorkspaceProxies = `-- name: GetWorkspaceProxies :many
|
||||||
SELECT
|
SELECT
|
||||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
-- name: InsertProvisionerKey :one
|
||||||
|
INSERT INTO
|
||||||
|
provisioner_keys (
|
||||||
|
id,
|
||||||
|
created_at,
|
||||||
|
organization_id,
|
||||||
|
name,
|
||||||
|
hashed_secret
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, lower(@name), $4) RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetProvisionerKeyByID :one
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
id = $1;
|
||||||
|
|
||||||
|
-- name: GetProvisionerKeyByName :one
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
organization_id = $1
|
||||||
|
AND
|
||||||
|
lower(name) = lower(@name);
|
||||||
|
|
||||||
|
-- name: ListProvisionerKeysByOrganization :many
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
organization_id = $1;
|
||||||
|
|
||||||
|
-- name: DeleteProvisionerKey :exec
|
||||||
|
DELETE FROM
|
||||||
|
provisioner_keys
|
||||||
|
WHERE
|
||||||
|
id = $1;
|
||||||
@@ -44,6 +44,7 @@ const (
|
|||||||
UniqueProvisionerDaemonsPkey UniqueConstraint = "provisioner_daemons_pkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id);
|
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);
|
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);
|
UniqueProvisionerJobsPkey UniqueConstraint = "provisioner_jobs_pkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id);
|
||||||
|
UniqueProvisionerKeysPkey UniqueConstraint = "provisioner_keys_pkey" // ALTER TABLE ONLY provisioner_keys ADD CONSTRAINT provisioner_keys_pkey PRIMARY KEY (id);
|
||||||
UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key);
|
UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key);
|
||||||
UniqueTailnetAgentsPkey UniqueConstraint = "tailnet_agents_pkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id);
|
UniqueTailnetAgentsPkey UniqueConstraint = "tailnet_agents_pkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id);
|
||||||
UniqueTailnetClientSubscriptionsPkey UniqueConstraint = "tailnet_client_subscriptions_pkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id);
|
UniqueTailnetClientSubscriptionsPkey UniqueConstraint = "tailnet_client_subscriptions_pkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id);
|
||||||
@@ -87,6 +88,7 @@ const (
|
|||||||
UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false);
|
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);
|
UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false);
|
||||||
UniqueOrganizationsSingleDefaultOrg UniqueConstraint = "organizations_single_default_org" // CREATE UNIQUE INDEX organizations_single_default_org ON organizations USING btree (is_default) WHERE (is_default = true);
|
UniqueOrganizationsSingleDefaultOrg UniqueConstraint = "organizations_single_default_org" // CREATE UNIQUE INDEX organizations_single_default_org ON organizations USING btree (is_default) WHERE (is_default = true);
|
||||||
|
UniqueProvisionerKeysOrganizationIDNameIndex UniqueConstraint = "provisioner_keys_organization_id_name_idx" // CREATE UNIQUE INDEX provisioner_keys_organization_id_name_idx ON provisioner_keys USING btree (organization_id, lower((name)::text));
|
||||||
UniqueTemplateUsageStatsStartTimeTemplateIDUserIDIndex UniqueConstraint = "template_usage_stats_start_time_template_id_user_id_idx" // CREATE UNIQUE INDEX template_usage_stats_start_time_template_id_user_id_idx ON template_usage_stats USING btree (start_time, template_id, user_id);
|
UniqueTemplateUsageStatsStartTimeTemplateIDUserIDIndex UniqueConstraint = "template_usage_stats_start_time_template_id_user_id_idx" // CREATE UNIQUE INDEX template_usage_stats_start_time_template_id_user_id_idx ON template_usage_stats USING btree (start_time, template_id, user_id);
|
||||||
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);
|
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);
|
||||||
UniqueUserLinksLinkedIDLoginTypeIndex UniqueConstraint = "user_links_linked_id_login_type_idx" // CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text);
|
UniqueUserLinksLinkedIDLoginTypeIndex UniqueConstraint = "user_links_linked_id_login_type_idx" // CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package httpmw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type provisionerKeyParamContextKey struct{}
|
||||||
|
|
||||||
|
// ProvisionerKeyParam returns the user from the ExtractProvisionerKeyParam handler.
|
||||||
|
func ProvisionerKeyParam(r *http.Request) database.ProvisionerKey {
|
||||||
|
user, ok := r.Context().Value(provisionerKeyParamContextKey{}).(database.ProvisionerKey)
|
||||||
|
if !ok {
|
||||||
|
panic("developer error: provisioner key parameter middleware not provided")
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractProvisionerKeyParam extracts a provisioner key from a name in the {provisionerKey} URL
|
||||||
|
// parameter.
|
||||||
|
func ExtractProvisionerKeyParam(db database.Store) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
organization := OrganizationParam(r)
|
||||||
|
|
||||||
|
provisionerKeyQuery := chi.URLParam(r, "provisionerkey")
|
||||||
|
if provisionerKeyQuery == "" {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: "\"provisionerkey\" must be provided.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provisionerKey, err := db.GetProvisionerKeyByName(ctx, database.GetProvisionerKeyByNameParams{
|
||||||
|
OrganizationID: organization.ID,
|
||||||
|
Name: provisionerKeyQuery,
|
||||||
|
})
|
||||||
|
if httpapi.Is404Error(err) {
|
||||||
|
httpapi.ResourceNotFound(rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
httpapi.InternalServerError(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, provisionerKeyParamContextKey{}, provisionerKey)
|
||||||
|
next.ServeHTTP(rw, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package provisionerkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
|
"github.com/coder/coder/v2/cryptorand"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(organizationID uuid.UUID, name string) (database.InsertProvisionerKeyParams, string, error) {
|
||||||
|
id := uuid.New()
|
||||||
|
secret, err := cryptorand.HexString(64)
|
||||||
|
if err != nil {
|
||||||
|
return database.InsertProvisionerKeyParams{}, "", xerrors.Errorf("generate token: %w", err)
|
||||||
|
}
|
||||||
|
hashedSecret := sha256.Sum256([]byte(secret))
|
||||||
|
token := fmt.Sprintf("%s:%s", id, secret)
|
||||||
|
|
||||||
|
return database.InsertProvisionerKeyParams{
|
||||||
|
ID: id,
|
||||||
|
CreatedAt: dbtime.Now(),
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
Name: name,
|
||||||
|
HashedSecret: hashedSecret[:],
|
||||||
|
}, token, nil
|
||||||
|
}
|
||||||
@@ -161,6 +161,15 @@ var (
|
|||||||
Type: "provisioner_daemon",
|
Type: "provisioner_daemon",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceProvisionerKeys
|
||||||
|
// Valid Actions
|
||||||
|
// - "ActionCreate" :: create a provisioner key
|
||||||
|
// - "ActionDelete" :: delete a provisioner key
|
||||||
|
// - "ActionRead" :: read provisioner keys
|
||||||
|
ResourceProvisionerKeys = Object{
|
||||||
|
Type: "provisioner_keys",
|
||||||
|
}
|
||||||
|
|
||||||
// ResourceReplicas
|
// ResourceReplicas
|
||||||
// Valid Actions
|
// Valid Actions
|
||||||
// - "ActionRead" :: read replicas
|
// - "ActionRead" :: read replicas
|
||||||
@@ -269,6 +278,7 @@ func AllResources() []Objecter {
|
|||||||
ResourceOrganization,
|
ResourceOrganization,
|
||||||
ResourceOrganizationMember,
|
ResourceOrganizationMember,
|
||||||
ResourceProvisionerDaemon,
|
ResourceProvisionerDaemon,
|
||||||
|
ResourceProvisionerKeys,
|
||||||
ResourceReplicas,
|
ResourceReplicas,
|
||||||
ResourceSystem,
|
ResourceSystem,
|
||||||
ResourceTailnetCoordinator,
|
ResourceTailnetCoordinator,
|
||||||
|
|||||||
@@ -160,6 +160,13 @@ var RBACPermissions = map[string]PermissionDefinition{
|
|||||||
ActionDelete: actDef("delete a provisioner daemon"),
|
ActionDelete: actDef("delete a provisioner daemon"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"provisioner_keys": {
|
||||||
|
Actions: map[Action]ActionDefinition{
|
||||||
|
ActionCreate: actDef("create a provisioner key"),
|
||||||
|
ActionRead: actDef("read provisioner keys"),
|
||||||
|
ActionDelete: actDef("delete a provisioner key"),
|
||||||
|
},
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
Actions: map[Action]ActionDefinition{
|
Actions: map[Action]ActionDefinition{
|
||||||
ActionCreate: actDef("create an organization"),
|
ActionCreate: actDef("create an organization"),
|
||||||
|
|||||||
@@ -488,6 +488,15 @@ func TestRolePermissions(t *testing.T) {
|
|||||||
false: {memberMe, otherOrgAdmin, otherOrgMember, userAdmin},
|
false: {memberMe, otherOrgAdmin, otherOrgMember, userAdmin},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "ProvisionerKeys",
|
||||||
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
||||||
|
Resource: rbac.ResourceProvisionerKeys.InOrg(orgID),
|
||||||
|
AuthorizeMap: map[bool][]authSubject{
|
||||||
|
true: {owner, orgAdmin},
|
||||||
|
false: {otherOrgAdmin, otherOrgMember, memberMe, orgMemberMe, userAdmin, templateAdmin},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "System",
|
Name: "System",
|
||||||
Actions: crud,
|
Actions: crud,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const (
|
|||||||
FeatureAccessControl FeatureName = "access_control"
|
FeatureAccessControl FeatureName = "access_control"
|
||||||
FeatureControlSharedPorts FeatureName = "control_shared_ports"
|
FeatureControlSharedPorts FeatureName = "control_shared_ports"
|
||||||
FeatureCustomRoles FeatureName = "custom_roles"
|
FeatureCustomRoles FeatureName = "custom_roles"
|
||||||
|
FeatureMultipleOrganizations FeatureName = "multiple_organizations"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FeatureNames must be kept in-sync with the Feature enum above.
|
// FeatureNames must be kept in-sync with the Feature enum above.
|
||||||
@@ -77,6 +78,7 @@ var FeatureNames = []FeatureName{
|
|||||||
FeatureAccessControl,
|
FeatureAccessControl,
|
||||||
FeatureControlSharedPorts,
|
FeatureControlSharedPorts,
|
||||||
FeatureCustomRoles,
|
FeatureCustomRoles,
|
||||||
|
FeatureMultipleOrganizations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Humanize returns the feature name in a human-readable format.
|
// Humanize returns the feature name in a human-readable format.
|
||||||
|
|||||||
@@ -265,3 +265,72 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione
|
|||||||
}
|
}
|
||||||
return proto.NewDRPCProvisionerDaemonClient(drpc.MultiplexedConn(session)), nil
|
return proto.NewDRPCProvisionerDaemonClient(drpc.MultiplexedConn(session)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProvisionerKey struct {
|
||||||
|
ID uuid.UUID `json:"id" format:"uuid"`
|
||||||
|
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization" format:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
// HashedSecret - never include the access token in the API response
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateProvisionerKeyRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateProvisionerKeyResponse struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateProvisionerKey creates a new provisioner key for an organization.
|
||||||
|
func (c *Client) CreateProvisionerKey(ctx context.Context, organizationID uuid.UUID, req CreateProvisionerKeyRequest) (CreateProvisionerKeyResponse, error) {
|
||||||
|
res, err := c.Request(ctx, http.MethodPost,
|
||||||
|
fmt.Sprintf("/api/v2/organizations/%s/provisionerkeys", organizationID.String()),
|
||||||
|
req,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return CreateProvisionerKeyResponse{}, xerrors.Errorf("make request: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusCreated {
|
||||||
|
return CreateProvisionerKeyResponse{}, ReadBodyAsError(res)
|
||||||
|
}
|
||||||
|
var resp CreateProvisionerKeyResponse
|
||||||
|
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListProvisionerKeys lists all provisioner keys for an organization.
|
||||||
|
func (c *Client) ListProvisionerKeys(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) {
|
||||||
|
res, err := c.Request(ctx, http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v2/organizations/%s/provisionerkeys", 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 []ProvisionerKey
|
||||||
|
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,
|
||||||
|
fmt.Sprintf("/api/v2/organizations/%s/provisionerkeys/%s", organizationID.String(), name),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("make request: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusNoContent {
|
||||||
|
return ReadBodyAsError(res)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
ResourceOrganization RBACResource = "organization"
|
ResourceOrganization RBACResource = "organization"
|
||||||
ResourceOrganizationMember RBACResource = "organization_member"
|
ResourceOrganizationMember RBACResource = "organization_member"
|
||||||
ResourceProvisionerDaemon RBACResource = "provisioner_daemon"
|
ResourceProvisionerDaemon RBACResource = "provisioner_daemon"
|
||||||
|
ResourceProvisionerKeys RBACResource = "provisioner_keys"
|
||||||
ResourceReplicas RBACResource = "replicas"
|
ResourceReplicas RBACResource = "replicas"
|
||||||
ResourceSystem RBACResource = "system"
|
ResourceSystem RBACResource = "system"
|
||||||
ResourceTailnetCoordinator RBACResource = "tailnet_coordinator"
|
ResourceTailnetCoordinator RBACResource = "tailnet_coordinator"
|
||||||
@@ -69,6 +70,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
|
|||||||
ResourceOrganization: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
ResourceOrganization: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||||
ResourceOrganizationMember: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
ResourceOrganizationMember: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||||
ResourceProvisionerDaemon: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
ResourceProvisionerDaemon: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||||
|
ResourceProvisionerKeys: {ActionCreate, ActionDelete, ActionRead},
|
||||||
ResourceReplicas: {ActionRead},
|
ResourceReplicas: {ActionRead},
|
||||||
ResourceSystem: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
ResourceSystem: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||||
ResourceTailnetCoordinator: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
ResourceTailnetCoordinator: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||||
|
|||||||
Generated
+118
@@ -1353,6 +1353,124 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
|
|||||||
|
|
||||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
## List provisioner key
|
||||||
|
|
||||||
|
### Code samples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Example request using curl
|
||||||
|
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisionerkeys \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Coder-Session-Token: API_KEY'
|
||||||
|
```
|
||||||
|
|
||||||
|
`GET /organizations/{organization}/provisionerkeys`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Name | In | Type | Required | Description |
|
||||||
|
| -------------- | ---- | ------ | -------- | --------------- |
|
||||||
|
| `organization` | path | string | true | Organization ID |
|
||||||
|
|
||||||
|
### Example responses
|
||||||
|
|
||||||
|
> 200 Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"name": "string",
|
||||||
|
"organization": "452c1a86-a0af-475b-b03f-724878b0f387"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responses
|
||||||
|
|
||||||
|
| Status | Meaning | Description | Schema |
|
||||||
|
| ------ | ------------------------------------------------------- | ----------- | --------------------------------------------------------------------- |
|
||||||
|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.ProvisionerKey](schemas.md#codersdkprovisionerkey) |
|
||||||
|
|
||||||
|
<h3 id="list-provisioner-key-responseschema">Response Schema</h3>
|
||||||
|
|
||||||
|
Status Code **200**
|
||||||
|
|
||||||
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|
| ---------------- | ----------------- | -------- | ------------ | ----------- |
|
||||||
|
| `[array item]` | array | false | | |
|
||||||
|
| `» created_at` | string(date-time) | false | | |
|
||||||
|
| `» id` | string(uuid) | false | | |
|
||||||
|
| `» name` | string | false | | |
|
||||||
|
| `» organization` | string(uuid) | false | | |
|
||||||
|
|
||||||
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
## Create provisioner key
|
||||||
|
|
||||||
|
### Code samples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Example request using curl
|
||||||
|
curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/provisionerkeys \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Coder-Session-Token: API_KEY'
|
||||||
|
```
|
||||||
|
|
||||||
|
`POST /organizations/{organization}/provisionerkeys`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Name | In | Type | Required | Description |
|
||||||
|
| -------------- | ---- | ------ | -------- | --------------- |
|
||||||
|
| `organization` | path | string | true | Organization ID |
|
||||||
|
|
||||||
|
### Example responses
|
||||||
|
|
||||||
|
> 201 Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"key": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responses
|
||||||
|
|
||||||
|
| Status | Meaning | Description | Schema |
|
||||||
|
| ------ | ------------------------------------------------------------ | ----------- | ---------------------------------------------------------------------------------------- |
|
||||||
|
| 201 | [Created](https://tools.ietf.org/html/rfc7231#section-6.3.2) | Created | [codersdk.CreateProvisionerKeyResponse](schemas.md#codersdkcreateprovisionerkeyresponse) |
|
||||||
|
|
||||||
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
## Delete provisioner key
|
||||||
|
|
||||||
|
### Code samples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Example request using curl
|
||||||
|
curl -X DELETE http://coder-server:8080/api/v2/organizations/{organization}/provisionerkeys/{provisionerkey} \
|
||||||
|
-H 'Coder-Session-Token: API_KEY'
|
||||||
|
```
|
||||||
|
|
||||||
|
`DELETE /organizations/{organization}/provisionerkeys/{provisionerkey}`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Name | In | Type | Required | Description |
|
||||||
|
| ---------------- | ---- | ------ | -------- | -------------------- |
|
||||||
|
| `organization` | path | string | true | Organization ID |
|
||||||
|
| `provisionerkey` | path | string | true | Provisioner key name |
|
||||||
|
|
||||||
|
### Responses
|
||||||
|
|
||||||
|
| Status | Meaning | Description | Schema |
|
||||||
|
| ------ | --------------------------------------------------------------- | ----------- | ------ |
|
||||||
|
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | |
|
||||||
|
|
||||||
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
## Get active replicas
|
## Get active replicas
|
||||||
|
|
||||||
### Code samples
|
### Code samples
|
||||||
|
|||||||
Generated
+3
@@ -182,6 +182,7 @@ Status Code **200**
|
|||||||
| `resource_type` | `organization` |
|
| `resource_type` | `organization` |
|
||||||
| `resource_type` | `organization_member` |
|
| `resource_type` | `organization_member` |
|
||||||
| `resource_type` | `provisioner_daemon` |
|
| `resource_type` | `provisioner_daemon` |
|
||||||
|
| `resource_type` | `provisioner_keys` |
|
||||||
| `resource_type` | `replicas` |
|
| `resource_type` | `replicas` |
|
||||||
| `resource_type` | `system` |
|
| `resource_type` | `system` |
|
||||||
| `resource_type` | `tailnet_coordinator` |
|
| `resource_type` | `tailnet_coordinator` |
|
||||||
@@ -304,6 +305,7 @@ Status Code **200**
|
|||||||
| `resource_type` | `organization` |
|
| `resource_type` | `organization` |
|
||||||
| `resource_type` | `organization_member` |
|
| `resource_type` | `organization_member` |
|
||||||
| `resource_type` | `provisioner_daemon` |
|
| `resource_type` | `provisioner_daemon` |
|
||||||
|
| `resource_type` | `provisioner_keys` |
|
||||||
| `resource_type` | `replicas` |
|
| `resource_type` | `replicas` |
|
||||||
| `resource_type` | `system` |
|
| `resource_type` | `system` |
|
||||||
| `resource_type` | `tailnet_coordinator` |
|
| `resource_type` | `tailnet_coordinator` |
|
||||||
@@ -578,6 +580,7 @@ Status Code **200**
|
|||||||
| `resource_type` | `organization` |
|
| `resource_type` | `organization` |
|
||||||
| `resource_type` | `organization_member` |
|
| `resource_type` | `organization_member` |
|
||||||
| `resource_type` | `provisioner_daemon` |
|
| `resource_type` | `provisioner_daemon` |
|
||||||
|
| `resource_type` | `provisioner_keys` |
|
||||||
| `resource_type` | `replicas` |
|
| `resource_type` | `replicas` |
|
||||||
| `resource_type` | `system` |
|
| `resource_type` | `system` |
|
||||||
| `resource_type` | `tailnet_coordinator` |
|
| `resource_type` | `tailnet_coordinator` |
|
||||||
|
|||||||
Generated
+35
@@ -1047,6 +1047,20 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||||||
| `icon` | string | false | | |
|
| `icon` | string | false | | |
|
||||||
| `name` | string | true | | |
|
| `name` | string | true | | |
|
||||||
|
|
||||||
|
## codersdk.CreateProvisionerKeyResponse
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"key": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|
| ----- | ------ | -------- | ------------ | ----------- |
|
||||||
|
| `key` | string | false | | |
|
||||||
|
|
||||||
## codersdk.CreateTemplateRequest
|
## codersdk.CreateTemplateRequest
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -3810,6 +3824,26 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
|||||||
| `failed` |
|
| `failed` |
|
||||||
| `unknown` |
|
| `unknown` |
|
||||||
|
|
||||||
|
## codersdk.ProvisionerKey
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"name": "string",
|
||||||
|
"organization": "452c1a86-a0af-475b-b03f-724878b0f387"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|
| -------------- | ------ | -------- | ------------ | ----------- |
|
||||||
|
| `created_at` | string | false | | |
|
||||||
|
| `id` | string | false | | |
|
||||||
|
| `name` | string | false | | |
|
||||||
|
| `organization` | string | false | | |
|
||||||
|
|
||||||
## codersdk.ProvisionerLogLevel
|
## codersdk.ProvisionerLogLevel
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -3958,6 +3992,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
|||||||
| `organization` |
|
| `organization` |
|
||||||
| `organization_member` |
|
| `organization_member` |
|
||||||
| `provisioner_daemon` |
|
| `provisioner_daemon` |
|
||||||
|
| `provisioner_keys` |
|
||||||
| `replicas` |
|
| `replicas` |
|
||||||
| `system` |
|
| `system` |
|
||||||
| `tailnet_coordinator` |
|
| `tailnet_coordinator` |
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
|||||||
})
|
})
|
||||||
r.Route("/workspaceproxies", func(r chi.Router) {
|
r.Route("/workspaceproxies", func(r chi.Router) {
|
||||||
r.Use(
|
r.Use(
|
||||||
api.moonsEnabledMW,
|
api.RequireFeatureMW(codersdk.FeatureWorkspaceProxy),
|
||||||
)
|
)
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(
|
r.Use(
|
||||||
@@ -254,6 +254,22 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
|||||||
r.Get("/", api.groupByOrganization)
|
r.Get("/", api.groupByOrganization)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
r.Route("/organizations/{organization}/provisionerkeys", func(r chi.Router) {
|
||||||
|
r.Use(
|
||||||
|
apiKeyMiddleware,
|
||||||
|
httpmw.ExtractOrganizationParam(api.Database),
|
||||||
|
api.RequireFeatureMW(codersdk.FeatureMultipleOrganizations),
|
||||||
|
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentMultiOrganization),
|
||||||
|
)
|
||||||
|
r.Get("/", api.provisionerKeys)
|
||||||
|
r.Post("/", api.postProvisionerKey)
|
||||||
|
r.Route("/{provisionerkey}", func(r chi.Router) {
|
||||||
|
r.Use(
|
||||||
|
httpmw.ExtractProvisionerKeyParam(options.Database),
|
||||||
|
)
|
||||||
|
r.Delete("/", api.deleteProvisionerKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
// TODO: provisioner daemons are not scoped to organizations in the database, so placing them
|
// TODO: provisioner daemons are not scoped to organizations in the database, so placing them
|
||||||
// under an organization route doesn't make sense. In order to allow the /serve endpoint to
|
// under an organization route doesn't make sense. In order to allow the /serve endpoint to
|
||||||
// work with a pre-shared key (PSK) without an API key, these routes will simply ignore the
|
// work with a pre-shared key (PSK) without an API key, these routes will simply ignore the
|
||||||
@@ -566,6 +582,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
|||||||
codersdk.FeatureUserRoleManagement: true,
|
codersdk.FeatureUserRoleManagement: true,
|
||||||
codersdk.FeatureAccessControl: true,
|
codersdk.FeatureAccessControl: true,
|
||||||
codersdk.FeatureControlSharedPorts: true,
|
codersdk.FeatureControlSharedPorts: true,
|
||||||
|
codersdk.FeatureMultipleOrganizations: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -751,6 +768,11 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
|||||||
api.AGPL.CustomRoleHandler.Store(&handler)
|
api.AGPL.CustomRoleHandler.Store(&handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if initial, changed, enabled := featureChanged(codersdk.FeatureMultipleOrganizations); shouldUpdate(initial, changed, enabled) {
|
||||||
|
var handler coderd.CustomRoleHandler = &enterpriseCustomRoleHandler{API: api, Enabled: enabled}
|
||||||
|
api.AGPL.CustomRoleHandler.Store(&handler)
|
||||||
|
}
|
||||||
|
|
||||||
// External token encryption is soft-enforced
|
// External token encryption is soft-enforced
|
||||||
featureExternalTokenEncryption := entitlements.Features[codersdk.FeatureExternalTokenEncryption]
|
featureExternalTokenEncryption := entitlements.Features[codersdk.FeatureExternalTokenEncryption]
|
||||||
featureExternalTokenEncryption.Enabled = len(api.ExternalTokenEncryption) > 0
|
featureExternalTokenEncryption.Enabled = len(api.ExternalTokenEncryption) > 0
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package coderd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
|
"github.com/coder/coder/v2/coderd/provisionerkey"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Summary Create provisioner key
|
||||||
|
// @ID create-provisioner-key
|
||||||
|
// @Security CoderSessionToken
|
||||||
|
// @Produce json
|
||||||
|
// @Tags Enterprise
|
||||||
|
// @Param organization path string true "Organization ID"
|
||||||
|
// @Success 201 {object} codersdk.CreateProvisionerKeyResponse
|
||||||
|
// @Router /organizations/{organization}/provisionerkeys [post]
|
||||||
|
func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
organization := httpmw.OrganizationParam(r)
|
||||||
|
|
||||||
|
var req codersdk.CreateProvisionerKeyRequest
|
||||||
|
if !httpapi.Read(ctx, rw, r, &req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == "" {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: "Name is required",
|
||||||
|
Validations: []codersdk.ValidationError{
|
||||||
|
{
|
||||||
|
Field: "name",
|
||||||
|
Detail: "Name is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Name) > 64 {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: "Name must be at most 64 characters",
|
||||||
|
Validations: []codersdk.ValidationError{
|
||||||
|
{
|
||||||
|
Field: "name",
|
||||||
|
Detail: "Name must be at most 64 characters",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params, token, err := provisionerkey.New(organization.ID, req.Name)
|
||||||
|
if err != nil {
|
||||||
|
httpapi.InternalServerError(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Database.InsertProvisionerKey(ctx, params)
|
||||||
|
if database.IsUniqueViolation(err, database.UniqueProvisionerKeysOrganizationIDNameIndex) {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||||
|
Message: fmt.Sprintf("Provisioner key with name '%s' already exists in organization", req.Name),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
httpapi.InternalServerError(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateProvisionerKeyResponse{
|
||||||
|
Key: token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary List provisioner key
|
||||||
|
// @ID list-provisioner-key
|
||||||
|
// @Security CoderSessionToken
|
||||||
|
// @Produce json
|
||||||
|
// @Tags Enterprise
|
||||||
|
// @Param organization path string true "Organization ID"
|
||||||
|
// @Success 200 {object} []codersdk.ProvisionerKey
|
||||||
|
// @Router /organizations/{organization}/provisionerkeys [get]
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
httpapi.InternalServerError(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(ctx, rw, http.StatusOK, convertProvisionerKeys(pks))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Delete provisioner key
|
||||||
|
// @ID delete-provisioner-key
|
||||||
|
// @Security CoderSessionToken
|
||||||
|
// @Tags Enterprise
|
||||||
|
// @Param organization path string true "Organization ID"
|
||||||
|
// @Param provisionerkey path string true "Provisioner key name"
|
||||||
|
// @Success 204
|
||||||
|
// @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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.Database.DeleteProvisionerKey(ctx, pk.ID)
|
||||||
|
if err != nil {
|
||||||
|
httpapi.InternalServerError(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertProvisionerKeys(dbKeys []database.ProvisionerKey) []codersdk.ProvisionerKey {
|
||||||
|
keys := make([]codersdk.ProvisionerKey, 0, len(dbKeys))
|
||||||
|
for _, dbKey := range dbKeys {
|
||||||
|
keys = append(keys, codersdk.ProvisionerKey{
|
||||||
|
ID: dbKey.ID,
|
||||||
|
CreatedAt: dbKey.CreatedAt,
|
||||||
|
OrganizationID: dbKey.OrganizationID,
|
||||||
|
Name: dbKey.Name,
|
||||||
|
// HashedSecret - never include the access token in the API response
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package coderd_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||||
|
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||||
|
"github.com/coder/coder/v2/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProvisionerKeys(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong*10)
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
dv := coderdtest.DeploymentValues(t)
|
||||||
|
dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)}
|
||||||
|
client, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||||
|
Options: &coderdtest.Options{
|
||||||
|
DeploymentValues: dv,
|
||||||
|
},
|
||||||
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||||
|
Features: license.Features{
|
||||||
|
codersdk.FeatureMultipleOrganizations: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
|
||||||
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||||
|
otherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
|
||||||
|
outsideOrgAdmin, _ := coderdtest.CreateAnotherUser(t, client, otherOrg.ID, rbac.ScopedRoleOrgAdmin(otherOrg.ID))
|
||||||
|
|
||||||
|
// member cannot create a provisioner key
|
||||||
|
_, err := member.CreateProvisionerKey(ctx, otherOrg.ID, codersdk.CreateProvisionerKeyRequest{
|
||||||
|
Name: "key",
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "Resource not found")
|
||||||
|
|
||||||
|
// member cannot list provisioner keys
|
||||||
|
_, err = member.ListProvisionerKeys(ctx, otherOrg.ID)
|
||||||
|
require.ErrorContains(t, err, "Resource not found")
|
||||||
|
|
||||||
|
// member cannot delete a provisioner key
|
||||||
|
err = member.DeleteProvisionerKey(ctx, otherOrg.ID, "key")
|
||||||
|
require.ErrorContains(t, err, "Resource not found")
|
||||||
|
|
||||||
|
// outside org admin cannot create a provisioner key
|
||||||
|
_, err = outsideOrgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||||
|
Name: "key",
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "Resource not found")
|
||||||
|
|
||||||
|
// outside org admin cannot list provisioner keys
|
||||||
|
_, err = outsideOrgAdmin.ListProvisionerKeys(ctx, owner.OrganizationID)
|
||||||
|
require.ErrorContains(t, err, "Resource not found")
|
||||||
|
|
||||||
|
// outside org admin cannot delete a provisioner key
|
||||||
|
err = outsideOrgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, "key")
|
||||||
|
require.ErrorContains(t, err, "Resource not found")
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
require.Len(t, keys, 0, "org admin list provisioner keys")
|
||||||
|
|
||||||
|
// org admin can create a provisioner key
|
||||||
|
_, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||||
|
Name: "Key", // case insensitive
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "org admin create provisioner key")
|
||||||
|
|
||||||
|
// org admin can conflict on name creating a provisioner key
|
||||||
|
_, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||||
|
Name: "KEY", // still conflicts
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "already exists in organization")
|
||||||
|
|
||||||
|
// key name cannot be too long
|
||||||
|
_, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||||
|
Name: "Everyone please pass your watermelons to the front of the pool, the storm is approaching.",
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "must be at most 64 characters")
|
||||||
|
|
||||||
|
// key name cannot be empty
|
||||||
|
_, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{
|
||||||
|
Name: "",
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "is required")
|
||||||
|
|
||||||
|
// org admin can list provisioner keys
|
||||||
|
keys, err = orgAdmin.ListProvisionerKeys(ctx, owner.OrganizationID)
|
||||||
|
require.NoError(t, err, "org admin list provisioner keys")
|
||||||
|
require.Len(t, keys, 1, "org admin list provisioner keys")
|
||||||
|
|
||||||
|
// org admin can delete a provisioner key
|
||||||
|
err = orgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, "key") // using lowercase here works
|
||||||
|
require.NoError(t, err, "org admin delete provisioner key")
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
@@ -327,7 +327,7 @@ func convertSDKTemplateRole(role codersdk.TemplateRole) []policy.Action {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO reduce the duplication across all of these.
|
// TODO move to api.RequireFeatureMW when we are OK with changing the behavior.
|
||||||
func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler {
|
func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
api.entitlementsMu.RLock()
|
api.entitlementsMu.RLock()
|
||||||
@@ -343,19 +343,21 @@ func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) moonsEnabledMW(next http.Handler) http.Handler {
|
func (api *API) RequireFeatureMW(feat codersdk.FeatureName) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
// Entitlement must be enabled.
|
// Entitlement must be enabled.
|
||||||
api.entitlementsMu.RLock()
|
api.entitlementsMu.RLock()
|
||||||
proxy := api.entitlements.Features[codersdk.FeatureWorkspaceProxy].Enabled
|
enabled := api.entitlements.Features[feat].Enabled
|
||||||
api.entitlementsMu.RUnlock()
|
api.entitlementsMu.RUnlock()
|
||||||
if !proxy {
|
if !enabled {
|
||||||
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
|
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
|
||||||
Message: "External workspace proxies is an Enterprise feature. Contact sales!",
|
Message: fmt.Sprintf("%s is an Enterprise feature. Contact sales!", feat.Humanize()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
next.ServeHTTP(rw, r)
|
next.ServeHTTP(rw, r)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+22
@@ -233,6 +233,16 @@ export interface CreateOrganizationRequest {
|
|||||||
readonly icon?: string;
|
readonly icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From codersdk/provisionerdaemons.go
|
||||||
|
export interface CreateProvisionerKeyRequest {
|
||||||
|
readonly name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From codersdk/provisionerdaemons.go
|
||||||
|
export interface CreateProvisionerKeyResponse {
|
||||||
|
readonly key: string;
|
||||||
|
}
|
||||||
|
|
||||||
// From codersdk/organizations.go
|
// From codersdk/organizations.go
|
||||||
export interface CreateTemplateRequest {
|
export interface CreateTemplateRequest {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
@@ -955,6 +965,14 @@ export interface ProvisionerJobLog {
|
|||||||
readonly output: string;
|
readonly output: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From codersdk/provisionerdaemons.go
|
||||||
|
export interface ProvisionerKey {
|
||||||
|
readonly id: string;
|
||||||
|
readonly created_at: string;
|
||||||
|
readonly organization: string;
|
||||||
|
readonly name: string;
|
||||||
|
}
|
||||||
|
|
||||||
// From codersdk/workspaceproxy.go
|
// From codersdk/workspaceproxy.go
|
||||||
export interface ProxyHealthReport {
|
export interface ProxyHealthReport {
|
||||||
readonly errors: readonly string[];
|
readonly errors: readonly string[];
|
||||||
@@ -2029,6 +2047,7 @@ export type FeatureName =
|
|||||||
| "external_token_encryption"
|
| "external_token_encryption"
|
||||||
| "high_availability"
|
| "high_availability"
|
||||||
| "multiple_external_auth"
|
| "multiple_external_auth"
|
||||||
|
| "multiple_organizations"
|
||||||
| "scim"
|
| "scim"
|
||||||
| "template_rbac"
|
| "template_rbac"
|
||||||
| "user_limit"
|
| "user_limit"
|
||||||
@@ -2047,6 +2066,7 @@ export const FeatureNames: FeatureName[] = [
|
|||||||
"external_token_encryption",
|
"external_token_encryption",
|
||||||
"high_availability",
|
"high_availability",
|
||||||
"multiple_external_auth",
|
"multiple_external_auth",
|
||||||
|
"multiple_organizations",
|
||||||
"scim",
|
"scim",
|
||||||
"template_rbac",
|
"template_rbac",
|
||||||
"user_limit",
|
"user_limit",
|
||||||
@@ -2206,6 +2226,7 @@ export type RBACResource =
|
|||||||
| "organization"
|
| "organization"
|
||||||
| "organization_member"
|
| "organization_member"
|
||||||
| "provisioner_daemon"
|
| "provisioner_daemon"
|
||||||
|
| "provisioner_keys"
|
||||||
| "replicas"
|
| "replicas"
|
||||||
| "system"
|
| "system"
|
||||||
| "tailnet_coordinator"
|
| "tailnet_coordinator"
|
||||||
@@ -2232,6 +2253,7 @@ export const RBACResources: RBACResource[] = [
|
|||||||
"organization",
|
"organization",
|
||||||
"organization_member",
|
"organization_member",
|
||||||
"provisioner_daemon",
|
"provisioner_daemon",
|
||||||
|
"provisioner_keys",
|
||||||
"replicas",
|
"replicas",
|
||||||
"system",
|
"system",
|
||||||
"tailnet_coordinator",
|
"tailnet_coordinator",
|
||||||
|
|||||||
Reference in New Issue
Block a user