diff --git a/internal/k8s-engine/graphql/domain/action/fixtures_test.go b/internal/k8s-engine/graphql/domain/action/fixtures_test.go index 818eab78c..01192c3f3 100644 --- a/internal/k8s-engine/graphql/domain/action/fixtures_test.go +++ b/internal/k8s-engine/graphql/domain/action/fixtures_test.go @@ -480,7 +480,7 @@ func fixModelInputSecret(name string, paramsEnabled, policyEnabled bool) *corev1 sec.StringData["parameter-input-parameters"] = `{"param":"one"}` } if policyEnabled { - sec.StringData["action-policy.json"] = `{"interface":{"rules":[{"interface":{"path":"cap.interface.dummy","revision":null},"oneOf":[{"implementationConstraints":{"requires":null,"attributes":null,"path":"cap.implementation.dummy"},"inject":{"requiredTypeInstances":[{"id":"policy-ti-id","description":"Sample description"}],"additionalParameters":[{"name":"additional-parameters","value":{"snapshot":true}}],"additionalTypeInstances":[{"name":"additional-ti","id":"additional-ti-id"}]}}]}]},"typeInstance":{"rules":[{"typeRef":{"path":"cap.type.aws.auth.credentials","revision":"0.1.0"},"backend":{"id":"00fd161c-01bd-47a6-9872-47490e11f996","description":"Vault TI"}},{"typeRef":{"path":"cap.type.aws.*","revision":null},"backend":{"id":"31bb8355-10d7-49ce-a739-4554d8a40b63","description":null}},{"typeRef":{"path":"cap.*","revision":null},"backend":{"id":"a36ed738-dfe7-45ec-acd1-8e44e8db893b","description":"Default Capact PostgreSQL backend"}}]}}` + sec.StringData["action-policy.json"] = `{"interface":{"default":null,"rules":[{"interface":{"path":"cap.interface.dummy","revision":null},"oneOf":[{"implementationConstraints":{"requires":null,"attributes":null,"path":"cap.implementation.dummy"},"inject":{"requiredTypeInstances":[{"id":"policy-ti-id","description":"Sample description"}],"additionalParameters":[{"name":"additional-parameters","value":{"snapshot":true}}],"additionalTypeInstances":[{"name":"additional-ti","id":"additional-ti-id"}]}}]}]},"typeInstance":{"rules":[{"typeRef":{"path":"cap.type.aws.auth.credentials","revision":"0.1.0"},"backend":{"id":"00fd161c-01bd-47a6-9872-47490e11f996","description":"Vault TI"}},{"typeRef":{"path":"cap.type.aws.*","revision":null},"backend":{"id":"31bb8355-10d7-49ce-a739-4554d8a40b63","description":null}},{"typeRef":{"path":"cap.*","revision":null},"backend":{"id":"a36ed738-dfe7-45ec-acd1-8e44e8db893b","description":"Default Capact PostgreSQL backend"}}]}}` } return sec diff --git a/internal/k8s-engine/graphql/domain/policy/converter.go b/internal/k8s-engine/graphql/domain/policy/converter.go index 3bc3c4566..1b1548e01 100644 --- a/internal/k8s-engine/graphql/domain/policy/converter.go +++ b/internal/k8s-engine/graphql/domain/policy/converter.go @@ -49,7 +49,19 @@ func (c *Converter) interfaceFromGraphQLInput(in *graphql.InterfacePolicyInput) }) } - return policy.InterfacePolicy{Rules: rules}, nil + var interfaceDefaults *policy.InterfaceDefault + if in.Default != nil && in.Default.Inject != nil { + interfaceDefaults = &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: c.requiredTypeInstancesToInjectFromGraphQLInput(in.Default.Inject.RequiredTypeInstances), + }, + } + } + + return policy.InterfacePolicy{ + Default: interfaceDefaults, + Rules: rules, + }, nil } func (c *Converter) typeInstanceFromGraphQLInput(in *graphql.TypeInstancePolicyInput) policy.TypeInstancePolicy { @@ -117,8 +129,18 @@ func (c *Converter) interfaceToGraphQL(in policy.InterfacePolicy) *graphql.Inter }) } + var defaultForInterface *graphql.DefaultForInterface + if in.DefaultRequiredTypeInstancesToInject() != nil { + defaultForInterface = &graphql.DefaultForInterface{ + Inject: &graphql.DefaultInjectForInterface{ + RequiredTypeInstances: c.requiredTypeInstancesToInjectToGraphQL(in.Default.Inject.RequiredTypeInstances), + }, + } + } + return &graphql.InterfacePolicy{ - Rules: gqlRules, + Default: defaultForInterface, + Rules: gqlRules, } } diff --git a/internal/k8s-engine/graphql/domain/policy/fixtures_test.go b/internal/k8s-engine/graphql/domain/policy/fixtures_test.go index 86de2dbb3..71d57e0eb 100644 --- a/internal/k8s-engine/graphql/domain/policy/fixtures_test.go +++ b/internal/k8s-engine/graphql/domain/policy/fixtures_test.go @@ -10,6 +10,16 @@ import ( func fixGQLInput() graphql.PolicyInput { return graphql.PolicyInput{ Interface: &graphql.InterfacePolicyInput{ + Default: &graphql.DefaultForInterfaceInput{ + Inject: &graphql.DefaultInjectForInterfaceInput{ + RequiredTypeInstances: []*graphql.RequiredTypeInstanceReferenceInput{ + { + ID: "28806e5a-3b13-4d58-915b-8357a51c3e95", + Description: ptr.String("Sample description"), + }, + }, + }, + }, Rules: []*graphql.RulesForInterfaceInput{ { Interface: &graphql.ManifestReferenceInput{ @@ -110,6 +120,16 @@ func fixGQLInput() graphql.PolicyInput { func fixGQL() graphql.Policy { return graphql.Policy{ Interface: &graphql.InterfacePolicy{ + Default: &graphql.DefaultForInterface{ + Inject: &graphql.DefaultInjectForInterface{ + RequiredTypeInstances: []*graphql.RequiredTypeInstanceReference{ + { + ID: "28806e5a-3b13-4d58-915b-8357a51c3e95", + Description: ptr.String("Sample description"), + }, + }, + }, + }, Rules: []*graphql.RulesForInterface{ { Interface: &graphql.ManifestReferenceWithOptionalRevision{ @@ -210,6 +230,18 @@ func fixGQL() graphql.Policy { func fixModel() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "28806e5a-3b13-4d58-915b-8357a51c3e95", + Description: ptr.String("Sample description"), + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ diff --git a/pkg/engine/api/graphql/config.yaml b/pkg/engine/api/graphql/config.yaml index de2675ea0..3c4cd5211 100644 --- a/pkg/engine/api/graphql/config.yaml +++ b/pkg/engine/api/graphql/config.yaml @@ -32,4 +32,6 @@ models: model: "capact.io/capact/pkg/engine/api/graphql.RequiredTypeInstanceReference" AdditionalTypeInstanceReference: model: "capact.io/capact/pkg/engine/api/graphql.AdditionalTypeInstanceReference" + InterfacePolicy: + model: "capact.io/capact/pkg/engine/api/graphql.InterfacePolicy" diff --git a/pkg/engine/api/graphql/examples.graphql b/pkg/engine/api/graphql/examples.graphql index e693924f3..868c985f1 100644 --- a/pkg/engine/api/graphql/examples.graphql +++ b/pkg/engine/api/graphql/examples.graphql @@ -166,6 +166,16 @@ mutation UpdatePolicy { updatePolicy( in: { interface: { + default: { + inject: { + requiredTypeInstances: [ + { + id: "28806e5a-3b13-4d58-915b-8357a51c3e95" + description: "My TypeInstance" + } + ] + } + } rules: [ { interface: { @@ -294,6 +304,14 @@ fragment ActionFields on Action { fragment PolicyFields on Policy { interface { + default { + inject { + requiredTypeInstances { + id + description + } + } + } rules { interface { path diff --git a/pkg/engine/api/graphql/models_gen.go b/pkg/engine/api/graphql/models_gen.go index b4457fe95..79cc93a8d 100644 --- a/pkg/engine/api/graphql/models_gen.go +++ b/pkg/engine/api/graphql/models_gen.go @@ -119,6 +119,22 @@ type AdvancedModeContinueRenderingInput struct { TypeInstances []*InputTypeInstanceData `json:"typeInstances"` } +type DefaultForInterface struct { + Inject *DefaultInjectForInterface `json:"inject"` +} + +type DefaultForInterfaceInput struct { + Inject *DefaultInjectForInterfaceInput `json:"inject"` +} + +type DefaultInjectForInterface struct { + RequiredTypeInstances []*RequiredTypeInstanceReference `json:"requiredTypeInstances"` +} + +type DefaultInjectForInterfaceInput struct { + RequiredTypeInstances []*RequiredTypeInstanceReferenceInput `json:"requiredTypeInstances"` +} + // Client input for Input TypeInstance type InputTypeInstanceData struct { Name string `json:"name"` @@ -137,12 +153,9 @@ type InputTypeInstanceToProvide struct { TypeRef *ManifestReference `json:"typeRef"` } -type InterfacePolicy struct { - Rules []*RulesForInterface `json:"rules"` -} - type InterfacePolicyInput struct { - Rules []*RulesForInterfaceInput `json:"rules"` + Default *DefaultForInterfaceInput `json:"default"` + Rules []*RulesForInterfaceInput `json:"rules"` } type ManifestReference struct { diff --git a/pkg/engine/api/graphql/policy.go b/pkg/engine/api/graphql/policy.go index 4b4ac2461..45a58d3d1 100644 --- a/pkg/engine/api/graphql/policy.go +++ b/pkg/engine/api/graphql/policy.go @@ -2,6 +2,12 @@ package graphql // The types had to be moved out from generated models to add `omitempty` tags. +//InterfacePolicy represents Interface Policy. +type InterfacePolicy struct { + Default *DefaultForInterface `json:"default,omitempty"` + Rules []*RulesForInterface `json:"rules"` +} + // PolicyRule represents a single policy rule. type PolicyRule struct { ImplementationConstraints *PolicyRuleImplementationConstraints `json:"implementationConstraints,omitempty"` diff --git a/pkg/engine/api/graphql/schema.graphql b/pkg/engine/api/graphql/schema.graphql index d8c9c0330..8175ef999 100644 --- a/pkg/engine/api/graphql/schema.graphql +++ b/pkg/engine/api/graphql/schema.graphql @@ -330,9 +330,18 @@ input TypeInstanceBackendRuleInput { # Interface Policy Input input InterfacePolicyInput { + default: DefaultForInterfaceInput rules: [RulesForInterfaceInput!]! } +input DefaultForInterfaceInput { + inject: DefaultInjectForInterfaceInput +} + +input DefaultInjectForInterfaceInput{ + requiredTypeInstances: [RequiredTypeInstanceReferenceInput!] +} + input RulesForInterfaceInput { interface: ManifestReferenceInput! oneOf: [PolicyRuleInput!]! @@ -393,9 +402,18 @@ type TypeInstanceBackendRule { # Interface Policy type InterfacePolicy { + default: DefaultForInterface rules: [RulesForInterface!]! } +type DefaultForInterface { + inject: DefaultInjectForInterface +} + +type DefaultInjectForInterface { + requiredTypeInstances: [RequiredTypeInstanceReference!] +} + type RulesForInterface { interface: ManifestReferenceWithOptionalRevision! oneOf: [PolicyRule!]! diff --git a/pkg/engine/api/graphql/schema_gen.go b/pkg/engine/api/graphql/schema_gen.go index 9b46e58ac..f8615077d 100644 --- a/pkg/engine/api/graphql/schema_gen.go +++ b/pkg/engine/api/graphql/schema_gen.go @@ -92,6 +92,14 @@ type ComplexityRoot struct { Name func(childComplexity int) int } + DefaultForInterface struct { + Inject func(childComplexity int) int + } + + DefaultInjectForInterface struct { + RequiredTypeInstances func(childComplexity int) int + } + InputTypeInstanceDetails struct { ID func(childComplexity int) int Name func(childComplexity int) int @@ -103,7 +111,8 @@ type ComplexityRoot struct { } InterfacePolicy struct { - Rules func(childComplexity int) int + Default func(childComplexity int) int + Rules func(childComplexity int) int } ManifestReference struct { @@ -433,6 +442,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AdditionalTypeInstanceReference.Name(childComplexity), true + case "DefaultForInterface.inject": + if e.complexity.DefaultForInterface.Inject == nil { + break + } + + return e.complexity.DefaultForInterface.Inject(childComplexity), true + + case "DefaultInjectForInterface.requiredTypeInstances": + if e.complexity.DefaultInjectForInterface.RequiredTypeInstances == nil { + break + } + + return e.complexity.DefaultInjectForInterface.RequiredTypeInstances(childComplexity), true + case "InputTypeInstanceDetails.id": if e.complexity.InputTypeInstanceDetails.ID == nil { break @@ -461,6 +484,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.InputTypeInstanceToProvide.TypeRef(childComplexity), true + case "InterfacePolicy.default": + if e.complexity.InterfacePolicy.Default == nil { + break + } + + return e.complexity.InterfacePolicy.Default(childComplexity), true + case "InterfacePolicy.rules": if e.complexity.InterfacePolicy.Rules == nil { break @@ -1203,9 +1233,18 @@ input TypeInstanceBackendRuleInput { # Interface Policy Input input InterfacePolicyInput { + default: DefaultForInterfaceInput rules: [RulesForInterfaceInput!]! } +input DefaultForInterfaceInput { + inject: DefaultInjectForInterfaceInput +} + +input DefaultInjectForInterfaceInput{ + requiredTypeInstances: [RequiredTypeInstanceReferenceInput!] +} + input RulesForInterfaceInput { interface: ManifestReferenceInput! oneOf: [PolicyRuleInput!]! @@ -1266,9 +1305,18 @@ type TypeInstanceBackendRule { # Interface Policy type InterfacePolicy { + default: DefaultForInterface rules: [RulesForInterface!]! } +type DefaultForInterface { + inject: DefaultInjectForInterface +} + +type DefaultInjectForInterface { + requiredTypeInstances: [RequiredTypeInstanceReference!] +} + type RulesForInterface { interface: ManifestReferenceWithOptionalRevision! oneOf: [PolicyRule!]! @@ -2526,6 +2574,70 @@ func (ec *executionContext) _AdditionalTypeInstanceReference_id(ctx context.Cont return ec.marshalNID2string(ctx, field.Selections, res) } +func (ec *executionContext) _DefaultForInterface_inject(ctx context.Context, field graphql.CollectedField, obj *DefaultForInterface) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DefaultForInterface", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Inject, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*DefaultInjectForInterface) + fc.Result = res + return ec.marshalODefaultInjectForInterface2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultInjectForInterface(ctx, field.Selections, res) +} + +func (ec *executionContext) _DefaultInjectForInterface_requiredTypeInstances(ctx context.Context, field graphql.CollectedField, obj *DefaultInjectForInterface) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DefaultInjectForInterface", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.RequiredTypeInstances, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*RequiredTypeInstanceReference) + fc.Result = res + return ec.marshalORequiredTypeInstanceReference2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRequiredTypeInstanceReferenceᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _InputTypeInstanceDetails_id(ctx context.Context, field graphql.CollectedField, obj *InputTypeInstanceDetails) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2666,6 +2778,38 @@ func (ec *executionContext) _InputTypeInstanceToProvide_typeRef(ctx context.Cont return ec.marshalNManifestReference2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐManifestReference(ctx, field.Selections, res) } +func (ec *executionContext) _InterfacePolicy_default(ctx context.Context, field graphql.CollectedField, obj *InterfacePolicy) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "InterfacePolicy", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Default, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*DefaultForInterface) + fc.Result = res + return ec.marshalODefaultForInterface2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultForInterface(ctx, field.Selections, res) +} + func (ec *executionContext) _InterfacePolicy_rules(ctx context.Context, field graphql.CollectedField, obj *InterfacePolicy) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5552,6 +5696,46 @@ func (ec *executionContext) unmarshalInputAdvancedModeContinueRenderingInput(ctx return it, nil } +func (ec *executionContext) unmarshalInputDefaultForInterfaceInput(ctx context.Context, obj interface{}) (DefaultForInterfaceInput, error) { + var it DefaultForInterfaceInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "inject": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inject")) + it.Inject, err = ec.unmarshalODefaultInjectForInterfaceInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultInjectForInterfaceInput(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputDefaultInjectForInterfaceInput(ctx context.Context, obj interface{}) (DefaultInjectForInterfaceInput, error) { + var it DefaultInjectForInterfaceInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "requiredTypeInstances": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requiredTypeInstances")) + it.RequiredTypeInstances, err = ec.unmarshalORequiredTypeInstanceReferenceInput2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRequiredTypeInstanceReferenceInputᚄ(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputInputTypeInstanceData(ctx context.Context, obj interface{}) (InputTypeInstanceData, error) { var it InputTypeInstanceData var asMap = obj.(map[string]interface{}) @@ -5586,6 +5770,14 @@ func (ec *executionContext) unmarshalInputInterfacePolicyInput(ctx context.Conte for k, v := range asMap { switch k { + case "default": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("default")) + it.Default, err = ec.unmarshalODefaultForInterfaceInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultForInterfaceInput(ctx, v) + if err != nil { + return it, err + } case "rules": var err error @@ -6156,6 +6348,54 @@ func (ec *executionContext) _AdditionalTypeInstanceReference(ctx context.Context return out } +var defaultForInterfaceImplementors = []string{"DefaultForInterface"} + +func (ec *executionContext) _DefaultForInterface(ctx context.Context, sel ast.SelectionSet, obj *DefaultForInterface) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, defaultForInterfaceImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DefaultForInterface") + case "inject": + out.Values[i] = ec._DefaultForInterface_inject(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var defaultInjectForInterfaceImplementors = []string{"DefaultInjectForInterface"} + +func (ec *executionContext) _DefaultInjectForInterface(ctx context.Context, sel ast.SelectionSet, obj *DefaultInjectForInterface) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, defaultInjectForInterfaceImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DefaultInjectForInterface") + case "requiredTypeInstances": + out.Values[i] = ec._DefaultInjectForInterface_requiredTypeInstances(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var inputTypeInstanceDetailsImplementors = []string{"InputTypeInstanceDetails"} func (ec *executionContext) _InputTypeInstanceDetails(ctx context.Context, sel ast.SelectionSet, obj *InputTypeInstanceDetails) graphql.Marshaler { @@ -6231,6 +6471,8 @@ func (ec *executionContext) _InterfacePolicy(ctx context.Context, sel ast.Select switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("InterfacePolicy") + case "default": + out.Values[i] = ec._InterfacePolicy_default(ctx, field, obj) case "rules": out.Values[i] = ec._InterfacePolicy_rules(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -8224,6 +8466,36 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return graphql.MarshalBoolean(*v) } +func (ec *executionContext) marshalODefaultForInterface2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultForInterface(ctx context.Context, sel ast.SelectionSet, v *DefaultForInterface) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._DefaultForInterface(ctx, sel, v) +} + +func (ec *executionContext) unmarshalODefaultForInterfaceInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultForInterfaceInput(ctx context.Context, v interface{}) (*DefaultForInterfaceInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputDefaultForInterfaceInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalODefaultInjectForInterface2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultInjectForInterface(ctx context.Context, sel ast.SelectionSet, v *DefaultInjectForInterface) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._DefaultInjectForInterface(ctx, sel, v) +} + +func (ec *executionContext) unmarshalODefaultInjectForInterfaceInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐDefaultInjectForInterfaceInput(ctx context.Context, v interface{}) (*DefaultInjectForInterfaceInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputDefaultInjectForInterfaceInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOInputTypeInstanceData2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐInputTypeInstanceDataᚄ(ctx context.Context, v interface{}) ([]*InputTypeInstanceData, error) { if v == nil { return nil, nil diff --git a/pkg/engine/client/fields.go b/pkg/engine/client/fields.go index d5523cde4..b72c33478 100644 --- a/pkg/engine/client/fields.go +++ b/pkg/engine/client/fields.go @@ -74,6 +74,14 @@ var actionFields = fmt.Sprintf(` const policyFields = ` interface { + default { + inject { + requiredTypeInstances { + id + description + } + } + } rules { interface { path diff --git a/pkg/engine/k8s/policy/metadata/fixtures_test.go b/pkg/engine/k8s/policy/metadata/fixtures_test.go index da34d40a7..d47f87e38 100644 --- a/pkg/engine/k8s/policy/metadata/fixtures_test.go +++ b/pkg/engine/k8s/policy/metadata/fixtures_test.go @@ -19,6 +19,17 @@ import ( func fixComplexPolicyWithoutTypeRef() *policy.Policy { return &policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id12", + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ @@ -157,6 +168,21 @@ func fixComplexPolicyWithoutTypeRef() *policy.Policy { func fixComplexPolicyWithTypeRef() *policy.Policy { return &policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id12", + TypeRef: &types.TypeRef{ + Path: "cap.type.type12", + Revision: "0.12.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ diff --git a/pkg/engine/k8s/policy/metadata/metadata.go b/pkg/engine/k8s/policy/metadata/metadata.go index be9185317..cf39fa048 100644 --- a/pkg/engine/k8s/policy/metadata/metadata.go +++ b/pkg/engine/k8s/policy/metadata/metadata.go @@ -12,6 +12,7 @@ type typeInstanceKind string const ( requiredTypeInstance typeInstanceKind = "RequiredTypeInstance" additionalTypeInstance typeInstanceKind = "AdditionalTypeInstance" + defaultTypeInstance typeInstanceKind = "DefaultTypeInstance" backendTypeInstance typeInstanceKind = "BackendTypeInstance" ) @@ -49,13 +50,16 @@ func (m TypeInstanceMetadata) String(withKind bool) string { func TypeInstanceIDsWithUnresolvedMetadataForPolicy(in policy.Policy) []TypeInstanceMetadata { var tis []TypeInstanceMetadata - // Interface + // Interface rules for _, rule := range in.Interface.Rules { for _, ruleItem := range rule.OneOf { tis = append(tis, TypeInstanceIDsWithUnresolvedMetadataForRule(ruleItem)...) } } + // Interface default + tis = append(tis, TypeInstanceIDsWithUnresolvedMetadataForDefault(in.Interface.Default)...) + // TypeInstances backends for _, rule := range in.TypeInstance.Rules { if rule.Backend.TypeRef != nil && rule.Backend.TypeRef.Path != "" && rule.Backend.TypeRef.Revision != "" { @@ -72,6 +76,27 @@ func TypeInstanceIDsWithUnresolvedMetadataForPolicy(in policy.Policy) []TypeInst return tis } +// TypeInstanceIDsWithUnresolvedMetadataForDefault filters TypeInstances that have unresolved metadata for a given Interface default. +func TypeInstanceIDsWithUnresolvedMetadataForDefault(in *policy.InterfaceDefault) []TypeInstanceMetadata { + if in == nil || in.Inject == nil { + return nil + } + + var tis []TypeInstanceMetadata + for _, defaultTI := range in.Inject.RequiredTypeInstances { + if defaultTI.TypeRef != nil && defaultTI.TypeRef.Path != "" && defaultTI.TypeRef.Revision != "" { + continue + } + tis = append(tis, TypeInstanceMetadata{ + ID: defaultTI.ID, + Description: defaultTI.Description, + Kind: defaultTypeInstance, + }) + } + + return tis +} + // TypeInstanceIDsWithUnresolvedMetadataForRule filters TypeInstances that have unresolved metadata for a given rule. func TypeInstanceIDsWithUnresolvedMetadataForRule(in policy.Rule) []TypeInstanceMetadata { if in.Inject == nil { diff --git a/pkg/engine/k8s/policy/metadata/metadata_resolver.go b/pkg/engine/k8s/policy/metadata/metadata_resolver.go index 9f41f5021..020d524eb 100644 --- a/pkg/engine/k8s/policy/metadata/metadata_resolver.go +++ b/pkg/engine/k8s/policy/metadata/metadata_resolver.go @@ -64,7 +64,6 @@ func (r *Resolver) ResolveTypeInstanceMetadata(ctx context.Context, policy *poli if typeRef, exists := resolvedTypeRefs[ti.ID]; exists && typeRef.Path != "" && typeRef.Revision != "" { continue } - multiErr = multierr.Append(multiErr, fmt.Errorf("missing Type reference for %s", ti.String(true))) } if multiErr.ErrorOrNil() != nil { @@ -78,6 +77,7 @@ func (r *Resolver) ResolveTypeInstanceMetadata(ctx context.Context, policy *poli r.setTypeRefsForAdditionalTypeInstances(policy, typeRefWithParentNodes) r.setTypeRefsForRequiredTypeInstances(policy, typeRefWithParentNodes) + r.setTypeRefsForDefaultTypeInstances(policy, typeRefWithParentNodes) r.setTypeRefsForBackendTypeInstances(policy, typeRefWithParentNodes) return nil @@ -115,6 +115,24 @@ func (r *Resolver) mapToTypeRefs(in map[string]hublocalgraphql.TypeInstanceTypeR return out } +func (r *Resolver) setTypeRefsForDefaultTypeInstances(policy *policy.Policy, typeRefs map[string]TypeRefWithAdditionalRefs) { + if policy.Interface.Default == nil || policy.Interface.Default.Inject == nil { + return + } + for reqTIIdx, reqTI := range policy.Interface.Default.Inject.RequiredTypeInstances { + typeRef, exists := typeRefs[reqTI.ID] + if !exists { + continue + } + + policy.Interface.Default.Inject.RequiredTypeInstances[reqTIIdx].TypeRef = &types.TypeRef{ + Path: typeRef.Path, + Revision: typeRef.Revision, + } + policy.Interface.Default.Inject.RequiredTypeInstances[reqTIIdx].ExtendsHubStorage = r.isExtendingHubStorage(typeRef) + } +} + func (r *Resolver) setTypeRefsForRequiredTypeInstances(policy *policy.Policy, typeRefs map[string]TypeRefWithAdditionalRefs) { for ruleIdx, rule := range policy.Interface.Rules { for ruleItemIdx, ruleItem := range rule.OneOf { diff --git a/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go b/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go index 9d012bf5d..4650bfc2e 100644 --- a/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go +++ b/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go @@ -37,13 +37,13 @@ func TestResolveTypeInstanceMetadata(t *testing.T) { { Name: "Unresolved TypeRefs", Input: fixComplexPolicyWithoutTypeRef(), - HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 12}, + HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 13}, Expected: fixComplexPolicyWithTypeRef(), }, { Name: "Partial result", Input: fixComplexPolicyWithoutTypeRef(), - HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 12, IgnoreIDs: map[string]struct{}{ + HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 13, IgnoreIDs: map[string]struct{}{ "id2": {}, "id4": {}, // required "id8": {}, // additional "id9": {}, "id11": {}, // backend diff --git a/pkg/engine/k8s/policy/types.go b/pkg/engine/k8s/policy/types.go index 327059670..e2fd6961a 100644 --- a/pkg/engine/k8s/policy/types.go +++ b/pkg/engine/k8s/policy/types.go @@ -36,7 +36,26 @@ type Policy struct { // InterfacePolicy holds the Policy for Interfaces. type InterfacePolicy struct { - Rules InterfaceRulesList `json:"rules"` + Default *InterfaceDefault `json:"default,omitempty"` + Rules InterfaceRulesList `json:"rules"` +} + +// DefaultRequiredTypeInstancesToInject returns default required TypeInstances to inject for a given interface. +func (in *InterfacePolicy) DefaultRequiredTypeInstancesToInject() []RequiredTypeInstanceToInject { + if in.Default == nil || in.Default.Inject == nil { + return nil + } + return in.Default.Inject.RequiredTypeInstances +} + +// InterfaceDefault holds a defaults for the Interface Policy. +type InterfaceDefault struct { + Inject *DefaultInject `json:"inject,omitempty"` +} + +//DefaultInject holds default injection for the Interface Policy. +type DefaultInject struct { + RequiredTypeInstances []RequiredTypeInstanceToInject `json:"requiredTypeInstances,omitempty"` } // ActionPolicy holds the Policy injected during Action creation properties. diff --git a/pkg/hub/client/policy_enforced_client.go b/pkg/hub/client/policy_enforced_client.go index ea5fe79e3..e49d80291 100644 --- a/pkg/hub/client/policy_enforced_client.go +++ b/pkg/hub/client/policy_enforced_client.go @@ -49,7 +49,6 @@ type PolicyEnforcedClient struct { hubCli HubClient globalPolicy policy.Policy actionPolicy policy.Policy - mergedPolicy policy.Policy policyOrder policy.MergeOrder workflowStepPolicies []policy.Policy validator PolicyIOValidator @@ -107,17 +106,13 @@ func (e *PolicyEnforcedClient) ListImplementationRevisionForInterface(ctx contex // ListTypeInstancesBackendsBasedOnPolicy returns default backends defined in Policy and those specified explicitly in a given policy rule. func (e *PolicyEnforcedClient) ListTypeInstancesBackendsBasedOnPolicy(_ context.Context, rule policy.Rule, implRev hubpublicgraphql.ImplementationRevision) (policy.TypeInstanceBackendCollection, error) { out := policy.TypeInstanceBackendCollection{} - // 1. Global Defaults based on TypeRefs - for _, rule := range e.mergedPolicy.TypeInstance.Rules { + mergedPolicy := e.MergedPolicy() + for _, rule := range mergedPolicy.TypeInstance.Rules { out.SetByTypeRef(rule.TypeRef, rule.Backend) } - // TODO(https://github.com/capactio/capact/issues/635): - // 2. Global defaults based on required TypeInstance injection - // e.mergedPolicy.Interface.Defaults - - //3. Override defaults with specific Interface Policy rule + //2. Override defaults with specific Interface Policy rule inject, err := e.listRequiredTypeInstancesToInjectBasedOnPolicy(rule, implRev) if err != nil { return policy.TypeInstanceBackendCollection{}, err @@ -153,7 +148,8 @@ func (e *PolicyEnforcedClient) ListRequiredTypeInstancesToInjectBasedOnPolicy(po type requiredTypeInstanceToInject map[string]policy.RequiredTypeInstanceToInject func (e *PolicyEnforcedClient) listRequiredTypeInstancesToInjectBasedOnPolicy(policyRule policy.Rule, implRev hubpublicgraphql.ImplementationRevision) (requiredTypeInstanceToInject, error) { - requiredTIs := policyRule.RequiredTypeInstancesToInject() + requiredTIs := e.MergeRequiredTypeInstancesForRule(policyRule) + if len(requiredTIs) == 0 { return nil, nil } @@ -255,7 +251,6 @@ func (e *PolicyEnforcedClient) FindInterfaceRevision(ctx context.Context, ref hu func (e *PolicyEnforcedClient) SetPolicyOrder(order policy.MergeOrder) { e.mu.Lock() e.policyOrder = order - e.mergePolicies() e.mu.Unlock() } @@ -263,7 +258,6 @@ func (e *PolicyEnforcedClient) SetPolicyOrder(order policy.MergeOrder) { func (e *PolicyEnforcedClient) SetGlobalPolicy(p policy.Policy) { e.mu.Lock() e.globalPolicy = p - e.mergePolicies() e.mu.Unlock() } @@ -271,7 +265,6 @@ func (e *PolicyEnforcedClient) SetGlobalPolicy(p policy.Policy) { func (e *PolicyEnforcedClient) SetActionPolicy(p policy.ActionPolicy) { e.mu.Lock() e.actionPolicy = policy.Policy(p) - e.mergePolicies() e.mu.Unlock() } @@ -283,7 +276,6 @@ func (e *PolicyEnforcedClient) PushWorkflowStepPolicy(workflowPolicy policy.Work return errors.Wrap(err, "while getting Policy from WorkflowPolicy") } e.workflowStepPolicies = append(e.workflowStepPolicies, p) - e.mergePolicies() e.mu.Unlock() return nil } @@ -294,19 +286,18 @@ func (e *PolicyEnforcedClient) PopWorkflowStepPolicy() { if len(e.workflowStepPolicies) > 0 { e.workflowStepPolicies = e.workflowStepPolicies[:len(e.workflowStepPolicies)-1] } - e.mergePolicies() e.mu.Unlock() } -// Policy gets policy which the Client uses. This getter is thread safe. -func (e *PolicyEnforcedClient) Policy() policy.Policy { - e.mu.Lock() - defer e.mu.Unlock() - return e.mergedPolicy +// MergedPolicy gets policy which the Client uses. This getter is thread safe. +func (e *PolicyEnforcedClient) MergedPolicy() policy.Policy { + e.mu.RLock() + defer e.mu.RUnlock() + return e.mergePolicies() } func (e *PolicyEnforcedClient) findRulesForInterface(interfaceRef hubpublicgraphql.InterfaceReference) policy.RulesForInterface { - rulesMap := e.interfaceRulesMapForPolicy(e.Policy()) + rulesMap := e.interfaceRulesMapForPolicy(e.MergedPolicy()) ruleKeysToCheck := []string{ fmt.Sprintf("%s:%s", interfaceRef.Path, interfaceRef.Revision), @@ -327,17 +318,34 @@ func (e *PolicyEnforcedClient) findRulesForInterface(interfaceRef hubpublicgraph } func (e *PolicyEnforcedClient) resolvePolicyTIMetadataIfShould(ctx context.Context) error { - if e.validator.AreTypeInstancesMetadataResolved(e.mergedPolicy) { - return nil - } + resolvePolicyIfShouldFn := func(ctx context.Context, policyToResolve *policy.Policy) error { + if policyToResolve == nil { + return errors.New("policy cannot be nil") + } - err := e.policyMetadataResolver.ResolveTypeInstanceMetadata(ctx, &e.mergedPolicy) - if err != nil { - return errors.Wrap(err, "while resolving TypeInstance metadata for Policy") + if e.validator.AreTypeInstancesMetadataResolved(*policyToResolve) { + return nil + } + + err := e.policyMetadataResolver.ResolveTypeInstanceMetadata(ctx, policyToResolve) + if err != nil { + return errors.Wrap(err, "while resolving TypeInstance metadata for Policy") + } + + if res := e.validator.ValidateTypeInstancesMetadata(*policyToResolve); res.ErrorOrNil() != nil { + return e.wrapValidationResultError(res.ErrorOrNil(), "while TypeInstance metadata validation after resolving TypeRefs") + } + + return nil } - if res := e.validator.ValidateTypeInstancesMetadata(e.mergedPolicy); res.ErrorOrNil() != nil { - return e.wrapValidationResultError(res.ErrorOrNil(), "while TypeInstance metadata validation after resolving TypeRefs") + // Ignore workflow policies as there's no TypeRefs to resolve anyway + policiesToResolve := []policy.Policy{e.globalPolicy, e.actionPolicy} + for i := range policiesToResolve { + err := resolvePolicyIfShouldFn(ctx, &policiesToResolve[i]) + if err != nil { + return errors.Wrap(err, "while resolving policy TypeInstances") + } } return nil @@ -431,9 +439,10 @@ func (e *PolicyEnforcedClient) hubFilterForPolicyRule(rule policy.Rule, allTypeI filter.RequirementsSatisfiedBy = allTypeInstances // Requirements Injection - if rule.Inject != nil { + tisToInject := e.MergeRequiredTypeInstancesForRule(rule) + if len(tisToInject) > 0 { var injectedRequiredTypeInstances []*hubpublicgraphql.TypeInstanceValue - for _, ti := range rule.Inject.RequiredTypeInstances { + for _, ti := range tisToInject { injectedRequiredTypeInstances = append(injectedRequiredTypeInstances, &hubpublicgraphql.TypeInstanceValue{ TypeRef: &hubpublicgraphql.TypeReferenceInput{ Path: ti.TypeRef.Path, @@ -444,7 +453,6 @@ func (e *PolicyEnforcedClient) hubFilterForPolicyRule(rule policy.Rule, allTypeI } filter.RequiredTypeInstancesInjectionSatisfiedBy = injectedRequiredTypeInstances } - return filter } diff --git a/pkg/hub/client/policy_merger.go b/pkg/hub/client/policy_merger.go index 986a1737b..55229f31a 100644 --- a/pkg/hub/client/policy_merger.go +++ b/pkg/hub/client/policy_merger.go @@ -8,7 +8,7 @@ import ( "capact.io/capact/pkg/engine/k8s/policy" ) -func (e *PolicyEnforcedClient) mergePolicies() { +func (e *PolicyEnforcedClient) mergePolicies() policy.Policy { currentPolicy := policy.Policy{} for _, p := range e.policyOrder { @@ -27,13 +27,32 @@ func (e *PolicyEnforcedClient) mergePolicies() { } } } - e.mergedPolicy = currentPolicy + return currentPolicy +} + +// MergeRequiredTypeInstancesForRule returns the merged list of TypeInstances from Rule and Defaults. +func (e *PolicyEnforcedClient) MergeRequiredTypeInstancesForRule(policyRule policy.Rule) []policy.RequiredTypeInstanceToInject { + mergedPolicy := e.MergedPolicy() + // prefer policy Rule over Default + return mergeRequiredTypeInstances(mergedPolicy.Interface.DefaultRequiredTypeInstancesToInject(), policyRule.RequiredTypeInstancesToInject()) } -// from new policy we are checking if there are the same rules. If yes we fill missing data, -// if not we add a rule to the end -// current policy is a higher priority policy func applyInterfacePolicy(currentPolicy *policy.InterfacePolicy, newPolicy policy.InterfacePolicy) { + // Default + if len(newPolicy.DefaultRequiredTypeInstancesToInject()) > 0 { + if currentPolicy.Default == nil || currentPolicy.Default.Inject == nil { + currentPolicy.Default = &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{}, + }, + } + } + currentPolicy.Default.Inject.RequiredTypeInstances = mergeRequiredTypeInstances(currentPolicy.Default.Inject.RequiredTypeInstances, newPolicy.Default.Inject.RequiredTypeInstances) + } + + // from new policy we are checking if there are the same rules. If yes we fill missing data, + // if not we add a rule to the end + // current policy is a higher priority policy for _, newRuleForInterface := range newPolicy.Rules { policyRuleIndex := getIndexOfInterfacePolicyRule(currentPolicy, newRuleForInterface) if policyRuleIndex == -1 { diff --git a/pkg/hub/client/policy_merger_test.go b/pkg/hub/client/policy_merger_test.go index 51d0b7def..af5700a29 100644 --- a/pkg/hub/client/policy_merger_test.go +++ b/pkg/hub/client/policy_merger_test.go @@ -28,6 +28,22 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { name: "only global policy", global: policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ @@ -83,6 +99,22 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { action: policy.ActionPolicy{}, expected: policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ @@ -141,6 +173,22 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { name: "only action policy", action: policy.ActionPolicy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ @@ -195,6 +243,22 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { global: policy.Policy{}, expected: policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ @@ -496,9 +560,25 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { order: policy.MergeOrder{policy.Action, policy.Global}, }, { - name: "merge type instances and additional input", + name: "merge type instances and additional input for default and rule", action: policy.ActionPolicy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.a", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ @@ -552,6 +632,22 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, global: policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.b", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ @@ -595,6 +691,32 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, expected: policy.Policy{ Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.a", + Revision: "0.1.0", + }, + }, + }, + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.b", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ @@ -678,7 +800,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { cli.SetActionPolicy(tt.action) // expect - assert.Equal(t, tt.expected, cli.Policy()) + assert.Equal(t, tt.expected, cli.MergedPolicy()) }) } } @@ -1056,7 +1178,438 @@ func TestPolicyEnforcedClient_mergeTypeInstancePolicies(t *testing.T) { cli.SetActionPolicy(tt.action) // expect - assert.Equal(t, tt.expected, cli.Policy()) + assert.Equal(t, tt.expected, cli.MergedPolicy()) + }) + } +} + +func TestPolicyEnforcedClient_mergeTypeInstancePoliciesForDefault(t *testing.T) { + tests := []struct { + name string + global policy.Policy + action policy.ActionPolicy + expected policy.Policy + order policy.MergeOrder + }{ + { + name: "TypeInstance with different Typeref for Global and Action Policy", + global: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("A"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "3333-3333-3333", + Description: ptr.String("C"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + action: policy.ActionPolicy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("B"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.2.0", + }, + }, + }, + }, + }, + }, + }, + }, + expected: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("A"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "3333-3333-3333", + Description: ptr.String("C"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("B"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.2.0", + }, + }, + }, + }, + }, + }, + }, + }, + order: policy.MergeOrder{policy.Global, policy.Action}, + }, + { + name: "TypeInstance with the same Typeref for Global and Action Policy", + global: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("A"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + action: policy.ActionPolicy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("B"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + expected: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("B"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + order: policy.MergeOrder{policy.Global, policy.Action}, + }, + { + name: "TypeInstance with the same Typeref for Global and Action Policy but Action has higher priority", + global: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("A"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + action: policy.ActionPolicy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("B"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + expected: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-123-123", + Description: ptr.String("A"), + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.credentials", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + order: policy.MergeOrder{policy.Action, policy.Global}, + }, + } + for _, test := range tests { + tt := test + t.Run(tt.name, func(t *testing.T) { + // given + cli := client.NewPolicyEnforcedClient(nil, nil) + cli.SetPolicyOrder(tt.order) + cli.SetGlobalPolicy(tt.global) + cli.SetActionPolicy(tt.action) + + // expect + assert.Equal(t, tt.expected, cli.MergedPolicy()) + }) + } +} + +func TestRequiredTypeInstancesForRule(t *testing.T) { + tests := []struct { + name string + rule policy.Rule + global policy.Policy + expected []policy.RequiredTypeInstanceToInject + }{ + { + name: "Rule TypeInstance injection is preferred over Default", + rule: policy.Rule{ + Inject: &policy.InjectData{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + global: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1314-142-123", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + expected: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + { + name: "TypeInstaces should not contain duplicates", + rule: policy.Rule{ + Inject: &policy.InjectData{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + global: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + expected: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-2222-3333", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + { + name: "TypeInstaces are correctly distinguished based on Typeref", + rule: policy.Rule{ + Inject: &policy.InjectData{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-1111-1111", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + }, + global: policy.Policy{ + Interface: policy.InterfacePolicy{ + Default: &policy.InterfaceDefault{ + Inject: &policy.DefaultInject{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "2222-2222-2222", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.2.0", + }, + }, + }, + }, + }, + }, + }, + }, + expected: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "2222-2222-2222", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.2.0", + }, + }, + }, + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1111-1111-1111", + Description: ptr.String("Sample TI"), + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + tt := test + t.Run(tt.name, func(t *testing.T) { + // given + cli := client.NewPolicyEnforcedClient(nil, nil) + cli.SetGlobalPolicy(tt.global) + + // expect + assert.Equal(t, tt.expected, cli.MergeRequiredTypeInstancesForRule(tt.rule)) }) } } @@ -1075,14 +1628,14 @@ func TestNestedWorkflowPolicy(t *testing.T) { err = cli.PushWorkflowStepPolicy(w1) assert.NoError(t, err) - assert.Equal(t, expected1, cli.Policy()) + assert.Equal(t, expected1, cli.MergedPolicy()) err = cli.PushWorkflowStepPolicy(w2) assert.NoError(t, err) - assert.Equal(t, expected2, cli.Policy()) + assert.Equal(t, expected2, cli.MergedPolicy()) cli.PopWorkflowStepPolicy() - assert.Equal(t, expected1, cli.Policy()) + assert.Equal(t, expected1, cli.MergedPolicy()) } func workflowPolicyWithAdditionalInput(input map[string]interface{}) policy.WorkflowPolicy { diff --git a/test/e2e/action_test.go b/test/e2e/action_test.go index 619204689..419586283 100644 --- a/test/e2e/action_test.go +++ b/test/e2e/action_test.go @@ -141,7 +141,7 @@ var _ = Describe("Action", func() { assertOutputTypeInstancesInActionStatus(ctx, engineClient, action.Name, And(ContainElements(expUpdatedTIOutput, uploadedTIOutput), HaveLen(2))) }) - It("should pick Implementation B", func() { + It("should pick Implementation B based on Policy rule", func() { implIndicatorValue := "Implementation B" // TODO: This can be extracted after switching to ginkgo v2 @@ -174,7 +174,7 @@ var _ = Describe("Action", func() { }, } - By("2. Modifying Policy to pick Implementation B...") + By("2. Modifying rule Policy to pick Implementation B...") globalPolicyRequiredTypeInstances := []*enginegraphql.RequiredTypeInstanceReferenceInput{ { ID: injectTypeInstance.ID, @@ -203,6 +203,68 @@ var _ = Describe("Action", func() { assertOutputTypeInstancesInActionStatus(ctx, engineClient, action.Name, And(ContainElements(uploadedTIOutput), HaveLen(1))) }) + It("should pick Implementation B based on Interface default", func() { + implIndicatorValue := "Implementation B" + + // TODO: This can be extracted after switching to ginkgo v2 + // see: https://github.com/onsi/ginkgo/issues/70#issuecomment-924250145 + By("1. Preparing input Type Instances") + By("1.1 Creating TypeInstance which will be downloaded") + download := getTypeInstanceInputForDownload(implIndicatorValue) + downloadTI, downloadTICleanup := createTypeInstance(ctx, hubClient, download) + defer downloadTICleanup() + + By("1.2 Creating TypeInstance which will be downloaded and updated") + update := getTypeInstanceInputForUpdate() + updateTI, updateTICleanup := createTypeInstance(ctx, hubClient, update) + defer updateTICleanup() + + By("1.3 Creating TypeInstance that describes Helm storage") + helmStorage := fixHelmStorageTypeInstanceCreateInput() + helmStorageTI, helmStorageTICleanup := createTypeInstance(ctx, hubClient, helmStorage) + defer helmStorageTICleanup() + + By("1.4 Create TypeInstance which is required for Implementation B to be picked based on Policy") + typeInstanceValue := getTypeInstanceInputForPolicy() + injectTypeInstance, tiCleanupFn := createTypeInstance(ctx, hubClient, typeInstanceValue) + defer tiCleanupFn() + + inputData := &enginegraphql.ActionInputData{ + TypeInstances: []*enginegraphql.InputTypeInstanceData{ + {Name: "testInput", ID: downloadTI.ID}, + {Name: "testUpdate", ID: updateTI.ID}, + }, + } + + By("2. Modifying default Policy to pick Implementation B...") + globalPolicyRequiredTypeInstances := []*enginegraphql.RequiredTypeInstanceReferenceInput{ + { + ID: injectTypeInstance.ID, + Description: ptr.String("Test TypeInstance"), + }, + { + ID: helmStorageTI.ID, + Description: ptr.String("Helm backend TypeInstance"), + }, + } + setGlobalTestPolicy(ctx, engineClient, addInterfacePolicyDefaultInjectionForPassingActionInterface(globalPolicyRequiredTypeInstances)) + + By("3. Expecting Implementation B is picked and injected Helm storage is used...") + action := createActionAndWaitForReadyToRunPhase(ctx, engineClient, actionName, actionPassingInterfacePath, inputData) + assertActionRenderedWorkflowContains(action, "echo '%s'", implIndicatorValue) + runActionAndWaitForSucceeded(ctx, engineClient, actionName) + + By("4.1 Check uploaded TypeInstances") + expUploadTIBackend := &hublocalgraphql.TypeInstanceBackendReference{ID: helmStorageTI.ID, Abstract: false} + uploadedTI, cleanupUploaded := getUploadedTypeInstanceByValue(ctx, hubClient, implIndicatorValue) + defer cleanupUploaded() // We need to clean it up as it's not deleted when Action is deleted. + Expect(uploadedTI.Backend).Should(Equal(expUploadTIBackend)) + + By("4.2 Check Action output TypeInstances") + uploadedTIOutput := mapToOutputTypeInstanceDetails(uploadedTI, expUploadTIBackend) + assertOutputTypeInstancesInActionStatus(ctx, engineClient, action.Name, And(ContainElements(uploadedTIOutput), HaveLen(1))) + }) + It("should have failed status after a failed workflow", func() { _, err := engineClient.CreateAction(ctx, &enginegraphql.ActionDetailsInput{ Name: failingActionName, @@ -547,6 +609,36 @@ func prependInjectRuleForPassingActionInterface(reqInput []*enginegraphql.Requir } } +func addInterfacePolicyDefaultInjectionForPassingActionInterface(reqInput []*enginegraphql.RequiredTypeInstanceReferenceInput) policyOption { + manifestRef := func(path string) []*enginegraphql.ManifestReferenceInput { + return []*enginegraphql.ManifestReferenceInput{ + { + Path: path, + }, + } + } + return func(policy *enginegraphql.PolicyInput) { + for idx, rule := range policy.Interface.Rules { + if rule.Interface.Path != actionPassingInterfacePath { + continue + } + policy.Interface.Rules[idx].OneOf = append([]*enginegraphql.PolicyRuleInput{ + { + ImplementationConstraints: &enginegraphql.PolicyRuleImplementationConstraintsInput{ + Requires: manifestRef(singleKeyTypePath), + Attributes: manifestRef("cap.attribute.capactio.capact.validation.policy.most-preferred"), + }, + }, + }, policy.Interface.Rules[idx].OneOf...) + } + policy.Interface.Default = &enginegraphql.DefaultForInterfaceInput{ + Inject: &enginegraphql.DefaultInjectForInterfaceInput{ + RequiredTypeInstances: reqInput, + }, + } + } +} + func setGlobalTestPolicy(ctx context.Context, client *engine.Client, opts ...policyOption) { p := fixGQLTestPolicyInput()