diff --git a/common/configuration/manager_test.go b/common/configuration/manager_test.go index 56c620ad5f..042391af3c 100644 --- a/common/configuration/manager_test.go +++ b/common/configuration/manager_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/alecthomas/assert/v2" + "github.com/alecthomas/types/optional" "github.com/zalando/go-keyring" "github.com/TBD54566975/ftl/internal/log" @@ -54,6 +55,7 @@ func TestManager(t *testing.T) { {Ref: Ref{Name: "baz"}, Accessor: URL("envar://baz")}, {Ref: Ref{Name: "foo"}, Accessor: URL("inline://ImJhciI")}, {Ref: Ref{Name: "mutable"}, Accessor: URL("inline://ImhlbGxvIg")}, + {Ref: Ref{Module: optional.Some[string]("echo"), Name: "default"}, Accessor: URL("inline://ImFub255bW91cyI")}, }) }) } diff --git a/common/configuration/projectconfig_resolver.go b/common/configuration/projectconfig_resolver.go index dbfe23dd97..5becd68eef 100644 --- a/common/configuration/projectconfig_resolver.go +++ b/common/configuration/projectconfig_resolver.go @@ -101,9 +101,9 @@ func (p ProjectConfigResolver[R]) getMapping(config pc.Config, module optional.O get := func(dest pc.ConfigAndSecrets) map[string]*pc.URL { switch any(k).(type) { case Configuration: - return dest.Config + return emptyMapIfNil(dest.Config) case Secrets: - return dest.Secrets + return emptyMapIfNil(dest.Secrets) default: panic("unsupported kind") } @@ -121,6 +121,13 @@ func (p ProjectConfigResolver[R]) getMapping(config pc.Config, module optional.O return mapping, nil } +func emptyMapIfNil(mapping map[string]*pc.URL) map[string]*pc.URL { + if mapping == nil { + return map[string]*pc.URL{} + } + return mapping +} + func (p ProjectConfigResolver[R]) setMapping(config pc.Config, module optional.Option[string], mapping map[string]*pc.URL) error { var k R set := func(dest *pc.ConfigAndSecrets, mapping map[string]*pc.URL) { @@ -143,5 +150,8 @@ func (p ProjectConfigResolver[R]) setMapping(config pc.Config, module optional.O set(&config.Global, mapping) } configPaths := pc.ConfigPaths(p.Config) + if len(configPaths) == 0 { + return pc.Save(pc.GetDefaultConfigPath(), config) + } return pc.Save(configPaths[len(configPaths)-1], config) } diff --git a/common/configuration/projectconfig_resolver_test.go b/common/configuration/projectconfig_resolver_test.go new file mode 100644 index 0000000000..ceda91717b --- /dev/null +++ b/common/configuration/projectconfig_resolver_test.go @@ -0,0 +1,63 @@ +package configuration + +import ( + "context" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/alecthomas/assert/v2" + "github.com/alecthomas/types/optional" + + "github.com/TBD54566975/ftl/common/projectconfig" + "github.com/TBD54566975/ftl/internal/log" +) + +func TestSet(t *testing.T) { + defaultPath := projectconfig.GetDefaultConfigPath() + origConfigBytes, err := os.ReadFile(defaultPath) + assert.NoError(t, err) + + config := filepath.Join(t.TempDir(), "ftl-project.toml") + existing, err := os.ReadFile("testdata/ftl-project.toml") + assert.NoError(t, err) + err = os.WriteFile(config, existing, 0600) + assert.NoError(t, err) + + t.Run("ExistingModule", func(t *testing.T) { + setAndAssert(t, "echo", []string{config}) + }) + t.Run("NewModule", func(t *testing.T) { + setAndAssert(t, "echooo", []string{config}) + }) + t.Run("MissingTOMLFile", func(t *testing.T) { + err := os.Remove(config) + assert.NoError(t, err) + setAndAssert(t, "echooooo", []string{}) + err = os.WriteFile(defaultPath, origConfigBytes, 0600) + assert.NoError(t, err) + }) +} + +func setAndAssert(t *testing.T, module string, config []string) { + t.Helper() + + ctx := log.ContextWithNewDefaultLogger(context.Background()) + + cf, err := New(ctx, + ProjectConfigResolver[Configuration]{Config: config}, + []Provider[Configuration]{ + EnvarProvider[Configuration]{}, + InlineProvider[Configuration]{Inline: true}, // Writer + }) + assert.NoError(t, err) + + var got *url.URL + want := URL("inline://asdfasdf") + err = cf.Set(ctx, Ref{Module: optional.Some[string](module), Name: "default"}, want) + assert.NoError(t, err) + err = cf.Get(ctx, Ref{Module: optional.Some[string](module), Name: "default"}, &got) + assert.NoError(t, err) + assert.Equal(t, want, got) +} diff --git a/common/configuration/testdata/ftl-project.toml b/common/configuration/testdata/ftl-project.toml index 94a29f2ba9..962394b673 100644 --- a/common/configuration/testdata/ftl-project.toml +++ b/common/configuration/testdata/ftl-project.toml @@ -8,3 +8,8 @@ mutable = "keychain://mutable" baz = "envar://baz" foo = "inline://ImJhciI" mutable = "inline://ImhlbGxvIg" + +[modules] + [modules.echo] + [modules.echo.configuration] + default = "inline://ImFub255bW91cyI" diff --git a/common/projectconfig/projectconfig.go b/common/projectconfig/projectconfig.go index 207e324d66..4711bb3d4b 100644 --- a/common/projectconfig/projectconfig.go +++ b/common/projectconfig/projectconfig.go @@ -42,7 +42,7 @@ func ConfigPaths(input []string) []string { if len(input) > 0 { return input } - path := filepath.Join(internal.GitRoot(""), "ftl-project.toml") + path := GetDefaultConfigPath() _, err := os.Stat(path) if err == nil { return []string{path} @@ -50,6 +50,10 @@ func ConfigPaths(input []string) []string { return []string{} } +func GetDefaultConfigPath() string { + return filepath.Join(internal.GitRoot(""), "ftl-project.toml") +} + func LoadConfig(ctx context.Context, input []string) (Config, error) { logger := log.FromContext(ctx) configPaths := ConfigPaths(input)