From c3d5da3dcaa1ccdf711918ccb8eddf23b64d510e Mon Sep 17 00:00:00 2001 From: Safeer Jiwan Date: Tue, 11 Jun 2024 14:57:12 -0700 Subject: [PATCH] fix: require a project toml for `ftl dev` and `ftl serve` --- cmd/ftl/cmd_dev.go | 7 +++++-- cmd/ftl/cmd_serve.go | 7 ++++++- common/projectconfig/merge.go | 2 +- common/projectconfig/projectconfig.go | 12 ++++++++---- common/projectconfig/projectconfig_test.go | 20 ++++++++++++++++++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/cmd/ftl/cmd_dev.go b/cmd/ftl/cmd_dev.go index c964f7ff2a..460d6ade93 100644 --- a/cmd/ftl/cmd_dev.go +++ b/cmd/ftl/cmd_dev.go @@ -34,6 +34,9 @@ func (d *devCmd) Run(ctx context.Context, projConfig projectconfig.Config) error if len(d.Dirs) == 0 && len(d.External) == 0 { return errors.New("no directories specified") } + if len(projConfig.FilePaths()) == 0 { + return errors.New("configuration file not found, create an ftl-project.toml file or specify one with -C") + } client := rpc.ClientFromContext[ftlv1connect.ControllerServiceClient](ctx) @@ -45,7 +48,7 @@ func (d *devCmd) Run(ctx context.Context, projConfig projectconfig.Config) error } if !d.NoServe { if d.ServeCmd.Stop { - err := d.ServeCmd.Run(ctx) + err := d.ServeCmd.Run(ctx, projConfig) if err != nil { return err } @@ -55,7 +58,7 @@ func (d *devCmd) Run(ctx context.Context, projConfig projectconfig.Config) error return errors.New(ftlRunningErrorMsg) } - g.Go(func() error { return d.ServeCmd.Run(ctx) }) + g.Go(func() error { return d.ServeCmd.Run(ctx, projConfig) }) } g.Go(func() error { diff --git a/cmd/ftl/cmd_serve.go b/cmd/ftl/cmd_serve.go index 2617f93d01..063366c4df 100644 --- a/cmd/ftl/cmd_serve.go +++ b/cmd/ftl/cmd_serve.go @@ -22,6 +22,7 @@ import ( "github.com/TBD54566975/ftl/backend/controller/sql/databasetesting" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" + "github.com/TBD54566975/ftl/common/projectconfig" "github.com/TBD54566975/ftl/internal/bind" "github.com/TBD54566975/ftl/internal/container" "github.com/TBD54566975/ftl/internal/log" @@ -44,7 +45,11 @@ type serveCmd struct { const ftlContainerName = "ftl-db-1" const ftlRunningErrorMsg = "FTL is already running. Use 'ftl serve --stop' to stop it" -func (s *serveCmd) Run(ctx context.Context) error { +func (s *serveCmd) Run(ctx context.Context, projConfig projectconfig.Config) error { + if len(projConfig.FilePaths()) == 0 { + return errors.New("configuration file not found, create an ftl-project.toml file or specify one with -C") + } + logger := log.FromContext(ctx) client := rpc.ClientFromContext[ftlv1connect.ControllerServiceClient](ctx) diff --git a/common/projectconfig/merge.go b/common/projectconfig/merge.go index baf64b7445..142c8f21bf 100644 --- a/common/projectconfig/merge.go +++ b/common/projectconfig/merge.go @@ -8,7 +8,7 @@ import ( // // Config is merged left to right, with later files taking precedence over earlier files. func Merge(paths ...string) (Config, error) { - config := Config{} + config := Config{filePaths: paths} for _, path := range paths { partial, err := loadFile(path) if err != nil { diff --git a/common/projectconfig/projectconfig.go b/common/projectconfig/projectconfig.go index 4112e97074..1969f6ac18 100644 --- a/common/projectconfig/projectconfig.go +++ b/common/projectconfig/projectconfig.go @@ -33,13 +33,17 @@ type Config struct { Commands Commands `toml:"commands"` FTLMinVersion string `toml:"ftl-min-version"` absModuleDirs []string + filePaths []string } // AbsModuleDirsOrDefault returns the absolute path for the module-dirs field from the ftl-project.toml, unless // that is not defined, in which case it defaults to the root directory. func (c Config) AbsModuleDirsOrDefault() []string { return c.absModuleDirs } -// ConfigPaths returns the computed list of configuration paths to load. +// FilePaths returns the list of configuration files that generated this Config. +func (c Config) FilePaths() []string { return c.filePaths } + +// ConfigPaths returns the computed list of configuration paths to load, or an empty list if none are found. func ConfigPaths(input []string) []string { if len(input) > 0 { return input @@ -80,7 +84,7 @@ func CreateDefaultFileIfNonexistent(ctx context.Context) error { return err } logger.Debugf("Creating a new project config file at %q", path) - return Save(path, Config{}) + return Save(path, Config{filePaths: []string{path}}) } func LoadConfig(ctx context.Context, input []string) (Config, error) { @@ -108,7 +112,7 @@ func LoadWritableConfig(ctx context.Context, input []string) (Config, error) { logger := log.FromContext(ctx) if _, err := os.Stat(target); errors.Is(err, os.ErrNotExist) { logger.Debugf("Creating a new project config file at %q", target) - err = Save(target, Config{}) + err = Save(target, Config{filePaths: []string{target}}) if err != nil { return Config{}, err } @@ -120,7 +124,7 @@ func LoadWritableConfig(ctx context.Context, input []string) (Config, error) { // Load project config from a file. func loadFile(path string) (Config, error) { - config := Config{} + config := Config{filePaths: []string{path}} md, err := toml.DecodeFile(path, &config) if err != nil { return Config{}, err diff --git a/common/projectconfig/projectconfig_test.go b/common/projectconfig/projectconfig_test.go index c9fb9fdfe2..af1eaa3b9f 100644 --- a/common/projectconfig/projectconfig_test.go +++ b/common/projectconfig/projectconfig_test.go @@ -30,6 +30,7 @@ func TestProjectConfig(t *testing.T) { Commands: Commands{ Startup: []string{"echo 'Executing global pre-build command'"}, }, + filePaths: []string{"testdata/ftl-project.toml"}, } assert.Equal(t, expected, actual) @@ -41,18 +42,33 @@ func TestProjectLoadConfig(t *testing.T) { paths []string err string }{ - {name: "AllValid", paths: []string{"testdata/ftl-project.toml"}}, + {name: "SingleValid", paths: []string{"testdata/ftl-project.toml"}}, + {name: "MultipleValid", paths: []string{"testdata/ftl-project.toml", "testdata/go/configs-ftl-project.toml"}}, {name: "IsNonExistent", paths: []string{"testdata/ftl-project-nonexistent.toml"}, err: "no such file or directory"}, {name: "ContainsNonExistent", paths: []string{"testdata/ftl-project.toml", "testdata/ftl-project-nonexistent.toml"}, err: "no such file or directory"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - _, err := LoadConfig(log.ContextWithNewDefaultLogger(context.Background()), test.paths) + config, err := LoadConfig(log.ContextWithNewDefaultLogger(context.Background()), test.paths) if test.err != "" { + assert.Error(t, err) assert.Contains(t, err.Error(), test.err) } else { assert.NoError(t, err) + + // Check that all test.paths exist in config.FilePaths + assert.Equal(t, len(test.paths), len(config.FilePaths())) + for _, path := range test.paths { + found := false + for _, configPath := range config.FilePaths() { + if path == configPath { + found = true + break + } + } + assert.True(t, found, "expected path %q not found in config.FilePaths", path) + } } }) }