Skip to content

Commit

Permalink
refactor: pull secrets from FTL runtime abstraction
Browse files Browse the repository at this point in the history
Complement to #1708
  • Loading branch information
alecthomas committed Jun 8, 2024
1 parent 520cf6d commit d6142f1
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 37 deletions.
19 changes: 19 additions & 0 deletions go-runtime/ftl/ftltest/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type fakeFTL struct {
mockMaps map[uintptr]mapImpl
allowMapCalls bool
configValues map[string][]byte
secretValues map[string][]byte
}

// mapImpl is a function that takes an object and returns an object of a potentially different
Expand All @@ -28,6 +29,7 @@ func newFakeFTL() *fakeFTL {
mockMaps: map[uintptr]mapImpl{},
allowMapCalls: false,
configValues: map[string][]byte{},
secretValues: map[string][]byte{},
}
}

Expand All @@ -50,6 +52,23 @@ func (f *fakeFTL) GetConfig(ctx context.Context, name string, dest any) error {
return json.Unmarshal(data, dest)
}

func (f *fakeFTL) setSecret(name string, value any) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
f.secretValues[name] = data
return nil
}

func (f *fakeFTL) GetSecret(ctx context.Context, name string, dest any) error {
data, ok := f.secretValues[name]
if !ok {
return fmt.Errorf("config value %q not found: %w", name, configuration.ErrNotFound)
}
return json.Unmarshal(data, dest)
}

func (f *fakeFTL) FSMSend(ctx context.Context, fsm string, instance string, event any) error {
return f.fsm.SendEvent(ctx, fsm, instance, event)
}
Expand Down
33 changes: 25 additions & 8 deletions go-runtime/ftl/ftltest/ftltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
)

