diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 01579c0c659a2..487aac8f7fb76 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11024,6 +11024,12 @@ const docTemplate = `{ "organization": { "type": "string", "format": "uuid" + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string" + } } } }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a9b61c05f18e4..be72fcb8d03ac 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -9950,6 +9950,12 @@ "organization": { "type": "string", "format": "uuid" + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string" + } } } }, diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 29f7b1f2e5a69..43cda96778841 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -472,6 +472,7 @@ func ProvisionerKey(t testing.TB, db database.Store, orig database.ProvisionerKe OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), HashedSecret: orig.HashedSecret, + Tags: orig.Tags, }) require.NoError(t, err, "insert provisioner key") return key diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index f74197e234fa8..827d99a2c14df 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6586,6 +6586,7 @@ func (q *FakeQuerier) InsertProvisionerKey(_ context.Context, arg database.Inser OrganizationID: arg.OrganizationID, Name: strings.ToLower(arg.Name), HashedSecret: arg.HashedSecret, + Tags: arg.Tags, } q.provisionerKeys = append(q.provisionerKeys, provisionerKey) @@ -7276,13 +7277,7 @@ func (q *FakeQuerier) ListProvisionerKeysByOrganization(_ context.Context, organ 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, - }) + keys = append(keys, key) } } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index d07519cff7de0..dc15cf9bd4af8 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -754,7 +754,8 @@ CREATE TABLE provisioner_keys ( created_at timestamp with time zone NOT NULL, organization_id uuid NOT NULL, name character varying(64) NOT NULL, - hashed_secret bytea NOT NULL + hashed_secret bytea NOT NULL, + tags jsonb NOT NULL ); CREATE TABLE replicas ( diff --git a/coderd/database/migrations/000231_provisioner_key_tags.down.sql b/coderd/database/migrations/000231_provisioner_key_tags.down.sql new file mode 100644 index 0000000000000..11ea29e62ec44 --- /dev/null +++ b/coderd/database/migrations/000231_provisioner_key_tags.down.sql @@ -0,0 +1 @@ +ALTER TABLE provisioner_keys DROP COLUMN tags; diff --git a/coderd/database/migrations/000231_provisioner_key_tags.up.sql b/coderd/database/migrations/000231_provisioner_key_tags.up.sql new file mode 100644 index 0000000000000..34a1d768cb285 --- /dev/null +++ b/coderd/database/migrations/000231_provisioner_key_tags.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE provisioner_keys ADD COLUMN tags jsonb DEFAULT '{}'::jsonb NOT NULL; +ALTER TABLE provisioner_keys ALTER COLUMN tags DROP DEFAULT; diff --git a/coderd/database/models.go b/coderd/database/models.go index 9b35e1c0f79b3..0ee78e286516e 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2191,6 +2191,7 @@ type ProvisionerKey struct { OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` Name string `db:"name" json:"name"` HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` + Tags StringMap `db:"tags" json:"tags"` } type Replica struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b761e451b3041..f383f2e7c0d5d 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5533,7 +5533,7 @@ func (q *sqlQuerier) DeleteProvisionerKey(ctx context.Context, id uuid.UUID) err const getProvisionerKeyByID = `-- name: GetProvisionerKeyByID :one SELECT - id, created_at, organization_id, name, hashed_secret + id, created_at, organization_id, name, hashed_secret, tags FROM provisioner_keys WHERE @@ -5549,13 +5549,14 @@ func (q *sqlQuerier) GetProvisionerKeyByID(ctx context.Context, id uuid.UUID) (P &i.OrganizationID, &i.Name, &i.HashedSecret, + &i.Tags, ) return i, err } const getProvisionerKeyByName = `-- name: GetProvisionerKeyByName :one SELECT - id, created_at, organization_id, name, hashed_secret + id, created_at, organization_id, name, hashed_secret, tags FROM provisioner_keys WHERE @@ -5578,21 +5579,23 @@ func (q *sqlQuerier) GetProvisionerKeyByName(ctx context.Context, arg GetProvisi &i.OrganizationID, &i.Name, &i.HashedSecret, + &i.Tags, ) return i, err } const insertProvisionerKey = `-- name: InsertProvisionerKey :one INSERT INTO - provisioner_keys ( - id, + provisioner_keys ( + id, created_at, organization_id, - name, - hashed_secret - ) + name, + hashed_secret, + tags + ) VALUES - ($1, $2, $3, lower($5), $4) RETURNING id, created_at, organization_id, name, hashed_secret + ($1, $2, $3, lower($6), $4, $5) RETURNING id, created_at, organization_id, name, hashed_secret, tags ` type InsertProvisionerKeyParams struct { @@ -5600,6 +5603,7 @@ type InsertProvisionerKeyParams struct { 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"` + Tags StringMap `db:"tags" json:"tags"` Name string `db:"name" json:"name"` } @@ -5609,6 +5613,7 @@ func (q *sqlQuerier) InsertProvisionerKey(ctx context.Context, arg InsertProvisi arg.CreatedAt, arg.OrganizationID, arg.HashedSecret, + arg.Tags, arg.Name, ) var i ProvisionerKey @@ -5618,13 +5623,14 @@ func (q *sqlQuerier) InsertProvisionerKey(ctx context.Context, arg InsertProvisi &i.OrganizationID, &i.Name, &i.HashedSecret, + &i.Tags, ) return i, err } const listProvisionerKeysByOrganization = `-- name: ListProvisionerKeysByOrganization :many SELECT - id, created_at, organization_id, name, hashed_secret + id, created_at, organization_id, name, hashed_secret, tags FROM provisioner_keys WHERE @@ -5646,6 +5652,7 @@ func (q *sqlQuerier) ListProvisionerKeysByOrganization(ctx context.Context, orga &i.OrganizationID, &i.Name, &i.HashedSecret, + &i.Tags, ); err != nil { return nil, err } diff --git a/coderd/database/queries/provisionerkeys.sql b/coderd/database/queries/provisionerkeys.sql index 22e714eca350d..ac41eb2d444d2 100644 --- a/coderd/database/queries/provisionerkeys.sql +++ b/coderd/database/queries/provisionerkeys.sql @@ -1,14 +1,15 @@ -- name: InsertProvisionerKey :one INSERT INTO - provisioner_keys ( - id, + provisioner_keys ( + id, created_at, organization_id, - name, - hashed_secret - ) + name, + hashed_secret, + tags + ) VALUES - ($1, $2, $3, lower(@name), $4) RETURNING *; + ($1, $2, $3, lower(@name), $4, $5) RETURNING *; -- name: GetProvisionerKeyByID :one SELECT diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index fc56cf943dc3b..2896e7035fcfa 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -44,6 +44,9 @@ sql: - column: "provisioner_daemons.tags" go_type: type: "StringMap" + - column: "provisioner_keys.tags" + go_type: + type: "StringMap" - column: "provisioner_jobs.tags" go_type: type: "StringMap" diff --git a/coderd/provisionerkey/provisionerkey.go b/coderd/provisionerkey/provisionerkey.go index 70354c140b73e..5be3658f6a5be 100644 --- a/coderd/provisionerkey/provisionerkey.go +++ b/coderd/provisionerkey/provisionerkey.go @@ -14,7 +14,7 @@ import ( "github.com/coder/coder/v2/cryptorand" ) -func New(organizationID uuid.UUID, name string) (database.InsertProvisionerKeyParams, string, error) { +func New(organizationID uuid.UUID, name string, tags map[string]string) (database.InsertProvisionerKeyParams, string, error) { id := uuid.New() secret, err := cryptorand.HexString(64) if err != nil { @@ -23,12 +23,17 @@ func New(organizationID uuid.UUID, name string) (database.InsertProvisionerKeyPa hashedSecret := HashSecret(secret) token := fmt.Sprintf("%s:%s", id, secret) + if tags == nil { + tags = map[string]string{} + } + return database.InsertProvisionerKeyParams{ ID: id, CreatedAt: dbtime.Now(), OrganizationID: organizationID, Name: name, HashedSecret: hashedSecret, + Tags: tags, }, token, nil } diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index e8be78525d6e6..df481dc04a18d 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -274,15 +274,17 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione } type ProvisionerKey struct { - ID uuid.UUID `json:"id" table:"-" format:"uuid"` - CreatedAt time.Time `json:"created_at" table:"created_at" format:"date-time"` - OrganizationID uuid.UUID `json:"organization" table:"organization_id" format:"uuid"` - Name string `json:"name" table:"name,default_sort"` + ID uuid.UUID `json:"id" table:"-" format:"uuid"` + CreatedAt time.Time `json:"created_at" table:"created_at" format:"date-time"` + OrganizationID uuid.UUID `json:"organization" table:"organization_id" format:"uuid"` + Name string `json:"name" table:"name,default_sort"` + Tags map[string]string `json:"tags" table:"tags"` // HashedSecret - never include the access token in the API response } type CreateProvisionerKeyRequest struct { - Name string `json:"name"` + Name string `json:"name"` + Tags map[string]string `json:"tags"` } type CreateProvisionerKeyResponse struct { diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 876e6e9d5c554..dec875eebaac3 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -1389,7 +1389,11 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi "created_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", - "organization": "452c1a86-a0af-475b-b03f-724878b0f387" + "organization": "452c1a86-a0af-475b-b03f-724878b0f387", + "tags": { + "property1": "string", + "property2": "string" + } } ] ``` @@ -1404,13 +1408,15 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi 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 | | | +| 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 | | | +| `» tags` | object | false | | | +| `»» [any property]` | string | false | | | To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/docs/api/schemas.md b/docs/api/schemas.md index e79e27377b324..8c8495a3def4c 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3995,18 +3995,24 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "created_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", - "organization": "452c1a86-a0af-475b-b03f-724878b0f387" + "organization": "452c1a86-a0af-475b-b03f-724878b0f387", + "tags": { + "property1": "string", + "property2": "string" + } } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `id` | string | false | | | -| `name` | string | false | | | -| `organization` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------ | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `id` | string | false | | | +| `name` | string | false | | | +| `organization` | string | false | | | +| `tags` | object | false | | | +| » `[any property]` | string | false | | | ## codersdk.ProvisionerLogLevel diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go index 8253d4826e164..9c2807cbf6439 100644 --- a/enterprise/cli/provisionerkeys.go +++ b/enterprise/cli/provisionerkeys.go @@ -33,7 +33,10 @@ func (r *RootCmd) provisionerKeys() *serpent.Command { } func (r *RootCmd) provisionerKeysCreate() *serpent.Command { - orgContext := agpl.NewOrganizationContext() + var ( + orgContext = agpl.NewOrganizationContext() + rawTags []string + ) client := new(codersdk.Client) cmd := &serpent.Command{ @@ -51,8 +54,14 @@ func (r *RootCmd) provisionerKeysCreate() *serpent.Command { return xerrors.Errorf("current organization: %w", err) } + tags, err := agpl.ParseProvisionerTags(rawTags) + if err != nil { + return err + } + res, err := client.CreateProvisionerKey(ctx, org.ID, codersdk.CreateProvisionerKeyRequest{ Name: inv.Args[0], + Tags: tags, }) if err != nil { return xerrors.Errorf("create provisioner key: %w", err) @@ -69,7 +78,15 @@ func (r *RootCmd) provisionerKeysCreate() *serpent.Command { }, } - cmd.Options = serpent.OptionSet{} + cmd.Options = serpent.OptionSet{ + { + Flag: "tag", + FlagShorthand: "t", + Env: "CODER_PROVISIONERD_TAGS", + Description: "Tags to filter provisioner jobs by.", + Value: serpent.StringArrayOf(&rawTags), + }, + } orgContext.AttachOptions(cmd) return cmd diff --git a/enterprise/cli/provisionerkeys_test.go b/enterprise/cli/provisionerkeys_test.go index dac764da616b9..5b62b1e9d46fd 100644 --- a/enterprise/cli/provisionerkeys_test.go +++ b/enterprise/cli/provisionerkeys_test.go @@ -41,7 +41,7 @@ func TestProvisionerKeys(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) inv, conf := newCLI( t, - "provisioner", "keys", "create", name, + "provisioner", "keys", "create", name, "--tag", "foo=bar", ) pty := ptytest.New(t) @@ -77,8 +77,10 @@ func TestProvisionerKeys(t *testing.T) { require.Contains(t, line, "NAME") require.Contains(t, line, "CREATED AT") require.Contains(t, line, "ORGANIZATION ID") + require.Contains(t, line, "TAGS") line = pty.ReadLine(ctx) require.Contains(t, line, strings.ToLower(name)) + require.Contains(t, line, "map[foo:bar]") inv, conf = newCLI( t, diff --git a/enterprise/coderd/provisionerdaemons_test.go b/enterprise/coderd/provisionerdaemons_test.go index 139a97199ee92..68055df5b77f5 100644 --- a/enterprise/coderd/provisionerdaemons_test.go +++ b/enterprise/coderd/provisionerdaemons_test.go @@ -559,7 +559,7 @@ func TestProvisionerDaemonServe(t *testing.T) { t.Run("ProvisionerKeyAuth", func(t *testing.T) { t.Parallel() - insertParams, token, err := provisionerkey.New(uuid.Nil, "dont-TEST-me") + insertParams, token, err := provisionerkey.New(uuid.Nil, "dont-TEST-me", nil) require.NoError(t, err) tcs := []struct { diff --git a/enterprise/coderd/provisionerkeys.go b/enterprise/coderd/provisionerkeys.go index 9cb66e2b7910d..a9f003682c6f2 100644 --- a/enterprise/coderd/provisionerkeys.go +++ b/enterprise/coderd/provisionerkeys.go @@ -54,7 +54,7 @@ func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) { return } - params, token, err := provisionerkey.New(organization.ID, req.Name) + params, token, err := provisionerkey.New(organization.ID, req.Name, req.Tags) if err != nil { httpapi.InternalServerError(rw, err) return @@ -142,6 +142,7 @@ func convertProvisionerKeys(dbKeys []database.ProvisionerKey) []codersdk.Provisi CreatedAt: dbKey.CreatedAt, OrganizationID: dbKey.OrganizationID, Name: dbKey.Name, + Tags: dbKey.Tags, // HashedSecret - never include the access token in the API response }) } diff --git a/enterprise/coderd/provisionerkeys_test.go b/enterprise/coderd/provisionerkeys_test.go index 4c9408e0a27de..6becbe657ced6 100644 --- a/enterprise/coderd/provisionerkeys_test.go +++ b/enterprise/coderd/provisionerkeys_test.go @@ -69,9 +69,13 @@ func TestProvisionerKeys(t *testing.T) { require.NoError(t, err, "org admin list provisioner keys") require.Len(t, keys, 0, "org admin list provisioner keys") + tags := map[string]string{ + "my": "way", + } // org admin can create a provisioner key _, err = orgAdmin.CreateProvisionerKey(ctx, owner.OrganizationID, codersdk.CreateProvisionerKeyRequest{ Name: "Key", // case insensitive + Tags: tags, }) require.NoError(t, err, "org admin create provisioner key") @@ -97,6 +101,8 @@ func TestProvisionerKeys(t *testing.T) { 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") + require.Equal(t, "key", keys[0].Name, "org admin list provisioner keys name matches") + require.EqualValues(t, tags, keys[0].Tags, "org admin list provisioner keys tags match") // org admin can delete a provisioner key err = orgAdmin.DeleteProvisionerKey(ctx, owner.OrganizationID, "key") // using lowercase here works diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index e0de1a184d6fc..2b69bf7b90424 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -237,6 +237,7 @@ export interface CreateOrganizationRequest { // From codersdk/provisionerdaemons.go export interface CreateProvisionerKeyRequest { readonly name: string; + readonly tags: Record; } // From codersdk/provisionerdaemons.go @@ -1002,6 +1003,7 @@ export interface ProvisionerKey { readonly created_at: string; readonly organization: string; readonly name: string; + readonly tags: Record; } // From codersdk/workspaceproxy.go