mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: Add backend API support for resource metadata (#3242)
* Initial support for metadata in provisioner API and Terraform provisioner * add support for nullable metadata fields * handle metadata fields in provisionerd and API
This commit is contained in:
@ -26,21 +26,22 @@ func New() database.Store {
|
||||
organizations: make([]database.Organization, 0),
|
||||
users: make([]database.User, 0),
|
||||
|
||||
auditLogs: make([]database.AuditLog, 0),
|
||||
files: make([]database.File, 0),
|
||||
gitSSHKey: make([]database.GitSSHKey, 0),
|
||||
parameterSchemas: make([]database.ParameterSchema, 0),
|
||||
parameterValues: make([]database.ParameterValue, 0),
|
||||
provisionerDaemons: make([]database.ProvisionerDaemon, 0),
|
||||
provisionerJobAgents: make([]database.WorkspaceAgent, 0),
|
||||
provisionerJobLogs: make([]database.ProvisionerJobLog, 0),
|
||||
provisionerJobResources: make([]database.WorkspaceResource, 0),
|
||||
provisionerJobs: make([]database.ProvisionerJob, 0),
|
||||
templateVersions: make([]database.TemplateVersion, 0),
|
||||
templates: make([]database.Template, 0),
|
||||
workspaceBuilds: make([]database.WorkspaceBuild, 0),
|
||||
workspaceApps: make([]database.WorkspaceApp, 0),
|
||||
workspaces: make([]database.Workspace, 0),
|
||||
auditLogs: make([]database.AuditLog, 0),
|
||||
files: make([]database.File, 0),
|
||||
gitSSHKey: make([]database.GitSSHKey, 0),
|
||||
parameterSchemas: make([]database.ParameterSchema, 0),
|
||||
parameterValues: make([]database.ParameterValue, 0),
|
||||
provisionerDaemons: make([]database.ProvisionerDaemon, 0),
|
||||
provisionerJobAgents: make([]database.WorkspaceAgent, 0),
|
||||
provisionerJobLogs: make([]database.ProvisionerJobLog, 0),
|
||||
provisionerJobResources: make([]database.WorkspaceResource, 0),
|
||||
provisionerJobResourceMetadata: make([]database.WorkspaceResourceMetadatum, 0),
|
||||
provisionerJobs: make([]database.ProvisionerJob, 0),
|
||||
templateVersions: make([]database.TemplateVersion, 0),
|
||||
templates: make([]database.Template, 0),
|
||||
workspaceBuilds: make([]database.WorkspaceBuild, 0),
|
||||
workspaceApps: make([]database.WorkspaceApp, 0),
|
||||
workspaces: make([]database.Workspace, 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -74,21 +75,22 @@ type data struct {
|
||||
users []database.User
|
||||
|
||||
// New tables
|
||||
auditLogs []database.AuditLog
|
||||
files []database.File
|
||||
gitSSHKey []database.GitSSHKey
|
||||
parameterSchemas []database.ParameterSchema
|
||||
parameterValues []database.ParameterValue
|
||||
provisionerDaemons []database.ProvisionerDaemon
|
||||
provisionerJobAgents []database.WorkspaceAgent
|
||||
provisionerJobLogs []database.ProvisionerJobLog
|
||||
provisionerJobResources []database.WorkspaceResource
|
||||
provisionerJobs []database.ProvisionerJob
|
||||
templateVersions []database.TemplateVersion
|
||||
templates []database.Template
|
||||
workspaceBuilds []database.WorkspaceBuild
|
||||
workspaceApps []database.WorkspaceApp
|
||||
workspaces []database.Workspace
|
||||
auditLogs []database.AuditLog
|
||||
files []database.File
|
||||
gitSSHKey []database.GitSSHKey
|
||||
parameterSchemas []database.ParameterSchema
|
||||
parameterValues []database.ParameterValue
|
||||
provisionerDaemons []database.ProvisionerDaemon
|
||||
provisionerJobAgents []database.WorkspaceAgent
|
||||
provisionerJobLogs []database.ProvisionerJobLog
|
||||
provisionerJobResources []database.WorkspaceResource
|
||||
provisionerJobResourceMetadata []database.WorkspaceResourceMetadatum
|
||||
provisionerJobs []database.ProvisionerJob
|
||||
templateVersions []database.TemplateVersion
|
||||
templates []database.Template
|
||||
workspaceBuilds []database.WorkspaceBuild
|
||||
workspaceApps []database.WorkspaceApp
|
||||
workspaces []database.Workspace
|
||||
|
||||
deploymentID string
|
||||
}
|
||||
@ -1331,6 +1333,34 @@ func (q *fakeQuerier) GetWorkspaceResourcesCreatedAfter(_ context.Context, after
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceResourceMetadataByResourceID(_ context.Context, id uuid.UUID) ([]database.WorkspaceResourceMetadatum, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
metadata := make([]database.WorkspaceResourceMetadatum, 0)
|
||||
for _, metadatum := range q.provisionerJobResourceMetadata {
|
||||
if metadatum.WorkspaceResourceID.String() == id.String() {
|
||||
metadata = append(metadata, metadatum)
|
||||
}
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceResourceMetadataByResourceIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceResourceMetadatum, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
metadata := make([]database.WorkspaceResourceMetadatum, 0)
|
||||
for _, metadatum := range q.provisionerJobResourceMetadata {
|
||||
for _, id := range ids {
|
||||
if metadatum.WorkspaceResourceID.String() == id.String() {
|
||||
metadata = append(metadata, metadatum)
|
||||
}
|
||||
}
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetProvisionerJobsByIDs(_ context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@ -1659,6 +1689,21 @@ func (q *fakeQuerier) InsertWorkspaceResource(_ context.Context, arg database.In
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertWorkspaceResourceMetadata(_ context.Context, arg database.InsertWorkspaceResourceMetadataParams) (database.WorkspaceResourceMetadatum, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
//nolint:gosimple
|
||||
metadatum := database.WorkspaceResourceMetadatum{
|
||||
WorkspaceResourceID: arg.WorkspaceResourceID,
|
||||
Key: arg.Key,
|
||||
Value: arg.Value,
|
||||
Sensitive: arg.Sensitive,
|
||||
}
|
||||
q.provisionerJobResourceMetadata = append(q.provisionerJobResourceMetadata, metadatum)
|
||||
return metadatum, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParams) (database.User, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
13
coderd/database/dump.sql
generated
13
coderd/database/dump.sql
generated
@ -328,6 +328,13 @@ CREATE TABLE workspace_builds (
|
||||
reason build_reason DEFAULT 'initiator'::public.build_reason NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_resource_metadata (
|
||||
workspace_resource_id uuid NOT NULL,
|
||||
key character varying(1024) NOT NULL,
|
||||
value character varying(65536),
|
||||
sensitive boolean NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_resources (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
@ -433,6 +440,9 @@ ALTER TABLE ONLY workspace_builds
|
||||
ALTER TABLE ONLY workspace_builds
|
||||
ADD CONSTRAINT workspace_builds_workspace_id_name_key UNIQUE (workspace_id, name);
|
||||
|
||||
ALTER TABLE ONLY workspace_resource_metadata
|
||||
ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (workspace_resource_id, key);
|
||||
|
||||
ALTER TABLE ONLY workspace_resources
|
||||
ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id);
|
||||
|
||||
@ -518,6 +528,9 @@ ALTER TABLE ONLY workspace_builds
|
||||
ALTER TABLE ONLY workspace_builds
|
||||
ADD CONSTRAINT workspace_builds_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY workspace_resource_metadata
|
||||
ADD CONSTRAINT workspace_resource_metadata_workspace_resource_id_fkey FOREIGN KEY (workspace_resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY workspace_resources
|
||||
ADD CONSTRAINT workspace_resources_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS workspace_resource_metadata;
|
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS workspace_resource_metadata (
|
||||
workspace_resource_id uuid NOT NULL,
|
||||
key varchar(1024) NOT NULL,
|
||||
value varchar(65536),
|
||||
sensitive boolean NOT NULL,
|
||||
PRIMARY KEY (workspace_resource_id, key),
|
||||
FOREIGN KEY (workspace_resource_id) REFERENCES workspace_resources (id) ON DELETE CASCADE
|
||||
);
|
@ -564,3 +564,10 @@ type WorkspaceResource struct {
|
||||
Type string `db:"type" json:"type"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
|
||||
type WorkspaceResourceMetadatum struct {
|
||||
WorkspaceResourceID uuid.UUID `db:"workspace_resource_id" json:"workspace_resource_id"`
|
||||
Key string `db:"key" json:"key"`
|
||||
Value sql.NullString `db:"value" json:"value"`
|
||||
Sensitive bool `db:"sensitive" json:"sensitive"`
|
||||
}
|
||||
|
@ -84,6 +84,8 @@ type querier interface {
|
||||
GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWorkspaceByOwnerIDAndNameParams) (Workspace, error)
|
||||
GetWorkspaceOwnerCountsByTemplateIDs(ctx context.Context, ids []uuid.UUID) ([]GetWorkspaceOwnerCountsByTemplateIDsRow, error)
|
||||
GetWorkspaceResourceByID(ctx context.Context, id uuid.UUID) (WorkspaceResource, error)
|
||||
GetWorkspaceResourceMetadataByResourceID(ctx context.Context, workspaceResourceID uuid.UUID) ([]WorkspaceResourceMetadatum, error)
|
||||
GetWorkspaceResourceMetadataByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResourceMetadatum, error)
|
||||
GetWorkspaceResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceResource, error)
|
||||
GetWorkspaceResourcesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResource, error)
|
||||
GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) ([]Workspace, error)
|
||||
@ -108,6 +110,7 @@ type querier interface {
|
||||
InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error)
|
||||
InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuild, error)
|
||||
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
|
||||
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) (WorkspaceResourceMetadatum, error)
|
||||
ParameterValue(ctx context.Context, id uuid.UUID) (ParameterValue, error)
|
||||
ParameterValues(ctx context.Context, arg ParameterValuesParams) ([]ParameterValue, error)
|
||||
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
|
||||
|
@ -3895,6 +3895,80 @@ func (q *sqlQuerier) GetWorkspaceResourceByID(ctx context.Context, id uuid.UUID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceResourceMetadataByResourceID = `-- name: GetWorkspaceResourceMetadataByResourceID :many
|
||||
SELECT
|
||||
workspace_resource_id, key, value, sensitive
|
||||
FROM
|
||||
workspace_resource_metadata
|
||||
WHERE
|
||||
workspace_resource_id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceResourceMetadataByResourceID(ctx context.Context, workspaceResourceID uuid.UUID) ([]WorkspaceResourceMetadatum, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceResourceMetadataByResourceID, workspaceResourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceResourceMetadatum
|
||||
for rows.Next() {
|
||||
var i WorkspaceResourceMetadatum
|
||||
if err := rows.Scan(
|
||||
&i.WorkspaceResourceID,
|
||||
&i.Key,
|
||||
&i.Value,
|
||||
&i.Sensitive,
|
||||
); 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 getWorkspaceResourceMetadataByResourceIDs = `-- name: GetWorkspaceResourceMetadataByResourceIDs :many
|
||||
SELECT
|
||||
workspace_resource_id, key, value, sensitive
|
||||
FROM
|
||||
workspace_resource_metadata
|
||||
WHERE
|
||||
workspace_resource_id = ANY($1 :: uuid [ ])
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceResourceMetadataByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResourceMetadatum, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceResourceMetadataByResourceIDs, pq.Array(ids))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceResourceMetadatum
|
||||
for rows.Next() {
|
||||
var i WorkspaceResourceMetadatum
|
||||
if err := rows.Scan(
|
||||
&i.WorkspaceResourceID,
|
||||
&i.Key,
|
||||
&i.Value,
|
||||
&i.Sensitive,
|
||||
); 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 getWorkspaceResourcesByJobID = `-- name: GetWorkspaceResourcesByJobID :many
|
||||
SELECT
|
||||
id, created_at, job_id, transition, type, name
|
||||
@ -4005,6 +4079,37 @@ func (q *sqlQuerier) InsertWorkspaceResource(ctx context.Context, arg InsertWork
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertWorkspaceResourceMetadata = `-- name: InsertWorkspaceResourceMetadata :one
|
||||
INSERT INTO
|
||||
workspace_resource_metadata (workspace_resource_id, key, value, sensitive)
|
||||
VALUES
|
||||
($1, $2, $3, $4) RETURNING workspace_resource_id, key, value, sensitive
|
||||
`
|
||||
|
||||
type InsertWorkspaceResourceMetadataParams struct {
|
||||
WorkspaceResourceID uuid.UUID `db:"workspace_resource_id" json:"workspace_resource_id"`
|
||||
Key string `db:"key" json:"key"`
|
||||
Value sql.NullString `db:"value" json:"value"`
|
||||
Sensitive bool `db:"sensitive" json:"sensitive"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) (WorkspaceResourceMetadatum, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertWorkspaceResourceMetadata,
|
||||
arg.WorkspaceResourceID,
|
||||
arg.Key,
|
||||
arg.Value,
|
||||
arg.Sensitive,
|
||||
)
|
||||
var i WorkspaceResourceMetadatum
|
||||
err := row.Scan(
|
||||
&i.WorkspaceResourceID,
|
||||
&i.Key,
|
||||
&i.Value,
|
||||
&i.Sensitive,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceByID = `-- name: GetWorkspaceByID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl
|
||||
|
@ -22,3 +22,25 @@ INSERT INTO
|
||||
workspace_resources (id, created_at, job_id, transition, type, name)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6) RETURNING *;
|
||||
|
||||
-- name: GetWorkspaceResourceMetadataByResourceID :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_resource_metadata
|
||||
WHERE
|
||||
workspace_resource_id = $1;
|
||||
|
||||
-- name: GetWorkspaceResourceMetadataByResourceIDs :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_resource_metadata
|
||||
WHERE
|
||||
workspace_resource_id = ANY(@ids :: uuid [ ]);
|
||||
|
||||
-- name: InsertWorkspaceResourceMetadata :one
|
||||
INSERT INTO
|
||||
workspace_resource_metadata (workspace_resource_id, key, value, sensitive)
|
||||
VALUES
|
||||
($1, $2, $3, $4) RETURNING *;
|
||||
|
@ -829,6 +829,25 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
snapshot.WorkspaceApps = append(snapshot.WorkspaceApps, telemetry.ConvertWorkspaceApp(dbApp))
|
||||
}
|
||||
}
|
||||
|
||||
for _, metadatum := range protoResource.Metadata {
|
||||
var value sql.NullString
|
||||
if !metadatum.IsNull {
|
||||
value.String = metadatum.Value
|
||||
value.Valid = true
|
||||
}
|
||||
|
||||
_, err := db.InsertWorkspaceResourceMetadata(ctx, database.InsertWorkspaceResourceMetadataParams{
|
||||
WorkspaceResourceID: resource.ID,
|
||||
Key: metadatum.Key,
|
||||
Value: value,
|
||||
Sensitive: metadatum.Sensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert metadata: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -241,6 +241,14 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request,
|
||||
})
|
||||
return
|
||||
}
|
||||
resourceMetadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(r.Context(), resourceIDs)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace metadata.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
apiResources := make([]codersdk.WorkspaceResource, 0)
|
||||
for _, resource := range resources {
|
||||
@ -266,7 +274,13 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request,
|
||||
}
|
||||
agents = append(agents, apiAgent)
|
||||
}
|
||||
apiResources = append(apiResources, convertWorkspaceResource(resource, agents))
|
||||
metadata := make([]database.WorkspaceResourceMetadatum, 0)
|
||||
for _, field := range resourceMetadata {
|
||||
if field.WorkspaceResourceID == resource.ID {
|
||||
metadata = append(metadata, field)
|
||||
}
|
||||
}
|
||||
apiResources = append(apiResources, convertWorkspaceResource(resource, agents, metadata))
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, apiResources)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/moby/moby/pkg/namesgenerator"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
@ -681,7 +682,35 @@ func convertWorkspaceBuild(
|
||||
}
|
||||
}
|
||||
|
||||
func convertWorkspaceResource(resource database.WorkspaceResource, agents []codersdk.WorkspaceAgent) codersdk.WorkspaceResource {
|
||||
func convertWorkspaceResource(resource database.WorkspaceResource, agents []codersdk.WorkspaceAgent, metadata []database.WorkspaceResourceMetadatum) codersdk.WorkspaceResource {
|
||||
metadataMap := map[string]database.WorkspaceResourceMetadatum{}
|
||||
|
||||
// implicit metadata fields come first
|
||||
metadataMap["type"] = database.WorkspaceResourceMetadatum{
|
||||
Key: "type",
|
||||
Value: sql.NullString{String: resource.Type, Valid: true},
|
||||
Sensitive: false,
|
||||
}
|
||||
// explicit metadata fields come afterward, and can override implicit ones
|
||||
for _, field := range metadata {
|
||||
metadataMap[field.Key] = field
|
||||
}
|
||||
|
||||
var convertedMetadata []codersdk.WorkspaceResourceMetadata
|
||||
for _, field := range metadataMap {
|
||||
if field.Value.Valid {
|
||||
convertedField := codersdk.WorkspaceResourceMetadata{
|
||||
Key: field.Key,
|
||||
Value: field.Value.String,
|
||||
Sensitive: field.Sensitive,
|
||||
}
|
||||
convertedMetadata = append(convertedMetadata, convertedField)
|
||||
}
|
||||
}
|
||||
slices.SortFunc(convertedMetadata, func(a, b codersdk.WorkspaceResourceMetadata) bool {
|
||||
return a.Key < b.Key
|
||||
})
|
||||
|
||||
return codersdk.WorkspaceResource{
|
||||
ID: resource.ID,
|
||||
CreatedAt: resource.CreatedAt,
|
||||
@ -690,5 +719,6 @@ func convertWorkspaceResource(resource database.WorkspaceResource, agents []code
|
||||
Type: resource.Type,
|
||||
Name: resource.Name,
|
||||
Agents: agents,
|
||||
Metadata: convertedMetadata,
|
||||
}
|
||||
}
|
||||
|
@ -80,5 +80,14 @@ func (api *API) workspaceResource(rw http.ResponseWriter, r *http.Request) {
|
||||
apiAgents = append(apiAgents, convertedAgent)
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertWorkspaceResource(workspaceResource, apiAgents))
|
||||
metadata, err := api.Database.GetWorkspaceResourceMetadataByResourceID(r.Context(), workspaceResource.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace resource metadata.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertWorkspaceResource(workspaceResource, apiAgents, metadata))
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
@ -90,4 +91,64 @@ func TestWorkspaceResource(t *testing.T) {
|
||||
require.Equal(t, app.Icon, got.Icon)
|
||||
require.Equal(t, app.Name, got.Name)
|
||||
})
|
||||
|
||||
t.Run("Metadata", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
IncludeProvisionerD: true,
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
Agents: []*proto.Agent{{
|
||||
Id: "something",
|
||||
Auth: &proto.Agent_Token{},
|
||||
}},
|
||||
Metadata: []*proto.Resource_Metadata{{
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
}, {
|
||||
Key: "null",
|
||||
IsNull: true,
|
||||
}, {
|
||||
Key: "empty",
|
||||
}, {
|
||||
Key: "secret",
|
||||
Value: "squirrel",
|
||||
Sensitive: true,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
|
||||
require.NoError(t, err)
|
||||
resource, err := client.WorkspaceResource(context.Background(), resources[0].ID)
|
||||
require.NoError(t, err)
|
||||
metadata := resource.Metadata
|
||||
require.Equal(t, []codersdk.WorkspaceResourceMetadata{{
|
||||
Key: "empty",
|
||||
}, {
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
}, {
|
||||
Key: "secret",
|
||||
Value: "squirrel",
|
||||
Sensitive: true,
|
||||
}, {
|
||||
Key: "type",
|
||||
Value: "example",
|
||||
}}, metadata)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user