Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: require an ftl-project.toml file #1759

Merged
merged 1 commit into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/controller/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func configProviderKey(p *ftlv1.ConfigProvider) string {
// ConfigSet sets the configuration at the given ref to the provided value.
func (s *AdminService) ConfigSet(ctx context.Context, req *connect.Request[ftlv1.SetConfigRequest]) (*connect.Response[ftlv1.SetConfigResponse], error) {
pkey := configProviderKey(req.Msg.Provider)
err := s.cm.Set(ctx, pkey, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name), string(req.Msg.Value))
err := s.cm.SetJSON(ctx, pkey, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name), req.Msg.Value)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -185,7 +185,7 @@ func secretProviderKey(p *ftlv1.SecretProvider) string {
// 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) {
pkey := secretProviderKey(req.Msg.Provider)
err := s.sm.Set(ctx, pkey, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name), string(req.Msg.Value))
err := s.sm.SetJSON(ctx, pkey, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name), req.Msg.Value)
if err != nil {
return nil, err
}
Expand Down
15 changes: 10 additions & 5 deletions cmd/ftl/cmd_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (s *configGetCmd) Run(ctx context.Context, adminClient admin.Client) error
Ref: configRefFromRef(s.Ref),
}))
if err != nil {
return err
return fmt.Errorf("failed to get config: %w", err)
}
fmt.Printf("%s\n", resp.Msg.Value)
return nil
Expand All @@ -117,18 +117,23 @@ func (s *configSetCmd) Run(ctx context.Context, scmd *configCmd, adminClient adm
}
}

var configValue []byte
var configJSON json.RawMessage
if s.JSON {
if err := json.Unmarshal(config, &configValue); err != nil {
var jsonValue any
if err := json.Unmarshal(config, &jsonValue); err != nil {
return fmt.Errorf("config is not valid JSON: %w", err)
}
configJSON = config
} else {
configValue = config
configJSON, err = json.Marshal(string(config))
if err != nil {
return fmt.Errorf("failed to encode config as JSON: %w", err)
}
}

req := &ftlv1.SetConfigRequest{
Ref: configRefFromRef(s.Ref),
Value: configValue,
Value: configJSON,
}
if provider, ok := scmd.provider().Get(); ok {
req.Provider = &provider
Expand Down
15 changes: 10 additions & 5 deletions cmd/ftl/cmd_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (s *secretGetCmd) Run(ctx context.Context, adminClient admin.Client) error
Ref: configRefFromRef(s.Ref),
}))
if err != nil {
return err
return fmt.Errorf("failed to get secret: %w", err)
}
fmt.Printf("%s\n", resp.Msg.Value)
return nil
Expand Down Expand Up @@ -124,18 +124,23 @@ func (s *secretSetCmd) Run(ctx context.Context, scmd *secretCmd, adminClient adm
}
}

