fix: preserve workspace resource metadata order (#5421)

Fixes #4511.
This commit is contained in:
Kyle Carberry
2022-12-14 13:08:22 -06:00
committed by GitHub
parent c0b251ac52
commit 84995b7320
15 changed files with 131 additions and 160 deletions

View File

@ -2193,19 +2193,6 @@ func (q *fakeQuerier) GetWorkspaceResourceMetadataCreatedAfter(ctx context.Conte
return metadata, 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.workspaceResourceMetadata {
if metadatum.WorkspaceResourceID == id {
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()
@ -2549,19 +2536,31 @@ func (q *fakeQuerier) InsertWorkspaceResource(_ context.Context, arg database.In
return resource, nil
}
func (q *fakeQuerier) InsertWorkspaceResourceMetadata(_ context.Context, arg database.InsertWorkspaceResourceMetadataParams) (database.WorkspaceResourceMetadatum, error) {
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,
metadata := make([]database.WorkspaceResourceMetadatum, 0)
id := int64(1)
if len(q.workspaceResourceMetadata) > 0 {
id = q.workspaceResourceMetadata[len(q.workspaceResourceMetadata)-1].ID
}
q.workspaceResourceMetadata = append(q.workspaceResourceMetadata, metadatum)
return metadatum, nil
for index, key := range arg.Key {
id++
value := arg.Value[index]
metadata = append(metadata, database.WorkspaceResourceMetadatum{
ID: id,
WorkspaceResourceID: arg.WorkspaceResourceID,
Key: key,
Value: sql.NullString{
String: value,
Valid: value != "",
},
Sensitive: arg.Sensitive[index],
})
}
q.workspaceResourceMetadata = append(q.workspaceResourceMetadata, metadata...)
return metadata, nil
}
func (q *fakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParams) (database.User, error) {

View File

@ -462,9 +462,19 @@ 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
sensitive boolean NOT NULL,
id bigint NOT NULL
);
CREATE SEQUENCE workspace_resource_metadata_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE workspace_resource_metadata_id_seq OWNED BY workspace_resource_metadata.id;
CREATE TABLE workspace_resources (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -496,6 +506,8 @@ ALTER TABLE ONLY licenses ALTER COLUMN id SET DEFAULT nextval('licenses_id_seq':
ALTER TABLE ONLY provisioner_job_logs ALTER COLUMN id SET DEFAULT nextval('provisioner_job_logs_id_seq'::regclass);
ALTER TABLE ONLY workspace_resource_metadata ALTER COLUMN id SET DEFAULT nextval('workspace_resource_metadata_id_seq'::regclass);
ALTER TABLE ONLY agent_stats
ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
@ -599,7 +611,10 @@ ALTER TABLE ONLY workspace_builds
ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number);
ALTER TABLE ONLY workspace_resource_metadata
ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (workspace_resource_id, key);
ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key);
ALTER TABLE ONLY workspace_resource_metadata
ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (id);
ALTER TABLE ONLY workspace_resources
ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id);

View File

@ -0,0 +1,4 @@
ALTER TABLE workspace_resource_metadata DROP COLUMN id;
ALTER TABLE workspace_resource_metadata DROP CONSTRAINT workspace_resource_metadata_name;
ALTER TABLE workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (workspace_resource_id, key);

View File

@ -0,0 +1,5 @@
ALTER TABLE workspace_resource_metadata DROP CONSTRAINT workspace_resource_metadata_pkey;
ALTER TABLE workspace_resource_metadata ADD COLUMN id BIGSERIAL PRIMARY KEY;
ALTER TABLE workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_name UNIQUE(workspace_resource_id, key);

View File

@ -730,4 +730,5 @@ type WorkspaceResourceMetadatum struct {
Key string `db:"key" json:"key"`
Value sql.NullString `db:"value" json:"value"`
Sensitive bool `db:"sensitive" json:"sensitive"`
ID int64 `db:"id" json:"id"`
}

View File

@ -122,7 +122,6 @@ type sqlcQuerier interface {
GetWorkspaceCountByUserID(ctx context.Context, ownerID uuid.UUID) (int64, 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)
GetWorkspaceResourceMetadataCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResourceMetadatum, error)
GetWorkspaceResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceResource, error)
@ -163,7 +162,7 @@ type sqlcQuerier 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)
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

View File

@ -5901,50 +5901,13 @@ 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
workspace_resource_id, key, value, sensitive, id
FROM
workspace_resource_metadata
WHERE
workspace_resource_id = ANY($1 :: uuid [ ])
workspace_resource_id = ANY($1 :: uuid [ ]) ORDER BY id ASC
`
func (q *sqlQuerier) GetWorkspaceResourceMetadataByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResourceMetadatum, error) {
@ -5961,6 +5924,7 @@ func (q *sqlQuerier) GetWorkspaceResourceMetadataByResourceIDs(ctx context.Conte
&i.Key,
&i.Value,
&i.Sensitive,
&i.ID,
); err != nil {
return nil, err
}
@ -5976,7 +5940,7 @@ func (q *sqlQuerier) GetWorkspaceResourceMetadataByResourceIDs(ctx context.Conte
}
const getWorkspaceResourceMetadataCreatedAfter = `-- name: GetWorkspaceResourceMetadataCreatedAfter :many
SELECT workspace_resource_id, key, value, sensitive FROM workspace_resource_metadata WHERE workspace_resource_id = ANY(
SELECT workspace_resource_id, key, value, sensitive, id FROM workspace_resource_metadata WHERE workspace_resource_id = ANY(
SELECT id FROM workspace_resources WHERE created_at > $1
)
`
@ -5995,6 +5959,7 @@ func (q *sqlQuerier) GetWorkspaceResourceMetadataCreatedAfter(ctx context.Contex
&i.Key,
&i.Value,
&i.Sensitive,
&i.ID,
); err != nil {
return nil, err
}
@ -6182,35 +6147,55 @@ func (q *sqlQuerier) InsertWorkspaceResource(ctx context.Context, arg InsertWork
return i, err
}
const insertWorkspaceResourceMetadata = `-- name: InsertWorkspaceResourceMetadata :one
const insertWorkspaceResourceMetadata = `-- name: InsertWorkspaceResourceMetadata :many
INSERT INTO
workspace_resource_metadata (workspace_resource_id, key, value, sensitive)
VALUES
($1, $2, $3, $4) RETURNING workspace_resource_id, key, value, sensitive
workspace_resource_metadata
SELECT
$1 :: uuid AS workspace_resource_id,
unnest($2 :: text [ ]) AS key,
unnest($3 :: text [ ]) AS value,
unnest($4 :: boolean [ ]) AS sensitive RETURNING workspace_resource_id, key, value, sensitive, id
`
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"`
WorkspaceResourceID uuid.UUID `db:"workspace_resource_id" json:"workspace_resource_id"`
Key []string `db:"key" json:"key"`
Value []string `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,
func (q *sqlQuerier) InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error) {
rows, err := q.db.QueryContext(ctx, insertWorkspaceResourceMetadata,
arg.WorkspaceResourceID,
arg.Key,
arg.Value,
arg.Sensitive,
pq.Array(arg.Key),
pq.Array(arg.Value),
pq.Array(arg.Sensitive),
)
var i WorkspaceResourceMetadatum
err := row.Scan(
&i.WorkspaceResourceID,
&i.Key,
&i.Value,
&i.Sensitive,
)
return i, err
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,
&i.ID,
); 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 getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one

View File

@ -31,27 +31,22 @@ INSERT INTO
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) 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 [ ]);
workspace_resource_id = ANY(@ids :: uuid [ ]) ORDER BY id ASC;
-- name: InsertWorkspaceResourceMetadata :one
-- name: InsertWorkspaceResourceMetadata :many
INSERT INTO
workspace_resource_metadata (workspace_resource_id, key, value, sensitive)
VALUES
($1, $2, $3, $4) RETURNING *;
workspace_resource_metadata
SELECT
@workspace_resource_id :: uuid AS workspace_resource_id,
unnest(@key :: text [ ]) AS key,
unnest(@value :: text [ ]) AS value,
unnest(@sensitive :: boolean [ ]) AS sensitive RETURNING *;
-- name: GetWorkspaceResourceMetadataCreatedAfter :many
SELECT * FROM workspace_resource_metadata WHERE workspace_resource_id = ANY(

View File

@ -19,6 +19,7 @@ const (
UniqueWorkspaceAppsAgentIDSlugIndex UniqueConstraint = "workspace_apps_agent_id_slug_idx" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug);
UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id);
UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number);
UniqueWorkspaceResourceMetadataName UniqueConstraint = "workspace_resource_metadata_name" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key);
UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name);
UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name));
UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false);

View File

@ -966,22 +966,23 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
}
}
arg := database.InsertWorkspaceResourceMetadataParams{
WorkspaceResourceID: resource.ID,
Key: []string{},
Value: []string{},
Sensitive: []bool{},
}
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)
if metadatum.IsNull {
continue
}
arg.Key = append(arg.Key, metadatum.Key)
arg.Value = append(arg.Value, metadatum.Value)
arg.Sensitive = append(arg.Sensitive, metadatum.Sensitive)
}
_, err = db.InsertWorkspaceResourceMetadata(ctx, arg)
if err != nil {
return xerrors.Errorf("insert workspace resource metadata: %w", err)
}
return nil

View File

@ -948,33 +948,14 @@ func (api *API) convertWorkspaceBuild(
}
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)
}
for _, field := range metadata {
convertedMetadata = append(convertedMetadata, codersdk.WorkspaceResourceMetadata{
Key: field.Key,
Value: field.Value.String,
Sensitive: field.Sensitive,
})
}
slices.SortFunc(convertedMetadata, func(a, b codersdk.WorkspaceResourceMetadata) bool {
return a.Key < b.Key
})
return codersdk.WorkspaceResource{
ID: resource.ID,

View File

@ -1770,17 +1770,14 @@ func TestWorkspaceResource(t *testing.T) {
require.NoError(t, err)
metadata := workspace.LatestBuild.Resources[0].Metadata
require.Equal(t, []codersdk.WorkspaceResourceMetadata{{
Key: "empty",
}, {
Key: "foo",
Value: "bar",
}, {
Key: "empty",
}, {
Key: "secret",
Value: "squirrel",
Sensitive: true,
}, {
Key: "type",
Value: "example",
}}, metadata)
})
}