diff --git a/.mlc.config.json b/.mlc.config.json index b0a5641df..50448da0d 100644 --- a/.mlc.config.json +++ b/.mlc.config.json @@ -14,6 +14,9 @@ }, { "pattern": "^http://capact-hub-local.capact-system/graphql$" + }, + { + "pattern": "^http://capact-hub-public.capact-system/graphql$" } ], "timeout": "20s", diff --git a/cmd/argo-actions/README.md b/cmd/argo-actions/README.md index 9efc6f218..2a64bf578 100644 --- a/cmd/argo-actions/README.md +++ b/cmd/argo-actions/README.md @@ -35,6 +35,7 @@ The following environment variables can be set: |--------------------------|----------|-------------------------------------------------|--------------------------------------------------------| | APP_ACTION | yes | | Defines action to perform | | APP_LOCAL_HUB_ENDPOINT | no | http://capact-hub-local.capact-system/graphql | Defines local Hub Endpoint | +| APP_PUBLIC_HUB_ENDPOINT | no | http://capact-hub-public.capact-system/graphql | Defines public Hub Endpoint | | APP_DOWNLOAD_CONFIG | no | | For download action defines Type Instances to download | | APP_LOGGER_DEV_MODE | no | `false` | Enable additional log messages | diff --git a/cmd/argo-actions/main.go b/cmd/argo-actions/main.go index dc576aa37..bf0eab4bf 100644 --- a/cmd/argo-actions/main.go +++ b/cmd/argo-actions/main.go @@ -8,7 +8,9 @@ import ( "capact.io/capact/internal/logger" argoactions "capact.io/capact/pkg/argo-actions" + hubclient "capact.io/capact/pkg/hub/client" "capact.io/capact/pkg/hub/client/local" + "capact.io/capact/pkg/hub/client/public" "github.com/vrischmann/envconfig" "go.uber.org/zap" @@ -16,12 +18,13 @@ import ( // Config for the argo-actions command. type Config struct { - Action string - DownloadConfig []argoactions.DownloadConfig `envconfig:"optional"` - UploadConfig argoactions.UploadConfig `envconfig:"optional"` - UpdateConfig argoactions.UpdateConfig `envconfig:"optional"` - LocalHubEndpoint string `envconfig:"default=http://capact-hub-local.capact-system/graphql"` - Logger logger.Config + Action string + DownloadConfig []argoactions.DownloadConfig `envconfig:"optional"` + UploadConfig argoactions.UploadConfig `envconfig:"optional"` + UpdateConfig argoactions.UpdateConfig `envconfig:"optional"` + LocalHubEndpoint string `envconfig:"default=http://capact-hub-local.capact-system/graphql"` + PublicHubEndpoint string `envconfig:"default=http://capact-hub-public.capact-system/graphql"` + Logger logger.Config } func main() { @@ -37,20 +40,24 @@ func main() { logger, err := logger.New(cfg.Logger) exitOnError(err, "while creating zap logger") - client := local.NewDefaultClient(cfg.LocalHubEndpoint) + // TODO: Consider using connection `hubclient.New` and route requests through Gateway + client := hubclient.Client{ + Local: local.NewDefaultClient(cfg.LocalHubEndpoint), + Public: public.NewDefaultClient(cfg.PublicHubEndpoint), + } switch cfg.Action { case argoactions.DownloadAction: log := logger.With(zap.String("Action", argoactions.DownloadAction)) - action = argoactions.NewDownloadAction(log, client, cfg.DownloadConfig) + action = argoactions.NewDownloadAction(log, &client, cfg.DownloadConfig) case argoactions.UploadAction: log := logger.With(zap.String("Action", argoactions.UploadAction)) - action = argoactions.NewUploadAction(log, client, cfg.UploadConfig) + action = argoactions.NewUploadAction(log, &client, cfg.UploadConfig) case argoactions.UpdateAction: log := logger.With(zap.String("Action", argoactions.UpdateAction)) - action = argoactions.NewUpdateAction(log, client, cfg.UpdateConfig) + action = argoactions.NewUpdateAction(log, &client, cfg.UpdateConfig) default: err := fmt.Errorf("Invalid action: %s", cfg.Action) diff --git a/cmd/cli/cmd/typeinstance/create.go b/cmd/cli/cmd/typeinstance/create.go index cfc69e1c0..f97eef567 100644 --- a/cmd/cli/cmd/typeinstance/create.go +++ b/cmd/cli/cmd/typeinstance/create.go @@ -13,6 +13,7 @@ import ( "capact.io/capact/internal/cli/heredoc" "capact.io/capact/internal/cli/printer" gqllocalapi "capact.io/capact/pkg/hub/api/graphql/local" + "capact.io/capact/pkg/sdk/validation" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -85,6 +86,12 @@ func NewCreate() *cobra.Command { func createTI(ctx context.Context, opts createOptions, resourcePrinter *printer.ResourcePrinter) error { typeInstanceToCreate := &gqllocalapi.CreateTypeInstancesInput{} + server := config.GetDefaultContext() + hubCli, err := client.NewHub(server) + if err != nil { + return err + } + for _, path := range opts.TypeInstancesFiles { out, err := loadCreateTypeInstanceFromFile(path) if err != nil { @@ -94,18 +101,20 @@ func createTI(ctx context.Context, opts createOptions, resourcePrinter *printer. typeInstanceToCreate = mergeCreateTypeInstances(typeInstanceToCreate, out) } + r := validation.ResultAggregator{} + err = r.Report(validation.ValidateTypeInstancesToCreate(ctx, hubCli, typeInstanceToCreate)) + if err != nil { + return errors.Wrap(err, "while validating TypeInstances") + } + if r.ErrorOrNil() != nil { + return r.ErrorOrNil() + } + // HACK: UsesRelations are required on GQL side so at least empty array needs to be send if typeInstanceToCreate.UsesRelations == nil { typeInstanceToCreate.UsesRelations = []*gqllocalapi.TypeInstanceUsesRelationInput{} } - server := config.GetDefaultContext() - - hubCli, err := client.NewHub(server) - if err != nil { - return err - } - createdTI, err := hubCli.CreateTypeInstances(ctx, typeInstanceToCreate) if err != nil { return err diff --git a/cmd/cli/cmd/typeinstance/edit.go b/cmd/cli/cmd/typeinstance/edit.go index 28d40efb1..e2d22aacf 100644 --- a/cmd/cli/cmd/typeinstance/edit.go +++ b/cmd/cli/cmd/typeinstance/edit.go @@ -9,10 +9,12 @@ import ( "capact.io/capact/internal/cli/client" "capact.io/capact/internal/cli/config" gqllocalapi "capact.io/capact/pkg/hub/api/graphql/local" + "capact.io/capact/pkg/sdk/validation" "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/fatih/color" + "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/yaml" ) @@ -58,6 +60,15 @@ func editTI(ctx context.Context, opts editOptions, w io.Writer) error { return err } + r := validation.ResultAggregator{} + err = r.Report(validation.ValidateTypeInstanceToUpdate(ctx, hubCli, typeInstanceToUpdate)) + if err != nil { + return errors.Wrap(err, "while validating TypeInstance") + } + if r.ErrorOrNil() != nil { + return r.ErrorOrNil() + } + _, err = hubCli.UpdateTypeInstances(ctx, typeInstanceToUpdate) if err != nil { return err diff --git a/cmd/k8s-engine/main.go b/cmd/k8s-engine/main.go index e0adc07c1..364c35e99 100644 --- a/cmd/k8s-engine/main.go +++ b/cmd/k8s-engine/main.go @@ -64,6 +64,9 @@ type Config struct { Logger logger.Config + LocalHubEndpoint string `envconfig:"default=http://capact-hub-local.capact-system/graphql"` + PublicHubEndpoint string `envconfig:"default=http://capact-hub-public.capact-system/graphql"` + GraphQLGateway struct { Endpoint string `envconfig:"default=http://capact-gateway/graphql"` Username string @@ -108,7 +111,7 @@ func main() { exitOnError(err, "while creating manager") hubClient := getHubClient(&cfg) - typeInstanceHandler := argo.NewTypeInstanceHandler(cfg.HubActionsImage) + typeInstanceHandler := argo.NewTypeInstanceHandler(cfg.HubActionsImage, cfg.LocalHubEndpoint, cfg.PublicHubEndpoint) interfaceIOValidator := actionvalidation.NewValidator(hubClient) policyIOValidator := policyvalidation.NewValidator(hubClient) wfValidator := renderer.NewWorkflowInputValidator(interfaceIOValidator, policyIOValidator) diff --git a/deploy/kubernetes/charts/capact/charts/engine/templates/deployment.yaml b/deploy/kubernetes/charts/capact/charts/engine/templates/deployment.yaml index bf642f528..a2080c910 100644 --- a/deploy/kubernetes/charts/capact/charts/engine/templates/deployment.yaml +++ b/deploy/kubernetes/charts/capact/charts/engine/templates/deployment.yaml @@ -48,6 +48,10 @@ spec: value: "{{ .Values.global.gateway.auth.username }}" - name: APP_GRAPHQLGATEWAY_PASSWORD value: "{{ .Values.global.gateway.auth.password }}" + - name: APP_LOCAL_HUB_ENDPOINT + value: "http://capact-hub-local.{{.Release.Namespace}}.svc.cluster.local/graphql" + - name: APP_PUBLIC_HUB_ENDPOINT + value: "http://capact-hub-public.{{.Release.Namespace}}.svc.cluster.local/graphql" - name: APP_BUILTIN_RUNNER_IMAGE value: "{{ .Values.global.containerRegistry.path }}/{{ .Values.builtInRunner.image.name }}:{{ .Values.global.containerRegistry.overrideTag | default .Chart.AppVersion }}" - name: APP_BUILTIN_RUNNER_TIMEOUT diff --git a/pkg/argo-actions/download_type_instances.go b/pkg/argo-actions/download_type_instances.go index 947e97844..7ef5c0c7b 100644 --- a/pkg/argo-actions/download_type_instances.go +++ b/pkg/argo-actions/download_type_instances.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "capact.io/capact/pkg/hub/client/local" + hubclient "capact.io/capact/pkg/hub/client" "capact.io/capact/pkg/runner" "github.com/pkg/errors" "go.uber.org/zap" @@ -25,11 +25,11 @@ type DownloadConfig struct { type Download struct { log *zap.Logger cfg []DownloadConfig - client *local.Client + client *hubclient.Client } // NewDownloadAction returns a new Download instance. -func NewDownloadAction(log *zap.Logger, client *local.Client, cfg []DownloadConfig) Action { +func NewDownloadAction(log *zap.Logger, client *hubclient.Client, cfg []DownloadConfig) Action { return &Download{ log: log, cfg: cfg, diff --git a/pkg/argo-actions/update_type_instances.go b/pkg/argo-actions/update_type_instances.go index faa30f87e..522796593 100644 --- a/pkg/argo-actions/update_type_instances.go +++ b/pkg/argo-actions/update_type_instances.go @@ -7,7 +7,8 @@ import ( "path/filepath" graphqllocal "capact.io/capact/pkg/hub/api/graphql/local" - "capact.io/capact/pkg/hub/client/local" + hubclient "capact.io/capact/pkg/hub/client" + "capact.io/capact/pkg/sdk/validation" "github.com/pkg/errors" "go.uber.org/zap" "sigs.k8s.io/yaml" @@ -26,12 +27,12 @@ type UpdateConfig struct { // It is used to update existing TypeInstances in the Local Hub. type Update struct { log *zap.Logger - client *local.Client + client *hubclient.Client cfg UpdateConfig } // NewUpdateAction returns a new Update instance. -func NewUpdateAction(log *zap.Logger, client *local.Client, cfg UpdateConfig) Action { +func NewUpdateAction(log *zap.Logger, client *hubclient.Client, cfg UpdateConfig) Action { return &Update{ log: log, client: client, @@ -83,6 +84,17 @@ func (u *Update) Do(ctx context.Context) error { return errors.Wrap(err, "while rendering UpdateTypeInstancesInput") } + u.log.Info("Validating TypeInstances") + + r := validation.ResultAggregator{} + err = r.Report(validation.ValidateTypeInstanceToUpdate(ctx, u.client, payload)) + if err != nil { + return errors.Wrap(err, "while validating TypeInstance") + } + if r.ErrorOrNil() != nil { + return r.ErrorOrNil() + } + u.log.Info("Updating TypeInstances in Hub...", zap.Int("TypeInstance count", len(payload))) uploadOutput, err := u.updateTypeInstances(ctx, payload) @@ -110,5 +122,5 @@ func (u *Update) render(payload []graphqllocal.UpdateTypeInstancesInput, values } func (u *Update) updateTypeInstances(ctx context.Context, in []graphqllocal.UpdateTypeInstancesInput) ([]graphqllocal.TypeInstance, error) { - return u.client.UpdateTypeInstances(ctx, in) + return u.client.Local.UpdateTypeInstances(ctx, in) } diff --git a/pkg/argo-actions/upload_type_instances.go b/pkg/argo-actions/upload_type_instances.go index 0812bf79c..a65c31fda 100644 --- a/pkg/argo-actions/upload_type_instances.go +++ b/pkg/argo-actions/upload_type_instances.go @@ -7,7 +7,9 @@ import ( "path/filepath" graphqllocal "capact.io/capact/pkg/hub/api/graphql/local" - "capact.io/capact/pkg/hub/client/local" + hubclient "capact.io/capact/pkg/hub/client" + "capact.io/capact/pkg/sdk/validation" + "github.com/pkg/errors" "go.uber.org/zap" "sigs.k8s.io/yaml" @@ -26,12 +28,12 @@ type UploadConfig struct { // It is used to upload TypeInstances to the Local Hub. type Upload struct { log *zap.Logger - client *local.Client + client *hubclient.Client cfg UploadConfig } // NewUploadAction returns a new Upload instance. -func NewUploadAction(log *zap.Logger, client *local.Client, cfg UploadConfig) Action { +func NewUploadAction(log *zap.Logger, client *hubclient.Client, cfg UploadConfig) Action { return &Upload{ log: log, client: client, @@ -83,6 +85,17 @@ func (u *Upload) Do(ctx context.Context) error { return errors.Wrap(err, "while rendering CreateTypeInstancesInput") } + u.log.Info("Validating TypeInstances") + + r := validation.ResultAggregator{} + err = r.Report(validation.ValidateTypeInstancesToCreate(ctx, u.client, payload)) + if err != nil { + return errors.Wrap(err, "while validating TypeInstances") + } + if r.ErrorOrNil() != nil { + return r.ErrorOrNil() + } + u.log.Info("Uploading TypeInstances to Hub...", zap.Int("TypeInstance count", len(payload.TypeInstances))) uploadOutput, err := u.uploadTypeInstances(ctx, payload) @@ -112,5 +125,5 @@ func (u *Upload) render(payload *graphqllocal.CreateTypeInstancesInput, values m } func (u *Upload) uploadTypeInstances(ctx context.Context, in *graphqllocal.CreateTypeInstancesInput) ([]graphqllocal.CreateTypeInstanceOutput, error) { - return u.client.CreateTypeInstances(ctx, in) + return u.client.Local.CreateTypeInstances(ctx, in) } diff --git a/pkg/hub/client/public/client.go b/pkg/hub/client/public/client.go index 8ea67ef55..6c98b5bdf 100644 --- a/pkg/hub/client/public/client.go +++ b/pkg/hub/client/public/client.go @@ -6,6 +6,7 @@ import ( "regexp" "strings" + "capact.io/capact/pkg/httputil" gqlpublicapi "capact.io/capact/pkg/hub/api/graphql/public" "github.com/avast/retry-go" @@ -25,6 +26,15 @@ func NewClient(cli *graphql.Client) *Client { return &Client{client: cli} } +// NewDefaultClient creates ready to use client with default values. +func NewDefaultClient(endpoint string, opts ...httputil.ClientOption) *Client { + httpClient := httputil.NewClient(opts...) + clientOpt := graphql.WithHTTPClient(httpClient) + client := graphql.NewClient(endpoint, clientOpt) + + return NewClient(client) +} + // FindInterfaceRevision returns the InterfaceRevision for the given InterfaceReference. // It will return nil, if the InterfaceRevision is not found. func (c *Client) FindInterfaceRevision(ctx context.Context, ref gqlpublicapi.InterfaceReference, opts ...InterfaceRevisionOption) (*gqlpublicapi.InterfaceRevision, error) { diff --git a/pkg/sdk/apis/0.0.1/types/types.extend.go b/pkg/sdk/apis/0.0.1/types/types.extend.go index 6d54cd2ce..3861f13b0 100644 --- a/pkg/sdk/apis/0.0.1/types/types.extend.go +++ b/pkg/sdk/apis/0.0.1/types/types.extend.go @@ -31,6 +31,10 @@ type ManifestRef struct { Revision string `json:"revision"` // Version of the manifest content in the SemVer format. } +func (in *ManifestRef) String() string { + return fmt.Sprintf("%s:%s", in.Path, in.Revision) +} + // ManifestRefWithOptRevision specifies type by path and optional revision. // +kubebuilder:object:generate=true type ManifestRefWithOptRevision struct { diff --git a/pkg/sdk/renderer/argo/dedicated_renderer_test.go b/pkg/sdk/renderer/argo/dedicated_renderer_test.go index 636a5fddb..0b0a95ace 100644 --- a/pkg/sdk/renderer/argo/dedicated_renderer_test.go +++ b/pkg/sdk/renderer/argo/dedicated_renderer_test.go @@ -16,7 +16,7 @@ func createFakeDedicatedRendererObject(t *testing.T) *dedicatedRenderer { require.NoError(t, err) genUUID := func() string { return "uuid" } - typeInstanceHandler := NewTypeInstanceHandler("alpine:3.7") + typeInstanceHandler := NewTypeInstanceHandler("alpine:3.7", "", "") typeInstanceHandler.SetGenUUID(genUUID) policyIOValidator := policyvalidation.NewValidator(fakeCli) diff --git a/pkg/sdk/renderer/argo/renderer_test.go b/pkg/sdk/renderer/argo/renderer_test.go index 3e34de08c..a2b9815d3 100644 --- a/pkg/sdk/renderer/argo/renderer_test.go +++ b/pkg/sdk/renderer/argo/renderer_test.go @@ -21,6 +21,12 @@ import ( "sigs.k8s.io/yaml" ) +const ( + hubActionsImage = "alpine:3.7" + localHubEndpoint = "http://capact-hub-local.capact-system/graphql" + publicHubEndpoint = "http://capact-hub-public.capact-system/graphql" +) + // TestRenderHappyPath tests that renderer generates valid Argo Workflows. // // This test is based on golden file. @@ -36,7 +42,7 @@ func TestRenderHappyPath(t *testing.T) { policy := policy.NewAllowAll() genUUID := func() string { return "uuid" } // it has to be static because of parallel testing - typeInstanceHandler := NewTypeInstanceHandler("alpine:3.7") + typeInstanceHandler := NewTypeInstanceHandler(hubActionsImage, localHubEndpoint, publicHubEndpoint) typeInstanceHandler.SetGenUUID(genUUID) ownerID := "default/action" @@ -293,7 +299,7 @@ func TestRenderHappyPathWithCustomPolicies(t *testing.T) { tt := test t.Run(tt.name, func(t *testing.T) { genUUID := genUUIDFn(strconv.Itoa(tc)) - typeInstanceHandler := NewTypeInstanceHandler("alpine:3.7") + typeInstanceHandler := NewTypeInstanceHandler(hubActionsImage, localHubEndpoint, publicHubEndpoint) typeInstanceHandler.SetGenUUID(genUUID) interfaceIOValidator := actionvalidation.NewValidator(fakeCli) @@ -340,7 +346,7 @@ func TestRendererMaxDepth(t *testing.T) { require.NoError(t, err) policy := policy.NewAllowAll() - typeInstanceHandler := NewTypeInstanceHandler("alpine:3.7") + typeInstanceHandler := NewTypeInstanceHandler(hubActionsImage, localHubEndpoint, publicHubEndpoint) typeInstanceHandler.SetGenUUID(genUUIDFn("")) interfaceIOValidator := actionvalidation.NewValidator(fakeCli) @@ -379,7 +385,7 @@ func TestRendererDenyAllPolicy(t *testing.T) { require.NoError(t, err) policy := policy.NewDenyAll() - typeInstanceHandler := NewTypeInstanceHandler("alpine:3.7") + typeInstanceHandler := NewTypeInstanceHandler(hubActionsImage, localHubEndpoint, publicHubEndpoint) typeInstanceHandler.SetGenUUID(genUUIDFn("")) interfaceIOValidator := actionvalidation.NewValidator(fakeCli) diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Mattermost_workflow_with_user_input.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Mattermost_workflow_with_user_input.golden.yaml index d4a2cabcd..0cf9d852a 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Mattermost_workflow_with_user_input.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Mattermost_workflow_with_user_input.golden.yaml @@ -1244,6 +1244,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Nested_PostgreSQL_change_password.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Nested_PostgreSQL_change_password.golden.yaml index 1690087bc..8ab9025c7 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Nested_PostgreSQL_change_password.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Nested_PostgreSQL_change_password.golden.yaml @@ -290,6 +290,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{6fc7dd6b-d150-4af3-a1aa-a868962b7d68,/firstRole.yaml},{f2421415-b8a4-464b-be12-b617794411c5,/postgresql.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -312,6 +316,10 @@ args: value: /update/payload - name: APP_UPDATE_CONFIG_TYPE_INSTANCES_DIR value: /update/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_change_password.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_change_password.golden.yaml index 6f458378f..672eaf07b 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_change_password.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_change_password.golden.yaml @@ -271,6 +271,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{6fc7dd6b-d150-4af3-a1aa-a868962b7d68,/role.yaml},{f2421415-b8a4-464b-be12-b617794411c5,/postgresql.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -293,6 +297,10 @@ args: value: /update/payload - name: APP_UPDATE_CONFIG_TYPE_INSTANCES_DIR value: /update/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_workflow_with_user_input_and_without_TypeInstances.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_workflow_with_user_input_and_without_TypeInstances.golden.yaml index 32777a5c2..4293f1ec2 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_workflow_with_user_input_and_without_TypeInstances.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/PostgreSQL_workflow_with_user_input_and_without_TypeInstances.golden.yaml @@ -266,6 +266,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Workflow_with_apps_stack_installation_with_user_input.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Workflow_with_apps_stack_installation_with_user_input.golden.yaml index 40446f4b1..7d910c323 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Workflow_with_apps_stack_installation_with_user_input.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPath/Workflow_with_apps_stack_installation_with_user_input.golden.yaml @@ -1224,6 +1224,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml index dae24c648..5fe0b9973 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml @@ -249,6 +249,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{c268d3f5-8834-434b-bea2-b677793611c5,/gcp-sa.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -297,6 +301,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_AWS_RDS_install.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_AWS_RDS_install.golden.yaml index 9a4352851..28b28d0e5 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_AWS_RDS_install.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_AWS_RDS_install.golden.yaml @@ -860,6 +860,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{517cf827-233c-4bf1-8fc9-48534424dd58,/aws-credentials.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -1576,6 +1580,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml index 2907f6dcc..548dde7c6 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_PostgreSQL_installation_with_GCP_SA_injected.golden.yaml @@ -583,6 +583,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{c268d3f5-8834-434b-bea2-b677793611c5,/gcp-sa.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -1275,6 +1279,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_using_Terraform.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_using_Terraform.golden.yaml index b97715d18..9394f066e 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_using_Terraform.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_CloudSQL_using_Terraform.golden.yaml @@ -644,6 +644,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{c268d3f5-8834-434b-bea2-b677793611c5,/gcp-sa.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -1348,6 +1352,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_existing_DB_installation.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_existing_DB_installation.golden.yaml index b8a07bdca..99131d9dd 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_existing_DB_installation.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Mattermost_with_existing_DB_installation.golden.yaml @@ -1063,6 +1063,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{f2421415-b8a4-464b-be12-b617794411c5,/postgresql.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -1109,6 +1113,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{f2421415-b8a4-464b-be12-b617794411c5,/postgresql.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -1128,6 +1136,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/RDS_installation_with_AWS_SA_and_additional_parameters_injected.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/RDS_installation_with_AWS_SA_and_additional_parameters_injected.golden.yaml index d63517542..ec3b9cc12 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/RDS_installation_with_AWS_SA_and_additional_parameters_injected.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/RDS_installation_with_AWS_SA_and_additional_parameters_injected.golden.yaml @@ -554,6 +554,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{517cf827-233c-4bf1-8fc9-48534424dd58,/aws-credentials.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -602,6 +606,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Unmet_policy_constraints_-_fallback_to_Bitnami_Implementation.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Unmet_policy_constraints_-_fallback_to_Bitnami_Implementation.golden.yaml index 21493476a..b828bbfdd 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Unmet_policy_constraints_-_fallback_to_Bitnami_Implementation.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Unmet_policy_constraints_-_fallback_to_Bitnami_Implementation.golden.yaml @@ -266,6 +266,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_ManifestRef.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_ManifestRef.golden.yaml index 075634e6a..5620b7a26 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_ManifestRef.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_ManifestRef.golden.yaml @@ -551,6 +551,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{517cf827-233c-4bf1-8fc9-48534424dd58,/aws-credentials.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -1002,6 +1006,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_alias.golden.yaml b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_alias.golden.yaml index 8e0730076..0c5913578 100644 --- a/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_alias.golden.yaml +++ b/pkg/sdk/renderer/argo/testdata/TestRenderHappyPathWithCustomPolicies/Workflow_policy_injects_additional_input_-_reference_by_alias.golden.yaml @@ -551,6 +551,10 @@ args: value: DownloadAction - name: APP_DOWNLOAD_CONFIG value: '{517cf827-233c-4bf1-8fc9-48534424dd58,/aws-credentials.yaml}' + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 name: "" resources: {} @@ -1002,6 +1006,10 @@ args: value: /upload/payload - name: APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR value: /upload/typeInstances + - name: APP_LOCAL_HUB_ENDPOINT + value: http://capact-hub-local.capact-system/graphql + - name: APP_PUBLIC_HUB_ENDPOINT + value: http://capact-hub-public.capact-system/graphql image: alpine:3.7 imagePullPolicy: IfNotPresent name: "" diff --git a/pkg/sdk/renderer/argo/typeinstance_handler.go b/pkg/sdk/renderer/argo/typeinstance_handler.go index f7627408a..869ac4173 100644 --- a/pkg/sdk/renderer/argo/typeinstance_handler.go +++ b/pkg/sdk/renderer/argo/typeinstance_handler.go @@ -18,14 +18,18 @@ import ( // TypeInstanceHandler provides functionality to handle TypeInstance operations such as // injecting download step and upload step. type TypeInstanceHandler struct { - hubActionsImage string - genUUID func() string + hubActionsImage string + localHubEndpoint string + publicHubEndpoint string + genUUID func() string } // NewTypeInstanceHandler returns a new TypeInstanceHandler instance. -func NewTypeInstanceHandler(hubActionsImage string) *TypeInstanceHandler { +func NewTypeInstanceHandler(hubActionsImage string, localHubEndpoint string, publicHubEndpoint string) *TypeInstanceHandler { return &TypeInstanceHandler{ - hubActionsImage: hubActionsImage, + hubActionsImage: hubActionsImage, + localHubEndpoint: localHubEndpoint, + publicHubEndpoint: publicHubEndpoint, genUUID: func() string { return uuid.New().String() }, @@ -71,6 +75,14 @@ func (r *TypeInstanceHandler) AddInputTypeInstances(rootWorkflow *Workflow, inst Name: "APP_DOWNLOAD_CONFIG", Value: strings.Join(typeInstanceToDownload, ","), }, + { + Name: "APP_LOCAL_HUB_ENDPOINT", + Value: r.localHubEndpoint, + }, + { + Name: "APP_PUBLIC_HUB_ENDPOINT", + Value: r.publicHubEndpoint, + }, }, }, Outputs: wfv1.Outputs{ @@ -205,6 +217,14 @@ func (r *TypeInstanceHandler) AddUploadTypeInstancesStep(rootWorkflow *Workflow, Name: "APP_UPLOAD_CONFIG_TYPE_INSTANCES_DIR", Value: "/upload/typeInstances", }, + { + Name: "APP_LOCAL_HUB_ENDPOINT", + Value: r.localHubEndpoint, + }, + { + Name: "APP_PUBLIC_HUB_ENDPOINT", + Value: r.publicHubEndpoint, + }, }, }, Inputs: wfv1.Inputs{ @@ -296,6 +316,14 @@ func (r *TypeInstanceHandler) AddUpdateTypeInstancesStep(rootWorkflow *Workflow, Name: "APP_UPDATE_CONFIG_TYPE_INSTANCES_DIR", Value: "/update/typeInstances", }, + { + Name: "APP_LOCAL_HUB_ENDPOINT", + Value: r.localHubEndpoint, + }, + { + Name: "APP_PUBLIC_HUB_ENDPOINT", + Value: r.publicHubEndpoint, + }, }, }, Inputs: wfv1.Inputs{ diff --git a/pkg/sdk/validation/issue_builder.go b/pkg/sdk/validation/issue_builder.go index 85ff7ef1a..f42fc19b6 100644 --- a/pkg/sdk/validation/issue_builder.go +++ b/pkg/sdk/validation/issue_builder.go @@ -39,8 +39,8 @@ func (bldr *IssueBuilder) ReportIssue(field, format string, args ...interface{}) return bldr } -// Result returns validation result index by field name. -func (bldr *IssueBuilder) Result() Result { +// ResultWithCustomErrorFormat returns validation result index by field name with custom error format. +func (bldr *IssueBuilder) ResultWithCustomErrorFormat(errorFormatFn func(bldr *IssueBuilder, issueField string) multierr.ErrorFormatFunc) Result { if bldr == nil { return nil } @@ -49,15 +49,22 @@ func (bldr *IssueBuilder) Result() Result { if issues == nil { continue } - issues.ErrorFormat = headeredErrListFormatFunc(fmt.Sprintf("- %s %q", bldr.header, field)) + issues.ErrorFormat = errorFormatFn(bldr, field) } return bldr.issues } -// headeredErrListFormatFunc is a basic formatter that outputs the errors as +// Result returns validation result index by field name. +func (bldr *IssueBuilder) Result() Result { + return bldr.ResultWithCustomErrorFormat(func(bldr *IssueBuilder, issueField string) multierr.ErrorFormatFunc { + return HeaderedErrListFormatFunc(fmt.Sprintf("- %s %q", bldr.header, issueField)) + }) +} + +// HeaderedErrListFormatFunc is a basic formatter that outputs the errors as // a bullet point list with a given header. -func headeredErrListFormatFunc(fieldName string) multierr.ErrorFormatFunc { +func HeaderedErrListFormatFunc(fieldName string) multierr.ErrorFormatFunc { return func(es []error) string { points := make([]string, len(es)) for i, err := range es { diff --git a/pkg/sdk/validation/type_refs.go b/pkg/sdk/validation/type_refs.go index 97507cc18..e4c36cc27 100644 --- a/pkg/sdk/validation/type_refs.go +++ b/pkg/sdk/validation/type_refs.go @@ -35,7 +35,7 @@ func ResolveTypeRefsToJSONSchemas(ctx context.Context, hubCli HubClient, inTypeR PathPattern: ptr.String(typeRefsPathFilter), })) if err != nil { - return nil, errors.Wrap(err, "while fetching JSONSchemas for input TypeRefs") + return nil, errors.Wrap(err, "while fetching JSONSchemas for TypeRefs") } indexedTypes := map[string]interface{}{} diff --git a/pkg/sdk/validation/type_refs_test.go b/pkg/sdk/validation/type_refs_test.go index a8a47f670..a8649be11 100644 --- a/pkg/sdk/validation/type_refs_test.go +++ b/pkg/sdk/validation/type_refs_test.go @@ -76,7 +76,7 @@ func TestResolveTypeRefsToJSONSchemasFailures(t *testing.T) { givenTypeRefs: validation.TypeRefCollection{ "aws-creds": {}, }, - expectedErrorMsg: "while fetching JSONSchemas for input TypeRefs: hub error for testing purposes", + expectedErrorMsg: "while fetching JSONSchemas for TypeRefs: hub error for testing purposes", }, } for tn, tc := range tests { diff --git a/pkg/sdk/validation/typeinstance.go b/pkg/sdk/validation/typeinstance.go new file mode 100644 index 000000000..064563b03 --- /dev/null +++ b/pkg/sdk/validation/typeinstance.go @@ -0,0 +1,155 @@ +package validation + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "capact.io/capact/internal/ptr" + graphqllocal "capact.io/capact/pkg/hub/api/graphql/local" + "capact.io/capact/pkg/sdk/apis/0.0.1/types" + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" + "github.com/xeipuuv/gojsonschema" +) + +// TypeInstanceEssentialData contains essential TypeInstance Data for validation purpose. +type TypeInstanceEssentialData struct { + Value interface{} + TypeRef types.ManifestRef + Alias *string + ID *string +} + +func (ti *TypeInstanceEssentialData) String() string { + emptyMetadataMsg := "empty metadata" + if ti == nil || (ti.ID == nil && ti.Alias == nil) { + return emptyMetadataMsg + } + var tiMetadata []string + if ti.ID != nil { + tiMetadata = append(tiMetadata, fmt.Sprintf("ID: %q", *ti.ID)) + } + if ti.Alias != nil { + tiMetadata = append(tiMetadata, fmt.Sprintf("Alias: %q", *ti.Alias)) + } + return strings.Join(tiMetadata, ", ") +} + +// TypeInstanceValidationHubClient defines Hub methods needed for validation of TypeInstances. +type TypeInstanceValidationHubClient interface { + HubClient + FindTypeInstancesTypeRef(ctx context.Context, ids []string) (map[string]graphqllocal.TypeInstanceTypeReference, error) +} + +// ValidateTypeInstancesToCreate is responsible for validating TypeInstance which do not exist and will be created. +func ValidateTypeInstancesToCreate(ctx context.Context, client TypeInstanceValidationHubClient, typeInstance *graphqllocal.CreateTypeInstancesInput) (Result, error) { + var typeInstanceCollection []*TypeInstanceEssentialData + typeRefCollection := TypeRefCollection{} + + for _, ti := range typeInstance.TypeInstances { + if ti == nil || ti.TypeRef == nil { + continue + } + manifestRef := types.ManifestRef{ + Path: ti.TypeRef.Path, + Revision: ti.TypeRef.Revision, + } + typeRefCollection[manifestRef.String()] = TypeRef{ + TypeRef: types.TypeRef(manifestRef), + } + typeInstanceCollection = append(typeInstanceCollection, &TypeInstanceEssentialData{ + Alias: ti.Alias, + Value: ti.Value, + TypeRef: manifestRef, + }) + } + + schemasCollection, err := ResolveTypeRefsToJSONSchemas(ctx, client, typeRefCollection) + if err != nil { + return nil, errors.Wrapf(err, "while resolving TypeRefs to JSON Schemas") + } + return ValidateTypeInstances(schemasCollection, typeInstanceCollection) +} + +// ValidateTypeInstanceToUpdate is responsible for validating TypeInstance which exists and will be updated. +func ValidateTypeInstanceToUpdate(ctx context.Context, client TypeInstanceValidationHubClient, typeInstanceToUpdate []graphqllocal.UpdateTypeInstancesInput) (Result, error) { + var typeInstanceIds []string + for _, ti := range typeInstanceToUpdate { + typeInstanceIds = append(typeInstanceIds, ti.ID) + } + + typeInstancesTypeRef, err := client.FindTypeInstancesTypeRef(ctx, typeInstanceIds) + if err != nil { + return nil, errors.Wrapf(err, "while finding TypeInstance Type reference") + } + + typeRefCollection := TypeRefCollection{} + for _, typeReference := range typeInstancesTypeRef { + manifestRef := types.ManifestRef{ + Path: typeReference.Path, + Revision: typeReference.Revision, + } + typeRefCollection[manifestRef.String()] = TypeRef{ + TypeRef: types.TypeRef(manifestRef), + } + } + + var typeInstanceCollection []*TypeInstanceEssentialData + for _, ti := range typeInstanceToUpdate { + if ti.TypeInstance == nil { + continue + } + typeRef, ok := typeInstancesTypeRef[ti.ID] + if !ok { + return nil, errors.Wrapf(err, "while finding TypeInstance Type reference for ID %q", ti.ID) + } + typeInstanceCollection = append(typeInstanceCollection, &TypeInstanceEssentialData{ + ID: ptr.String(ti.ID), + Value: ti.TypeInstance.Value, + TypeRef: types.ManifestRef(typeRef), + }) + } + + schemasCollection, err := ResolveTypeRefsToJSONSchemas(ctx, client, typeRefCollection) + if err != nil { + return nil, errors.Wrapf(err, "while resolving TypeRefs to JSON Schemas") + } + + return ValidateTypeInstances(schemasCollection, typeInstanceCollection) +} + +//ValidateTypeInstances is responsible for validating TypeInstance. +func ValidateTypeInstances(schemaCollection SchemaCollection, typeInstanceCollection []*TypeInstanceEssentialData) (Result, error) { + resultBldr := NewResultBuilder("TypeInstance with") + + for _, ti := range typeInstanceCollection { + if _, ok := ti.Value.(map[string]interface{}); !ok { + return Result{}, errors.New("could not create map from TypeInstance Value") + } + valuesJSON, err := json.Marshal(ti.Value) + if err != nil { + return Result{}, errors.Wrap(err, "while converting TypeInstance value to JSON bytes") + } + if _, ok := schemaCollection[ti.TypeRef.String()]; !ok { + return Result{}, fmt.Errorf("could not find Schema for type %q", ti.TypeRef.String()) + } + + schemaLoader := gojsonschema.NewStringLoader(schemaCollection[ti.TypeRef.String()].Value) + dataLoader := gojsonschema.NewBytesLoader(valuesJSON) + + result, err := gojsonschema.Validate(schemaLoader, dataLoader) + if err != nil { + return nil, errors.Wrap(err, "while validating JSON schema for TypeInstance") + } + + for _, err := range result.Errors() { + resultBldr.ReportIssue(ti.String(), err.String()) + } + } + + return resultBldr.ResultWithCustomErrorFormat(func(bldr *IssueBuilder, issueField string) multierror.ErrorFormatFunc { + return HeaderedErrListFormatFunc(fmt.Sprintf("- %s %s", bldr.header, issueField)) + }), nil +} diff --git a/pkg/sdk/validation/typeinstance_test.go b/pkg/sdk/validation/typeinstance_test.go new file mode 100644 index 000000000..408460a18 --- /dev/null +++ b/pkg/sdk/validation/typeinstance_test.go @@ -0,0 +1,125 @@ +package validation + +import ( + "errors" + "fmt" + "testing" + + "capact.io/capact/internal/ptr" + "capact.io/capact/pkg/sdk/apis/0.0.1/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidateTypeInstances(t *testing.T) { + tests := map[string]struct { + schemaCollection SchemaCollection + typeInstanceCollection []*TypeInstanceEssentialData + expError error + }{ + "When TypeInstance values do not contain the required property": { + schemaCollection: SchemaCollection{ + "cap.type.aws.auth.creds:0.1.0": { + Value: fmt.Sprintf("%v", AWSCredsTypeRevFixture().Revisions[0].Spec.JSONSchema), + Required: false, + }, + }, + typeInstanceCollection: []*TypeInstanceEssentialData{ + { + TypeRef: types.ManifestRef{ + Path: "cap.type.aws.auth.creds", + Revision: "0.1.0", + }, + Value: map[string]interface{}{ + "test1": "test", + }, + Alias: ptr.String("aws-creds"), + }, + }, + expError: errors.New("- TypeInstance with Alias: \"aws-creds\":\n * (root): key is required"), + }, + "When TypeInstance value does not meet Type property constraints": { + schemaCollection: SchemaCollection{ + "cap.type.aws.elasticsearch.install-input:0.1.0": { + Value: fmt.Sprintf("%v", AWSElasticsearchTypeRevFixture().Revisions[0].Spec.JSONSchema), + Required: false, + }, + }, + typeInstanceCollection: []*TypeInstanceEssentialData{ + { + TypeRef: types.ManifestRef{ + Path: "cap.type.aws.elasticsearch.install-input", + Revision: "0.1.0", + }, + Value: map[string]interface{}{ + "replicas": 5, + }, + ID: ptr.String("5605af48-c34f-4bdc-b2d8-53c679bdfa5a"), + }, + }, + expError: errors.New("- TypeInstance with ID: \"5605af48-c34f-4bdc-b2d8-53c679bdfa5a\":\n * replicas: Invalid type. Expected: string, given: integer"), + }, + "When TypeInstance contains the required property": { + schemaCollection: SchemaCollection{ + "cap.type.aws.auth.creds:0.1.0": { + Value: fmt.Sprintf("%v", AWSCredsTypeRevFixture().Revisions[0].Spec.JSONSchema), + Required: false, + }, + }, + typeInstanceCollection: []*TypeInstanceEssentialData{ + { + TypeRef: types.ManifestRef{ + Path: "cap.type.aws.auth.creds", + Revision: "0.1.0", + }, + Value: map[string]interface{}{ + "key": "aaa", + }, + }, + }, + expError: nil, + }, + "When there is a collection of TypeInstance with an incorrect value": { + schemaCollection: SchemaCollection{ + "cap.type.aws.auth.creds:0.1.0": { + Value: fmt.Sprintf("%v", AWSCredsTypeRevFixture().Revisions[0].Spec.JSONSchema), + Required: false, + }, + }, + typeInstanceCollection: []*TypeInstanceEssentialData{ + { + TypeRef: types.ManifestRef{ + Path: "cap.type.aws.auth.creds", + Revision: "0.1.0", + }, + Value: map[string]interface{}{ + "test1": "test", + }, + Alias: ptr.String("aws-creds"), + }, + { + TypeRef: types.ManifestRef{ + Path: "cap.type.aws.auth.creds", + Revision: "0.1.0", + }, + Value: map[string]interface{}{ + "test2": "test", + }, + Alias: ptr.String("aws-creds-2"), + }, + }, + expError: errors.New("- TypeInstance with Alias: \"aws-creds\":\n * (root): key is required\n- TypeInstance with Alias: \"aws-creds-2\":\n * (root): key is required"), + }, + } + + for tn, tc := range tests { + t.Run(tn, func(t *testing.T) { + // when + validationResults, err := ValidateTypeInstances(tc.schemaCollection, tc.typeInstanceCollection) + + // then + require.NoError(t, err) + assert.Equal(t, tc.expError, validationResults.ErrorOrNil()) + }) + } +} diff --git a/test/e2e/hub_test.go b/test/e2e/hub_test.go index 9b36348ed..746c7475c 100644 --- a/test/e2e/hub_test.go +++ b/test/e2e/hub_test.go @@ -251,7 +251,7 @@ var _ = Describe("GraphQL API", func() { // create TypeInstance createdTypeInstance, err := cli.CreateTypeInstance(ctx, &gqllocalapi.CreateTypeInstanceInput{ TypeRef: &gqllocalapi.TypeInstanceTypeReferenceInput{ - Path: "cap.type.capactio.capact.ti", + Path: "cap.type.capactio.capact.validation.single-key", Revision: "0.1.0", }, Attributes: []*gqllocalapi.AttributeReferenceInput{ @@ -261,7 +261,7 @@ var _ = Describe("GraphQL API", func() { }, }, Value: map[string]interface{}{ - "foo": "bar", + "key": "bar", }, }) Expect(err).ToNot(HaveOccurred()) @@ -282,14 +282,14 @@ var _ = Describe("GraphQL API", func() { }, Spec: &gqllocalapi.TypeInstanceResourceVersionSpec{ Value: map[string]interface{}{ - "foo": "bar", + "key": "bar", }, }, } Expect(typeInstance).To(Equal(&gqllocalapi.TypeInstance{ ID: createdTypeInstance.ID, TypeRef: &gqllocalapi.TypeInstanceTypeReference{ - Path: "cap.type.capactio.capact.ti", + Path: "cap.type.capactio.capact.validation.single-key", Revision: "0.1.0", }, Backend: &gqllocalapi.TypeInstanceBackendReference{ @@ -645,7 +645,7 @@ func includes(ids []string, expID string) bool { func typeInstance(ver string) *gqllocalapi.CreateTypeInstanceInput { return &gqllocalapi.CreateTypeInstanceInput{ TypeRef: &gqllocalapi.TypeInstanceTypeReferenceInput{ - Path: "cap.type.sample-v" + ver, + Path: "cap.type.capactio.capact.validation.single-key", Revision: "0.1.0", }, Attributes: []*gqllocalapi.AttributeReferenceInput{ @@ -655,7 +655,7 @@ func typeInstance(ver string) *gqllocalapi.CreateTypeInstanceInput { }, }, Value: map[string]interface{}{ - "sample-v" + ver: true, + "key": "sample-v" + ver, }, } }