Skip to content

Commit

Permalink
refactor: move to single configuration provider
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed Nov 4, 2024
1 parent 2d62e00 commit 21c240c
Show file tree
Hide file tree
Showing 28 changed files with 303 additions and 512 deletions.
35 changes: 4 additions & 31 deletions backend/controller/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ func (s *AdminService) ConfigSet(ctx context.Context, req *connect.Request[ftlv1
return nil, err
}

pkey := configProviderKey(req.Msg.Provider)
err = s.cm.SetJSON(ctx, pkey, refFromConfigRef(req.Msg.GetRef()), req.Msg.Value)
err = s.cm.SetJSON(ctx, refFromConfigRef(req.Msg.GetRef()), req.Msg.Value)
if err != nil {
return nil, fmt.Errorf("failed to set config: %w", err)
}
Expand All @@ -130,8 +129,7 @@ func (s *AdminService) ConfigSet(ctx context.Context, req *connect.Request[ftlv1

// ConfigUnset unsets the config value at the given ref.
func (s *AdminService) ConfigUnset(ctx context.Context, req *connect.Request[ftlv1.UnsetConfigRequest]) (*connect.Response[ftlv1.UnsetConfigResponse], error) {
pkey := configProviderKey(req.Msg.Provider)
err := s.cm.Unset(ctx, pkey, refFromConfigRef(req.Msg.GetRef()))
err := s.cm.Unset(ctx, refFromConfigRef(req.Msg.GetRef()))
if err != nil {
return nil, fmt.Errorf("failed to unset config: %w", err)
}
Expand All @@ -146,10 +144,6 @@ func (s *AdminService) SecretsList(ctx context.Context, req *connect.Request[ftl
}
secrets := []*ftlv1.ListSecretsResponse_Secret{}
for _, secret := range listing {
if req.Msg.Provider != nil && manager.ProviderKeyForAccessor(secret.Accessor) != secretProviderKey(req.Msg.Provider) {
// Skip secrets that don't match the provider in the request
continue
}
module, ok := secret.Module.Get()
if req.Msg.Module != nil && *req.Msg.Module != "" && module != *req.Msg.Module {
continue
Expand Down Expand Up @@ -192,34 +186,14 @@ func (s *AdminService) SecretGet(ctx context.Context, req *connect.Request[ftlv1
return connect.NewResponse(&ftlv1.GetSecretResponse{Value: vb}), nil
}

func secretProviderKey(p *ftlv1.SecretProvider) configuration.ProviderKey {
if p == nil {
return ""
}
switch *p {
case ftlv1.SecretProvider_SECRET_INLINE:
return providers.InlineProviderKey
case ftlv1.SecretProvider_SECRET_ENVAR:
return providers.EnvarProviderKey
case ftlv1.SecretProvider_SECRET_KEYCHAIN:
return providers.KeychainProviderKey
case ftlv1.SecretProvider_SECRET_OP:
return providers.OnePasswordProviderKey
case ftlv1.SecretProvider_SECRET_ASM:
return providers.ASMProviderKey
}
return ""
}

// SecretSet sets the secret at the given ref to the provided value.
func (s *AdminService) SecretSet(ctx context.Context, req *connect.Request[ftlv1.SetSecretRequest]) (*connect.Response[ftlv1.SetSecretResponse], error) {
err := s.validateAgainstSchema(ctx, true, refFromConfigRef(req.Msg.GetRef()), req.Msg.Value)
if err != nil {
return nil, err
}

pkey := secretProviderKey(req.Msg.Provider)
err = s.sm.SetJSON(ctx, pkey, refFromConfigRef(req.Msg.GetRef()), req.Msg.Value)
err = s.sm.SetJSON(ctx, refFromConfigRef(req.Msg.GetRef()), req.Msg.Value)
if err != nil {
return nil, fmt.Errorf("failed to set secret: %w", err)
}
Expand All @@ -228,8 +202,7 @@ func (s *AdminService) SecretSet(ctx context.Context, req *connect.Request[ftlv1

// SecretUnset unsets the secret value at the given ref.
func (s *AdminService) SecretUnset(ctx context.Context, req *connect.Request[ftlv1.UnsetSecretRequest]) (*connect.Response[ftlv1.UnsetSecretResponse], error) {
pkey := secretProviderKey(req.Msg.Provider)
err := s.sm.Unset(ctx, pkey, refFromConfigRef(req.Msg.GetRef()))
err := s.sm.Unset(ctx, refFromConfigRef(req.Msg.GetRef()))
if err != nil {
return nil, fmt.Errorf("failed to unset secret: %w", err)
}
Expand Down
19 changes: 5 additions & 14 deletions backend/controller/admin/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,14 @@ import (
)

func TestAdminService(t *testing.T) {
t.Skip("This will be replaced soon")
config := tempConfigPath(t, "testdata/ftl-project.toml", "admin")
ctx := log.ContextWithNewDefaultLogger(context.Background())

cm, err := manager.NewConfigurationManager(ctx, routers.ProjectConfig[configuration.Configuration]{Config: config})
cm, err := manager.New(ctx, routers.ProjectConfig[configuration.Configuration]{Config: config}, providers.Inline[configuration.Configuration]{})
assert.NoError(t, err)

sm, err := manager.New(ctx,
routers.ProjectConfig[configuration.Secrets]{Config: config},
[]configuration.Provider[configuration.Secrets]{
providers.Envar[configuration.Secrets]{},
providers.Inline[configuration.Secrets]{},
})
sm, err := manager.New(ctx, routers.ProjectConfig[configuration.Secrets]{Config: config}, providers.Inline[configuration.Secrets]{})
assert.NoError(t, err)
admin := NewAdminService(cm, sm, &diskSchemaRetriever{})
assert.NotZero(t, admin)
Expand Down Expand Up @@ -207,15 +203,10 @@ func TestAdminValidation(t *testing.T) {
config := tempConfigPath(t, "testdata/ftl-project.toml", "admin")
ctx := log.ContextWithNewDefaultLogger(context.Background())

cm, err := manager.NewConfigurationManager(ctx, routers.ProjectConfig[configuration.Configuration]{Config: config})
cm, err := manager.New(ctx, routers.ProjectConfig[configuration.Configuration]{Config: config}, providers.Inline[configuration.Configuration]{})
assert.NoError(t, err)

sm, err := manager.New(ctx,
routers.ProjectConfig[configuration.Secrets]{Config: config},
[]configuration.Provider[configuration.Secrets]{
providers.Envar[configuration.Secrets]{},
providers.Inline[configuration.Secrets]{},
})
sm, err := manager.New(ctx, routers.ProjectConfig[configuration.Secrets]{Config: config}, providers.Inline[configuration.Secrets]{})
assert.NoError(t, err)
admin := NewAdminService(cm, sm, &mockSchemaRetriever{})
assert.NotZero(t, admin)
Expand Down
9 changes: 7 additions & 2 deletions backend/controller/admin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net"
"net/url"
"time"

"connectrpc.com/connect"

Expand Down Expand Up @@ -54,7 +55,9 @@ func ShouldUseLocalClient(ctx context.Context, adminClient ftlv1connect.AdminSer
if err != nil {
return false, err
}
_, err = adminClient.Ping(ctx, connect.NewRequest(&ftlv1.PingRequest{}))
timeoutCtx, cancel := context.WithTimeout(ctx, time.Millisecond*500)
defer cancel()
_, err = adminClient.Ping(timeoutCtx, connect.NewRequest(&ftlv1.PingRequest{}))
if isConnectUnavailableError(err) && isLocal {
return true, nil
}
Expand All @@ -63,7 +66,9 @@ func ShouldUseLocalClient(ctx context.Context, adminClient ftlv1connect.AdminSer

func isConnectUnavailableError(err error) bool {
var connectErr *connect.Error
if errors.As(err, &connectErr) {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return true
} else if errors.As(err, &connectErr) {
return connectErr.Code() == connect.CodeUnavailable
}
return false
Expand Down
9 changes: 2 additions & 7 deletions backend/controller/admin/local_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,10 @@ func TestAdminNoValidationWithNoSchema(t *testing.T) {
config := tempConfigPath(t, "testdata/ftl-project.toml", "admin")
ctx := log.ContextWithNewDefaultLogger(context.Background())

cm, err := manager.NewConfigurationManager(ctx, routers.ProjectConfig[cf.Configuration]{Config: config})
cm, err := manager.New(ctx, routers.ProjectConfig[cf.Configuration]{Config: config}, providers.NewInline[cf.Configuration]())
assert.NoError(t, err)

sm, err := manager.New(ctx,
routers.ProjectConfig[cf.Secrets]{Config: config},
[]cf.Provider[cf.Secrets]{
providers.Envar[cf.Secrets]{},
providers.Inline[cf.Secrets]{},
})
sm, err := manager.New(ctx, routers.ProjectConfig[cf.Secrets]{Config: config}, providers.NewInline[cf.Secrets]())
assert.NoError(t, err)

dsr := &diskSchemaRetriever{deployRoot: optional.Some(string(t.TempDir()))}
Expand Down
9 changes: 6 additions & 3 deletions backend/controller/admin/testdata/ftl-project.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
name = "testdata"
secrets-provider = "inline"
config-provider = "inline"

[global]
[global.secrets]
bar = "envar://baza"
Expand All @@ -10,6 +13,6 @@ foo = "inline://ImZvb2JhciI"
mutable = "inline://ImhlbGxvd29ybGQi"

[modules]
[modules.echo]
[modules.echo.configuration]
default = "inline://ImFub255bW91cyI"
[modules.echo]
[modules.echo.configuration]
default = "inline://ImFub255bW91cyI"
51 changes: 27 additions & 24 deletions backend/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"io"
"net/http"
"net/url"
goslices "slices"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -132,7 +131,15 @@ func (c *Config) OpenDBAndInstrument() (*sql.DB, error) {
}

// Start the Controller. Blocks until the context is cancelled.
func Start(ctx context.Context, config Config, runnerScaling scaling.RunnerScaling, conn *sql.DB, devel bool) error {
func Start(
ctx context.Context,
config Config,
runnerScaling scaling.RunnerScaling,
cm *cf.Manager[configuration.Configuration],
sm *cf.Manager[configuration.Secrets],
conn *sql.DB,
devel bool,
) error {
config.SetDefaults()

logger := log.FromContext(ctx)
Expand All @@ -153,16 +160,13 @@ func Start(ctx context.Context, config Config, runnerScaling scaling.RunnerScali
logger.Infof("Web console available at: %s", config.Bind)
}

svc, err := New(ctx, conn, config, devel, runnerScaling)
svc, err := New(ctx, conn, cm, sm, config, devel, runnerScaling)
if err != nil {
return err
}
logger.Debugf("Listening on %s", config.Bind)
logger.Debugf("Advertising as %s", config.Advertise)

cm := cf.ConfigFromContext(ctx)
sm := cf.SecretsFromContext(ctx)

admin := admin.NewAdminService(cm, sm, svc.dal)
console := console.NewService(svc.dal, svc.timeline)

Expand Down Expand Up @@ -222,6 +226,9 @@ type Service struct {
key model.ControllerKey
deploymentLogsSink *deploymentLogsSink

cm *cf.Manager[configuration.Configuration]
sm *cf.Manager[configuration.Secrets]

tasks *scheduledtask.Scheduler
cronJobs *cronjobs.Service
pubSub *pubsub.Service
Expand All @@ -243,7 +250,15 @@ type Service struct {
runnerScaling scaling.RunnerScaling
}

func New(ctx context.Context, conn *sql.DB, config Config, devel bool, runnerScaling scaling.RunnerScaling) (*Service, error) {
func New(
ctx context.Context,
conn *sql.DB,
cm *cf.Manager[configuration.Configuration],
sm *cf.Manager[configuration.Secrets],
config Config,
devel bool,
runnerScaling scaling.RunnerScaling,
) (*Service, error) {
key := config.Key
if config.Key.IsZero() {
key = model.NewControllerKey(config.Bind.Hostname(), config.Bind.Port())
Expand All @@ -265,6 +280,8 @@ func New(ctx context.Context, conn *sql.DB, config Config, devel bool, runnerSca
scheduler := scheduledtask.New(ctx, key, ldb)

svc := &Service{
cm: cm,
sm: sm,
tasks: scheduler,
dbleaser: ldb,
conn: conn,
Expand Down Expand Up @@ -729,20 +746,17 @@ func (s *Service) Ping(ctx context.Context, req *connect.Request[ftlv1.PingReque
func (s *Service) GetModuleContext(ctx context.Context, req *connect.Request[ftlv1.ModuleContextRequest], resp *connect.ServerStream[ftlv1.ModuleContextResponse]) error {
name := req.Msg.Module

cm := cf.ConfigFromContext(ctx)
sm := cf.SecretsFromContext(ctx)

// Initialize checksum to -1; a zero checksum does occur when the context contains no settings
lastChecksum := int64(-1)

for {
h := sha.New()

configs, err := cm.MapForModule(ctx, name)
configs, err := s.cm.MapForModule(ctx, name)
if err != nil {
return connect.NewError(connect.CodeInternal, fmt.Errorf("could not get configs: %w", err))
}
secrets, err := sm.MapForModule(ctx, name)
secrets, err := s.sm.MapForModule(ctx, name)
if err != nil {
return connect.NewError(connect.CodeInternal, fmt.Errorf("could not get secrets: %w", err))
}
Expand Down Expand Up @@ -1043,22 +1057,11 @@ func (s *Service) CreateDeployment(ctx context.Context, req *connect.Request[ftl
return nil, fmt.Errorf("invalid module schema: %w", err)
}

sm := cf.SecretsFromContext(ctx)
for _, d := range module.Decls {
if db, ok := d.(*schema.Database); ok && db.Runtime != nil {
key := dsnSecretKey(module.Name, db.Name)

// TODO: Use a cluster specific default provider
allKeys := sm.ProviderKeys()
var providerKey configuration.ProviderKey
for _, key := range []configuration.ProviderKey{"inline", "asm"} {
if goslices.Contains(allKeys, key) {
providerKey = key
break
}
}

if err := sm.Set(ctx, providerKey, configuration.NewRef(module.Name, key), db.Runtime.DSN); err != nil {
if err := s.sm.Set(ctx, configuration.NewRef(module.Name, key), db.Runtime.DSN); err != nil {
return nil, fmt.Errorf("could not set database secret %s: %w", key, err)
}
logger.Infof("Database declaration: %s -> %s", db.Name, db.Runtime.DSN)
Expand Down
10 changes: 3 additions & 7 deletions cmd/ftl-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,18 @@ func main() {

configDal := cfdal.New(conn)
kctx.FatalIfErrorf(err)
configProviders := []cf.Provider[cf.Configuration]{providers.NewDatabaseConfig(configDal)}
configResolver := routers.NewDatabaseConfig(configDal)
cm, err := manager.New(ctx, configResolver, configProviders)
cm, err := manager.New(ctx, configResolver, providers.NewDatabaseConfig(configDal))
kctx.FatalIfErrorf(err)

ctx = manager.ContextWithConfig(ctx, cm)

// The FTL controller currently only supports AWS Secrets Manager as a secrets provider.
awsConfig, err := config.LoadDefaultConfig(ctx)
kctx.FatalIfErrorf(err)
asmSecretProvider := providers.NewASM(ctx, secretsmanager.NewFromConfig(awsConfig), cli.ControllerConfig.Advertise, dal)
dbSecretResolver := routers.NewDatabaseSecrets(configDal)
sm, err := manager.New[cf.Secrets](ctx, dbSecretResolver, []cf.Provider[cf.Secrets]{asmSecretProvider})
sm, err := manager.New[cf.Secrets](ctx, dbSecretResolver, asmSecretProvider)
kctx.FatalIfErrorf(err)
ctx = manager.ContextWithSecrets(ctx, sm)

err = controller.Start(ctx, cli.ControllerConfig, k8sscaling.NewK8sScaling(cli.DisableIstio), conn, false)
err = controller.Start(ctx, cli.ControllerConfig, k8sscaling.NewK8sScaling(cli.DisableIstio), cm, sm, conn, false)
kctx.FatalIfErrorf(err)
}
11 changes: 9 additions & 2 deletions frontend/cli/cmd_box_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
"github.com/TBD54566975/ftl/internal/bind"
"github.com/TBD54566975/ftl/internal/buildengine"
"github.com/TBD54566975/ftl/internal/configuration"
"github.com/TBD54566975/ftl/internal/configuration/manager"
"github.com/TBD54566975/ftl/internal/log"
"github.com/TBD54566975/ftl/internal/model"
"github.com/TBD54566975/ftl/internal/projectconfig"
Expand All @@ -33,7 +35,12 @@ type boxRunCmd struct {
ControllerTimeout time.Duration `help:"Timeout for Controller start." default:"30s"`
}

func (b *boxRunCmd) Run(ctx context.Context, projConfig projectconfig.Config) error {
func (b *boxRunCmd) Run(
ctx context.Context,
projConfig projectconfig.Config,
sm *manager.Manager[configuration.Secrets],
cm *manager.Manager[configuration.Configuration],
) error {
_, err := databasetesting.CreateForDevel(ctx, b.DSN, b.Recreate)
if err != nil {
return fmt.Errorf("failed to create database: %w", err)
Expand Down Expand Up @@ -64,7 +71,7 @@ func (b *boxRunCmd) Run(ctx context.Context, projConfig projectconfig.Config) er

wg := errgroup.Group{}
wg.Go(func() error {
return controller.Start(ctx, config, runnerScaling, conn, false)
return controller.Start(ctx, config, runnerScaling, cm, sm, conn, false)
})

// Wait for the controller to come up.
Expand Down
Loading

0 comments on commit 21c240c

Please sign in to comment.