Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions internal/datastore/aurora/aurora_generators.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
// © 2026 Platform Engineering Labs Inc.
//
// SPDX-License-Identifier: FSL-1.1-ALv2

package aurora

import (
"context"
"fmt"
"log/slog"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/rdsdata/types"
"github.com/demula/mksuid/v2"

"github.com/platform-engineering-labs/formae/internal/datastore"
pkgmodel "github.com/platform-engineering-labs/formae/pkg/model"
)

// CreateGenerator persists a new generator. stack_id stores the stack's
// resolved KSUID — like policy_id on an inline policy, not the label — read
// off gen.GetStackID(). Unlike CreatePolicy the column is never NULL: a
// generator is always inline to exactly one stack.
func (d *DatastoreAuroraDataAPI) CreateGenerator(gen pkgmodel.Generator, commandID string) (string, error) {
ctx := context.Background()

id := mksuid.New().String()
version := mksuid.New().String()

data, err := datastore.GeneratorData(gen)
if err != nil {
return "", err
}

query := `INSERT INTO generators (id, version, command_id, operation, label, generator_type, stack_id, generator_data)
VALUES (:id, :version, :command_id, :operation, :label, :generator_type, :stack_id, :generator_data)`
params := []types.SqlParameter{
{Name: aws.String("id"), Value: &types.FieldMemberStringValue{Value: id}},
{Name: aws.String("version"), Value: &types.FieldMemberStringValue{Value: version}},
{Name: aws.String("command_id"), Value: &types.FieldMemberStringValue{Value: commandID}},
{Name: aws.String("operation"), Value: &types.FieldMemberStringValue{Value: "create"}},
{Name: aws.String("label"), Value: &types.FieldMemberStringValue{Value: gen.GetLabel()}},
{Name: aws.String("generator_type"), Value: &types.FieldMemberStringValue{Value: gen.GetType()}},
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: gen.GetStackID()}},
{Name: aws.String("generator_data"), Value: &types.FieldMemberStringValue{Value: string(data)}},
}

_, err = d.executeStatement(ctx, query, params)
if err != nil {
slog.Error("Failed to create generator", "error", err, "label", gen.GetLabel())
return "", err
}

return version, nil
}

// UpdateGenerator persists a new version of an existing generator. The
// existing row is found by label and stack ID — a generator has no
// standalone form, so unlike UpdatePolicy there is no NULL-stack branch —
// and the new version row carries forward the same id.
func (d *DatastoreAuroraDataAPI) UpdateGenerator(gen pkgmodel.Generator, commandID string) (string, error) {
ctx := context.Background()

selectQuery := `
SELECT id FROM generators
WHERE label = :label AND stack_id = :stack_id
ORDER BY version COLLATE "C" DESC
LIMIT 1
`
selectParams := []types.SqlParameter{
{Name: aws.String("label"), Value: &types.FieldMemberStringValue{Value: gen.GetLabel()}},
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: gen.GetStackID()}},
}

result, err := d.executeStatement(ctx, selectQuery, selectParams)
if err != nil {
return "", fmt.Errorf("failed to find existing generator: %w", err)
}
if len(result.Records) == 0 {
return "", fmt.Errorf("generator not found: %s", gen.GetLabel())
}

id, err := getStringField(result.Records[0][0])
if err != nil {
return "", fmt.Errorf("failed to get generator id: %w", err)
}

version := mksuid.New().String()
data, err := datastore.GeneratorData(gen)
if err != nil {
return "", err
}