var secretValue []byte
var secretJSON json.RawMessage
if s.JSON {
if err := json.Unmarshal(secret, &secretValue); err != nil {
var jsonValue any
if err := json.Unmarshal(secret, &jsonValue); err != nil {
return fmt.Errorf("secret is not valid JSON: %w", err)
}
secretJSON = secret
} else {
secretValue = secret
secretJSON, err = json.Marshal(string(secret))
if err != nil {
return fmt.Errorf("failed to encode secret as JSON: %w", err)
}
}

req := &ftlv1.SetSecretRequest{
Ref: configRefFromRef(s.Ref),
Value: secretValue,
Value: secretJSON,
}
if provider, ok := scmd.provider().Get(); ok {
req.Provider = &provider
Expand Down
17 changes: 14 additions & 3 deletions cmd/ftl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,25 @@ func main() {
logger := log.Configure(os.Stderr, cli.LogConfig)
ctx = log.ContextWithLogger(ctx, logger)

config, err := projectconfig.Load(ctx, cli.ConfigFlag)
configPath := cli.ConfigFlag
if configPath == "" {
var ok bool
configPath, ok = projectconfig.DefaultConfigPath().Get()
if !ok {
kctx.Fatalf("could not determine default config path, either place an ftl-project.toml file in the root of your project, use --config=FILE, or set the FTL_CONFIG envar")
}
}

os.Setenv("FTL_CONFIG", configPath)

config, err := projectconfig.Load(ctx, configPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
kctx.Fatalf(err.Error())
}
kctx.Bind(config)

sr := cf.ProjectConfigResolver[cf.Secrets]{Config: cli.ConfigFlag}
cr := cf.ProjectConfigResolver[cf.Configuration]{Config: cli.ConfigFlag}
sr := cf.ProjectConfigResolver[cf.Secrets]{Config: configPath}
cr := cf.ProjectConfigResolver[cf.Configuration]{Config: configPath}
kctx.BindTo(sr, (*cf.Resolver[cf.Secrets])(nil))
kctx.BindTo(cr, (*cf.Resolver[cf.Configuration])(nil))

Expand Down
28 changes: 22 additions & 6 deletions common/configuration/manager.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package configuration

import (
"bytes"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -108,18 +109,26 @@ func (m *Manager[R]) availableProviderKeys() []string {
return keys
}

// Set a configuration value.
// Set a configuration value, encoding "value" as JSON before storing it.
func (m *Manager[R]) Set(ctx context.Context, pkey string, ref Ref, value any) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return m.SetJSON(ctx, pkey, ref, data)
}

// SetJSON sets a configuration value using raw JSON data.
func (m *Manager[R]) SetJSON(ctx context.Context, pkey string, ref Ref, value json.RawMessage) error {
if err := checkJSON(value); err != nil {
return fmt.Errorf("invalid value for %s, must be JSON: %w", m.resolver.Role(), err)
}
provider, ok := m.providers[pkey]
if !ok {
pkeys := strings.Join(m.availableProviderKeys(), ", ")
return fmt.Errorf("no provider for key %q, specify one of: %s", pkey, pkeys)
}
data, err := json.Marshal(value)
if err != nil {
return err
}
key, err := provider.Store(ctx, ref, data)
key, err := provider.Store(ctx, ref, value)
if err != nil {
return err
}
Expand Down Expand Up @@ -173,3 +182,10 @@ func (m *Manager[R]) Unset(ctx context.Context, pkey string, ref Ref) error {
func (m *Manager[R]) List(ctx context.Context) ([]Entry, error) {
return m.resolver.List(ctx)
}

func checkJSON(data []byte) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
var v any
return dec.Decode(&v)
}
32 changes: 32 additions & 0 deletions common/projectconfig/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"github.com/alecthomas/assert/v2"

in "github.com/TBD54566975/ftl/integration"
"github.com/TBD54566975/ftl/internal/exec"
"github.com/TBD54566975/ftl/internal/log"
)

func TestCmdsCreateProjectTomlFilesIfNonexistent(t *testing.T) {
Expand Down Expand Up @@ -50,3 +52,33 @@ func TestConfigCmdWithoutController(t *testing.T) {
in.ExecWithExpectedOutput("\"value\"\n", "ftl", "config", "get", "key"),
)
}

func TestFindConfig(t *testing.T) {
checkConfig := func(subdir string) in.Action {
return func(t testing.TB, ic in.TestContext) {
in.Infof("Running ftl config list --values")
cmd := exec.Command(ic, log.Debug, filepath.Join(ic.WorkingDir(), subdir), "ftl", "config", "list", "--values")
cmd.Stdout = nil
cmd.Stderr = nil
output, err := cmd.CombinedOutput()
assert.NoError(t, err, "%s", output)
assert.Equal(t, "test = \"test\"\n", string(output))
in.Infof("Running ftl secret list --values")
cmd = exec.Command(ic, log.Debug, filepath.Join(ic.WorkingDir(), subdir), "ftl", "secret", "list", "--values")
cmd.Stdout = nil
cmd.Stderr = nil
output, err = cmd.CombinedOutput()
assert.NoError(t, err, "%s", output)
assert.Equal(t, "test = \"test\"\n", string(output))
}
}
in.RunWithoutController(t, "",
in.CopyModule("findconfig"),
checkConfig("findconfig"),
checkConfig("findconfig/subdir"),
in.SetEnv("FTL_CONFIG", func(ic in.TestContext) string {
return filepath.Join(ic.WorkingDir(), "findconfig", "ftl-project.toml")
}),
checkConfig("."),
)
}
22 changes: 18 additions & 4 deletions common/projectconfig/projectconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/alecthomas/types/optional"

"github.com/TBD54566975/ftl"
"github.com/TBD54566975/ftl/internal"
"github.com/TBD54566975/ftl/internal/log"
)

Expand Down Expand Up @@ -76,11 +75,26 @@ func DefaultConfigPath() optional.Option[string] {
}
return optional.Some(absPath)
}
gitRoot, ok := internal.GitRoot("").Get()
if !ok {
dir, err := os.Getwd()
if err != nil {
return optional.None[string]()
}
return optional.Some(filepath.Join(gitRoot, "ftl-project.toml"))
// Find the first ftl-project.toml file in the parent directories.
for {
path := filepath.Join(dir, "ftl-project.toml")
_, err := os.Stat(path)
if err == nil {
return optional.Some(path)
}
if !errors.Is(err, os.ErrNotExist) {
return optional.None[string]()
}
dir = filepath.Dir(dir)
if dir == "/" || dir == "." {
break
}
}
return optional.Some(filepath.Join(dir, "ftl-project.toml"))
}

// MaybeCreateDefault creates the ftl-project.toml file in the Git root if it
Expand Down
21 changes: 21 additions & 0 deletions common/projectconfig/testdata/go/findconfig/findconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package findconfig

import (
"context"
"fmt"

"github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK.
)

type EchoRequest struct {
Name ftl.Option[string] `json:"name"`
}

type EchoResponse struct {
Message string `json:"message"`
}

//ftl:verb
func Echo(ctx context.Context, req EchoRequest) (EchoResponse, error) {
return EchoResponse{Message: fmt.Sprintf("Hello, %s!", req.Name.Default("anonymous"))}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[global]
[global.configuration]
test = "inline://InRlc3Qi"
[global.secrets]
test = "inline://InRlc3Qi"
2 changes: 2 additions & 0 deletions common/projectconfig/testdata/go/findconfig/ftl.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "findconfig"
language = "go"
46 changes: 46 additions & 0 deletions common/projectconfig/testdata/go/findconfig/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module ftl/findconfig

go 1.22.2

toolchain go1.22.4

require github.com/TBD54566975/ftl v1.1.5

require (
connectrpc.com/connect v1.16.1 // indirect
connectrpc.com/grpcreflect v1.2.0 // indirect
connectrpc.com/otelconnect v0.7.0 // indirect
github.com/alecthomas/concurrency v0.0.2 // indirect
github.com/alecthomas/participle/v2 v2.1.1 // indirect
github.com/alecthomas/types v0.16.0 // indirect
github.com/alessio/shellescape v1.4.2 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/swaggest/jsonschema-go v0.3.70 // indirect
github.com/swaggest/refl v1.3.0 // indirect
github.com/zalando/go-keyring v0.2.5 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

replace github.com/TBD54566975/ftl => ../../../../..
Loading
Loading