From bf4539feb8958b51bded3d0be6ae4361c00a3062 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Fri, 5 Jul 2024 10:55:30 +1000 Subject: [PATCH] =?UTF-8?q?revert:=20"fix:=20validate=20configs=20and=20se?= =?UTF-8?q?crets=20against=20the=20schema=20before=20s=E2=80=A6=20(#1978)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …etting (#1858)" This reverts commit f4cb64adaae99fd2ab1739a0d84c2393f794ce9e. # Conflicts: # backend/controller/admin/testdata/go/ftl-project-dr.toml --- backend/controller/admin/admin.go | 115 ++------ backend/controller/admin/admin_test.go | 130 +-------- backend/controller/admin/local_client.go | 43 +-- backend/controller/admin/local_client_test.go | 65 ----- .../admin/testdata/go/dischema/dischema.go | 23 -- .../admin/testdata/go/dischema/ftl.toml | 2 - .../admin/testdata/go/dischema/go.mod | 43 --- .../admin/testdata/go/dischema/go.sum | 142 ---------- .../admin/testdata/go/ftl-project-dr.toml | 9 - backend/controller/controller.go | 18 +- backend/controller/dal/dal.go | 17 -- backend/controller/ingress/ingress.go | 203 +++++++++++++- backend/controller/ingress/ingress_test.go | 4 +- backend/controller/ingress/request.go | 42 ++- backend/schema/jsonvalidate.go | 251 ------------------ 15 files changed, 279 insertions(+), 828 deletions(-) delete mode 100644 backend/controller/admin/local_client_test.go delete mode 100644 backend/controller/admin/testdata/go/dischema/dischema.go delete mode 100644 backend/controller/admin/testdata/go/dischema/ftl.toml delete mode 100644 backend/controller/admin/testdata/go/dischema/go.mod delete mode 100644 backend/controller/admin/testdata/go/dischema/go.sum delete mode 100644 backend/controller/admin/testdata/go/ftl-project-dr.toml delete mode 100644 backend/schema/jsonvalidate.go diff --git a/backend/controller/admin/admin.go b/backend/controller/admin/admin.go index 02eb45a853..3ca0e6ae75 100644 --- a/backend/controller/admin/admin.go +++ b/backend/controller/admin/admin.go @@ -9,29 +9,20 @@ import ( 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/backend/schema" cf "github.com/TBD54566975/ftl/common/configuration" - "github.com/TBD54566975/ftl/go-runtime/encoding" - "github.com/TBD54566975/ftl/internal/log" ) type AdminService struct { - schr SchemaRetriever - cm *cf.Manager[cf.Configuration] - sm *cf.Manager[cf.Secrets] + cm *cf.Manager[cf.Configuration] + sm *cf.Manager[cf.Secrets] } var _ ftlv1connect.AdminServiceHandler = (*AdminService)(nil) -type SchemaRetriever interface { - GetActiveSchema(ctx context.Context) (*schema.Schema, error) -} - -func NewAdminService(cm *cf.Manager[cf.Configuration], sm *cf.Manager[cf.Secrets], schr SchemaRetriever) *AdminService { +func NewAdminService(cm *cf.Manager[cf.Configuration], sm *cf.Manager[cf.Secrets]) *AdminService { return &AdminService{ - schr: schr, - cm: cm, - sm: sm, + cm: cm, + sm: sm, } } @@ -43,7 +34,7 @@ func (s *AdminService) Ping(ctx context.Context, req *connect.Request[ftlv1.Ping func (s *AdminService) ConfigList(ctx context.Context, req *connect.Request[ftlv1.ListConfigRequest]) (*connect.Response[ftlv1.ListConfigResponse], error) { listing, err := s.cm.List(ctx) if err != nil { - return nil, fmt.Errorf("failed to list configs: %w", err) + return nil, err } configs := []*ftlv1.ListConfigResponse_Config{} @@ -82,13 +73,13 @@ func (s *AdminService) ConfigList(ctx context.Context, req *connect.Request[ftlv // ConfigGet returns the configuration value for a given ref string. func (s *AdminService) ConfigGet(ctx context.Context, req *connect.Request[ftlv1.GetConfigRequest]) (*connect.Response[ftlv1.GetConfigResponse], error) { var value any - err := s.cm.Get(ctx, refFromConfigRef(req.Msg.GetRef()), &value) + err := s.cm.Get(ctx, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name), &value) if err != nil { - return nil, fmt.Errorf("failed to get from config manager: %w", err) + return nil, err } vb, err := json.MarshalIndent(value, "", " ") if err != nil { - return nil, fmt.Errorf("failed to marshal value: %w", err) + return nil, err } return connect.NewResponse(&ftlv1.GetConfigResponse{Value: vb}), nil } @@ -110,15 +101,10 @@ 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) { - err := s.validateAgainstSchema(ctx, false, refFromConfigRef(req.Msg.GetRef()), req.Msg.Value) - if err != nil { - return nil, err - } - pkey := configProviderKey(req.Msg.Provider) - err = s.cm.SetJSON(ctx, pkey, refFromConfigRef(req.Msg.GetRef()), 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, fmt.Errorf("failed to set config: %w", err) + return nil, err } return connect.NewResponse(&ftlv1.SetConfigResponse{}), nil } @@ -126,9 +112,9 @@ func (s *AdminService) ConfigSet(ctx context.Context, req *connect.Request[ftlv1 // ConfigUnset unsets the config value at the given ref. func (s *AdminService) ConfigUnset(ctx context.Context, req *connect.Request[ftlv1.UnsetConfigRequest]) (*connect.Response[ftlv1.UnsetConfigResponse], error) { pkey := configProviderKey(req.Msg.Provider) - err := s.cm.Unset(ctx, pkey, refFromConfigRef(req.Msg.GetRef())) + err := s.cm.Unset(ctx, pkey, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name)) if err != nil { - return nil, fmt.Errorf("failed to unset config: %w", err) + return nil, err } return connect.NewResponse(&ftlv1.UnsetConfigResponse{}), nil } @@ -137,7 +123,7 @@ func (s *AdminService) ConfigUnset(ctx context.Context, req *connect.Request[ftl func (s *AdminService) SecretsList(ctx context.Context, req *connect.Request[ftlv1.ListSecretsRequest]) (*connect.Response[ftlv1.ListSecretsResponse], error) { listing, err := s.sm.List(ctx) if err != nil { - return nil, fmt.Errorf("failed to list secrets: %w", err) + return nil, err } secrets := []*ftlv1.ListSecretsResponse_Secret{} for _, secret := range listing { @@ -172,13 +158,13 @@ func (s *AdminService) SecretsList(ctx context.Context, req *connect.Request[ftl // SecretGet returns the secret value for a given ref string. func (s *AdminService) SecretGet(ctx context.Context, req *connect.Request[ftlv1.GetSecretRequest]) (*connect.Response[ftlv1.GetSecretResponse], error) { var value any - err := s.sm.Get(ctx, refFromConfigRef(req.Msg.GetRef()), &value) + err := s.sm.Get(ctx, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name), &value) if err != nil { - return nil, fmt.Errorf("failed to get from secret manager: %w", err) + return nil, err } vb, err := json.MarshalIndent(value, "", " ") if err != nil { - return nil, fmt.Errorf("failed to marshal value: %w", err) + return nil, err } return connect.NewResponse(&ftlv1.GetSecretResponse{Value: vb}), nil } @@ -204,15 +190,10 @@ 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) { - err := s.validateAgainstSchema(ctx, true, refFromConfigRef(req.Msg.GetRef()), req.Msg.Value) - if err != nil { - return nil, err - } - pkey := secretProviderKey(req.Msg.Provider) - err = s.sm.SetJSON(ctx, pkey, refFromConfigRef(req.Msg.GetRef()), 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, fmt.Errorf("failed to set secret: %w", err) + return nil, err } return connect.NewResponse(&ftlv1.SetSecretResponse{}), nil } @@ -220,63 +201,9 @@ func (s *AdminService) SecretSet(ctx context.Context, req *connect.Request[ftlv1 // SecretUnset unsets the secret value at the given ref. func (s *AdminService) SecretUnset(ctx context.Context, req *connect.Request[ftlv1.UnsetSecretRequest]) (*connect.Response[ftlv1.UnsetSecretResponse], error) { pkey := secretProviderKey(req.Msg.Provider) - err := s.sm.Unset(ctx, pkey, refFromConfigRef(req.Msg.GetRef())) + err := s.sm.Unset(ctx, pkey, cf.NewRef(*req.Msg.Ref.Module, req.Msg.Ref.Name)) if err != nil { - return nil, fmt.Errorf("failed to unset secret: %w", err) + return nil, err } return connect.NewResponse(&ftlv1.UnsetSecretResponse{}), nil } - -func refFromConfigRef(cr *ftlv1.ConfigRef) cf.Ref { - return cf.NewRef(cr.GetModule(), cr.GetName()) -} - -func (s *AdminService) validateAgainstSchema(ctx context.Context, isSecret bool, ref cf.Ref, value json.RawMessage) error { - logger := log.FromContext(ctx) - - // Globals aren't in the module schemas, so we have nothing to validate against. - if !ref.Module.Ok() { - return nil - } - - // If we can't retrieve an active schema, skip validation. - sch, err := s.schr.GetActiveSchema(ctx) - if err != nil { - logger.Debugf("skipping validation; could not get the active schema: %v", err) - return nil - } - - r := schema.RefKey{Module: ref.Module.Default(""), Name: ref.Name}.ToRef() - decl, ok := sch.Resolve(r).Get() - if !ok { - return fmt.Errorf("declaration %q not found", ref.Name) - } - - var fieldType schema.Type - if isSecret { - decl, ok := decl.(*schema.Secret) - if !ok { - return fmt.Errorf("%q is not a secret declaration", ref.Name) - } - fieldType = decl.Type - } else { - decl, ok := decl.(*schema.Config) - if !ok { - return fmt.Errorf("%q is not a config declaration", ref.Name) - } - fieldType = decl.Type - } - - var v any - err = encoding.Unmarshal(value, &v) - if err != nil { - return fmt.Errorf("could not unmarshal JSON value: %w", err) - } - - err = schema.ValidateJSONValue(fieldType, []string{ref.Name}, v, sch) - if err != nil { - return fmt.Errorf("JSON validation failed: %w", err) - } - - return nil -} diff --git a/backend/controller/admin/admin_test.go b/backend/controller/admin/admin_test.go index 636d46044f..a0add822bf 100644 --- a/backend/controller/admin/admin_test.go +++ b/backend/controller/admin/admin_test.go @@ -13,7 +13,6 @@ import ( "github.com/alecthomas/types/optional" 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/log" ) @@ -32,7 +31,7 @@ func TestAdminService(t *testing.T) { cf.InlineProvider[cf.Secrets]{}, }) assert.NoError(t, err) - admin := NewAdminService(cm, sm, &diskSchemaRetriever{}) + admin := NewAdminService(cm, sm) assert.NotZero(t, admin) expectedEnvarValue, err := json.MarshalIndent(map[string]string{"bar": "barfoo"}, "", " ") @@ -141,130 +140,3 @@ func testAdminSecrets( assert.Equal(t, entry.Value, string(resp.Msg.Value)) } } - -var testSchema = schema.MustValidate(&schema.Schema{ - Modules: []*schema.Module{ - { - Name: "batmobile", - Comments: []string{"A batmobile comment"}, - Decls: []schema.Decl{ - &schema.Secret{ - Comments: []string{"top secret"}, - Name: "owner", - Type: &schema.String{}, - }, - &schema.Secret{ - Comments: []string{"ultra secret"}, - Name: "horsepower", - Type: &schema.Int{}, - }, - &schema.Config{ - Comments: []string{"car color"}, - Name: "color", - Type: &schema.Ref{Module: "batmobile", Name: "Color"}, - }, - &schema.Config{ - Comments: []string{"car capacity"}, - Name: "capacity", - Type: &schema.Ref{Module: "batmobile", Name: "Capacity"}, - }, - &schema.Enum{ - Comments: []string{"Car colors"}, - Name: "Color", - Type: &schema.String{}, - Variants: []*schema.EnumVariant{ - {Name: "Black", Value: &schema.StringValue{Value: "Black"}}, - {Name: "Blue", Value: &schema.StringValue{Value: "Blue"}}, - {Name: "Green", Value: &schema.StringValue{Value: "Green"}}, - }, - }, - &schema.Enum{ - Comments: []string{"Car capacities"}, - Name: "Capacity", - Type: &schema.Int{}, - Variants: []*schema.EnumVariant{ - {Name: "One", Value: &schema.IntValue{Value: int(1)}}, - {Name: "Two", Value: &schema.IntValue{Value: int(2)}}, - {Name: "Four", Value: &schema.IntValue{Value: int(4)}}, - }, - }, - }, - }, - }, -}) - -type mockSchemaRetriever struct { -} - -func (d *mockSchemaRetriever) GetActiveSchema(ctx context.Context) (*schema.Schema, error) { - return testSchema, nil -} - -func TestAdminValidation(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: config}) - assert.NoError(t, err) - - sm, err := cf.New(ctx, - cf.ProjectConfigResolver[cf.Secrets]{Config: config}, - []cf.Provider[cf.Secrets]{ - cf.EnvarProvider[cf.Secrets]{}, - cf.InlineProvider[cf.Secrets]{}, - }) - assert.NoError(t, err) - admin := NewAdminService(cm, sm, &mockSchemaRetriever{}) - assert.NotZero(t, admin) - - testSetConfig(t, ctx, admin, "batmobile", "color", "Black", "") - testSetConfig(t, ctx, admin, "batmobile", "color", "Red", "JSON validation failed: Red is not a valid variant of enum batmobile.Color") - testSetConfig(t, ctx, admin, "batmobile", "capacity", 2, "") - testSetConfig(t, ctx, admin, "batmobile", "capacity", 3, "JSON validation failed: %!s(float64=3) is not a valid variant of enum batmobile.Capacity") - - testSetSecret(t, ctx, admin, "batmobile", "owner", "Bruce Wayne", "") - testSetSecret(t, ctx, admin, "batmobile", "owner", 99, "JSON validation failed: owner has wrong type, expected String found float64") - testSetSecret(t, ctx, admin, "batmobile", "horsepower", 1000, "") - testSetSecret(t, ctx, admin, "batmobile", "horsepower", "thousand", "JSON validation failed: horsepower has wrong type, expected Int found string") - - testSetConfig(t, ctx, admin, "", "city", "Gotham", "") - testSetSecret(t, ctx, admin, "", "universe", "DC", "") -} - -// nolint -func testSetConfig(t testing.TB, ctx context.Context, admin *AdminService, module string, name string, jsonVal any, expectedError string) { - t.Helper() - buffer, err := json.Marshal(jsonVal) - assert.NoError(t, err) - - configRef := &ftlv1.ConfigRef{Name: name} - if module != "" { - configRef.Module = &module - } - - _, err = admin.ConfigSet(ctx, connect.NewRequest(&ftlv1.SetConfigRequest{ - Provider: ftlv1.ConfigProvider_CONFIG_INLINE.Enum(), - Ref: configRef, - Value: buffer, - })) - assert.EqualError(t, err, expectedError) -} - -// nolint -func testSetSecret(t testing.TB, ctx context.Context, admin *AdminService, module string, name string, jsonVal any, expectedError string) { - t.Helper() - buffer, err := json.Marshal(jsonVal) - assert.NoError(t, err) - - configRef := &ftlv1.ConfigRef{Name: name} - if module != "" { - configRef.Module = &module - } - - _, err = admin.SecretSet(ctx, connect.NewRequest(&ftlv1.SetSecretRequest{ - Provider: ftlv1.SecretProvider_SECRET_INLINE.Enum(), - Ref: configRef, - Value: buffer, - })) - assert.EqualError(t, err, expectedError) -} diff --git a/backend/controller/admin/local_client.go b/backend/controller/admin/local_client.go index ad87330a29..73c659b0fb 100644 --- a/backend/controller/admin/local_client.go +++ b/backend/controller/admin/local_client.go @@ -2,14 +2,8 @@ package admin import ( "context" - "fmt" - "path/filepath" - "github.com/TBD54566975/ftl/backend/schema" - "github.com/TBD54566975/ftl/buildengine" "github.com/TBD54566975/ftl/common/configuration" - "github.com/TBD54566975/ftl/common/projectconfig" - "github.com/alecthomas/types/optional" ) // localClient reads and writes to local projectconfig files without making any network @@ -19,43 +13,8 @@ type localClient struct { *AdminService } -type diskSchemaRetriever struct { - // Omit to use the project root as the deploy root. - deployRoot optional.Option[string] -} - func newLocalClient(ctx context.Context) *localClient { cm := configuration.ConfigFromContext(ctx) sm := configuration.SecretsFromContext(ctx) - return &localClient{NewAdminService(cm, sm, &diskSchemaRetriever{})} -} - -func (s *diskSchemaRetriever) GetActiveSchema(ctx context.Context) (*schema.Schema, error) { - path, ok := projectconfig.DefaultConfigPath().Get() - if !ok { - return nil, fmt.Errorf("no project config path available") - } - projConfig, err := projectconfig.Load(ctx, path) - if err != nil { - return nil, fmt.Errorf("could not load project config: %w", err) - } - modules, err := buildengine.DiscoverModules(ctx, projConfig.AbsModuleDirs()) - if err != nil { - return nil, fmt.Errorf("could not discover modules: %w", err) - } - - sch := &schema.Schema{} - for _, m := range modules { - schemaPath := m.Config.Abs().Schema - if r, ok := s.deployRoot.Get(); ok { - schemaPath = filepath.Join(r, m.Config.Module, m.Config.DeployDir, m.Config.Schema) - } - - module, err := schema.ModuleFromProtoFile(schemaPath) - if err != nil { - return nil, fmt.Errorf("could not load module schema: %w", err) - } - sch.Upsert(module) - } - return sch, nil + return &localClient{NewAdminService(cm, sm)} } diff --git a/backend/controller/admin/local_client_test.go b/backend/controller/admin/local_client_test.go deleted file mode 100644 index 1d4f9eab4e..0000000000 --- a/backend/controller/admin/local_client_test.go +++ /dev/null @@ -1,65 +0,0 @@ -//go:build integration - -package admin - -import ( - "context" - "testing" - - cf "github.com/TBD54566975/ftl/common/configuration" - in "github.com/TBD54566975/ftl/integration" - "github.com/TBD54566975/ftl/internal/log" - "github.com/alecthomas/assert/v2" - "github.com/alecthomas/types/optional" -) - -func TestDiskSchemaRetrieverWithBuildArtefact(t *testing.T) { - in.RunWithoutController(t, "ftl-project-dr.toml", - in.CopyModule("dischema"), - in.Build("dischema"), - func(t testing.TB, ic in.TestContext) { - dsr := &diskSchemaRetriever{deployRoot: optional.Some[string](ic.WorkingDir())} - sch, err := dsr.GetActiveSchema(ic.Context) - assert.NoError(t, err) - - module, ok := sch.Module("dischema").Get() - assert.Equal(t, ok, true) - assert.Equal(t, "dischema", module.Name) - }, - ) -} - -func TestDiskSchemaRetrieverWithNoSchema(t *testing.T) { - in.RunWithoutController(t, "ftl-project-dr.toml", - in.CopyModule("dischema"), - func(t testing.TB, ic in.TestContext) { - dsr := &diskSchemaRetriever{} - _, err := dsr.GetActiveSchema(ic.Context) - assert.Error(t, err) - }, - ) -} - -func TestAdminNoValidationWithNoSchema(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: config}) - assert.NoError(t, err) - - sm, err := cf.New(ctx, - cf.ProjectConfigResolver[cf.Secrets]{Config: config}, - []cf.Provider[cf.Secrets]{ - cf.EnvarProvider[cf.Secrets]{}, - cf.InlineProvider[cf.Secrets]{}, - }) - assert.NoError(t, err) - - dsr := &diskSchemaRetriever{deployRoot: optional.Some(string(t.TempDir()))} - _, err = dsr.GetActiveSchema(ctx) - assert.Error(t, err) - - admin := NewAdminService(cm, sm, dsr) - testSetConfig(t, ctx, admin, "batmobile", "color", "Red", "") - testSetSecret(t, ctx, admin, "batmobile", "owner", 99, "") -} diff --git a/backend/controller/admin/testdata/go/dischema/dischema.go b/backend/controller/admin/testdata/go/dischema/dischema.go deleted file mode 100644 index f780f6a518..0000000000 --- a/backend/controller/admin/testdata/go/dischema/dischema.go +++ /dev/null @@ -1,23 +0,0 @@ -package dischema - -import ( - "context" - "fmt" - - "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. -) - -var defaultUser = ftl.Config[string]("defaultUser") - -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(defaultUser.Get(ctx)))}, nil -} diff --git a/backend/controller/admin/testdata/go/dischema/ftl.toml b/backend/controller/admin/testdata/go/dischema/ftl.toml deleted file mode 100644 index 19ad570a97..0000000000 --- a/backend/controller/admin/testdata/go/dischema/ftl.toml +++ /dev/null @@ -1,2 +0,0 @@ -module = "dischema" -language = "go" diff --git a/backend/controller/admin/testdata/go/dischema/go.mod b/backend/controller/admin/testdata/go/dischema/go.mod deleted file mode 100644 index 60aa2617ca..0000000000 --- a/backend/controller/admin/testdata/go/dischema/go.mod +++ /dev/null @@ -1,43 +0,0 @@ -module ftl/dischema - -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/swaggest/jsonschema-go v0.3.70 // indirect - github.com/swaggest/refl v1.3.0 // indirect - github.com/zalando/go-keyring v0.2.4 // 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.23.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect -) diff --git a/backend/controller/admin/testdata/go/dischema/go.sum b/backend/controller/admin/testdata/go/dischema/go.sum deleted file mode 100644 index e2d61f4b4e..0000000000 --- a/backend/controller/admin/testdata/go/dischema/go.sum +++ /dev/null @@ -1,142 +0,0 @@ -connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= -connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= -connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= -connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= -connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= -connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= -github.com/TBD54566975/ftl v1.1.5 h1:PZq322WiBWUFnOHv51bnDjT/SbztEUVkEAHyGJA1HWY= -github.com/TBD54566975/ftl v1.1.5/go.mod h1:Z64GLoDe603sxe7Nytpx6Le/bB/Jlk8m52MVz+JKXtM= -github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= -github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= -github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= -github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= -github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= -github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= -github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= -github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= -github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= -github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= -github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= -github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/bool64/dev v0.2.34 h1:P9n315P8LdpxusnYQ0X7MP1CZXwBK5ae5RZrd+GdSZE= -github.com/bool64/dev v0.2.34/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= -github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= -github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= -github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= -github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= -github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= -github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= -github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= -github.com/swaggest/jsonschema-go v0.3.70 h1:8Vx5nm5t/6DBFw2+WC0/Vp1ZVe9/4mpuA0tuAe0wwCI= -github.com/swaggest/jsonschema-go v0.3.70/go.mod h1:7N43/CwdaWgPUDfYV70K7Qm79tRqe/al7gLSt9YeGIE= -github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= -github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= -github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= -go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk= -modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM= -modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/backend/controller/admin/testdata/go/ftl-project-dr.toml b/backend/controller/admin/testdata/go/ftl-project-dr.toml deleted file mode 100644 index a65b02f7ff..0000000000 --- a/backend/controller/admin/testdata/go/ftl-project-dr.toml +++ /dev/null @@ -1,9 +0,0 @@ -name = "dr" -ftl-min-version = "dev" -hermit = false -no-git = false - -[global] - -[commands] - startup = ["echo disk retreiver"] diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 66811b0c87..0881645a26 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -127,7 +127,7 @@ func Start(ctx context.Context, config Config, runnerScaling scaling.RunnerScali cm := cf.ConfigFromContext(ctx) sm := cf.SecretsFromContext(ctx) - admin := admin.NewAdminService(cm, sm, dal) + admin := admin.NewAdminService(cm, sm) console := NewConsoleService(dal) ingressHandler := http.Handler(svc) @@ -282,7 +282,7 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - sch, err := s.dal.GetActiveSchema(r.Context()) + sch, err := s.getActiveSchema(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -905,7 +905,7 @@ func (s *Service) callWithRequest( return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("body is required")) } - sch, err := s.dal.GetActiveSchema(ctx) + sch, err := s.getActiveSchema(ctx) if err != nil { return nil, err } @@ -1658,6 +1658,18 @@ func (s *Service) syncSchema(ctx context.Context) { } } +func (s *Service) getActiveSchema(ctx context.Context) (*schema.Schema, error) { + deployments, err := s.dal.GetActiveDeployments(ctx) + if err != nil { + return nil, err + } + return schema.ValidateSchema(&schema.Schema{ + Modules: slices.Map(deployments, func(d dal.Deployment) *schema.Module { + return d.Schema + }), + }) +} + func extractIngressRoutingEntries(req *ftlv1.CreateDeploymentRequest) []dal.IngressRoutingEntry { var ingressRoutes []dal.IngressRoutingEntry for _, decl := range req.Schema.Decls { diff --git a/backend/controller/dal/dal.go b/backend/controller/dal/dal.go index 968140315e..dcc3e70e3a 100644 --- a/backend/controller/dal/dal.go +++ b/backend/controller/dal/dal.go @@ -841,23 +841,6 @@ func (d *DAL) GetActiveDeployments(ctx context.Context) ([]Deployment, error) { }) } -// GetActiveSchema returns the schema for all active deployments. -func (d *DAL) GetActiveSchema(ctx context.Context) (*schema.Schema, error) { - deployments, err := d.GetActiveDeployments(ctx) - if err != nil { - return nil, err - } - sch, err := schema.ValidateSchema(&schema.Schema{ - Modules: slices.Map(deployments, func(d Deployment) *schema.Module { - return d.Schema - }), - }) - if err != nil { - return nil, fmt.Errorf("could not validate schema: %w", err) - } - return sch, nil -} - func (d *DAL) GetDeploymentsWithMinReplicas(ctx context.Context) ([]Deployment, error) { rows, err := d.db.GetDeploymentsWithMinReplicas(ctx) if err != nil { diff --git a/backend/controller/ingress/ingress.go b/backend/controller/ingress/ingress.go index fbcafd7192..afa896058a 100644 --- a/backend/controller/ingress/ingress.go +++ b/backend/controller/ingress/ingress.go @@ -1,10 +1,14 @@ package ingress import ( + "encoding/base64" "encoding/json" "fmt" "math/rand" + "reflect" + "strconv" "strings" + "time" "github.com/TBD54566975/ftl/backend/controller/dal" dalerrs "github.com/TBD54566975/ftl/backend/dal" @@ -12,6 +16,12 @@ import ( "github.com/TBD54566975/ftl/internal/slices" ) +type path []string + +func (p path) String() string { + return strings.TrimLeft(strings.Join(p, ""), ".") +} + func GetIngressRoute(routes []dal.IngressRoute, method string, path string) (*dal.IngressRoute, error) { var matchedRoutes = slices.Filter(routes, func(route dal.IngressRoute) bool { return matchSegments(route.Path, path, func(segment, value string) {}) @@ -56,11 +66,7 @@ func ValidateCallBody(body []byte, verb *schema.Verb, sch *schema.Schema) error return fmt.Errorf("HTTP request body is not valid JSON: %w", err) } - err = schema.ValidateJSONValue(verb.Request, []string{verb.Request.String()}, requestMap, sch) - if err != nil { - return fmt.Errorf("could not validate HTTP request body: %w", err) - } - return nil + return validateValue(verb.Request, []string{verb.Request.String()}, requestMap, sch) } func getBodyField(ref *schema.Ref, sch *schema.Schema) (*schema.Field, error) { @@ -82,3 +88,190 @@ func getBodyField(ref *schema.Ref, sch *schema.Schema) (*schema.Field, error) { return bodyField, nil } + +func validateValue(fieldType schema.Type, path path, value any, sch *schema.Schema) error { //nolint:maintidx + var typeMatches bool + switch fieldType := fieldType.(type) { + case *schema.Any: + typeMatches = true + + case *schema.Unit: + // TODO: Use type assertions consistently in this function rather than reflection. + rv := reflect.ValueOf(value) + if rv.Kind() != reflect.Map || rv.Len() != 0 { + return fmt.Errorf("%s must be an empty map", path) + } + return nil + + case *schema.Time: + str, ok := value.(string) + if !ok { + return fmt.Errorf("time %s must be an RFC3339 formatted string", path) + } + _, err := time.Parse(time.RFC3339Nano, str) + if err != nil { + return fmt.Errorf("time %s must be an RFC3339 formatted string: %w", path, err) + } + return nil + + case *schema.Int: + switch value := value.(type) { + case int64, float64: + typeMatches = true + case string: + if _, err := strconv.ParseInt(value, 10, 64); err == nil { + typeMatches = true + } + } + + case *schema.Float: + switch value := value.(type) { + case float64: + typeMatches = true + case string: + if _, err := strconv.ParseFloat(value, 64); err == nil { + typeMatches = true + } + } + + case *schema.String: + _, typeMatches = value.(string) + + case *schema.Bool: + switch value := value.(type) { + case bool: + typeMatches = true + case string: + if _, err := strconv.ParseBool(value); err == nil { + typeMatches = true + } + } + + case *schema.Array: + rv := reflect.ValueOf(value) + if rv.Kind() != reflect.Slice { + return fmt.Errorf("%s is not a slice", path) + } + elementType := fieldType.Element + for i := range rv.Len() { + elemPath := append(path, fmt.Sprintf("[%d]", i)) //nolint:gocritic + elem := rv.Index(i).Interface() + if err := validateValue(elementType, elemPath, elem, sch); err != nil { + return err + } + } + typeMatches = true + + case *schema.Map: + rv := reflect.ValueOf(value) + if rv.Kind() != reflect.Map { + return fmt.Errorf("%s is not a map", path) + } + keyType := fieldType.Key + valueType := fieldType.Value + for _, key := range rv.MapKeys() { + elemPath := append(path, fmt.Sprintf("[%q]", key)) //nolint:gocritic + elem := rv.MapIndex(key).Interface() + if err := validateValue(keyType, elemPath, key.Interface(), sch); err != nil { + return err + } + if err := validateValue(valueType, elemPath, elem, sch); err != nil { + return err + } + } + typeMatches = true + case *schema.Ref: + decl, ok := sch.Resolve(fieldType).Get() + if !ok { + return fmt.Errorf("unknown ref %v", fieldType) + } + + switch d := decl.(type) { + case *schema.Data: + if valueMap, ok := value.(map[string]any); ok { + if err := validateRequestMap(fieldType, path, valueMap, sch); err != nil { + return err + } + typeMatches = true + } + case *schema.TypeAlias: + return validateValue(d.Type, path, value, sch) + case *schema.Enum: + var inputName any + inputName = value + for _, v := range d.Variants { + switch t := v.Value.(type) { + case *schema.StringValue: + if valueStr, ok := value.(string); ok { + if t.Value == valueStr { + typeMatches = true + break + } + } + case *schema.IntValue: + if valueInt, ok := value.(int); ok { + if t.Value == valueInt { + typeMatches = true + break + } + } + case *schema.TypeValue: + if reqVariant, ok := value.(map[string]any); ok { + vName, ok := reqVariant["name"] + if !ok { + return fmt.Errorf(`missing name field in enum type %q: expected structure is `+ + "{\"name\": \"\", \"value\": }", value) + } + vNameStr, ok := vName.(string) + if !ok { + return fmt.Errorf(`invalid type for enum %q; name field must be a string, was %T`, + fieldType, vName) + } + inputName = fmt.Sprintf("%q", vNameStr) + + vValue, ok := reqVariant["value"] + if !ok { + return fmt.Errorf(`missing value field in enum type %q: expected structure is `+ + "{\"name\": \"\", \"value\": }", value) + } + + if v.Name == vNameStr { + return validateValue(t.Value, path, vValue, sch) + } + } else { + return fmt.Errorf(`malformed enum type %s: expected structure is `+ + "{\"name\": \"\", \"value\": }", path) + } + } + } + if !typeMatches { + return fmt.Errorf("%s is not a valid variant of enum %s", inputName, fieldType) + } + + case *schema.Config, *schema.Database, *schema.Secret, *schema.Verb, *schema.FSM, *schema.Topic, *schema.Subscription: + + } + + case *schema.Bytes: + _, typeMatches = value.([]byte) + if bodyStr, ok := value.(string); ok { + _, err := base64.StdEncoding.DecodeString(bodyStr) + if err != nil { + return fmt.Errorf("%s is not a valid base64 string", path) + } + typeMatches = true + } + + case *schema.Optional: + if value == nil { + typeMatches = true + } else { + return validateValue(fieldType.Type, path, value, sch) + } + } + + if !typeMatches { + return fmt.Errorf("%s has wrong type, expected %s found %T", path, fieldType, value) + } + return nil +} diff --git a/backend/controller/ingress/ingress_test.go b/backend/controller/ingress/ingress_test.go index a448580fbc..1d502f2645 100644 --- a/backend/controller/ingress/ingress_test.go +++ b/backend/controller/ingress/ingress_test.go @@ -143,7 +143,7 @@ func TestValidation(t *testing.T) { sch, err := schema.ParseString("", test.schema) assert.NoError(t, err) - err = schema.ValidateRequestMap(&schema.Ref{Module: "test", Name: "Test"}, nil, test.request, sch) + err = validateRequestMap(&schema.Ref{Module: "test", Name: "Test"}, nil, test.request, sch) if test.err != "" { assert.EqualError(t, err, test.err) } else { @@ -407,7 +407,7 @@ func TestEnumValidation(t *testing.T) { } for _, test := range tests { - err := schema.ValidateJSONValue(test.validateRoot, []string{test.validateRoot.String()}, test.req, sch) + err := validateValue(test.validateRoot, []string{test.validateRoot.String()}, test.req, sch) if test.err == "" { assert.NoError(t, err) } else { diff --git a/backend/controller/ingress/request.go b/backend/controller/ingress/request.go index d9bb18a2b4..4c8ed01e38 100644 --- a/backend/controller/ingress/request.go +++ b/backend/controller/ingress/request.go @@ -2,6 +2,7 @@ package ingress import ( "encoding/json" + "errors" "fmt" "io" "net/http" @@ -83,7 +84,7 @@ func BuildRequestBody(route *dal.IngressRoute, r *http.Request, sch *schema.Sche return nil, err } - err = schema.ValidateRequestMap(request, []string{request.String()}, requestMap, sch) + err = validateRequestMap(request, []string{request.String()}, requestMap, sch) if err != nil { return nil, err } @@ -234,6 +235,45 @@ func buildRequestMap(route *dal.IngressRoute, r *http.Request, ref *schema.Ref, return requestMap, nil } +func validateRequestMap(ref *schema.Ref, path path, request map[string]any, sch *schema.Schema) error { + data, err := sch.ResolveMonomorphised(ref) + if err != nil { + return err + } + + var errs []error + for _, field := range data.Fields { + fieldPath := append(path, "."+field.Name) //nolint:gocritic + + value, haveValue := request[field.Name] + if !haveValue && !allowMissingField(field) { + errs = append(errs, fmt.Errorf("%s is required", fieldPath)) + continue + } + + if haveValue { + err := validateValue(field.Type, fieldPath, value, sch) + if err != nil { + errs = append(errs, err) + } + } + + } + + return errors.Join(errs...) +} + +// Fields of these types can be omitted from the JSON representation. +func allowMissingField(field *schema.Field) bool { + switch field.Type.(type) { + case *schema.Optional, *schema.Any, *schema.Array, *schema.Map, *schema.Bytes, *schema.Unit: + return true + + case *schema.Bool, *schema.Ref, *schema.Float, *schema.Int, *schema.String, *schema.Time: + } + return false +} + func parseQueryParams(values url.Values, data *schema.Data) (map[string]any, error) { if jsonStr, ok := values["@json"]; ok { if len(values) > 1 { diff --git a/backend/schema/jsonvalidate.go b/backend/schema/jsonvalidate.go deleted file mode 100644 index 3e5336b8c2..0000000000 --- a/backend/schema/jsonvalidate.go +++ /dev/null @@ -1,251 +0,0 @@ -package schema - -import ( - "encoding/base64" - "errors" - "fmt" - "reflect" - "strconv" - "strings" - "time" -) - -type path []string - -func (p path) String() string { - return strings.TrimLeft(strings.Join(p, ""), ".") -} - -// ValidateJSONValue validates a given JSON value against the provided schema. -func ValidateJSONValue(fieldType Type, path path, value any, sch *Schema) error { //nolint:maintidx - var typeMatches bool - switch fieldType := fieldType.(type) { - case *Any: - typeMatches = true - - case *Unit: - // TODO: Use type assertions consistently in this function rather than reflection. - rv := reflect.ValueOf(value) - if rv.Kind() != reflect.Map || rv.Len() != 0 { - return fmt.Errorf("%s must be an empty map", path) - } - return nil - - case *Time: - str, ok := value.(string) - if !ok { - return fmt.Errorf("time %s must be an RFC3339 formatted string", path) - } - _, err := time.Parse(time.RFC3339Nano, str) - if err != nil { - return fmt.Errorf("time %s must be an RFC3339 formatted string: %w", path, err) - } - return nil - - case *Int: - switch value := value.(type) { - case int64, float64: - typeMatches = true - case string: - fmt.Printf("found string %s\n", value) - if _, err := strconv.ParseInt(value, 10, 64); err == nil { - typeMatches = true - } - } - - case *Float: - switch value := value.(type) { - case float64: - typeMatches = true - case string: - if _, err := strconv.ParseFloat(value, 64); err == nil { - typeMatches = true - } - } - - case *String: - _, typeMatches = value.(string) - - case *Bool: - switch value := value.(type) { - case bool: - typeMatches = true - case string: - if _, err := strconv.ParseBool(value); err == nil { - typeMatches = true - } - } - - case *Array: - rv := reflect.ValueOf(value) - if rv.Kind() != reflect.Slice { - return fmt.Errorf("%s is not a slice", path) - } - elementType := fieldType.Element - for i := range rv.Len() { - elemPath := append(path, fmt.Sprintf("[%d]", i)) //nolint:gocritic - elem := rv.Index(i).Interface() - if err := ValidateJSONValue(elementType, elemPath, elem, sch); err != nil { - return err - } - } - typeMatches = true - - case *Map: - rv := reflect.ValueOf(value) - if rv.Kind() != reflect.Map { - return fmt.Errorf("%s is not a map", path) - } - keyType := fieldType.Key - valueType := fieldType.Value - for _, key := range rv.MapKeys() { - elemPath := append(path, fmt.Sprintf("[%q]", key)) //nolint:gocritic - elem := rv.MapIndex(key).Interface() - if err := ValidateJSONValue(keyType, elemPath, key.Interface(), sch); err != nil { - return err - } - if err := ValidateJSONValue(valueType, elemPath, elem, sch); err != nil { - return err - } - } - typeMatches = true - case *Ref: - decl, ok := sch.Resolve(fieldType).Get() - if !ok { - return fmt.Errorf("unknown ref %v", fieldType) - } - - switch d := decl.(type) { - case *Data: - if valueMap, ok := value.(map[string]any); ok { - if err := ValidateRequestMap(fieldType, path, valueMap, sch); err != nil { - return err - } - typeMatches = true - } - case *TypeAlias: - return ValidateJSONValue(d.Type, path, value, sch) - case *Enum: - var inputName any - inputName = value - for _, v := range d.Variants { - switch t := v.Value.(type) { - case *StringValue: - if valueStr, ok := value.(string); ok { - if t.Value == valueStr { - typeMatches = true - break - } - } - case *IntValue: - switch value := value.(type) { - case int, int64: - if t.Value == value { - typeMatches = true - break - } - case float64: - if float64(t.Value) == value { - typeMatches = true - break - } - } - case *TypeValue: - if reqVariant, ok := value.(map[string]any); ok { - vName, ok := reqVariant["name"] - if !ok { - return fmt.Errorf(`missing name field in enum type %q: expected structure is `+ - "{\"name\": \"\", \"value\": }", value) - } - vNameStr, ok := vName.(string) - if !ok { - return fmt.Errorf(`invalid type for enum %q; name field must be a string, was %T`, - fieldType, vName) - } - inputName = fmt.Sprintf("%q", vNameStr) - - vValue, ok := reqVariant["value"] - if !ok { - return fmt.Errorf(`missing value field in enum type %q: expected structure is `+ - "{\"name\": \"\", \"value\": }", value) - } - - if v.Name == vNameStr { - return ValidateJSONValue(t.Value, path, vValue, sch) - } - } else { - return fmt.Errorf(`malformed enum type %s: expected structure is `+ - "{\"name\": \"\", \"value\": }", path) - } - } - } - if !typeMatches { - return fmt.Errorf("%s is not a valid variant of enum %s", inputName, fieldType) - } - - case *Config, *Database, *Secret, *Verb, *FSM, *Topic, *Subscription: - - } - - case *Bytes: - _, typeMatches = value.([]byte) - if bodyStr, ok := value.(string); ok { - _, err := base64.StdEncoding.DecodeString(bodyStr) - if err != nil { - return fmt.Errorf("%s is not a valid base64 string", path) - } - typeMatches = true - } - - case *Optional: - if value == nil { - typeMatches = true - } else { - return ValidateJSONValue(fieldType.Type, path, value, sch) - } - } - - if !typeMatches { - return fmt.Errorf("%s has wrong type, expected %s found %T", path, fieldType, value) - } - return nil -} - -func ValidateRequestMap(ref *Ref, path path, request map[string]any, sch *Schema) error { - data, err := sch.ResolveMonomorphised(ref) - if err != nil { - return err - } - - var errs []error - for _, field := range data.Fields { - fieldPath := append(path, "."+field.Name) //nolint:gocritic - - value, haveValue := request[field.Name] - if !haveValue && !allowMissingField(field) { - errs = append(errs, fmt.Errorf("%s is required", fieldPath)) - continue - } - - if haveValue { - err := ValidateJSONValue(field.Type, fieldPath, value, sch) - if err != nil { - errs = append(errs, err) - } - } - - } - - return errors.Join(errs...) -} - -// Fields of these types can be omitted from the JSON representation. -func allowMissingField(field *Field) bool { - switch field.Type.(type) { - case *Optional, *Any, *Array, *Map, *Bytes, *Unit: - return true - - case *Bool, *Ref, *Float, *Int, *String, *Time: - } - return false -}