From f04a9216916bc01605af304f6273749df142e3f8 Mon Sep 17 00:00:00 2001 From: Safeer Jiwan Date: Wed, 5 Jun 2024 14:43:46 -0700 Subject: [PATCH] fix: AdminService: move to admin package, add tests (#1656) --- backend/controller/{ => admin}/admin.go | 2 +- backend/controller/admin/admin_test.go | 141 ++++++++++++++++++ .../admin/testdata/ftl-project.toml | 14 ++ backend/controller/controller.go | 3 +- common/projectconfig/integration_test.go | 15 +- 5 files changed, 166 insertions(+), 9 deletions(-) rename backend/controller/{ => admin}/admin.go (99%) create mode 100644 backend/controller/admin/admin_test.go create mode 100644 backend/controller/admin/testdata/ftl-project.toml diff --git a/backend/controller/admin.go b/backend/controller/admin/admin.go similarity index 99% rename from backend/controller/admin.go rename to backend/controller/admin/admin.go index a1d88950ad..a08f30e883 100644 --- a/backend/controller/admin.go +++ b/backend/controller/admin/admin.go @@ -1,4 +1,4 @@ -package controller +package admin import ( "context" diff --git a/backend/controller/admin/admin_test.go b/backend/controller/admin/admin_test.go new file mode 100644 index 0000000000..c45d1e1e9b --- /dev/null +++ b/backend/controller/admin/admin_test.go @@ -0,0 +1,141 @@ +package admin + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" + + "connectrpc.com/connect" + ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" + cf "github.com/TBD54566975/ftl/common/configuration" + "github.com/TBD54566975/ftl/internal/log" + "github.com/alecthomas/assert/v2" + "github.com/alecthomas/types/optional" +) + +func TestAdminService(t *testing.T) { + config := tempConfigPath(t, "testdata/ftl-project.toml", "admin") + ctx := log.ContextWithNewDefaultLogger(context.Background()) + + cm, err := cf.NewConfigurationManager(ctx, cf.ProjectConfigResolver[cf.Configuration]{Config: []string{config}}) + assert.NoError(t, err) + + sm, err := cf.New(ctx, + cf.ProjectConfigResolver[cf.Secrets]{Config: []string{config}}, + []cf.Provider[cf.Secrets]{ + cf.EnvarProvider[cf.Secrets]{}, + cf.InlineProvider[cf.Secrets]{}, + }) + assert.NoError(t, err) + admin := NewAdminService(cm, sm) + assert.NotZero(t, admin) + + expectedEnvarValue, err := json.MarshalIndent(map[string]string{"bar": "barfoo"}, "", " ") + assert.NoError(t, err) + + testAdminConfigs(t, ctx, "FTL_CONFIG_YmFy", admin, []expectedEntry{ + {Ref: cf.Ref{Name: "bar"}, Value: string(expectedEnvarValue)}, + {Ref: cf.Ref{Name: "foo"}, Value: `"foobar"`}, + {Ref: cf.Ref{Name: "mutable"}, Value: `"helloworld"`}, + {Ref: cf.Ref{Module: optional.Some[string]("echo"), Name: "default"}, Value: `"anonymous"`}, + }) + + testAdminSecrets(t, ctx, "FTL_SECRET_YmFy", admin, []expectedEntry{ + {Ref: cf.Ref{Name: "bar"}, Value: string(expectedEnvarValue)}, + {Ref: cf.Ref{Name: "foo"}, Value: `"foobarsecret"`}, + }) +} + +type expectedEntry struct { + Ref cf.Ref + Value string +} + +func tempConfigPath(t *testing.T, existingPath string, prefix string) string { + t.Helper() + config := filepath.Join(t.TempDir(), fmt.Sprintf("%s-ftl-project.toml", prefix)) + var existing []byte + var err error + if existingPath == "" { + existing = []byte{} + } else { + existing, err = os.ReadFile(existingPath) + assert.NoError(t, err) + } + err = os.WriteFile(config, existing, 0600) + assert.NoError(t, err) + return config +} + +// nolint +func testAdminConfigs( + t *testing.T, + ctx context.Context, + envarName string, + admin *AdminService, + entries []expectedEntry, +) { + t.Helper() + t.Setenv(envarName, "eyJiYXIiOiJiYXJmb28ifQ") // bar={"bar": "barfoo"} + + module := "" + includeValues := true + resp, err := admin.ConfigList(ctx, connect.NewRequest(&ftlv1.ListConfigRequest{ + Module: &module, + IncludeValues: &includeValues, + })) + assert.NoError(t, err) + assert.NotZero(t, resp) + + configs := resp.Msg.Configs + assert.Equal(t, len(entries), len(configs)) + + for _, entry := range entries { + module := entry.Ref.Module.Default("") + ref := &ftlv1.ConfigRef{ + Module: &module, + Name: entry.Ref.Name, + } + resp, err := admin.ConfigGet(ctx, connect.NewRequest(&ftlv1.GetConfigRequest{Ref: ref})) + assert.NoError(t, err) + assert.Equal(t, entry.Value, string(resp.Msg.Value)) + } +} + +// nolint +func testAdminSecrets( + t *testing.T, + ctx context.Context, + envarName string, + admin *AdminService, + entries []expectedEntry, +) { + t.Helper() + t.Setenv(envarName, "eyJiYXIiOiJiYXJmb28ifQ") // bar={"bar": "barfoo"} + + module := "" + includeValues := true + resp, err := admin.SecretsList(ctx, connect.NewRequest(&ftlv1.ListSecretsRequest{ + Module: &module, + IncludeValues: &includeValues, + })) + assert.NoError(t, err) + assert.NotZero(t, resp) + + secrets := resp.Msg.Secrets + assert.Equal(t, len(entries), len(secrets)) + + for _, entry := range entries { + module := entry.Ref.Module.Default("") + ref := &ftlv1.ConfigRef{ + Module: &module, + Name: entry.Ref.Name, + } + resp, err := admin.SecretGet(ctx, connect.NewRequest(&ftlv1.GetSecretRequest{Ref: ref})) + assert.NoError(t, err) + assert.Equal(t, entry.Value, string(resp.Msg.Value)) + } +} diff --git a/backend/controller/admin/testdata/ftl-project.toml b/backend/controller/admin/testdata/ftl-project.toml new file mode 100644 index 0000000000..0f2031dc05 --- /dev/null +++ b/backend/controller/admin/testdata/ftl-project.toml @@ -0,0 +1,14 @@ +[global] +[global.secrets] +bar = "envar://baza" +foo = "inline://ImZvb2JhcnNlY3JldCI" + +[global.configuration] +bar = "envar://bazb" +foo = "inline://ImZvb2JhciI" +mutable = "inline://ImhlbGxvd29ybGQi" + +[modules] + [modules.echo] + [modules.echo.configuration] + default = "inline://ImFub255bW91cyI" diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 5890e36461..d2c503c6f8 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -32,6 +32,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/TBD54566975/ftl" + "github.com/TBD54566975/ftl/backend/controller/admin" "github.com/TBD54566975/ftl/backend/controller/cronjobs" "github.com/TBD54566975/ftl/backend/controller/dal" "github.com/TBD54566975/ftl/backend/controller/ingress" @@ -134,7 +135,7 @@ func Start(ctx context.Context, config Config, runnerScaling scaling.RunnerScali cm := cf.ConfigFromContext(ctx) sm := cf.SecretsFromContext(ctx) - admin := NewAdminService(cm, sm) + admin := admin.NewAdminService(cm, sm) console := NewConsoleService(dal) ingressHandler := http.Handler(svc) diff --git a/common/projectconfig/integration_test.go b/common/projectconfig/integration_test.go index dbf6c1de91..de2b47810f 100644 --- a/common/projectconfig/integration_test.go +++ b/common/projectconfig/integration_test.go @@ -13,20 +13,21 @@ import ( ) func TestCmdsCreateProjectTomlFilesIfNonexistent(t *testing.T) { + cwd, err := os.Getwd() + assert.NoError(t, err) + fileName := "ftl-project-nonexistent.toml" + configPath := filepath.Join(cwd, "testdata", "go", fileName) + in.Run(t, fileName, in.CopyModule("echo"), in.Exec("ftl", "config", "set", "key", "--inline", "value"), + in.FileContains(configPath, "key"), + in.FileContains(configPath, "InZhbHVlIg"), ) // The FTL config path is special-cased to use the testdata directory - // instead of tmpDir. - configPath := filepath.Join("testdata", "go", fileName) - - fmt.Printf("Checking that %s exists\n", configPath) - _, err := os.Stat(configPath) - assert.NoError(t, err) - + // instead of tmpDir, so we need to clean it up manually. fmt.Printf("Removing config file %s\n", configPath) err = os.Remove(configPath) assert.NoError(t, err)