insertQuery := `INSERT INTO generators (id, version, command_id, operation, label, generator_type, stack_id, generator_data)
VALUES (:id, :version, :command_id, :operation, :label, :generator_type, :stack_id, :generator_data)`
insertParams := []types.SqlParameter{
{Name: aws.String("id"), Value: &types.FieldMemberStringValue{Value: id}},
{Name: aws.String("version"), Value: &types.FieldMemberStringValue{Value: version}},
{Name: aws.String("command_id"), Value: &types.FieldMemberStringValue{Value: commandID}},
{Name: aws.String("operation"), Value: &types.FieldMemberStringValue{Value: "update"}},
{Name: aws.String("label"), Value: &types.FieldMemberStringValue{Value: gen.GetLabel()}},
{Name: aws.String("generator_type"), Value: &types.FieldMemberStringValue{Value: gen.GetType()}},
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: gen.GetStackID()}},
{Name: aws.String("generator_data"), Value: &types.FieldMemberStringValue{Value: string(data)}},
}

_, err = d.executeStatement(ctx, insertQuery, insertParams)
if err != nil {
slog.Error("Failed to update generator", "error", err, "label", gen.GetLabel())
return "", err
}

return version, nil
}

// DeleteGenerator soft-deletes the generator with the given label on the
// given stack. The stack is resolved from its label the same way
// GetGenerator does; a stack that doesn't exist has nothing to delete. A
// label with no live match is a no-op success that returns an empty version,
// mirroring DeletePolicy.
func (d *DatastoreAuroraDataAPI) DeleteGenerator(label, stackLabel string) (string, error) {
ctx := context.Background()

stack, err := d.GetStackByLabel(stackLabel)
if err != nil {
return "", fmt.Errorf("failed to resolve stack %q: %w", stackLabel, err)
}
if stack == nil {
return "", nil
}

query := `
WITH latest_generators AS (
SELECT id, generator_type, operation,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY version COLLATE "C" DESC) as rn
FROM generators
WHERE stack_id = :stack_id AND label = :label
)
SELECT id, generator_type
FROM latest_generators
WHERE rn = 1 AND operation != 'delete'
`
params := []types.SqlParameter{
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: stack.ID}},
{Name: aws.String("label"), Value: &types.FieldMemberStringValue{Value: label}},
}
result, err := d.executeStatement(ctx, query, params)
if err != nil {
return "", fmt.Errorf("failed to get generator for deletion: %w", err)
}
if len(result.Records) == 0 {
return "", nil
}

record := result.Records[0]
id, err := getStringField(record[0])
if err != nil {
return "", fmt.Errorf("failed to get generator id: %w", err)
}
generatorType, err := getStringField(record[1])
if err != nil {
return "", fmt.Errorf("failed to get generator type: %w", err)
}

version := mksuid.New().String()
insertQuery := `INSERT INTO generators (id, version, command_id, operation, label, generator_type, stack_id, generator_data)
VALUES (:id, :version, :command_id, :operation, :label, :generator_type, :stack_id, :generator_data)`
insertParams := []types.SqlParameter{
{Name: aws.String("id"), Value: &types.FieldMemberStringValue{Value: id}},
{Name: aws.String("version"), Value: &types.FieldMemberStringValue{Value: version}},
{Name: aws.String("command_id"), Value: &types.FieldMemberStringValue{Value: ""}},
{Name: aws.String("operation"), Value: &types.FieldMemberStringValue{Value: "delete"}},
{Name: aws.String("label"), Value: &types.FieldMemberStringValue{Value: label}},
{Name: aws.String("generator_type"), Value: &types.FieldMemberStringValue{Value: generatorType}},
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: stack.ID}},
{Name: aws.String("generator_data"), Value: &types.FieldMemberStringValue{Value: "{}"}},
}
_, err = d.executeStatement(ctx, insertQuery, insertParams)
if err != nil {
return "", fmt.Errorf("failed to delete generator: %w", err)
}

slog.Debug("Deleted generator", "label", label, "id", id, "stackLabel", stackLabel)

return version, nil
}

