Skip to content

Commit

Permalink
serverside no longer uses modulecontext directly
Browse files Browse the repository at this point in the history
  • Loading branch information
matt2e committed Apr 22, 2024
1 parent 1929079 commit b290154
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 252 deletions.
25 changes: 1 addition & 24 deletions backend/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import (
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema"
"github.com/TBD54566975/ftl/backend/schema"
"github.com/TBD54566975/ftl/common/configuration"
"github.com/TBD54566975/ftl/common/modulecontext"
frontend "github.com/TBD54566975/ftl/frontend"
"github.com/TBD54566975/ftl/internal/cors"
"github.com/TBD54566975/ftl/internal/log"
Expand Down Expand Up @@ -649,28 +647,7 @@ func (s *Service) GetModuleContext(ctx context.Context, req *connect.Request[ftl
if err != nil {
return nil, err
}
schemas = slices.Filter(schemas, func(s *schema.Module) bool {
return s.Name == req.Msg.Module
})
if len(schemas) == 0 {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("no schema found for module %q", req.Msg.Module))
} else if len(schemas) > 1 {
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("multiple schemas found for module %q", req.Msg.Module))
}

b := modulecontext.NewBuilder(req.Msg.Module)
b.AddConfigFromManager(configuration.ConfigFromContext(ctx))
b.AddSecretsFromManager(configuration.SecretsFromContext(ctx))
b.AddDSNsFromEnvarsForModule(schemas[0])
moduleCtx, err := b.Build(ctx)
if err != nil {
return nil, err
}
out, err := moduleCtx.ToProto(ctx)
if err != nil {
return nil, err
}
return connect.NewResponse(out), nil
return moduleContextToProto(ctx, req.Msg.Module, schemas)
}

func (s *Service) Call(ctx context.Context, req *connect.Request[ftlv1.CallRequest]) (*connect.Response[ftlv1.CallResponse], error) {
Expand Down
97 changes: 97 additions & 0 deletions backend/controller/modulecontext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package controller

import (
"context"
"fmt"
"os"
"strings"

"connectrpc.com/connect"
ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1"
"github.com/TBD54566975/ftl/backend/schema"
"github.com/TBD54566975/ftl/common/configuration"

Check failure on line 12 in backend/controller/modulecontext.go

View workflow job for this annotation

GitHub Actions / Lint

ST1019: package "github.com/TBD54566975/ftl/common/configuration" is being imported more than once (stylecheck)
cf "github.com/TBD54566975/ftl/common/configuration"

Check failure on line 13 in backend/controller/modulecontext.go

View workflow job for this annotation

GitHub Actions / Lint

ST1019(related information): other import of "github.com/TBD54566975/ftl/common/configuration" (stylecheck)
"github.com/TBD54566975/ftl/internal/slices"
)

func moduleContextToProto(ctx context.Context, name string, schemas []*schema.Module) (*connect.Response[ftlv1.ModuleContextResponse], error) {
schemas = slices.Filter(schemas, func(s *schema.Module) bool {
return s.Name == name
})
if len(schemas) == 0 {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("no schema found for module %q", name))
} else if len(schemas) > 1 {
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("multiple schemas found for module %q", name))
}

// configs
configManager := configuration.ConfigFromContext(ctx)
configList, err := configManager.List(ctx)
if err != nil {
return nil, err
}
configProtos := []*ftlv1.ModuleContextResponse_Config{}
for _, entry := range configList {
data, err := configManager.GetData(ctx, entry.Ref)
if err != nil {
return nil, err
}
configProtos = append(configProtos, &ftlv1.ModuleContextResponse_Config{
Ref: configRefToProto(entry.Ref),
Data: data,
})
}

// secrets
secretsManager := configuration.SecretsFromContext(ctx)
secretsList, err := secretsManager.List(ctx)
if err != nil {
return nil, err
}
secretProtos := []*ftlv1.ModuleContextResponse_Secret{}
for _, entry := range secretsList {
data, err := secretsManager.GetData(ctx, entry.Ref)
if err != nil {
return nil, err
}
secretProtos = append(secretProtos, &ftlv1.ModuleContextResponse_Secret{
Ref: configRefToProto(entry.Ref),
Data: data,
})
}

// DSNs
dsnProtos := []*ftlv1.ModuleContextResponse_DSN{}
for _, decl := range schemas[0].Decls {
dbDecl, ok := decl.(*schema.Database)
if !ok {
continue
}
key := fmt.Sprintf("FTL_POSTGRES_DSN_%s_%s", strings.ToUpper(name), strings.ToUpper(dbDecl.Name))
dsn, ok := os.LookupEnv(key)
if !ok {
return nil, fmt.Errorf("missing environment variable %q", key)
}
dsnProtos = append(dsnProtos, &ftlv1.ModuleContextResponse_DSN{
Name: dbDecl.Name,
Type: ftlv1.ModuleContextResponse_POSTGRES,
Dsn: dsn,
})
}