type OptionsState struct {
secrets map[string][]byte
databases map[string]modulecontext.Database
mockVerbs map[schema.RefKey]modulecontext.Verb
allowDirectVerbBehavior bool
Expand All @@ -37,7 +36,6 @@ type Option func(context.Context, *OptionsState) error
// Context suitable for use in testing FTL verbs with provided options
func Context(options ...Option) context.Context {
state := &OptionsState{
secrets: make(map[string][]byte),
databases: make(map[string]modulecontext.Database),
mockVerbs: make(map[schema.RefKey]modulecontext.Verb),
}
Expand All @@ -53,7 +51,7 @@ func Context(options ...Option) context.Context {
}
}

builder := modulecontext.NewBuilder(name).AddSecrets(state.secrets).AddDatabases(state.databases)
builder := modulecontext.NewBuilder(name).AddDatabases(state.databases)
builder = builder.UpdateForTesting(state.mockVerbs, state.allowDirectVerbBehavior, newFakeLeaseClient())
return builder.Build().ApplyToContext(ctx)
}
Expand Down Expand Up @@ -123,7 +121,9 @@ func WithProjectFiles(paths ...string) Option {
return fmt.Errorf("could not read secrets: %w", err)
}
for name, data := range secrets {
state.secrets[name] = data
if err := fftl.setSecret(name, json.RawMessage(data)); err != nil {
return err
}
}
return nil
}
Expand Down Expand Up @@ -163,11 +163,10 @@ func WithSecret[T ftl.SecretType](secret ftl.SecretValue[T], value T) Option {
if secret.Module != reflection.Module() {
return fmt.Errorf("secret %v does not match current module %s", secret.Module, reflection.Module())
}
data, err := json.Marshal(value)
if err != nil {
fftl := internal.FromContext(ctx).(*fakeFTL) //nolint:forcetypeassert
if err := fftl.setSecret(secret.Name, value); err != nil {
return err
}
state.secrets[secret.Name] = data
return nil
}
}
Expand All @@ -182,7 +181,8 @@ func WithSecret[T ftl.SecretType](secret ftl.SecretValue[T], value T) Option {
// )
func WithDatabase(dbHandle ftl.Database) Option {
return func(ctx context.Context, state *OptionsState) error {
originalDSN, err := modulecontext.GetDSNFromSecret(reflection.Module(), dbHandle.Name, state.secrets)
fftl := internal.FromContext(ctx)
originalDSN, err := getDSNFromSecret(fftl, reflection.Module(), dbHandle.Name)
if err != nil {
return err
}
Expand Down Expand Up @@ -354,3 +354,20 @@ func WithMapsAllowed() Option {
return nil
}
}

// dsnSecretKey returns the key for the secret that is expected to hold the DSN for a database.
//
// The format is FTL_DSN_<MODULE>_<DBNAME>
func dsnSecretKey(module, name string) string {
return fmt.Sprintf("FTL_DSN_%s_%s", strings.ToUpper(module), strings.ToUpper(name))
}

// getDSNFromSecret returns the DSN for a database from the relevant secret
func getDSNFromSecret(ftl internal.FTL, module, name string) (string, error) {
key := dsnSecretKey(module, name)
var dsn string
if err := ftl.GetSecret(context.Background(), key, &dsn); err != nil {
return "", fmt.Errorf("could not get DSN for database %q from secret %q: %w", name, key, err)
}
return dsn, nil
}
2 changes: 1 addition & 1 deletion go-runtime/ftl/ftltest/ftltest_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func TestModuleUnitTests(t *testing.T) {
in.Run(t, "wrapped/ftl-project.toml",
in.RunWithoutController(t, "wrapped/ftl-project.toml",
in.GitInit(),
in.CopyModule("time"),
in.CopyModule("wrapped"),
Expand Down
11 changes: 3 additions & 8 deletions go-runtime/ftl/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@ import (

type MapHandle[T, U any] struct {
fn func(context.Context, T) (U, error)
getter Handle[T]
}

// Underlying returns the underlying value of the handle being mapped.
func (mh *MapHandle[T, U]) Underlying(ctx context.Context) T {
return mh.getter.Get(ctx)
handle Handle[T]
}

// Get the mapped value.
func (mh *MapHandle[T, U]) Get(ctx context.Context) U {
value := mh.getter.Get(ctx)
value := mh.handle.Get(ctx)
out := internal.FromContext(ctx).CallMap(ctx, mh, value, func(ctx context.Context) (any, error) {
return mh.fn(ctx, value)
})
Expand All @@ -34,6 +29,6 @@ func (mh *MapHandle[T, U]) Get(ctx context.Context) U {
func Map[T, U any](getter Handle[T], fn func(context.Context, T) (U, error)) MapHandle[T, U] {
return MapHandle[T, U]{
fn: fn,
getter: getter,
handle: getter,
}
}
4 changes: 2 additions & 2 deletions go-runtime/ftl/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"

"github.com/TBD54566975/ftl/go-runtime/ftl/reflection"
"github.com/TBD54566975/ftl/internal/modulecontext"
"github.com/TBD54566975/ftl/go-runtime/internal"
)

// SecretType is a type that can be used as a secret value.
Expand All @@ -31,7 +31,7 @@ func (s SecretValue[T]) GoString() string {

// Get returns the value of the secret from FTL.
func (s SecretValue[T]) Get(ctx context.Context) (out T) {
if err := modulecontext.FromContext(ctx).GetSecret(s.Name, &out); err != nil {
if err := internal.FromContext(ctx).GetSecret(ctx, s.Name, &out); err != nil {
panic(fmt.Errorf("failed to get %s: %w", s, err))
}
return
Expand Down
3 changes: 3 additions & 0 deletions go-runtime/internal/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type FTL interface {

// GetConfig unmarshals a configuration value into dest.
GetConfig(ctx context.Context, name string, dest any) error

// GetSecret unmarshals a secret value into dest.
GetSecret(ctx context.Context, name string, dest any) error
}

type ftlContextKey struct{}
Expand Down
4 changes: 4 additions & 0 deletions go-runtime/internal/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (r *RealFTL) GetConfig(ctx context.Context, name string, dest any) error {
return r.mctx.GetConfig(name, dest)
}

func (r *RealFTL) GetSecret(ctx context.Context, name string, dest any) error {
return r.mctx.GetSecret(name, dest)
}

func (r *RealFTL) FSMSend(ctx context.Context, fsm, instance string, event any) error {
client := rpc.ClientFromContext[ftlv1connect.VerbServiceClient](ctx)
body, err := encoding.Marshal(event)
Expand Down
18 changes: 0 additions & 18 deletions internal/modulecontext/from_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,3 @@ func DatabasesFromSecrets(ctx context.Context, module string, secrets map[string
}
return databases, nil
}

// DSNSecretKey returns the key for the secret that is expected to hold the DSN for a database.
//
// The format is FTL_DSN_<MODULE>_<DBNAME>
func DSNSecretKey(module, name string) string {
return fmt.Sprintf("FTL_DSN_%s_%s", strings.ToUpper(module), strings.ToUpper(name))
}

// GetDSNFromSecret returns the DSN for a database from the relevant secret
func GetDSNFromSecret(module, name string, secrets map[string][]byte) (string, error) {
key := DSNSecretKey(module, name)
dsn, ok := secrets[key]
if !ok {
return "", fmt.Errorf("secrets map %v is missing DSN with key %q", secrets, key)
}
dsnStr := string(dsn)
return dsnStr[1 : len(dsnStr)-1], nil // chop leading + trailing quotes
}

0 comments on commit d6142f1

Please sign in to comment.