// GetGenerator retrieves the current (latest, non-deleted) generator with the
// given label on the given stack. The stack label is resolved to its
// current KSUID first, since generators.stack_id stores the stack's id, not
// its label — mirroring how a policy's inline lookups are scoped by stack
// ID. Returns nil, nil if no live stack or no live generator matches.
func (d *DatastoreAuroraDataAPI) GetGenerator(label, stackLabel string) (pkgmodel.Generator, error) {
ctx := context.Background()

stack, err := d.GetStackByLabel(stackLabel)
if err != nil {
return nil, fmt.Errorf("failed to resolve stack %q: %w", stackLabel, err)
}
if stack == nil {
return nil, nil
}

query := `
WITH latest_generators AS (
SELECT generator_data, operation,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY version COLLATE "C" DESC) as rn
FROM generators
WHERE stack_id = :stack_id AND label = :label
)
SELECT generator_data
FROM latest_generators
WHERE rn = 1 AND operation != 'delete'
`
params := []types.SqlParameter{
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: stack.ID}},
{Name: aws.String("label"), Value: &types.FieldMemberStringValue{Value: label}},
}
result, err := d.executeStatement(ctx, query, params)
if err != nil {
return nil, fmt.Errorf("failed to get generator: %w", err)
}
if len(result.Records) == 0 {
return nil, nil
}

dataStr, err := getStringField(result.Records[0][0])
if err != nil {
return nil, fmt.Errorf("failed to get generator data: %w", err)
}

return datastore.GeneratorFromData([]byte(dataStr))
}

// LoadGeneratorsByStack returns all non-deleted generators owned by a stack.
// The stack label is resolved to its current KSUID first, for the same
// reason GetGenerator does. A stack that doesn't exist owns no generators.
func (d *DatastoreAuroraDataAPI) LoadGeneratorsByStack(stackLabel string) ([]pkgmodel.Generator, error) {
ctx := context.Background()

stack, err := d.GetStackByLabel(stackLabel)
if err != nil {
return nil, fmt.Errorf("failed to resolve stack %q: %w", stackLabel, err)
}
if stack == nil {
return nil, nil
}

query := `
WITH latest_generators AS (
SELECT id, generator_data, operation,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY version COLLATE "C" DESC) as rn
FROM generators
WHERE stack_id = :stack_id
)
SELECT generator_data
FROM latest_generators
WHERE rn = 1 AND operation != 'delete'
`
params := []types.SqlParameter{
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: stack.ID}},
}
result, err := d.executeStatement(ctx, query, params)
if err != nil {
return nil, err
}

var generators []pkgmodel.Generator
for _, record := range result.Records {
if len(record) < 1 {
continue
}
dataStr, err := getStringField(record[0])
if err != nil {
slog.Warn("Failed to read generator data, skipping", "error", err, "stackLabel", stackLabel)
continue
}
gen, err := datastore.GeneratorFromData([]byte(dataStr))
if err != nil {
slog.Warn("Failed to deserialize generator, skipping", "error", err, "stackLabel", stackLabel)
continue
}
generators = append(generators, gen)
}

return generators, nil
}