return connect.NewResponse(&ftlv1.ModuleContextResponse{
Configs: configProtos,
Secrets: secretProtos,
Databases: dsnProtos,
}), nil
}

func configRefToProto(r cf.Ref) *ftlv1.ModuleContextResponse_Ref {
protoRef := &ftlv1.ModuleContextResponse_Ref{
Name: r.Name,
}
if module, ok := r.Module.Get(); ok {
protoRef.Module = &module
}
return protoRef
}
122 changes: 17 additions & 105 deletions common/modulecontext/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package modulecontext
import (
"context"
"fmt"
"os"
"strings"

ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1"
"github.com/TBD54566975/ftl/backend/schema"
cf "github.com/TBD54566975/ftl/common/configuration"
"github.com/TBD54566975/ftl/internal/slices"
"github.com/alecthomas/types/optional"
Expand All @@ -23,8 +20,6 @@ type refValuePair struct {
resolver resolver
}

func (r refValuePair) configOrSecretItem() {}

type dsnEntry struct {
name string
dbType DBType
Expand All @@ -43,45 +38,30 @@ type stringResolver interface {
}

var _ stringResolver = valueResolver{}
var _ stringResolver = envarResolver{}

type configManager[R cf.Role] struct {
manager *cf.Manager[R]
}

func (m configManager[R]) configOrSecretItem() {}

// sumtype:decl
type configOrSecretItem interface {
configOrSecretItem()
}

var _ configOrSecretItem = refValuePair{}
var _ configOrSecretItem = configManager[cf.Configuration]{}

type Builder struct {
moduleName string
configs []configOrSecretItem
secrets []configOrSecretItem
configs []refValuePair
secrets []refValuePair
dsns []dsnEntry
}

func NewBuilder(moduleName string) *Builder {
return &Builder{
moduleName: moduleName,
configs: []configOrSecretItem{},
secrets: []configOrSecretItem{},
configs: []refValuePair{},
secrets: []refValuePair{},
dsns: []dsnEntry{},
}
}

func NewBuilderFromProto(moduleName string, response *ftlv1.ModuleContextResponse) *Builder {
return &Builder{
moduleName: moduleName,
configs: slices.Map(response.Configs, func(c *ftlv1.ModuleContextResponse_Config) configOrSecretItem {
configs: slices.Map(response.Configs, func(c *ftlv1.ModuleContextResponse_Config) refValuePair {
return refValuePair{ref: refFromProto(c.Ref), resolver: dataResolver{data: c.Data}}
}),
secrets: slices.Map(response.Secrets, func(s *ftlv1.ModuleContextResponse_Secret) configOrSecretItem {
secrets: slices.Map(response.Secrets, func(s *ftlv1.ModuleContextResponse_Secret) refValuePair {
return refValuePair{ref: refFromProto(s.Ref), resolver: dataResolver{data: s.Data}}
}),
dsns: slices.Map(response.Databases, func(d *ftlv1.ModuleContextResponse_DSN) dsnEntry {
Expand All @@ -105,10 +85,10 @@ func (b *Builder) Build(ctx context.Context) (*ModuleContext, error) {
dbProvider: NewDBProvider(),
}

if err := buildConfigOrSecrets[cf.Configuration](ctx, b, *moduleCtx.configManager, b.configs); err != nil {
if err := buildConfigOrSecrets[cf.Configuration](ctx, *moduleCtx.configManager, b.configs); err != nil {
return nil, err
}
if err := buildConfigOrSecrets[cf.Secrets](ctx, b, *moduleCtx.secretsManager, b.secrets); err != nil {
if err := buildConfigOrSecrets[cf.Secrets](ctx, *moduleCtx.secretsManager, b.secrets); err != nil {
return nil, err
}

Expand All @@ -135,73 +115,25 @@ func newInMemoryConfigManager[R cf.Role](ctx context.Context) (*cf.Manager[R], e
return manager, nil
}

func buildConfigOrSecrets[R cf.Role](ctx context.Context, b *Builder, manager cf.Manager[R], items []configOrSecretItem) error {
func buildConfigOrSecrets[R cf.Role](ctx context.Context, manager cf.Manager[R], items []refValuePair) error {
for _, item := range items {
switch item := item.(type) {
case refValuePair:
isData, value, data, err := item.resolver.Resolve()
if err != nil {
isData, value, data, err := item.resolver.Resolve()
if err != nil {
return err
}
if isData {
if err := manager.SetData(ctx, cf.Ref(item.ref), data); err != nil {
return err
}
if isData {
if err := manager.SetData(ctx, cf.Ref(item.ref), data); err != nil {
return err
}
} else {
if err := manager.Set(ctx, cf.Ref(item.ref), value); err != nil {
return err
}
}
case configManager[R]:
list, err := item.manager.List(ctx)
if err != nil {
} else {
if err := manager.Set(ctx, cf.Ref(item.ref), value); err != nil {
return err
}
for _, e := range list {
if m, isModuleSpecific := e.Module.Get(); isModuleSpecific && b.moduleName != m {
continue
}
data, err := item.manager.GetData(ctx, e.Ref)
if err != nil {
return err
}
if err := manager.SetData(ctx, e.Ref, data); err != nil {
return err
}
}
}
}
return nil
}

func (b *Builder) AddDSNsFromEnvarsForModule(module *schema.Module) *Builder {
// remove in favor of a non-envar approach once it is available
for _, decl := range module.Decls {
dbDecl, ok := decl.(*schema.Database)
if !ok {
continue
}
b.dsns = append(b.dsns, dsnEntry{
name: dbDecl.Name,
dbType: DBTypePostgres,
resolver: envarResolver{
name: fmt.Sprintf("FTL_POSTGRES_DSN_%s_%s", strings.ToUpper(module.Name), strings.ToUpper(dbDecl.Name)),
},
})
}
return b
}

func (b *Builder) AddConfigFromManager(cm *cf.Manager[cf.Configuration]) *Builder {
b.configs = append(b.configs, configManager[cf.Configuration]{manager: cm})
return b
}

func (b *Builder) AddSecretsFromManager(sm *cf.Manager[cf.Secrets]) *Builder {
b.secrets = append(b.secrets, configManager[cf.Secrets]{manager: sm})
return b
}

func refFromProto(r *ftlv1.ModuleContextResponse_Ref) ref {
return ref{
Module: optional.Ptr(r.Module),
Expand Down Expand Up @@ -232,23 +164,3 @@ type dataResolver struct {
func (r dataResolver) Resolve() (isData bool, value any, data []byte, err error) {
return true, nil, r.data, nil
}

type envarResolver struct {
name string
}

func (r envarResolver) Resolve() (isData bool, value any, data []byte, err error) {
value, err = r.ResolveString()
if err != nil {
return false, nil, nil, err
}
return false, value, nil, nil
}

func (r envarResolver) ResolveString() (string, error) {
value, ok := os.LookupEnv(r.name)
if !ok {
return "", fmt.Errorf("missing environment variable %q", r.name)
}
return value, nil
}
47 changes: 0 additions & 47 deletions common/modulecontext/builder_test.go
Original file line number Diff line number Diff line change
@@ -1,48 +1 @@
package modulecontext

import (
"context"
"testing"

cf "github.com/TBD54566975/ftl/common/configuration"
"github.com/TBD54566975/ftl/internal/log"
"github.com/alecthomas/assert/v2"
"github.com/alecthomas/types/optional"
)

type jsonableStruct struct {
Str string `json:"string"`
TestStruct *jsonableStruct `json:"struct,omitempty"`
}

func TestConfigManagerInAndOut(t *testing.T) {
ctx := log.ContextWithNewDefaultLogger(context.Background())

cm, err := newInMemoryConfigManager[cf.Configuration](ctx)
assert.NoError(t, err)

moduleName := "test"
strValue := "HelloWorld"
intValue := 42
structValue := jsonableStruct{Str: "HelloWorld", TestStruct: &jsonableStruct{Str: "HelloWorld"}}
cm.Set(ctx, cf.Ref{Module: optional.Some(moduleName), Name: "str"}, strValue)
cm.Set(ctx, cf.Ref{Module: optional.Some(moduleName), Name: "int"}, intValue)
cm.Set(ctx, cf.Ref{Module: optional.Some(moduleName), Name: "struct"}, structValue)

builder := NewBuilder(moduleName).AddConfigFromManager(cm)

moduleCtx, err := builder.Build(ctx)
assert.NoError(t, err)

var outStr string
moduleCtx.configManager.Get(ctx, cf.Ref{Module: optional.Some(moduleName), Name: "str"}, &outStr)
assert.Equal(t, strValue, outStr, "expected string value to be set and retrieved correctly")

var outInt int
moduleCtx.configManager.Get(ctx, cf.Ref{Module: optional.Some(moduleName), Name: "int"}, &outInt)
assert.Equal(t, intValue, outInt, "expected int value to be set and retrieved correctly")

var outStruct jsonableStruct
moduleCtx.configManager.Get(ctx, cf.Ref{Module: optional.Some(moduleName), Name: "struct"}, &outStruct)
assert.Equal(t, structValue, outStruct, "expected struct value to be set and retrieved correctly")
}
Loading

0 comments on commit b290154

Please sign in to comment.