diff --git a/service/policy/db/migrations/20240806142109_add_resource_mapping_groups.md b/service/policy/db/migrations/20240806142109_add_resource_mapping_groups.md new file mode 100644 index 0000000000..b76d2a0e4b --- /dev/null +++ b/service/policy/db/migrations/20240806142109_add_resource_mapping_groups.md @@ -0,0 +1,32 @@ +# Diagram for 20240806142109_add_resource_mapping_groups.sql + +## Background + +This schema reflects the addition of a `resource_mapping_groups` table, allowing existing Resource Mappings to be grouped by namespace and a common name. The migration also updates the `resource_mappings` table to include a `group_id` column, which will be used to optionally associate a Resource Mapping with a group. + +# ERD + +```mermaid +--- +title: Database Schema Mermaid Diagram +--- + +erDiagram + + ResourceMappingGroup ||--|{ ResourceMapping : has + + ResourceMappingGroup { + uuid id PK + uuid namespace_id + varchar name + compIdx comp_key UK "namespace_id + name" + } + + ResourceMapping { + uuid id PK + uuid attribute_value_id FK + varchar[] terms + jsonb metadata + uuid group_id FK + } +``` \ No newline at end of file diff --git a/service/policy/db/migrations/20240806142109_add_resource_mapping_groups.sql b/service/policy/db/migrations/20240806142109_add_resource_mapping_groups.sql new file mode 100644 index 0000000000..1cef3156c6 --- /dev/null +++ b/service/policy/db/migrations/20240806142109_add_resource_mapping_groups.sql @@ -0,0 +1,29 @@ +-- +goose Up +-- +goose StatementBegin + +CREATE TABLE IF NOT EXISTS resource_mapping_groups ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + namespace_id UUID NOT NULL REFERENCES attribute_namespaces(id) ON DELETE CASCADE, + name VARCHAR NOT NULL, + UNIQUE(namespace_id, name) +); + +COMMENT ON TABLE resource_mapping_groups IS 'Table to store the groups of resource mappings by unique namespace and group name combinations'; +COMMENT ON COLUMN resource_mapping_groups.id IS 'Primary key for the table'; +COMMENT ON COLUMN resource_mapping_groups.namespace_id IS 'Foreign key to the namespace of the attribute'; +COMMENT ON COLUMN resource_mapping_groups.name IS 'Name for the group of resource mappings'; + +ALTER TABLE resource_mappings ADD COLUMN group_id UUID REFERENCES resource_mapping_groups(id) ON DELETE SET NULL; + +COMMENT ON COLUMN resource_mappings.group_id IS 'Foreign key to the parent group of the resource mapping (optional, a resource mapping may not be in a group)'; + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +ALTER TABLE resource_mappings DROP COLUMN group_id; + +DROP TABLE IF EXISTS resource_mapping_groups; + +-- +goose StatementEnd diff --git a/service/policy/db/models.go b/service/policy/db/models.go index 020c3759ec..33d67905ff 100644 --- a/service/policy/db/models.go +++ b/service/policy/db/models.go @@ -168,6 +168,18 @@ type ResourceMapping struct { Metadata []byte `json:"metadata"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` + // Foreign key to the parent group of the resource mapping (optional, a resource mapping may not be in a group) + GroupID pgtype.UUID `json:"group_id"` +} + +// Table to store the groups of resource mappings by unique namespace and group name combinations +type ResourceMappingGroup struct { + // Primary key for the table + ID string `json:"id"` + // Foreign key to the namespace of the attribute + NamespaceID string `json:"namespace_id"` + // Name for the group of resource mappings + Name string `json:"name"` } // Table to store sets of conditions that logically entitle subject entity representations to attribute values via a subject mapping diff --git a/service/policy/db/query.sql b/service/policy/db/query.sql index 1af91bef7f..eb21a0d63d 100644 --- a/service/policy/db/query.sql +++ b/service/policy/db/query.sql @@ -1,4 +1,6 @@ +---------------------------------------------------------------- -- KEY ACCESS SERVERS +---------------------------------------------------------------- -- name: ListKeyAccessServers :many SELECT id, uri, public_key, @@ -27,3 +29,31 @@ RETURNING id; -- name: DeleteKeyAccessServer :execrows DELETE FROM key_access_servers WHERE id = $1; +---------------------------------------------------------------- +-- RESOURCE MAPPING GROUPS +---------------------------------------------------------------- + +-- name: ListResourceMappingGroups :many +SELECT id, namespace_id, name +FROM resource_mapping_groups; + +-- name: GetResourceMappingGroup :one +SELECT id, namespace_id, name +FROM resource_mapping_groups +WHERE id = $1; + +-- name: CreateResourceMappingGroup :one +INSERT INTO resource_mapping_groups (namespace_id, name) +VALUES ($1, $2) +RETURNING id; + +-- name: UpdateResourceMappingGroup :one +UPDATE resource_mapping_groups +SET + namespace_id = coalesce(sqlc.narg('namespace_id'), namespace_id), + name = coalesce(sqlc.narg('name'), name) +WHERE id = $1 +RETURNING id; + +-- name: DeleteResourceMappingGroup :execrows +DELETE FROM resource_mapping_groups WHERE id = $1; diff --git a/service/policy/db/query.sql.go b/service/policy/db/query.sql.go index 2bcdbab10a..7b767d9163 100644 --- a/service/policy/db/query.sql.go +++ b/service/policy/db/query.sql.go @@ -35,6 +35,29 @@ func (q *Queries) CreateKeyAccessServer(ctx context.Context, arg CreateKeyAccess return id, err } +const createResourceMappingGroup = `-- name: CreateResourceMappingGroup :one +INSERT INTO resource_mapping_groups (namespace_id, name) +VALUES ($1, $2) +RETURNING id +` + +type CreateResourceMappingGroupParams struct { + NamespaceID string `json:"namespace_id"` + Name string `json:"name"` +} + +// CreateResourceMappingGroup +// +// INSERT INTO resource_mapping_groups (namespace_id, name) +// VALUES ($1, $2) +// RETURNING id +func (q *Queries) CreateResourceMappingGroup(ctx context.Context, arg CreateResourceMappingGroupParams) (string, error) { + row := q.db.QueryRow(ctx, createResourceMappingGroup, arg.NamespaceID, arg.Name) + var id string + err := row.Scan(&id) + return id, err +} + const deleteKeyAccessServer = `-- name: DeleteKeyAccessServer :execrows DELETE FROM key_access_servers WHERE id = $1 ` @@ -50,6 +73,21 @@ func (q *Queries) DeleteKeyAccessServer(ctx context.Context, id string) (int64, return result.RowsAffected(), nil } +const deleteResourceMappingGroup = `-- name: DeleteResourceMappingGroup :execrows +DELETE FROM resource_mapping_groups WHERE id = $1 +` + +// DeleteResourceMappingGroup +// +// DELETE FROM resource_mapping_groups WHERE id = $1 +func (q *Queries) DeleteResourceMappingGroup(ctx context.Context, id string) (int64, error) { + result, err := q.db.Exec(ctx, deleteResourceMappingGroup, id) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} + const getKeyAccessServer = `-- name: GetKeyAccessServer :one SELECT id, uri, public_key, JSON_STRIP_NULLS(JSON_BUILD_OBJECT('labels', metadata -> 'labels', 'created_at', created_at, 'updated_at', updated_at)) as metadata @@ -80,6 +118,24 @@ func (q *Queries) GetKeyAccessServer(ctx context.Context, id string) (GetKeyAcce return i, err } +const getResourceMappingGroup = `-- name: GetResourceMappingGroup :one +SELECT id, namespace_id, name +FROM resource_mapping_groups +WHERE id = $1 +` + +// GetResourceMappingGroup +// +// SELECT id, namespace_id, name +// FROM resource_mapping_groups +// WHERE id = $1 +func (q *Queries) GetResourceMappingGroup(ctx context.Context, id string) (ResourceMappingGroup, error) { + row := q.db.QueryRow(ctx, getResourceMappingGroup, id) + var i ResourceMappingGroup + err := row.Scan(&i.ID, &i.NamespaceID, &i.Name) + return i, err +} + const listKeyAccessServers = `-- name: ListKeyAccessServers :many SELECT id, uri, public_key, @@ -94,7 +150,9 @@ type ListKeyAccessServersRow struct { Metadata []byte `json:"metadata"` } +// -------------------------------------------------------------- // KEY ACCESS SERVERS +// -------------------------------------------------------------- // // SELECT id, uri, public_key, // JSON_STRIP_NULLS(JSON_BUILD_OBJECT('labels', metadata -> 'labels', 'created_at', created_at, 'updated_at', updated_at)) as metadata @@ -124,6 +182,38 @@ func (q *Queries) ListKeyAccessServers(ctx context.Context) ([]ListKeyAccessServ return items, nil } +const listResourceMappingGroups = `-- name: ListResourceMappingGroups :many + +SELECT id, namespace_id, name +FROM resource_mapping_groups +` + +// -------------------------------------------------------------- +// RESOURCE MAPPING GROUPS +// -------------------------------------------------------------- +// +// SELECT id, namespace_id, name +// FROM resource_mapping_groups +func (q *Queries) ListResourceMappingGroups(ctx context.Context) ([]ResourceMappingGroup, error) { + rows, err := q.db.Query(ctx, listResourceMappingGroups) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ResourceMappingGroup + for rows.Next() { + var i ResourceMappingGroup + if err := rows.Scan(&i.ID, &i.NamespaceID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const updateKeyAccessServer = `-- name: UpdateKeyAccessServer :one UPDATE key_access_servers SET @@ -161,3 +251,33 @@ func (q *Queries) UpdateKeyAccessServer(ctx context.Context, arg UpdateKeyAccess err := row.Scan(&id) return id, err } + +const updateResourceMappingGroup = `-- name: UpdateResourceMappingGroup :one +UPDATE resource_mapping_groups +SET + namespace_id = coalesce($2, namespace_id), + name = coalesce($3, name) +WHERE id = $1 +RETURNING id +` + +type UpdateResourceMappingGroupParams struct { + ID string `json:"id"` + NamespaceID pgtype.UUID `json:"namespace_id"` + Name pgtype.Text `json:"name"` +} + +// UpdateResourceMappingGroup +// +// UPDATE resource_mapping_groups +// SET +// namespace_id = coalesce($2, namespace_id), +// name = coalesce($3, name) +// WHERE id = $1 +// RETURNING id +func (q *Queries) UpdateResourceMappingGroup(ctx context.Context, arg UpdateResourceMappingGroupParams) (string, error) { + row := q.db.QueryRow(ctx, updateResourceMappingGroup, arg.ID, arg.NamespaceID, arg.Name) + var id string + err := row.Scan(&id) + return id, err +}