generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: database-backed secret resolver (#1901)
Fixes #1887
- Loading branch information
Showing
12 changed files
with
339 additions
and
38 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package configuration | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/url" | ||
|
||
"github.com/TBD54566975/ftl/common/configuration/dal" | ||
"github.com/alecthomas/types/optional" | ||
) | ||
|
||
// DBSecretResolver loads values a project's secrets from the given database. | ||
type DBSecretResolver struct { | ||
dal DBSecretResolverDAL | ||
} | ||
|
||
type DBSecretResolverDAL interface { | ||
GetModuleSecretURL(ctx context.Context, module optional.Option[string], name string) (string, error) | ||
ListModuleSecrets(ctx context.Context) ([]dal.ModuleSecret, error) | ||
SetModuleSecretURL(ctx context.Context, module optional.Option[string], name string, url string) error | ||
UnsetModuleSecret(ctx context.Context, module optional.Option[string], name string) error | ||
} | ||
|
||
// DBSecretResolver should only be used for secrets | ||
var _ Router[Secrets] = DBSecretResolver{} | ||
|
||
func NewDBSecretResolver(db DBSecretResolverDAL) DBSecretResolver { | ||
return DBSecretResolver{dal: db} | ||
} | ||
|
||
func (d DBSecretResolver) Role() Secrets { return Secrets{} } | ||
|
||
func (d DBSecretResolver) Get(ctx context.Context, ref Ref) (*url.URL, error) { | ||
u, err := d.dal.GetModuleSecretURL(ctx, ref.Module, ref.Name) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get secret URL: %w", err) | ||
} | ||
url, err := url.Parse(u) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse secret URL: %w", err) | ||
} | ||
return url, nil | ||
} | ||
|
||
func (d DBSecretResolver) List(ctx context.Context) ([]Entry, error) { | ||
secrets, err := d.dal.ListModuleSecrets(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not list module secrets: %w", err) | ||
} | ||
entries := make([]Entry, len(secrets)) | ||
for i, s := range secrets { | ||
url, err := url.Parse(s.Url) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse secret URL: %w", err) | ||
} | ||
entries[i] = Entry{ | ||
Ref: Ref{ | ||
Module: s.Module, | ||
Name: s.Name, | ||
}, | ||
Accessor: url, | ||
} | ||
} | ||
return entries, nil | ||
} | ||
|
||
func (d DBSecretResolver) Set(ctx context.Context, ref Ref, key *url.URL) error { | ||
err := d.dal.SetModuleSecretURL(ctx, ref.Module, ref.Name, key.String()) | ||
if err != nil { | ||
return fmt.Errorf("failed to set secret URL: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
func (d DBSecretResolver) Unset(ctx context.Context, ref Ref) error { | ||
err := d.dal.UnsetModuleSecret(ctx, ref.Module, ref.Name) | ||
if err != nil { | ||
return fmt.Errorf("failed to unset secret: %w", err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package configuration | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/url" | ||
"testing" | ||
|
||
"github.com/TBD54566975/ftl/common/configuration/dal" | ||
"github.com/alecthomas/assert/v2" | ||
. "github.com/alecthomas/types/optional" | ||
) | ||
|
||
type mockDBSecretResolverDAL struct { | ||
entries []dal.ModuleSecret | ||
} | ||
|
||
func (d *mockDBSecretResolverDAL) findEntry(module Option[string], name string) (Option[dal.ModuleSecret], int) { | ||
for i := range d.entries { | ||
if d.entries[i].Module.Default("") == module.Default("") && d.entries[i].Name == name { | ||
return Some(d.entries[i]), i | ||
} | ||
} | ||
return None[dal.ModuleSecret](), -1 | ||
} | ||
|
||
func (d *mockDBSecretResolverDAL) GetModuleSecretURL(ctx context.Context, module Option[string], name string) (string, error) { | ||
entry, _ := d.findEntry(module, name) | ||
if e, ok := entry.Get(); ok { | ||
return e.Url, nil | ||
} | ||
return "", fmt.Errorf("secret not found") | ||
} | ||
|
||
func (d *mockDBSecretResolverDAL) ListModuleSecrets(ctx context.Context) ([]dal.ModuleSecret, error) { | ||
return d.entries, nil | ||
} | ||
|
||
func (d *mockDBSecretResolverDAL) SetModuleSecretURL(ctx context.Context, module Option[string], name string, url string) error { | ||
err := d.UnsetModuleSecret(ctx, module, name) | ||
if err != nil { | ||
return fmt.Errorf("could not unset secret %w", err) | ||
} | ||
d.entries = append(d.entries, dal.ModuleSecret{Module: module, Name: name, Url: url}) | ||
return nil | ||
} | ||
|
||
func (d *mockDBSecretResolverDAL) UnsetModuleSecret(ctx context.Context, module Option[string], name string) error { | ||
entry, i := d.findEntry(module, name) | ||
if _, ok := entry.Get(); ok { | ||
d.entries = append(d.entries[:i], d.entries[i+1:]...) | ||
} | ||
return nil | ||
} | ||
|
||
func TestDBSecretResolverList(t *testing.T) { | ||
ctx := context.Background() | ||
resolver := NewDBSecretResolver(&mockDBSecretResolverDAL{}) | ||
|
||
rone := Ref{Module: Some("foo"), Name: "one"} | ||
err := resolver.Set(ctx, rone, &url.URL{Scheme: "asm", Host: rone.String()}) | ||
assert.NoError(t, err) | ||
|
||
rtwo := Ref{Module: Some("foo"), Name: "two"} | ||
err = resolver.Set(ctx, rtwo, &url.URL{Scheme: "asm", Host: rtwo.String()}) | ||
assert.NoError(t, err) | ||
|
||
entries, err := resolver.List(ctx) | ||
assert.NoError(t, err) | ||
assert.Equal(t, len(entries), 2) | ||
|
||
err = resolver.Unset(ctx, rone) | ||
assert.NoError(t, err) | ||
|
||
entries, err = resolver.List(ctx) | ||
assert.NoError(t, err) | ||
assert.Equal(t, len(entries), 1) | ||
|
||
url, err := resolver.Get(ctx, rtwo) | ||
assert.NoError(t, err) | ||
assert.Equal(t, url.String(), "asm://foo.two") | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.