// GeneratorIDForTesting returns the internal KSUID identity (the id column,
// stable across CreateGenerator/UpdateGenerator) of the current (max-version)
// generator row with the given label on the given stack, or "" if none
// exists. Generator has no public API that exposes this id — the Datastore
// interface returns only version strings — so the dstest suite needs a
// direct accessor to prove the id survives an update unchanged.
func (d *DatastoreAuroraDataAPI) GeneratorIDForTesting(label, stackLabel string) (string, error) {
ctx := context.Background()

stack, err := d.GetStackByLabel(stackLabel)
if err != nil {
return "", fmt.Errorf("failed to resolve stack %q: %w", stackLabel, err)
}
if stack == nil {
return "", nil
}

query := `
SELECT id FROM generators
WHERE stack_id = :stack_id AND label = :label
ORDER BY version COLLATE "C" DESC
LIMIT 1
`
params := []types.SqlParameter{
{Name: aws.String("stack_id"), Value: &types.FieldMemberStringValue{Value: stack.ID}},
{Name: aws.String("label"), Value: &types.FieldMemberStringValue{Value: label}},
}
result, err := d.executeStatement(ctx, query, params)
if err != nil {
return "", err
}
if len(result.Records) == 0 {
return "", nil
}

return getStringField(result.Records[0][0])
}
3 changes: 3 additions & 0 deletions internal/datastore/aurora/aurora_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ func TestDatastore(t *testing.T) {
NullFormaCommandSubjectForTest: func(commandID string) error {
return d.NullFormaCommandSubjectForTesting(commandID)
},
GeneratorIDForTest: func(label, stackLabel string) (string, error) {
return d.GeneratorIDForTesting(label, stackLabel)
},
}
})
}
Expand Down
22 changes: 22 additions & 0 deletions internal/datastore/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,28 @@ type Datastore interface {
// that are not in a terminal state (Success, Failed, Canceled)
StackHasActiveCommands(stackLabel string) (bool, error)

// Generator operations - a generator produces a value (e.g. a random
// password) that a secret will later reference. Unlike a policy, a
// generator has no standalone form: it is always owned by exactly one
// stack, so there is no stack_generators junction table and no
// attach/detach.

// CreateGenerator persists a new generator (returns version string)
CreateGenerator(gen pkgmodel.Generator, commandID string) (string, error)
// UpdateGenerator persists a new version of an existing generator, found
// by label and stack (returns version string)
UpdateGenerator(gen pkgmodel.Generator, commandID string) (string, error)
// DeleteGenerator soft-deletes the generator with the given label on the
// given stack (returns version string). A label with no live match is a
// no-op success that returns an empty version.
DeleteGenerator(label, stackLabel string) (string, error)
// GetGenerator retrieves the current generator with the given label on
// the given stack. Returns nil, nil if no live generator is found.
GetGenerator(label, stackLabel string) (pkgmodel.Generator, error)
// LoadGeneratorsByStack returns all non-deleted generators owned by a
// stack.
LoadGeneratorsByStack(stackLabel string) ([]pkgmodel.Generator, error)

// Close releases database connections
Close()

Expand Down
16 changes: 16 additions & 0 deletions internal/datastore/dstest/dstest.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ type TestDatastore struct {
// Backends that don't provide it leave it nil and the relevant tests
// t.Skip().
NullFormaCommandSubjectForTest func(commandID string) error
// GeneratorIDForTest returns the internal KSUID identity (the id column,
// stable across CreateGenerator/UpdateGenerator) of the current
// (max-version) generator row with the given label on the given stack, or
// "" if none exists. Generator has no public API that exposes this id —
// the Datastore interface returns only version strings — so the suite
// needs a direct accessor to prove the id survives an update unchanged.
// Backends that don't provide it leave it nil and the relevant tests
// t.Skip().
GeneratorIDForTest func(label, stackLabel string) (string, error)
}

// RunAll runs the full datastore test suite against the provided factory.
Expand Down Expand Up @@ -231,6 +240,13 @@ func RunAll(t *testing.T, newDS func(t *testing.T) TestDatastore) {
RunDeleteInlinePolicyClearsExpiry(t, newDS)
RunDeleteInlinePolicyThenRecreate(t, newDS)

RunCreateGeneratorThenGet(t, newDS)
RunGetGeneratorAbsentReturnsNil(t, newDS)
RunUpdateGeneratorBumpsVersionAndReadBackReflectsIt(t, newDS)
RunDeleteGeneratorThenGetReturnsNil(t, newDS)
RunLoadGeneratorsByStackReturnsOnlyThatStacksGenerators(t, newDS)
RunGeneratorKSUIDStableAcrossUpdate(t, newDS)

RunFindResourcesDependingOn(t, newDS)
RunFindResourcesDependingOnMultipleRefs(t, newDS)
RunFindResourcesDependingOnNoRefs(t, newDS)
Expand Down
Loading
Loading