diff --git a/.editorconfig b/.editorconfig index c461e151f..ea98e12f3 100755 --- a/.editorconfig +++ b/.editorconfig @@ -4,8 +4,8 @@ insert_final_newline = true [{*.yml, *.yaml}] indent_size = 2 -[*.graphql] -indent_size = 4 +[{*.graphql, *.js, *.ts}] +indent_size = 2 indent_style = space [Makefile] diff --git a/cmd/k8s-engine/main.go b/cmd/k8s-engine/main.go index a4fc02591..e0adc07c1 100644 --- a/cmd/k8s-engine/main.go +++ b/cmd/k8s-engine/main.go @@ -42,6 +42,7 @@ var ( const ( graphQLServerName = "engine-graphql" policyServiceName = "policy-svc" + argoRendererName = "argo-renderer" ) // Config holds application related configuration @@ -111,7 +112,7 @@ func main() { interfaceIOValidator := actionvalidation.NewValidator(hubClient) policyIOValidator := policyvalidation.NewValidator(hubClient) wfValidator := renderer.NewWorkflowInputValidator(interfaceIOValidator, policyIOValidator) - argoRenderer := argo.NewRenderer(cfg.Renderer, hubClient, typeInstanceHandler, wfValidator) + argoRenderer := argo.NewRenderer(logger.Named(argoRendererName), cfg.Renderer, hubClient, typeInstanceHandler, wfValidator) wfCli, err := wfclientset.NewForConfig(k8sCfg) exitOnError(err, "while creating Argo client") diff --git a/deploy/kubernetes/crds/core.capact.io_actions.yaml b/deploy/kubernetes/crds/core.capact.io_actions.yaml index 80e89c619..3711a293d 100644 --- a/deploy/kubernetes/crds/core.capact.io_actions.yaml +++ b/deploy/kubernetes/crds/core.capact.io_actions.yaml @@ -272,6 +272,18 @@ spec: description: OutputTypeInstanceDetails describes the output TypeInstance. properties: + backend: + description: Backend contains information in which backend + this TypeInstance is stored. + properties: + abstract: + type: boolean + id: + type: string + required: + - abstract + - id + type: object id: description: ID is a unique identifier of the TypeInstance. type: string @@ -291,6 +303,7 @@ spec: - path type: object required: + - backend - id - typeReference type: object diff --git a/hack/lib/utilities.sh b/hack/lib/utilities.sh index f5b0b3720..db95e5f4a 100644 --- a/hack/lib/utilities.sh +++ b/hack/lib/utilities.sh @@ -220,7 +220,8 @@ capact::install() { export ENABLE_ADDING_TRUSTED_CERT=${ENABLE_ADDING_TRUSTED_CERT:-"true"} export HUB_MANIFESTS_SOURCE_REPO_REF=${HUB_MANIFESTS_SOURCE_REPO_REF:-${CAPACT_HUB_MANIFESTS_SOURCE_REPO_REF}} export HUB_MANIFESTS_SOURCE_REPO_URL=${HUB_MANIFESTS_SOURCE_REPO_URL:-${CAPACT_HUB_MANIFESTS_SOURCE_REPO_URL}} - export COMPONENTS="neo4j,ingress-nginx,argo,cert-manager,capact" + export COMPONENTS=${COMPONENTS:-"neo4j,ingress-nginx,argo,cert-manager,capact"} + export BUILD_IMAGES_LIST=${BUILD_IMAGES_LIST:-"argo-actions,argo-runner,e2e-test,gateway,hub-js,k8s-engine,populator"} export INGRESS_CONTROLLER_OVERRIDES=${INGRESS_CONTROLLER_OVERRIDES:=""} export CAPACT_OVERRIDES=${CAPACT_OVERRIDES:=""} export CAPACT_INSTALL_ADDITIONAL_OPTS="" @@ -243,11 +244,11 @@ capact::install() { fi if [ -n "${HUB_MANIFESTS_SOURCE_REPO_REF:-}" ]; then - CAPACT_OVERRIDES+=",hub-public.populator.manifestsLocation.branch=${HUB_MANIFESTS_SOURCE_REPO_REF}" + CAPACT_OVERRIDES+=",hub-public.populator.manifestsLocations[0].branch=${HUB_MANIFESTS_SOURCE_REPO_REF}" fi if [ -n "${HUB_MANIFESTS_SOURCE_REPO_URL:-}" ]; then - CAPACT_OVERRIDES+=",hub-public.populator.manifestsLocation.repository=${HUB_MANIFESTS_SOURCE_REPO_URL}" + CAPACT_OVERRIDES+=",hub-public.populator.manifestsLocations[0].repository=${HUB_MANIFESTS_SOURCE_REPO_URL}" fi if [ -n "${DOCKER_REPOSITORY:-}" ]; then @@ -255,7 +256,7 @@ capact::install() { fi if [[ "${BUILD_IMAGES:-"true"}" == "false" ]]; then - BUILD_IMAGES_FLAG=--build-image="" + BUILD_IMAGES_LIST="" fi if [ -n "${CAPACT_HELM_REPO:-}" ]; then @@ -277,7 +278,7 @@ capact::install() { --update-hosts-file="${ENABLE_HOSTS_UPDATE}" \ --update-trusted-certs="${ENABLE_ADDING_TRUSTED_CERT}" \ --install-component="${COMPONENTS}" \ - ${BUILD_IMAGES_FLAG:-} \ + --build-image="${BUILD_IMAGES_LIST}" \ --version="${CAPACT_VERSION}" \ ${CAPACT_INSTALL_ADDITIONAL_OPTS} } diff --git a/hub-js/graphql/local/examples.graphql b/hub-js/graphql/local/examples.graphql index a9a31f958..ae3431383 100644 --- a/hub-js/graphql/local/examples.graphql +++ b/hub-js/graphql/local/examples.graphql @@ -167,6 +167,10 @@ fragment TypeInstance on TypeInstance { revision } lockedBy + backend { + id + abstract + } latestResourceVersion { ...TypeInstanceResourceVersion diff --git a/hub-js/graphql/local/schema.graphql b/hub-js/graphql/local/schema.graphql index 307b68f5d..241e1b481 100644 --- a/hub-js/graphql/local/schema.graphql +++ b/hub-js/graphql/local/schema.graphql @@ -44,6 +44,7 @@ type TypeInstance { @relation(name: "OF_TYPE", direction: "OUT") uses: [TypeInstance!]! @relation(name: "USES", direction: "OUT") usedBy: [TypeInstance!]! @relation(name: "USES", direction: "IN") + backend: TypeInstanceBackendReference! @relation(name: "STORED_IN", direction: "OUT") latestResourceVersion: TypeInstanceResourceVersion @cypher( @@ -95,6 +96,11 @@ type TypeInstanceResourceVersionSpec { @relation(name: "INSTRUMENTED_WITH", direction: "OUT") } +type TypeInstanceBackendReference { + id: String! + abstract: Boolean! +} + type TypeInstanceTypeReference { path: NodePath! revision: Version! @@ -200,6 +206,10 @@ input TypeInstanceTypeReferenceInput { revision: Version! } +input TypeInstanceBackendInput { + id: String! +} + input CreateTypeInstanceInput { """ Used to define the relationships, between the created TypeInstances @@ -210,6 +220,10 @@ input CreateTypeInstanceInput { typeRef: TypeInstanceTypeReferenceInput! attributes: [AttributeReferenceInput!] value: Any + """ + If not provided, TypeInstance value is stored as static value in Local Hub core storage. + """ + backend: TypeInstanceBackendInput } input TypeInstanceUsesRelationInput { @@ -342,17 +356,41 @@ type Mutation { createTypeInstance(in: CreateTypeInstanceInput!): TypeInstance! @cypher( statement: """ - WITH apoc.convert.toJson($in.value) as value - MERGE (typeRef:TypeInstanceTypeReference {path: $in.typeRef.path, revision: $in.typeRef.revision}) - CREATE (ti:TypeInstance {id: apoc.create.uuid()}) + + // Backend + WITH * + CALL apoc.do.when( + $in.backend.id IS NOT NULL, + ' + WITH false as abstract + RETURN $in.backend.id as id, abstract + ', + ' + // TODO(storage): this should be resolved by Local Hub server during the insertion, not in cypher. + WITH true as abstract + MATCH (backend:TypeInstance)-[:OF_TYPE]->(typeRef {path: "cap.core.type.hub.storage.neo4j"}) + RETURN backend.id as id, abstract + ', + {in: $in} + ) YIELD value as backend + MATCH (backendTI:TypeInstance {id: backend.id}) + CREATE (ti)-[:USES]->(backendTI) + // TODO(storage): It should be taken from the uses relation but we don't have access to the TypeRef.additionalRefs to check + // if a given type is a backend or not. Maybe we will introduce a dedicated property to distinguish them from others. + MERGE (storageRef:TypeInstanceBackendReference {abstract: backend.abstract, id: backendTI.id}) + CREATE (ti)-[:STORED_IN]->(storageRef) + + // TypeRef + MERGE (typeRef:TypeInstanceTypeReference {path: $in.typeRef.path, revision: $in.typeRef.revision}) CREATE (ti)-[:OF_TYPE]->(typeRef) + // Revision CREATE (tir: TypeInstanceResourceVersion {resourceVersion: 1, createdBy: $in.createdBy}) CREATE (ti)-[:CONTAINS]->(tir) CREATE (tir)-[:DESCRIBED_BY]->(metadata: TypeInstanceResourceVersionMetadata) - CREATE (tir)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec {value: value}) + CREATE (tir)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec {value: apoc.convert.toJson($in.value)}) FOREACH (attr in $in.attributes | MERGE (attrRef: AttributeReference {path: attr.path, revision: attr.revision}) diff --git a/hub-js/src/index.ts b/hub-js/src/index.ts index e232db2ce..3b57b73eb 100644 --- a/hub-js/src/index.ts +++ b/hub-js/src/index.ts @@ -1,20 +1,21 @@ -import { ApolloServer } from "apollo-server-express"; +import {ApolloServer} from "apollo-server-express"; import * as express from "express"; -import neo4j, { Driver } from "neo4j-driver"; +import neo4j, {Driver} from "neo4j-driver"; import { createTerminus, HealthCheck, HealthCheckError, } from "@godaddy/terminus"; import * as http from "http"; -import { GraphQLSchema } from "graphql"; +import {GraphQLSchema} from "graphql"; -import { assertSchemaOnDatabase, getSchemaForMode } from "./schema"; -import { config } from "./config"; -import { logger } from "./logger"; +import {assertSchemaOnDatabase, getSchemaForMode, HubMode} from "./schema"; +import {config} from "./config"; +import {logger} from "./logger"; +import {ensureCoreStorageTypeInstance} from "./schema/local"; async function main() { - logger.info("Using Neo4j database", { endpoint: config.neo4j.endpoint }); + logger.info("Using Neo4j database", {endpoint: config.neo4j.endpoint}); const driver = neo4j.driver( config.neo4j.endpoint, @@ -37,9 +38,14 @@ async function main() { }; const server = await setupHttpServer(schema, driver, healthCheck); - const { bindPort, bindAddress } = config.graphql; + const {bindPort, bindAddress} = config.graphql; - logger.info("Starting Hub", { mode: config.hubMode }); + logger.info("Starting Hub", {mode: config.hubMode}); + + if (config.hubMode === HubMode.Local) { + await ensureCoreStorageTypeInstance({driver}) + logger.info("Successfully registered TypeInstance for core backend storage"); + } server.listen(bindPort, bindAddress, () => { logger.info("GraphQL API is listening", { @@ -54,16 +60,16 @@ async function setupHttpServer( healthCheck: HealthCheck ): Promise { const app = express(); - app.use(express.json({ limit: config.express.bodySizeLimit })); + app.use(express.json({limit: config.express.bodySizeLimit})); - const apolloServer = new ApolloServer({ schema, context: { driver } }); + const apolloServer = new ApolloServer({schema, context: {driver}}); await apolloServer.start(); - apolloServer.applyMiddleware({ app }); + apolloServer.applyMiddleware({app}); const server = http.createServer(app); createTerminus(server, { - healthChecks: { "/healthz": healthCheck }, + healthChecks: {"/healthz": healthCheck}, }); return server; diff --git a/hub-js/src/schema/local.ts b/hub-js/src/schema/local.ts index 8159a1d27..7b1415d8d 100644 --- a/hub-js/src/schema/local.ts +++ b/hub-js/src/schema/local.ts @@ -1,6 +1,6 @@ -import { readFileSync } from "fs"; -import { makeAugmentedSchema, neo4jgraphql } from "neo4j-graphql-js"; -import { Driver, Transaction } from "neo4j-driver"; +import {readFileSync} from "fs"; +import {makeAugmentedSchema, neo4jgraphql} from "neo4j-graphql-js"; +import {Driver, Transaction} from "neo4j-driver"; const typeDefs = readFileSync("./graphql/local/schema.graphql", "utf-8"); @@ -54,7 +54,7 @@ export const schema = makeAugmentedSchema({ args: CreateTypeInstancesArgs, context: ContextWithDriver ) => { - const { typeInstances, usesRelations } = args.in; + const {typeInstances, usesRelations} = args.in; const aliases = typeInstances .filter((x) => x.alias !== undefined) @@ -73,14 +73,39 @@ export const schema = makeAugmentedSchema({ // create TypeInstances const createTypeInstanceResult = await tx.run( `UNWIND $typeInstances AS typeInstance - MERGE (typeRef:TypeInstanceTypeReference {path: typeInstance.typeRef.path, revision: typeInstance.typeRef.revision}) - CREATE (ti:TypeInstance {id: apoc.create.uuid()}) + + // Backend + WITH * + CALL apoc.do.when( + typeInstance.backend.id IS NOT NULL, + ' + WITH false as abstract + RETURN $in.backend.id as id, abstract + ', + ' + // TODO(storage): this should be resolved by Local Hub server during the insertion, not in cypher. + WITH true as abstract + MATCH (backend:TypeInstance)-[:OF_TYPE]->(typeRef {path: "cap.core.type.hub.storage.neo4j", revision: "0.1.0"}) + RETURN backend.id as id, abstract + ', + {in: typeInstance} + ) YIELD value as backend + MATCH (backendTI:TypeInstance {id: backend.id}) + CREATE (ti)-[:USES]->(backendTI) + // TODO(storage): It should be taken from the uses relation but we don't have access to the TypeRef.additionalRefs to check + // if a given type is a backend or not. Maybe we will introduce a dedicated property to distinguish them from others. + MERGE (storageRef:TypeInstanceBackendReference {abstract: backend.abstract, id: backendTI.id}) + CREATE (ti)-[:STORED_IN]->(storageRef) + + // TypeRef + MERGE (typeRef:TypeInstanceTypeReference {path: typeInstance.typeRef.path, revision: typeInstance.typeRef.revision}) CREATE (ti)-[:OF_TYPE]->(typeRef) - + + // Revision CREATE (tir: TypeInstanceResourceVersion {resourceVersion: 1, createdBy: typeInstance.createdBy}) CREATE (ti)-[:CONTAINS]->(tir) - + CREATE (tir)-[:DESCRIBED_BY]->(metadata: TypeInstanceResourceVersionMetadata) CREATE (tir)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec {value: apoc.convert.toJson(typeInstance.value)}) @@ -91,7 +116,7 @@ export const schema = makeAugmentedSchema({ RETURN ti.id as uuid, typeInstance.alias as alias `, - { typeInstances } + {typeInstances} ); if ( @@ -117,7 +142,7 @@ export const schema = makeAugmentedSchema({ {} ); const usesRelationsParams = usesRelations.map( - ({ from, to }: { from: string; to: string }) => ({ + ({from, to}: { from: string; to: string }) => ({ from: aliasMappings[from] || from, to: aliasMappings[to] || to, }) @@ -178,7 +203,7 @@ export const schema = makeAugmentedSchema({ ); break; case UpdateTypeInstanceErrorCode.NotFound: { - const ids = args.in.map(({ id }) => id); + const ids = args.in.map(({id}) => id); const notFoundIDs = ids.filter( (x) => !customErr.ids.includes(x) ); @@ -206,7 +231,7 @@ export const schema = makeAugmentedSchema({ await tx.run( ` OPTIONAL MATCH (ti:TypeInstance {id: $id}) - + // Check if a given TypeInstance was found CALL apoc.util.validate(ti IS NULL, apoc.convert.toJson({code: 404}), null) @@ -226,9 +251,9 @@ export const schema = makeAugmentedSchema({ MATCH (metadata:TypeInstanceResourceVersionMetadata)<-[:DESCRIBED_BY]-(tirs) MATCH (tirs)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec) OPTIONAL MATCH (metadata)-[:CHARACTERIZED_BY]->(attrRef: AttributeReference) - + DETACH DELETE ti, metadata, spec, tirs - + WITH typeRef CALL { MATCH (typeRef) @@ -236,7 +261,7 @@ export const schema = makeAugmentedSchema({ DELETE (typeRef) RETURN 'remove typeRef' } - + WITH * CALL { MATCH (attrRef) @@ -244,9 +269,9 @@ export const schema = makeAugmentedSchema({ DELETE (attrRef) RETURN 'remove attr' } - + RETURN $id`, - { id: args.id, ownerID: args.ownerID || null } + {id: args.id, ownerID: args.ownerID || null} ); return args.id; } @@ -346,9 +371,9 @@ async function switchLocking( ) { const instanceLockedByOthers = await tx.run( `MATCH (ti:TypeInstance) - WHERE ti.id IN $in.ids + WHERE ti.id IN $in.ids WITH collect(ti) as allIDs - + // Check if all TypeInstances were found CALL apoc.when( size(allIDs) < size($in.ids), @@ -356,7 +381,7 @@ async function switchLocking( 'RETURN false as notFoundErr', {in: $in, allIDs: allIDs} ) YIELD value as checkIDs - + // Check if given TypeInstances are not already locked by others CALL { MATCH (ti:TypeInstance) @@ -364,7 +389,7 @@ async function switchLocking( WITH collect(ti) as lockedIDs RETURN lockedIDs } - + // Execute lock only if all TypeInstance were found and none of them are already locked by another owner WITH * CALL apoc.do.when( @@ -377,9 +402,9 @@ async function switchLocking( ', {in: $in, checkIDs: checkIDs, lockedIDs: lockedIDs} ) YIELD value as lockingProcess - + RETURN allIDs, lockedIDs, lockingProcess`, - { in: args.in } + {in: args.in} ); if (!instanceLockedByOthers.records.length) { @@ -453,3 +478,37 @@ function tryToExtractCustomError( return null; } + +export async function ensureCoreStorageTypeInstance(context: ContextWithDriver) { + const neo4jSession = context.driver.session(); + const value = { + acceptValue: false, + contextSchema: null, + } + try { + await neo4jSession.writeTransaction( + async (tx: Transaction) => { + await tx.run(` + MERGE (ti:TypeInstance {id: "318b99bd-9b26-4bc1-8259-0a7ff5dae61c"}) + MERGE (typeRef:TypeInstanceTypeReference {path: "cap.core.type.hub.storage.neo4j", revision: "0.1.0"}) + MERGE (backend:TypeInstanceBackendReference {abstract: true, id: ti.id, description: "Built-in Hub storage"}) + MERGE (tir: TypeInstanceResourceVersion {resourceVersion: 1, createdBy: "core"}) + MERGE (spec: TypeInstanceResourceVersionSpec {value: apoc.convert.toJson($value)}) + + MERGE (ti)-[:OF_TYPE]->(typeRef) + MERGE (ti)-[:STORED_IN]->(backend) + MERGE (ti)-[:CONTAINS]->(tir) + MERGE (tir)-[:DESCRIBED_BY]->(metadata:TypeInstanceResourceVersionMetadata) + MERGE (tir)-[:SPECIFIED_BY]->(spec) + + RETURN ti + `, {value}); + } + ); + } catch (e) { + const err = e as Error; + throw new Error(`while ensuring TypeInstance for core backend storage: ${err.message}`); + } finally { + await neo4jSession.close(); + } +} diff --git a/internal/k8s-engine/controller/action_service.go b/internal/k8s-engine/controller/action_service.go index 6d2aa36e9..f3de4589d 100644 --- a/internal/k8s-engine/controller/action_service.go +++ b/internal/k8s-engine/controller/action_service.go @@ -43,6 +43,8 @@ const ( k8sJobRunnerInputDataMountPath = "/mnt" k8sJobRunnerVolumeName = "input-volume" k8sJobActiveDeadlinePadding = 10 * time.Second + + listTypeInstanceFields = local.TypeInstanceRootFields | local.TypeInstanceTypeRefFields | local.TypeInstanceBackendFields ) type ( @@ -591,20 +593,25 @@ func (a *ActionService) GetTypeInstancesFromAction(ctx context.Context, action * typeInstances, err := a.typeInstanceGetter.ListTypeInstances(ctx, &gqllocalapi.TypeInstanceFilter{ CreatedBy: &ownerID, - }, local.WithFields(local.TypeInstanceRootFields|local.TypeInstanceTypeRefFields)) + }, local.WithFields(listTypeInstanceFields)) if err != nil { return nil, errors.Wrap(err, "while listing TypeInstances") } var res []v1alpha1.OutputTypeInstanceDetails for _, ti := range typeInstances { - res = append(res, v1alpha1.OutputTypeInstanceDetails{ + out := v1alpha1.OutputTypeInstanceDetails{ ID: ti.ID, TypeRef: &v1alpha1.ManifestReference{ Path: v1alpha1.NodePath(ti.TypeRef.Path), Revision: &ti.TypeRef.Revision, }, - }) + } + if ti.Backend != nil { + out.Backend = v1alpha1.TypeInstanceBackend(*ti.Backend) + } + + res = append(res, out) } return res, nil diff --git a/internal/k8s-engine/controller/suite_test.go b/internal/k8s-engine/controller/suite_test.go index f0c72829a..308e4a343 100644 --- a/internal/k8s-engine/controller/suite_test.go +++ b/internal/k8s-engine/controller/suite_test.go @@ -4,9 +4,9 @@ package controller import ( + "capact.io/capact/internal/logger" "capact.io/capact/pkg/hub/client/local" "context" - "io/ioutil" "path/filepath" "strings" "testing" @@ -83,7 +83,7 @@ var _ = BeforeSuite(func(done Done) { }, } - svc := NewActionService(zap.NewRaw(zap.WriteTo(ioutil.Discard)), mgr.GetClient(), + svc := NewActionService(logger.Noop(), mgr.GetClient(), &argoRendererFake{}, &actionValidatorFake{}, &policyServiceFake{}, policy.MergeOrder{policy.Action, policy.Global}, &typeInstanceLockerFake{}, &typeInstanceGetterFake{}, cfg) diff --git a/internal/k8s-engine/graphql/domain/action/converter.go b/internal/k8s-engine/graphql/domain/action/converter.go index be8074e23..2e19003aa 100644 --- a/internal/k8s-engine/graphql/domain/action/converter.go +++ b/internal/k8s-engine/graphql/domain/action/converter.go @@ -314,9 +314,11 @@ func (c *Converter) actionOutputToGraphQL(in *v1alpha1.ActionOutput) *graphql.Ac var gqlTypeInstances []*graphql.OutputTypeInstanceDetails for _, item := range *in.TypeInstances { + gqlBackend := graphql.TypeInstanceBackendDetails(item.Backend) gqlTypeInstances = append(gqlTypeInstances, &graphql.OutputTypeInstanceDetails{ ID: item.ID, TypeRef: c.manifestRefToGraphQL(item.TypeRef), + Backend: &gqlBackend, }) } diff --git a/internal/k8s-engine/graphql/domain/action/fixtures_test.go b/internal/k8s-engine/graphql/domain/action/fixtures_test.go index 0003a199a..818eab78c 100644 --- a/internal/k8s-engine/graphql/domain/action/fixtures_test.go +++ b/internal/k8s-engine/graphql/domain/action/fixtures_test.go @@ -53,6 +53,10 @@ func fixGQLAction(t *testing.T, name string) graphql.Action { Path: "path1", Revision: "0.1.0", }, + Backend: &graphql.TypeInstanceBackendDetails{ + ID: "id11", + Abstract: true, + }, }, { ID: "id2", @@ -60,6 +64,11 @@ func fixGQLAction(t *testing.T, name string) graphql.Action { Path: "path2", Revision: "0.1.0", }, + + Backend: &graphql.TypeInstanceBackendDetails{ + ID: "id22", + Abstract: false, + }, }, }, }, @@ -167,6 +176,10 @@ func fixK8sAction(t *testing.T, name, namespace string) v1alpha1.Action { Path: "path1", Revision: ptr.String("0.1.0"), }, + Backend: v1alpha1.TypeInstanceBackend{ + ID: "id11", + Abstract: true, + }, }, { ID: "id2", @@ -174,6 +187,10 @@ func fixK8sAction(t *testing.T, name, namespace string) v1alpha1.Action { Path: "path2", Revision: ptr.String("0.1.0"), }, + Backend: v1alpha1.TypeInstanceBackend{ + ID: "id22", + Abstract: false, + }, }, }, }, @@ -338,6 +355,37 @@ func fixGQLInputActionPolicy() *graphql.PolicyInput { }, }, }, + TypeInstance: &graphql.TypeInstancePolicyInput{ + Rules: []*graphql.RulesForTypeInstanceInput{ + { + TypeRef: &graphql.ManifestReferenceInput{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: &graphql.TypeInstanceBackendRuleInput{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + { + TypeRef: &graphql.ManifestReferenceInput{ + Path: "cap.type.aws.*", + }, + Backend: &graphql.TypeInstanceBackendRuleInput{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + { + TypeRef: &graphql.ManifestReferenceInput{ + Path: "cap.*", + }, + Backend: &graphql.TypeInstanceBackendRuleInput{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, } } @@ -432,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"}]}}]}]}}` + 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"}}]}}` } return sec diff --git a/internal/k8s-engine/graphql/domain/policy/converter.go b/internal/k8s-engine/graphql/domain/policy/converter.go index 21980bd4b..3bc3c4566 100644 --- a/internal/k8s-engine/graphql/domain/policy/converter.go +++ b/internal/k8s-engine/graphql/domain/policy/converter.go @@ -22,8 +22,11 @@ func (c *Converter) FromGraphQLInput(in graphql.PolicyInput) (policy.Policy, err return policy.Policy{}, err } + typeInstanceRules := c.typeInstanceFromGraphQLInput(in.TypeInstance) + return policy.Policy{ - Interface: ifaceRules, + Interface: ifaceRules, + TypeInstance: typeInstanceRules, }, nil } @@ -31,7 +34,7 @@ func (c *Converter) interfaceFromGraphQLInput(in *graphql.InterfacePolicyInput) if in == nil { return policy.InterfacePolicy{}, nil } - var rules policy.RulesList + var rules policy.InterfaceRulesList for _, gqlRule := range in.Rules { iface := c.manifestRefFromGraphQLInput(gqlRule.Interface) @@ -49,10 +52,58 @@ func (c *Converter) interfaceFromGraphQLInput(in *graphql.InterfacePolicyInput) return policy.InterfacePolicy{Rules: rules}, nil } +func (c *Converter) typeInstanceFromGraphQLInput(in *graphql.TypeInstancePolicyInput) policy.TypeInstancePolicy { + if in == nil { + return policy.TypeInstancePolicy{} + } + var rules []policy.RulesForTypeInstance + + for _, gqlRule := range in.Rules { + gqlRef, gqlBackend := gqlRule.TypeRef, gqlRule.Backend + if gqlRef == nil || gqlBackend == nil { + continue + } + + ref := types.ManifestRefWithOptRevision(*gqlRef) + rules = append(rules, policy.RulesForTypeInstance{ + TypeRef: ref, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: gqlBackend.ID, + Description: gqlBackend.Description, + }, + }, + }) + } + + return policy.TypeInstancePolicy{Rules: rules} +} + // ToGraphQL converts Policy model representation to GraphQL DTO. func (c *Converter) ToGraphQL(in policy.Policy) graphql.Policy { return graphql.Policy{ - Interface: c.interfaceToGraphQL(in.Interface), + Interface: c.interfaceToGraphQL(in.Interface), + TypeInstance: c.typeInstanceToGraphQL(in.TypeInstance), + } +} + +func (c *Converter) typeInstanceToGraphQL(in policy.TypeInstancePolicy) *graphql.TypeInstancePolicy { + var gqlRules []*graphql.RulesForTypeInstance + + for _, rule := range in.Rules { + ref := graphql.ManifestReferenceWithOptionalRevision(rule.TypeRef) + + gqlRules = append(gqlRules, &graphql.RulesForTypeInstance{ + TypeRef: &ref, + Backend: &graphql.TypeInstanceBackendRule{ + ID: rule.Backend.ID, + Description: rule.Backend.Description, + }, + }) + } + + return &graphql.TypeInstancePolicy{ + Rules: gqlRules, } } @@ -243,7 +294,7 @@ func (c *Converter) requiredTypeInstancesToInjectFromGraphQLInput(in []*graphql. var out []policy.RequiredTypeInstanceToInject for _, item := range in { out = append(out, policy.RequiredTypeInstanceToInject{ - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: item.ID, Description: item.Description, }, diff --git a/internal/k8s-engine/graphql/domain/policy/fixtures_test.go b/internal/k8s-engine/graphql/domain/policy/fixtures_test.go index 1d1cf4a70..86de2dbb3 100644 --- a/internal/k8s-engine/graphql/domain/policy/fixtures_test.go +++ b/internal/k8s-engine/graphql/domain/policy/fixtures_test.go @@ -73,6 +73,37 @@ func fixGQLInput() graphql.PolicyInput { }, }, }, + TypeInstance: &graphql.TypeInstancePolicyInput{ + Rules: []*graphql.RulesForTypeInstanceInput{ + { + TypeRef: &graphql.ManifestReferenceInput{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: &graphql.TypeInstanceBackendRuleInput{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + { + TypeRef: &graphql.ManifestReferenceInput{ + Path: "cap.type.aws.*", + }, + Backend: &graphql.TypeInstanceBackendRuleInput{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + { + TypeRef: &graphql.ManifestReferenceInput{ + Path: "cap.*", + }, + Backend: &graphql.TypeInstanceBackendRuleInput{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, } } @@ -142,13 +173,44 @@ func fixGQL() graphql.Policy { }, }, }, + TypeInstance: &graphql.TypeInstancePolicy{ + Rules: []*graphql.RulesForTypeInstance{ + { + TypeRef: &graphql.ManifestReferenceWithOptionalRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: &graphql.TypeInstanceBackendRule{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + { + TypeRef: &graphql.ManifestReferenceWithOptionalRevision{ + Path: "cap.type.aws.*", + }, + Backend: &graphql.TypeInstanceBackendRule{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + { + TypeRef: &graphql.ManifestReferenceWithOptionalRevision{ + Path: "cap.*", + }, + Backend: &graphql.TypeInstanceBackendRule{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, } } func fixModel() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -172,7 +234,7 @@ func fixModel() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "c268d3f5-8834-434b-bea2-b677793611c5", Description: ptr.String("Sample description"), }, @@ -215,5 +277,42 @@ func fixModel() policy.Policy { }, }, }, + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, } } diff --git a/internal/k8s-engine/policy/fixtures_test.go b/internal/k8s-engine/policy/fixtures_test.go index 9e1f8064a..dfd005ead 100644 --- a/internal/k8s-engine/policy/fixtures_test.go +++ b/internal/k8s-engine/policy/fixtures_test.go @@ -33,7 +33,7 @@ func fixCfgMap(t *testing.T, in policy.Policy) *v1.ConfigMap { func fixModel() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -57,7 +57,7 @@ func fixModel() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "c268d3f5-8834-434b-bea2-b677793611c5", Description: ptr.String("Sample description"), }, diff --git a/internal/logger/noop.go b/internal/logger/noop.go new file mode 100644 index 000000000..1c38cfd43 --- /dev/null +++ b/internal/logger/noop.go @@ -0,0 +1,13 @@ +package logger + +import ( + "io/ioutil" + + uberzap "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// Noop discards all logged messages. +func Noop() *uberzap.Logger { + return zap.NewRaw(zap.WriteTo(ioutil.Discard)) +} diff --git a/pkg/engine/api/graphql/models_gen.go b/pkg/engine/api/graphql/models_gen.go index 9e507c82d..b4457fe95 100644 --- a/pkg/engine/api/graphql/models_gen.go +++ b/pkg/engine/api/graphql/models_gen.go @@ -159,16 +159,19 @@ type ManifestReferenceInput struct { // Describes output TypeInstance of an Action type OutputTypeInstanceDetails struct { - ID string `json:"id"` - TypeRef *ManifestReference `json:"typeRef"` + ID string `json:"id"` + TypeRef *ManifestReference `json:"typeRef"` + Backend *TypeInstanceBackendDetails `json:"backend"` } type Policy struct { - Interface *InterfacePolicy `json:"interface"` + Interface *InterfacePolicy `json:"interface"` + TypeInstance *TypeInstancePolicy `json:"typeInstance"` } type PolicyInput struct { - Interface *InterfacePolicyInput `json:"interface"` + Interface *InterfacePolicyInput `json:"interface"` + TypeInstance *TypeInstancePolicyInput `json:"typeInstance"` } type PolicyRuleImplementationConstraintsInput struct { @@ -206,12 +209,45 @@ type RulesForInterfaceInput struct { OneOf []*PolicyRuleInput `json:"oneOf"` } +type RulesForTypeInstance struct { + TypeRef *ManifestReferenceWithOptionalRevision `json:"typeRef"` + Backend *TypeInstanceBackendRule `json:"backend"` +} + +type RulesForTypeInstanceInput struct { + TypeRef *ManifestReferenceInput `json:"typeRef"` + Backend *TypeInstanceBackendRuleInput `json:"backend"` +} + // Additional Action status from the Runner type RunnerStatus struct { // Status of a given Runner e.g. Argo Workflow Runner status object with argoWorkflowRef field Status interface{} `json:"status"` } +type TypeInstanceBackendDetails struct { + ID string `json:"id"` + Abstract bool `json:"abstract"` +} + +type TypeInstanceBackendRule struct { + ID string `json:"id"` + Description *string `json:"description"` +} + +type TypeInstanceBackendRuleInput struct { + ID string `json:"id"` + Description *string `json:"description"` +} + +type TypeInstancePolicy struct { + Rules []*RulesForTypeInstance `json:"rules"` +} + +type TypeInstancePolicyInput struct { + Rules []*RulesForTypeInstanceInput `json:"rules"` +} + // Stores user information type UserInfo struct { Username string `json:"username"` diff --git a/pkg/engine/api/graphql/schema.graphql b/pkg/engine/api/graphql/schema.graphql index ca7d4c5b9..d8c9c0330 100644 --- a/pkg/engine/api/graphql/schema.graphql +++ b/pkg/engine/api/graphql/schema.graphql @@ -257,6 +257,12 @@ Describes output TypeInstance of an Action type OutputTypeInstanceDetails { id: ID! typeRef: ManifestReference! + backend: TypeInstanceBackendDetails! +} + +type TypeInstanceBackendDetails { + id: String! + abstract: Boolean! } """ @@ -303,16 +309,33 @@ enum ActionStatusPhase { } input PolicyInput { - interface: InterfacePolicyInput + interface: InterfacePolicyInput + typeInstance: TypeInstancePolicyInput +} + +# TypeInstance Policy Input +input TypeInstancePolicyInput { + rules: [RulesForTypeInstanceInput!]! +} + +input RulesForTypeInstanceInput { + typeRef: ManifestReferenceInput! + backend: TypeInstanceBackendRuleInput! +} + +input TypeInstanceBackendRuleInput { + id: ID! + description: String } +# Interface Policy Input input InterfacePolicyInput { - rules: [RulesForInterfaceInput!]! + rules: [RulesForInterfaceInput!]! } input RulesForInterfaceInput { interface: ManifestReferenceInput! - oneOf: [PolicyRuleInput!]! + oneOf: [PolicyRuleInput!]! } input PolicyRuleInput { @@ -327,8 +350,8 @@ input PolicyRuleInjectDataInput { } input AdditionalParameterInput { - name: String! - value: Any! + name: String! + value: Any! } input PolicyRuleImplementationConstraintsInput { @@ -350,15 +373,32 @@ input PolicyRuleImplementationConstraintsInput { type Policy { interface: InterfacePolicy + typeInstance: TypeInstancePolicy +} + +# TypeInstance Policy +type TypeInstancePolicy { + rules: [RulesForTypeInstance!]! +} + +type RulesForTypeInstance { + typeRef: ManifestReferenceWithOptionalRevision! + backend: TypeInstanceBackendRule! } +type TypeInstanceBackendRule { + id: ID! + description: String +} + +# Interface Policy type InterfacePolicy { - rules: [RulesForInterface!]! + rules: [RulesForInterface!]! } type RulesForInterface { interface: ManifestReferenceWithOptionalRevision! - oneOf: [PolicyRule!]! + oneOf: [PolicyRule!]! } type PolicyRule { @@ -378,8 +418,8 @@ type AdditionalTypeInstanceReference { } type AdditionalParameter { - name: String! - value: Any! + name: String! + value: Any! } type PolicyRuleImplementationConstraints { diff --git a/pkg/engine/api/graphql/schema_gen.go b/pkg/engine/api/graphql/schema_gen.go index f3134b4ce..9b46e58ac 100644 --- a/pkg/engine/api/graphql/schema_gen.go +++ b/pkg/engine/api/graphql/schema_gen.go @@ -127,12 +127,14 @@ type ComplexityRoot struct { } OutputTypeInstanceDetails struct { + Backend func(childComplexity int) int ID func(childComplexity int) int TypeRef func(childComplexity int) int } Policy struct { - Interface func(childComplexity int) int + Interface func(childComplexity int) int + TypeInstance func(childComplexity int) int } PolicyRule struct { @@ -168,10 +170,29 @@ type ComplexityRoot struct { OneOf func(childComplexity int) int } + RulesForTypeInstance struct { + Backend func(childComplexity int) int + TypeRef func(childComplexity int) int + } + RunnerStatus struct { Status func(childComplexity int) int } + TypeInstanceBackendDetails struct { + Abstract func(childComplexity int) int + ID func(childComplexity int) int + } + + TypeInstanceBackendRule struct { + Description func(childComplexity int) int + ID func(childComplexity int) int + } + + TypeInstancePolicy struct { + Rules func(childComplexity int) int + } + UserInfo struct { Extra func(childComplexity int) int Groups func(childComplexity int) int @@ -559,6 +580,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdatePolicy(childComplexity, args["in"].(PolicyInput)), true + case "OutputTypeInstanceDetails.backend": + if e.complexity.OutputTypeInstanceDetails.Backend == nil { + break + } + + return e.complexity.OutputTypeInstanceDetails.Backend(childComplexity), true + case "OutputTypeInstanceDetails.id": if e.complexity.OutputTypeInstanceDetails.ID == nil { break @@ -580,6 +608,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Policy.Interface(childComplexity), true + case "Policy.typeInstance": + if e.complexity.Policy.TypeInstance == nil { + break + } + + return e.complexity.Policy.TypeInstance(childComplexity), true + case "PolicyRule.implementationConstraints": if e.complexity.PolicyRule.ImplementationConstraints == nil { break @@ -695,6 +730,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RulesForInterface.OneOf(childComplexity), true + case "RulesForTypeInstance.backend": + if e.complexity.RulesForTypeInstance.Backend == nil { + break + } + + return e.complexity.RulesForTypeInstance.Backend(childComplexity), true + + case "RulesForTypeInstance.typeRef": + if e.complexity.RulesForTypeInstance.TypeRef == nil { + break + } + + return e.complexity.RulesForTypeInstance.TypeRef(childComplexity), true + case "RunnerStatus.status": if e.complexity.RunnerStatus.Status == nil { break @@ -702,6 +751,41 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RunnerStatus.Status(childComplexity), true + case "TypeInstanceBackendDetails.abstract": + if e.complexity.TypeInstanceBackendDetails.Abstract == nil { + break + } + + return e.complexity.TypeInstanceBackendDetails.Abstract(childComplexity), true + + case "TypeInstanceBackendDetails.id": + if e.complexity.TypeInstanceBackendDetails.ID == nil { + break + } + + return e.complexity.TypeInstanceBackendDetails.ID(childComplexity), true + + case "TypeInstanceBackendRule.description": + if e.complexity.TypeInstanceBackendRule.Description == nil { + break + } + + return e.complexity.TypeInstanceBackendRule.Description(childComplexity), true + + case "TypeInstanceBackendRule.id": + if e.complexity.TypeInstanceBackendRule.ID == nil { + break + } + + return e.complexity.TypeInstanceBackendRule.ID(childComplexity), true + + case "TypeInstancePolicy.rules": + if e.complexity.TypeInstancePolicy.Rules == nil { + break + } + + return e.complexity.TypeInstancePolicy.Rules(childComplexity), true + case "UserInfo.extra": if e.complexity.UserInfo.Extra == nil { break @@ -1046,6 +1130,12 @@ Describes output TypeInstance of an Action type OutputTypeInstanceDetails { id: ID! typeRef: ManifestReference! + backend: TypeInstanceBackendDetails! +} + +type TypeInstanceBackendDetails { + id: String! + abstract: Boolean! } """ @@ -1092,16 +1182,33 @@ enum ActionStatusPhase { } input PolicyInput { - interface: InterfacePolicyInput + interface: InterfacePolicyInput + typeInstance: TypeInstancePolicyInput +} + +# TypeInstance Policy Input +input TypeInstancePolicyInput { + rules: [RulesForTypeInstanceInput!]! +} + +input RulesForTypeInstanceInput { + typeRef: ManifestReferenceInput! + backend: TypeInstanceBackendRuleInput! +} + +input TypeInstanceBackendRuleInput { + id: ID! + description: String } +# Interface Policy Input input InterfacePolicyInput { - rules: [RulesForInterfaceInput!]! + rules: [RulesForInterfaceInput!]! } input RulesForInterfaceInput { interface: ManifestReferenceInput! - oneOf: [PolicyRuleInput!]! + oneOf: [PolicyRuleInput!]! } input PolicyRuleInput { @@ -1116,8 +1223,8 @@ input PolicyRuleInjectDataInput { } input AdditionalParameterInput { - name: String! - value: Any! + name: String! + value: Any! } input PolicyRuleImplementationConstraintsInput { @@ -1139,15 +1246,32 @@ input PolicyRuleImplementationConstraintsInput { type Policy { interface: InterfacePolicy + typeInstance: TypeInstancePolicy +} + +# TypeInstance Policy +type TypeInstancePolicy { + rules: [RulesForTypeInstance!]! +} + +type RulesForTypeInstance { + typeRef: ManifestReferenceWithOptionalRevision! + backend: TypeInstanceBackendRule! } +type TypeInstanceBackendRule { + id: ID! + description: String +} + +# Interface Policy type InterfacePolicy { - rules: [RulesForInterface!]! + rules: [RulesForInterface!]! } type RulesForInterface { interface: ManifestReferenceWithOptionalRevision! - oneOf: [PolicyRule!]! + oneOf: [PolicyRule!]! } type PolicyRule { @@ -1167,8 +1291,8 @@ type AdditionalTypeInstanceReference { } type AdditionalParameter { - name: String! - value: Any! + name: String! + value: Any! } type PolicyRuleImplementationConstraints { @@ -3078,6 +3202,41 @@ func (ec *executionContext) _OutputTypeInstanceDetails_typeRef(ctx context.Conte return ec.marshalNManifestReference2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐManifestReference(ctx, field.Selections, res) } +func (ec *executionContext) _OutputTypeInstanceDetails_backend(ctx context.Context, field graphql.CollectedField, obj *OutputTypeInstanceDetails) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "OutputTypeInstanceDetails", + 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.Backend, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*TypeInstanceBackendDetails) + fc.Result = res + return ec.marshalNTypeInstanceBackendDetails2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstanceBackendDetails(ctx, field.Selections, res) +} + func (ec *executionContext) _Policy_interface(ctx context.Context, field graphql.CollectedField, obj *Policy) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3110,6 +3269,38 @@ func (ec *executionContext) _Policy_interface(ctx context.Context, field graphql return ec.marshalOInterfacePolicy2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐInterfacePolicy(ctx, field.Selections, res) } +func (ec *executionContext) _Policy_typeInstance(ctx context.Context, field graphql.CollectedField, obj *Policy) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Policy", + 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.TypeInstance, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*TypeInstancePolicy) + fc.Result = res + return ec.marshalOTypeInstancePolicy2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstancePolicy(ctx, field.Selections, res) +} + func (ec *executionContext) _PolicyRule_implementationConstraints(ctx context.Context, field graphql.CollectedField, obj *PolicyRule) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3690,7 +3881,7 @@ func (ec *executionContext) _RulesForInterface_oneOf(ctx context.Context, field return ec.marshalNPolicyRule2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐPolicyRuleᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _RunnerStatus_status(ctx context.Context, field graphql.CollectedField, obj *RunnerStatus) (ret graphql.Marshaler) { +func (ec *executionContext) _RulesForTypeInstance_typeRef(ctx context.Context, field graphql.CollectedField, obj *RulesForTypeInstance) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3698,7 +3889,7 @@ func (ec *executionContext) _RunnerStatus_status(ctx context.Context, field grap } }() fc := &graphql.FieldContext{ - Object: "RunnerStatus", + Object: "RulesForTypeInstance", Field: field, Args: nil, IsMethod: false, @@ -3708,21 +3899,24 @@ func (ec *executionContext) _RunnerStatus_status(ctx context.Context, field grap 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.Status, nil + return obj.TypeRef, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(interface{}) + res := resTmp.(*ManifestReferenceWithOptionalRevision) fc.Result = res - return ec.marshalOAny2interface(ctx, field.Selections, res) + return ec.marshalNManifestReferenceWithOptionalRevision2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐManifestReferenceWithOptionalRevision(ctx, field.Selections, res) } -func (ec *executionContext) _UserInfo_username(ctx context.Context, field graphql.CollectedField, obj *UserInfo) (ret graphql.Marshaler) { +func (ec *executionContext) _RulesForTypeInstance_backend(ctx context.Context, field graphql.CollectedField, obj *RulesForTypeInstance) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3730,7 +3924,7 @@ func (ec *executionContext) _UserInfo_username(ctx context.Context, field graphq } }() fc := &graphql.FieldContext{ - Object: "UserInfo", + Object: "RulesForTypeInstance", Field: field, Args: nil, IsMethod: false, @@ -3740,7 +3934,7 @@ func (ec *executionContext) _UserInfo_username(ctx context.Context, field graphq 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.Username, nil + return obj.Backend, nil }) if err != nil { ec.Error(ctx, err) @@ -3752,12 +3946,12 @@ func (ec *executionContext) _UserInfo_username(ctx context.Context, field graphq } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*TypeInstanceBackendRule) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNTypeInstanceBackendRule2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstanceBackendRule(ctx, field.Selections, res) } -func (ec *executionContext) _UserInfo_groups(ctx context.Context, field graphql.CollectedField, obj *UserInfo) (ret graphql.Marshaler) { +func (ec *executionContext) _RunnerStatus_status(ctx context.Context, field graphql.CollectedField, obj *RunnerStatus) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3765,7 +3959,7 @@ func (ec *executionContext) _UserInfo_groups(ctx context.Context, field graphql. } }() fc := &graphql.FieldContext{ - Object: "UserInfo", + Object: "RunnerStatus", Field: field, Args: nil, IsMethod: false, @@ -3775,24 +3969,21 @@ func (ec *executionContext) _UserInfo_groups(ctx context.Context, field graphql. 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.Groups, nil + return obj.Status, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.([]string) + res := resTmp.(interface{}) fc.Result = res - return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) + return ec.marshalOAny2interface(ctx, field.Selections, res) } -func (ec *executionContext) _UserInfo_extra(ctx context.Context, field graphql.CollectedField, obj *UserInfo) (ret graphql.Marshaler) { +func (ec *executionContext) _TypeInstanceBackendDetails_id(ctx context.Context, field graphql.CollectedField, obj *TypeInstanceBackendDetails) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3800,7 +3991,7 @@ func (ec *executionContext) _UserInfo_extra(ctx context.Context, field graphql.C } }() fc := &graphql.FieldContext{ - Object: "UserInfo", + Object: "TypeInstanceBackendDetails", Field: field, Args: nil, IsMethod: false, @@ -3810,21 +4001,24 @@ func (ec *executionContext) _UserInfo_extra(ctx context.Context, field graphql.C 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.Extra, nil + return obj.ID, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(interface{}) + res := resTmp.(string) fc.Result = res - return ec.marshalOAny2interface(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) _TypeInstanceBackendDetails_abstract(ctx context.Context, field graphql.CollectedField, obj *TypeInstanceBackendDetails) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3832,7 +4026,7 @@ func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql } }() fc := &graphql.FieldContext{ - Object: "__Directive", + Object: "TypeInstanceBackendDetails", Field: field, Args: nil, IsMethod: false, @@ -3842,7 +4036,7 @@ func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql 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.Name, nil + return obj.Abstract, nil }) if err != nil { ec.Error(ctx, err) @@ -3854,12 +4048,12 @@ func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql } return graphql.Null } - res := resTmp.(string) + res := resTmp.(bool) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) _TypeInstanceBackendRule_id(ctx context.Context, field graphql.CollectedField, obj *TypeInstanceBackendRule) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3867,7 +4061,7 @@ func (ec *executionContext) ___Directive_description(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "__Directive", + Object: "TypeInstanceBackendRule", Field: field, Args: nil, IsMethod: false, @@ -3877,21 +4071,24 @@ func (ec *executionContext) ___Directive_description(ctx context.Context, field 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.Description, nil + return obj.ID, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } res := resTmp.(string) fc.Result = res - return ec.marshalOString2string(ctx, field.Selections, res) + return ec.marshalNID2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) _TypeInstanceBackendRule_description(ctx context.Context, field graphql.CollectedField, obj *TypeInstanceBackendRule) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3899,7 +4096,7 @@ func (ec *executionContext) ___Directive_locations(ctx context.Context, field gr } }() fc := &graphql.FieldContext{ - Object: "__Directive", + Object: "TypeInstanceBackendRule", Field: field, Args: nil, IsMethod: false, @@ -3909,24 +4106,21 @@ func (ec *executionContext) ___Directive_locations(ctx context.Context, field gr 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.Locations, nil + return obj.Description, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.([]string) + res := resTmp.(*string) fc.Result = res - return ec.marshalN__DirectiveLocation2ᚕstringᚄ(ctx, field.Selections, res) + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { +func (ec *executionContext) _TypeInstancePolicy_rules(ctx context.Context, field graphql.CollectedField, obj *TypeInstancePolicy) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3934,7 +4128,7 @@ func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql } }() fc := &graphql.FieldContext{ - Object: "__Directive", + Object: "TypeInstancePolicy", Field: field, Args: nil, IsMethod: false, @@ -3944,7 +4138,7 @@ func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql 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.Args, nil + return obj.Rules, nil }) if err != nil { ec.Error(ctx, err) @@ -3956,12 +4150,12 @@ func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql } return graphql.Null } - res := resTmp.([]introspection.InputValue) + res := resTmp.([]*RulesForTypeInstance) fc.Result = res - return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) + return ec.marshalNRulesForTypeInstance2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstanceᚄ(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) _UserInfo_username(ctx context.Context, field graphql.CollectedField, obj *UserInfo) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3969,7 +4163,7 @@ func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", + Object: "UserInfo", Field: field, Args: nil, IsMethod: false, @@ -3979,7 +4173,7 @@ func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql 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.Name, nil + return obj.Username, nil }) if err != nil { ec.Error(ctx, err) @@ -3996,7 +4190,7 @@ func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) _UserInfo_groups(ctx context.Context, field graphql.CollectedField, obj *UserInfo) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -4004,7 +4198,7 @@ func (ec *executionContext) ___EnumValue_description(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", + Object: "UserInfo", Field: field, Args: nil, IsMethod: false, @@ -4014,21 +4208,24 @@ func (ec *executionContext) ___EnumValue_description(ctx context.Context, field 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.Description, nil + return obj.Groups, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(string) + res := resTmp.([]string) fc.Result = res - return ec.marshalOString2string(ctx, field.Selections, res) + return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) _UserInfo_extra(ctx context.Context, field graphql.CollectedField, obj *UserInfo) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -4036,34 +4233,31 @@ func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", + Object: "UserInfo", Field: field, Args: nil, - IsMethod: true, + 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.IsDeprecated(), nil + return obj.Extra, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(bool) + res := resTmp.(interface{}) fc.Result = res - return ec.marshalNBoolean2bool(ctx, field.Selections, res) + return ec.marshalOAny2interface(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { +func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -4071,31 +4265,34 @@ func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", + Object: "__Directive", Field: field, Args: nil, - IsMethod: true, + 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.DeprecationReason(), nil + return obj.Name, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.(string) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { +func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -4103,7 +4300,7 @@ func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.Col } }() fc := &graphql.FieldContext{ - Object: "__Field", + Object: "__Directive", Field: field, Args: nil, IsMethod: false, @@ -4113,7 +4310,243 @@ func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.Col 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.Name, nil + return obj.Description, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalOString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "__Directive", + 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.Locations, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalN__DirectiveLocation2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "__Directive", + 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.Args, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.InputValue) + fc.Result = res + return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "__EnumValue", + 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.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "__EnumValue", + 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.Description, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalOString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + Args: nil, + IsMethod: true, + 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.IsDeprecated(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + Args: nil, + IsMethod: true, + 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.DeprecationReason(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "__Field", + 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.Name, nil }) if err != nil { ec.Error(ctx, err) @@ -5209,6 +5642,14 @@ func (ec *executionContext) unmarshalInputPolicyInput(ctx context.Context, obj i if err != nil { return it, err } + case "typeInstance": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("typeInstance")) + it.TypeInstance, err = ec.unmarshalOTypeInstancePolicyInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstancePolicyInput(ctx, v) + if err != nil { + return it, err + } } } @@ -5371,6 +5812,82 @@ func (ec *executionContext) unmarshalInputRulesForInterfaceInput(ctx context.Con return it, nil } +func (ec *executionContext) unmarshalInputRulesForTypeInstanceInput(ctx context.Context, obj interface{}) (RulesForTypeInstanceInput, error) { + var it RulesForTypeInstanceInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "typeRef": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("typeRef")) + it.TypeRef, err = ec.unmarshalNManifestReferenceInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐManifestReferenceInput(ctx, v) + if err != nil { + return it, err + } + case "backend": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("backend")) + it.Backend, err = ec.unmarshalNTypeInstanceBackendRuleInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstanceBackendRuleInput(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputTypeInstanceBackendRuleInput(ctx context.Context, obj interface{}) (TypeInstanceBackendRuleInput, error) { + var it TypeInstanceBackendRuleInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "id": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + it.ID, err = ec.unmarshalNID2string(ctx, v) + if err != nil { + return it, err + } + case "description": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("description")) + it.Description, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputTypeInstancePolicyInput(ctx context.Context, obj interface{}) (TypeInstancePolicyInput, error) { + var it TypeInstancePolicyInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "rules": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("rules")) + it.Rules, err = ec.unmarshalNRulesForTypeInstanceInput2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstanceInputᚄ(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -5873,6 +6390,11 @@ func (ec *executionContext) _OutputTypeInstanceDetails(ctx context.Context, sel if out.Values[i] == graphql.Null { invalids++ } + case "backend": + out.Values[i] = ec._OutputTypeInstanceDetails_backend(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -5897,6 +6419,8 @@ func (ec *executionContext) _Policy(ctx context.Context, sel ast.SelectionSet, o out.Values[i] = graphql.MarshalString("Policy") case "interface": out.Values[i] = ec._Policy_interface(ctx, field, obj) + case "typeInstance": + out.Values[i] = ec._Policy_typeInstance(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -6120,6 +6644,38 @@ func (ec *executionContext) _RulesForInterface(ctx context.Context, sel ast.Sele return out } +var rulesForTypeInstanceImplementors = []string{"RulesForTypeInstance"} + +func (ec *executionContext) _RulesForTypeInstance(ctx context.Context, sel ast.SelectionSet, obj *RulesForTypeInstance) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, rulesForTypeInstanceImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("RulesForTypeInstance") + case "typeRef": + out.Values[i] = ec._RulesForTypeInstance_typeRef(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "backend": + out.Values[i] = ec._RulesForTypeInstance_backend(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var runnerStatusImplementors = []string{"RunnerStatus"} func (ec *executionContext) _RunnerStatus(ctx context.Context, sel ast.SelectionSet, obj *RunnerStatus) graphql.Marshaler { @@ -6144,6 +6700,94 @@ func (ec *executionContext) _RunnerStatus(ctx context.Context, sel ast.Selection return out } +var typeInstanceBackendDetailsImplementors = []string{"TypeInstanceBackendDetails"} + +func (ec *executionContext) _TypeInstanceBackendDetails(ctx context.Context, sel ast.SelectionSet, obj *TypeInstanceBackendDetails) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, typeInstanceBackendDetailsImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("TypeInstanceBackendDetails") + case "id": + out.Values[i] = ec._TypeInstanceBackendDetails_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "abstract": + out.Values[i] = ec._TypeInstanceBackendDetails_abstract(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var typeInstanceBackendRuleImplementors = []string{"TypeInstanceBackendRule"} + +func (ec *executionContext) _TypeInstanceBackendRule(ctx context.Context, sel ast.SelectionSet, obj *TypeInstanceBackendRule) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, typeInstanceBackendRuleImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("TypeInstanceBackendRule") + case "id": + out.Values[i] = ec._TypeInstanceBackendRule_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "description": + out.Values[i] = ec._TypeInstanceBackendRule_description(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var typeInstancePolicyImplementors = []string{"TypeInstancePolicy"} + +func (ec *executionContext) _TypeInstancePolicy(ctx context.Context, sel ast.SelectionSet, obj *TypeInstancePolicy) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, typeInstancePolicyImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("TypeInstancePolicy") + case "rules": + out.Values[i] = ec._TypeInstancePolicy_rules(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var userInfoImplementors = []string{"UserInfo"} func (ec *executionContext) _UserInfo(ctx context.Context, sel ast.SelectionSet, obj *UserInfo) graphql.Marshaler { @@ -6941,6 +7585,79 @@ func (ec *executionContext) unmarshalNRulesForInterfaceInput2ᚖcapactᚗioᚋca return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) marshalNRulesForTypeInstance2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstanceᚄ(ctx context.Context, sel ast.SelectionSet, v []*RulesForTypeInstance) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNRulesForTypeInstance2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstance(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalNRulesForTypeInstance2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstance(ctx context.Context, sel ast.SelectionSet, v *RulesForTypeInstance) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._RulesForTypeInstance(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNRulesForTypeInstanceInput2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstanceInputᚄ(ctx context.Context, v interface{}) ([]*RulesForTypeInstanceInput, error) { + var vSlice []interface{} + if v != nil { + if tmp1, ok := v.([]interface{}); ok { + vSlice = tmp1 + } else { + vSlice = []interface{}{v} + } + } + var err error + res := make([]*RulesForTypeInstanceInput, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNRulesForTypeInstanceInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstanceInput(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) unmarshalNRulesForTypeInstanceInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐRulesForTypeInstanceInput(ctx context.Context, v interface{}) (*RulesForTypeInstanceInput, error) { + res, err := ec.unmarshalInputRulesForTypeInstanceInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -6996,6 +7713,31 @@ func (ec *executionContext) marshalNTimestamp2capactᚗioᚋcapactᚋpkgᚋengin return v } +func (ec *executionContext) marshalNTypeInstanceBackendDetails2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstanceBackendDetails(ctx context.Context, sel ast.SelectionSet, v *TypeInstanceBackendDetails) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._TypeInstanceBackendDetails(ctx, sel, v) +} + +func (ec *executionContext) marshalNTypeInstanceBackendRule2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstanceBackendRule(ctx context.Context, sel ast.SelectionSet, v *TypeInstanceBackendRule) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._TypeInstanceBackendRule(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNTypeInstanceBackendRuleInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstanceBackendRuleInput(ctx context.Context, v interface{}) (*TypeInstanceBackendRuleInput, error) { + res, err := ec.unmarshalInputTypeInstanceBackendRuleInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNVersion2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -7779,6 +8521,21 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as return graphql.MarshalString(*v) } +func (ec *executionContext) marshalOTypeInstancePolicy2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstancePolicy(ctx context.Context, sel ast.SelectionSet, v *TypeInstancePolicy) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._TypeInstancePolicy(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOTypeInstancePolicyInput2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐTypeInstancePolicyInput(ctx context.Context, v interface{}) (*TypeInstancePolicyInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputTypeInstancePolicyInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOUserInfo2ᚖcapactᚗioᚋcapactᚋpkgᚋengineᚋapiᚋgraphqlᚐUserInfo(ctx context.Context, sel ast.SelectionSet, v *UserInfo) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/pkg/engine/client/action.go b/pkg/engine/client/action.go new file mode 100644 index 000000000..bd66b2206 --- /dev/null +++ b/pkg/engine/client/action.go @@ -0,0 +1,137 @@ +package client + +import ( + "context" + "fmt" + + "capact.io/capact/internal/k8s-engine/graphql/namespace" + gqlengine "capact.io/capact/pkg/engine/api/graphql" + + "github.com/machinebox/graphql" + "github.com/pkg/errors" +) + +// Action knows how to execute GraphQL queries and mutations for Action type. +type Action struct { + client *graphql.Client +} + +// CreateAction creates Action in the Namespace extracted from a given ctx. +func (c *Action) CreateAction(ctx context.Context, in *gqlengine.ActionDetailsInput) (*gqlengine.Action, error) { + req := graphql.NewRequest(fmt.Sprintf(`mutation($in: ActionDetailsInput) { + createAction( + in: $in + ) { + %s + } + }`, actionFields)) + + c.enrichWithNamespace(ctx, req) + req.Var("in", in) + + var resp struct { + Action gqlengine.Action `json:"createAction"` + } + if err := c.client.Run(ctx, req, &resp); err != nil { + return nil, errors.Wrap(err, "while executing mutation to create Action") + } + + return &resp.Action, nil +} + +// GetAction returns Action with a given name from Namespace extracted from a given ctx. +func (c *Action) GetAction(ctx context.Context, name string) (*gqlengine.Action, error) { + req := graphql.NewRequest(fmt.Sprintf(`query($name: String!) { + action(name: $name) { + %s + } + }`, actionFields)) + + c.enrichWithNamespace(ctx, req) + req.Var("name", name) + + var resp struct { + Action *gqlengine.Action `json:"action"` + } + if err := c.client.Run(ctx, req, &resp); err != nil { + return nil, errors.Wrap(err, "while executing query to get Action") + } + + return resp.Action, nil +} + +// ListActions returns all Actions which meet filter criteria. +// Namespace extracted from a given ctx. +func (c *Action) ListActions(ctx context.Context, filter *gqlengine.ActionFilter) ([]*gqlengine.Action, error) { + req := graphql.NewRequest(fmt.Sprintf(`query($filter: ActionFilter) { + actions(filter: $filter) { + %s + } + }`, actionFields)) + + c.enrichWithNamespace(ctx, req) + req.Var("filter", filter) + + var resp struct { + Actions []*gqlengine.Action `json:"actions"` + } + if err := c.client.Run(ctx, req, &resp); err != nil { + return nil, errors.Wrap(err, "while executing query to get Action") + } + + return resp.Actions, nil +} + +// RunAction executes a given Action. +func (c *Action) RunAction(ctx context.Context, name string) error { + req := graphql.NewRequest(fmt.Sprintf(`mutation($name: String!) { + runAction( + name: $name + ) { + %s + } + }`, actionFields)) + + c.enrichWithNamespace(ctx, req) + req.Var("name", name) + + var resp struct { + Action gqlengine.Action `json:"runAction"` + } + if err := c.client.Run(ctx, req, &resp); err != nil { + return errors.Wrap(err, "while executing mutation to run Action") + } + + return nil +} + +// DeleteAction deletes a given Action. +func (c *Action) DeleteAction(ctx context.Context, name string) error { + req := graphql.NewRequest(fmt.Sprintf(`mutation($name: String!) { + deleteAction( + name: $name + ) { + %s + } + }`, actionFields)) + + c.enrichWithNamespace(ctx, req) + req.Var("name", name) + + var resp struct { + Action gqlengine.Action `json:"deleteAction"` + } + if err := c.client.Run(ctx, req, &resp); err != nil { + return errors.Wrap(err, "while executing mutation to delete Action") + } + + return nil +} + +func (c *Action) enrichWithNamespace(ctx context.Context, req *graphql.Request) { + ns, err := namespace.FromContext(ctx) + if err != nil { + return + } + req.Header.Add(namespace.NamespaceHeaderName, ns) +} diff --git a/pkg/engine/client/client.go b/pkg/engine/client/client.go index b631283f6..e1a376d58 100644 --- a/pkg/engine/client/client.go +++ b/pkg/engine/client/client.go @@ -1,183 +1,19 @@ package client import ( - "context" - "fmt" - - "capact.io/capact/internal/k8s-engine/graphql/namespace" - enginegraphql "capact.io/capact/pkg/engine/api/graphql" - "github.com/machinebox/graphql" - "github.com/pkg/errors" ) // Client used to communicate with the Capact Engine GraphQL API type Client struct { - client *graphql.Client + Action + Policy } // New returns a new Client instance. func New(gqlClient *graphql.Client) *Client { return &Client{ - client: gqlClient, - } -} - -// CreateAction creates Action in the Namespace extracted from a given ctx. -func (c *Client) CreateAction(ctx context.Context, in *enginegraphql.ActionDetailsInput) (*enginegraphql.Action, error) { - req := graphql.NewRequest(fmt.Sprintf(`mutation($in: ActionDetailsInput) { - createAction( - in: $in - ) { - %s - } - }`, actionFields)) - - c.enrichWithNamespace(ctx, req) - req.Var("in", in) - - var resp struct { - Action enginegraphql.Action `json:"createAction"` - } - if err := c.client.Run(ctx, req, &resp); err != nil { - return nil, errors.Wrap(err, "while executing mutation to create Action") - } - - return &resp.Action, nil -} - -// GetAction returns Action with a given name from Namespace extracted from a given ctx. -func (c *Client) GetAction(ctx context.Context, name string) (*enginegraphql.Action, error) { - req := graphql.NewRequest(fmt.Sprintf(`query($name: String!) { - action(name: $name) { - %s - } - }`, actionFields)) - - c.enrichWithNamespace(ctx, req) - req.Var("name", name) - - var resp struct { - Action *enginegraphql.Action `json:"action"` - } - if err := c.client.Run(ctx, req, &resp); err != nil { - return nil, errors.Wrap(err, "while executing query to get Action") - } - - return resp.Action, nil -} - -// ListActions returns all Actions which meet filter criteria. -// Namespace extracted from a given ctx. -func (c *Client) ListActions(ctx context.Context, filter *enginegraphql.ActionFilter) ([]*enginegraphql.Action, error) { - req := graphql.NewRequest(fmt.Sprintf(`query($filter: ActionFilter) { - actions(filter: $filter) { - %s - } - }`, actionFields)) - - c.enrichWithNamespace(ctx, req) - req.Var("filter", filter) - - var resp struct { - Actions []*enginegraphql.Action `json:"actions"` - } - if err := c.client.Run(ctx, req, &resp); err != nil { - return nil, errors.Wrap(err, "while executing query to get Action") - } - - return resp.Actions, nil -} - -// RunAction executes a given Action. -func (c *Client) RunAction(ctx context.Context, name string) error { - req := graphql.NewRequest(fmt.Sprintf(`mutation($name: String!) { - runAction( - name: $name - ) { - %s - } - }`, actionFields)) - - c.enrichWithNamespace(ctx, req) - req.Var("name", name) - - var resp struct { - Action enginegraphql.Action `json:"runAction"` - } - if err := c.client.Run(ctx, req, &resp); err != nil { - return errors.Wrap(err, "while executing mutation to run Action") - } - - return nil -} - -// DeleteAction deletes a given Action. -func (c *Client) DeleteAction(ctx context.Context, name string) error { - req := graphql.NewRequest(fmt.Sprintf(`mutation($name: String!) { - deleteAction( - name: $name - ) { - %s - } - }`, actionFields)) - - c.enrichWithNamespace(ctx, req) - req.Var("name", name) - - var resp struct { - Action enginegraphql.Action `json:"deleteAction"` - } - if err := c.client.Run(ctx, req, &resp); err != nil { - return errors.Wrap(err, "while executing mutation to delete Action") - } - - return nil -} - -// UpdatePolicy updates Capact Policy on cluster side. -func (c *Client) UpdatePolicy(ctx context.Context, policy *enginegraphql.PolicyInput) (*enginegraphql.Policy, error) { - req := graphql.NewRequest(fmt.Sprintf(`mutation($in: PolicyInput!) { - updatePolicy( - in: $in - ) { - %s - } - }`, policyFields)) - req.Var("in", policy) - - var resp struct { - Policy *enginegraphql.Policy `json:"updatePolicy"` - } - if err := c.client.Run(ctx, req, nil); err != nil { - return nil, errors.Wrap(err, "while executing mutation to update Policy") - } - - return resp.Policy, nil -} - -// GetPolicy returns current Capact Policy. -func (c *Client) GetPolicy(ctx context.Context) (*enginegraphql.Policy, error) { - req := graphql.NewRequest(fmt.Sprintf(`query { - policy{ - %s - } - }`, policyFields)) - - var resp struct { - Policy *enginegraphql.Policy `json:"policy"` - } - if err := c.client.Run(ctx, req, &resp); err != nil { - return nil, errors.Wrap(err, "while executing query to get Policy") - } - - return resp.Policy, nil -} - -func (c *Client) enrichWithNamespace(ctx context.Context, req *graphql.Request) { - ns, err := namespace.FromContext(ctx) - if err != nil { - return + Action: Action{client: gqlClient}, + Policy: Policy{client: gqlClient}, } - req.Header.Add(namespace.NamespaceHeaderName, ns) } diff --git a/pkg/engine/client/fields.go b/pkg/engine/client/fields.go index 8653d349d..d5523cde4 100644 --- a/pkg/engine/client/fields.go +++ b/pkg/engine/client/fields.go @@ -18,6 +18,10 @@ var actionFields = fmt.Sprintf(` output { typeInstances { id + backend { + id + abstract + } typeRef { path revision @@ -104,4 +108,16 @@ const policyFields = ` } } } + typeInstance { + rules { + typeRef { + path + revision + } + backend { + id + description + } + } + } ` diff --git a/pkg/engine/client/policy.go b/pkg/engine/client/policy.go new file mode 100644 index 000000000..2bc9b768c --- /dev/null +++ b/pkg/engine/client/policy.go @@ -0,0 +1,55 @@ +package client + +import ( + "context" + "fmt" + + gqlengine "capact.io/capact/pkg/engine/api/graphql" + + "github.com/machinebox/graphql" + "github.com/pkg/errors" +) + +// Policy knows how to execute GraphQL queries and mutations for Policy type. +type Policy struct { + client *graphql.Client +} + +// UpdatePolicy updates Capact Policy on cluster side. +func (c *Policy) UpdatePolicy(ctx context.Context, policy *gqlengine.PolicyInput) (*gqlengine.Policy, error) { + req := graphql.NewRequest(fmt.Sprintf(`mutation($in: PolicyInput!) { + updatePolicy( + in: $in + ) { + %s + } + }`, policyFields)) + req.Var("in", policy) + + var resp struct { + Policy *gqlengine.Policy `json:"updatePolicy"` + } + if err := c.client.Run(ctx, req, nil); err != nil { + return nil, errors.Wrap(err, "while executing mutation to update Policy") + } + + return resp.Policy, nil +} + +// GetPolicy returns current Capact Policy. +func (c *Policy) GetPolicy(ctx context.Context) (*gqlengine.Policy, error) { + req := graphql.NewRequest(fmt.Sprintf(`query { + policy{ + %s + } + }`, policyFields)) + + var resp struct { + Policy *gqlengine.Policy `json:"policy"` + } + if err := c.client.Run(ctx, req, &resp); err != nil { + return nil, errors.Wrap(err, "while executing query to get Policy") + } + + return resp.Policy, nil +} diff --git a/pkg/engine/k8s/api/v1alpha1/action_types.go b/pkg/engine/k8s/api/v1alpha1/action_types.go index 0b86db04d..3deb3248b 100644 --- a/pkg/engine/k8s/api/v1alpha1/action_types.go +++ b/pkg/engine/k8s/api/v1alpha1/action_types.go @@ -388,6 +388,15 @@ type OutputTypeInstanceDetails struct { // TypeRef contains data needed to resolve Type manifest. TypeRef *ManifestReference `json:"typeReference"` + + // Backend contains information in which backend this TypeInstance is stored. + Backend TypeInstanceBackend `json:"backend"` +} + +// TypeInstanceBackend holds information about TypeInstance backend. +type TypeInstanceBackend struct { + ID string `json:"id"` + Abstract bool `json:"abstract"` } // InputTypeInstanceToProvide describes optional input TypeInstance for advanced rendering mode iteration. diff --git a/pkg/engine/k8s/api/v1alpha1/zz_generated.deepcopy.go b/pkg/engine/k8s/api/v1alpha1/zz_generated.deepcopy.go index c83d930ce..9e7ee9b03 100644 --- a/pkg/engine/k8s/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/engine/k8s/api/v1alpha1/zz_generated.deepcopy.go @@ -361,6 +361,7 @@ func (in *OutputTypeInstanceDetails) DeepCopyInto(out *OutputTypeInstanceDetails *out = new(ManifestReference) (*in).DeepCopyInto(*out) } + out.Backend = in.Backend } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OutputTypeInstanceDetails. @@ -502,3 +503,18 @@ func (in *RunnerStatus) DeepCopy() *RunnerStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TypeInstanceBackend) DeepCopyInto(out *TypeInstanceBackend) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeInstanceBackend. +func (in *TypeInstanceBackend) DeepCopy() *TypeInstanceBackend { + if in == nil { + return nil + } + out := new(TypeInstanceBackend) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/engine/k8s/policy/fixtures_test.go b/pkg/engine/k8s/policy/fixtures_test.go index f94c9a0e1..f3b3e7825 100644 --- a/pkg/engine/k8s/policy/fixtures_test.go +++ b/pkg/engine/k8s/policy/fixtures_test.go @@ -9,7 +9,7 @@ import ( func fixPolicyWithTypeRef() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.*", @@ -20,22 +20,22 @@ func fixPolicyWithTypeRef() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id", - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.sample", - Revision: "0.1.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.sample", + Revision: "0.1.0", + }, }, }, { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id2", Description: ptr.String("ID 2"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.sample2", - Revision: "0.2.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.sample2", + Revision: "0.2.0", + }, }, }, }, diff --git a/pkg/engine/k8s/policy/from_yaml_test.go b/pkg/engine/k8s/policy/from_yaml_test.go index 38b4c96df..138704563 100644 --- a/pkg/engine/k8s/policy/from_yaml_test.go +++ b/pkg/engine/k8s/policy/from_yaml_test.go @@ -48,7 +48,7 @@ func loadInput(t *testing.T, path string) string { func fixValidPolicy() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -77,7 +77,7 @@ func fixValidPolicy() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "sample-uuid", Description: ptr.String("Google Cloud Platform Service Account"), }, diff --git a/pkg/engine/k8s/policy/metadata/fixtures_test.go b/pkg/engine/k8s/policy/metadata/fixtures_test.go index 3ff0a27b8..da34d40a7 100644 --- a/pkg/engine/k8s/policy/metadata/fixtures_test.go +++ b/pkg/engine/k8s/policy/metadata/fixtures_test.go @@ -3,19 +3,23 @@ package metadata_test import ( "context" "fmt" + "regexp" "strings" "capact.io/capact/internal/ptr" "capact.io/capact/pkg/engine/k8s/policy" gqllocalapi "capact.io/capact/pkg/hub/api/graphql/local" + hubpublicgraphql "capact.io/capact/pkg/hub/api/graphql/public" + "capact.io/capact/pkg/hub/client/public" "capact.io/capact/pkg/sdk/apis/0.0.1/types" + "github.com/pkg/errors" ) func fixComplexPolicyWithoutTypeRef() *policy.Policy { return &policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.*", @@ -26,12 +30,12 @@ func fixComplexPolicyWithoutTypeRef() *policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id1", }, }, { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id2", Description: ptr.String("ID 2"), }, @@ -58,7 +62,7 @@ func fixComplexPolicyWithoutTypeRef() *policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id4", }, }, @@ -91,7 +95,7 @@ func fixComplexPolicyWithoutTypeRef() *policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id7", }, }, @@ -110,13 +114,50 @@ func fixComplexPolicyWithoutTypeRef() *policy.Policy { }, }, }, + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id9", + Description: ptr.String("ID 9"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id10", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id11", + Description: ptr.String("ID 11"), + }, + }, + }, + }, + }, } } func fixComplexPolicyWithTypeRef() *policy.Policy { return &policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.*", @@ -127,22 +168,22 @@ func fixComplexPolicyWithTypeRef() *policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id1", - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.type1", - Revision: "0.1.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.type1", + Revision: "0.1.0", + }, }, }, { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id2", Description: ptr.String("ID 2"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.type2", - Revision: "0.2.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.type2", + Revision: "0.2.0", + }, }, }, }, @@ -175,12 +216,12 @@ func fixComplexPolicyWithTypeRef() *policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id4", - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.type4", - Revision: "0.4.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.type4", + Revision: "0.4.0", + }, }, }, }, @@ -220,12 +261,12 @@ func fixComplexPolicyWithTypeRef() *policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id7", - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.type7", - Revision: "0.7.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.type7", + Revision: "0.7.0", + }, }, }, }, @@ -247,6 +288,55 @@ func fixComplexPolicyWithTypeRef() *policy.Policy { }, }, }, + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id9", + Description: ptr.String("ID 9"), + TypeRef: &types.TypeRef{ + Path: "cap.type.type9", + Revision: "0.9.0", + }, + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id10", + TypeRef: &types.TypeRef{ + Path: "cap.type.type10", + Revision: "0.10.0", + }, + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "id11", + Description: ptr.String("ID 11"), + TypeRef: &types.TypeRef{ + Path: "cap.type.type11", + Revision: "0.11.0", + }, + }, + }, + }, + }, + }, } } @@ -284,3 +374,40 @@ func (f *fakeHub) FindTypeInstancesTypeRef(_ context.Context, ids []string) (map return res, nil } + +func (f *fakeHub) ListTypes(_ context.Context, opts ...public.TypeOption) ([]*hubpublicgraphql.Type, error) { + if !f.ShouldRun { + return nil, errors.New("shouldn't run") + } + + var allTypes []*hubpublicgraphql.Type + for id := 0; id < f.ExpectedIDLen; id++ { + allTypes = append(allTypes, &hubpublicgraphql.Type{ + Path: fmt.Sprintf("cap.type.type%d", id), + Revision: &hubpublicgraphql.TypeRevision{ + Revision: fmt.Sprintf("0.%d.0", id), + }, + }) + } + + typeOpts := &public.TypeOptions{} + typeOpts.Apply(opts...) + + if typeOpts.Filter.PathPattern == nil { + return allTypes, nil + } + + var out []*hubpublicgraphql.Type + for _, item := range allTypes { + matched, err := regexp.MatchString(*typeOpts.Filter.PathPattern, item.Path) + if err != nil { + return nil, err + } + if !matched { + continue + } + out = append(out, item) + } + + return out, nil +} diff --git a/pkg/engine/k8s/policy/metadata/metadata.go b/pkg/engine/k8s/policy/metadata/metadata.go index 6485394e5..be9185317 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" + backendTypeInstance typeInstanceKind = "BackendTypeInstance" ) // TypeInstanceMetadata defines metadata for required and additional TypeInstances defined in Policy. @@ -55,6 +56,19 @@ func TypeInstanceIDsWithUnresolvedMetadataForPolicy(in policy.Policy) []TypeInst } } + // TypeInstances backends + for _, rule := range in.TypeInstance.Rules { + if rule.Backend.TypeRef != nil && rule.Backend.TypeRef.Path != "" && rule.Backend.TypeRef.Revision != "" { + continue + } + + tis = append(tis, TypeInstanceMetadata{ + ID: rule.Backend.ID, + Description: rule.Backend.Description, + Kind: backendTypeInstance, + }) + } + return tis } diff --git a/pkg/engine/k8s/policy/metadata/metadata_resolver.go b/pkg/engine/k8s/policy/metadata/metadata_resolver.go index ea94ace28..9f41f5021 100644 --- a/pkg/engine/k8s/policy/metadata/metadata_resolver.go +++ b/pkg/engine/k8s/policy/metadata/metadata_resolver.go @@ -3,19 +3,23 @@ package metadata import ( "context" "fmt" + "strings" "capact.io/capact/internal/multierror" - "capact.io/capact/pkg/engine/k8s/policy" hublocalgraphql "capact.io/capact/pkg/hub/api/graphql/local" + hubpublicgraphql "capact.io/capact/pkg/hub/api/graphql/public" + "capact.io/capact/pkg/hub/client/public" "capact.io/capact/pkg/sdk/apis/0.0.1/types" multierr "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" ) // HubClient defines Hub client which is able to find TypeInstance Type references. type HubClient interface { FindTypeInstancesTypeRef(ctx context.Context, ids []string) (map[string]hublocalgraphql.TypeInstanceTypeReference, error) + ListTypes(ctx context.Context, opts ...public.TypeOption) ([]*hubpublicgraphql.Type, error) } // Resolver resolves Policy metadata against Hub. @@ -49,7 +53,7 @@ func (r *Resolver) ResolveTypeInstanceMetadata(ctx context.Context, policy *poli return nil } - res, err := r.hubCli.FindTypeInstancesTypeRef(ctx, idsToQuery) + resolvedTypeRefs, err := r.hubCli.FindTypeInstancesTypeRef(ctx, idsToQuery) if err != nil { return errors.Wrap(err, "while finding TypeRef for TypeInstances") } @@ -57,7 +61,7 @@ func (r *Resolver) ResolveTypeInstanceMetadata(ctx context.Context, policy *poli // verify if all TypeInstances are resolved multiErr := multierror.New() for _, ti := range unresolvedTIs { - if typeRef, exists := res[ti.ID]; exists && typeRef.Path != "" && typeRef.Revision != "" { + if typeRef, exists := resolvedTypeRefs[ti.ID]; exists && typeRef.Path != "" && typeRef.Revision != "" { continue } @@ -67,13 +71,51 @@ func (r *Resolver) ResolveTypeInstanceMetadata(ctx context.Context, policy *poli return multiErr } - r.setTypeRefsForRequiredTypeInstances(policy, res) - r.setTypeRefsForAdditionalTypeInstances(policy, res) + typeRefWithParentNodes, err := r.enrichWithParentNodes(ctx, resolvedTypeRefs) + if err != nil { + return errors.Wrap(err, "while resolving parent nodes for TypeRefs") + } + + r.setTypeRefsForAdditionalTypeInstances(policy, typeRefWithParentNodes) + r.setTypeRefsForRequiredTypeInstances(policy, typeRefWithParentNodes) + r.setTypeRefsForBackendTypeInstances(policy, typeRefWithParentNodes) return nil } -func (r *Resolver) setTypeRefsForRequiredTypeInstances(policy *policy.Policy, typeRefs map[string]hublocalgraphql.TypeInstanceTypeReference) { +// TypeRefWithAdditionalRefs holds TypeRef associated with its additional references (parent nodes). +type TypeRefWithAdditionalRefs struct { + types.TypeRef + AdditionalRefs []string +} + +func (r *Resolver) enrichWithParentNodes(ctx context.Context, refs map[string]hublocalgraphql.TypeInstanceTypeReference) (map[string]TypeRefWithAdditionalRefs, error) { + typesPath := r.mapToTypeRefs(refs) + gotAttachedTypes, err := public.ListAdditionalRefs(ctx, r.hubCli, typesPath) + if err != nil { + return nil, errors.Wrap(err, "while fetching Types") + } + + out := map[string]TypeRefWithAdditionalRefs{} + for id, ref := range refs { + out[id] = TypeRefWithAdditionalRefs{ + TypeRef: types.TypeRef(ref), + AdditionalRefs: gotAttachedTypes[types.TypeRef(ref)], + } + } + + return out, nil +} + +func (r *Resolver) mapToTypeRefs(in map[string]hublocalgraphql.TypeInstanceTypeReference) []types.TypeRef { + var out []types.TypeRef + for _, expType := range in { + out = append(out, types.TypeRef(expType)) + } + return out +} + +func (r *Resolver) setTypeRefsForRequiredTypeInstances(policy *policy.Policy, typeRefs map[string]TypeRefWithAdditionalRefs) { for ruleIdx, rule := range policy.Interface.Rules { for ruleItemIdx, ruleItem := range rule.OneOf { if ruleItem.Inject == nil { @@ -85,16 +127,17 @@ func (r *Resolver) setTypeRefsForRequiredTypeInstances(policy *policy.Policy, ty continue } - policy.Interface.Rules[ruleIdx].OneOf[ruleItemIdx].Inject.RequiredTypeInstances[reqTIIdx].TypeRef = &types.ManifestRef{ + policy.Interface.Rules[ruleIdx].OneOf[ruleItemIdx].Inject.RequiredTypeInstances[reqTIIdx].TypeRef = &types.TypeRef{ Path: typeRef.Path, Revision: typeRef.Revision, } + policy.Interface.Rules[ruleIdx].OneOf[ruleItemIdx].Inject.RequiredTypeInstances[reqTIIdx].ExtendsHubStorage = r.isExtendingHubStorage(typeRef) } } } } -func (r *Resolver) setTypeRefsForAdditionalTypeInstances(policy *policy.Policy, typeRefs map[string]hublocalgraphql.TypeInstanceTypeReference) { +func (r *Resolver) setTypeRefsForAdditionalTypeInstances(policy *policy.Policy, typeRefs map[string]TypeRefWithAdditionalRefs) { for ruleIdx, rule := range policy.Interface.Rules { for ruleItemIdx, ruleItem := range rule.OneOf { if ruleItem.Inject == nil { @@ -114,3 +157,33 @@ func (r *Resolver) setTypeRefsForAdditionalTypeInstances(policy *policy.Policy, } } } + +func (r *Resolver) setTypeRefsForBackendTypeInstances(policy *policy.Policy, typeRefs map[string]TypeRefWithAdditionalRefs) { + for ruleIdx, rule := range policy.TypeInstance.Rules { + typeRef, exists := typeRefs[rule.Backend.ID] + if !exists { + continue + } + + policy.TypeInstance.Rules[ruleIdx].Backend.TypeRef = &types.TypeRef{ + Path: typeRef.Path, + Revision: typeRef.Revision, + } + policy.TypeInstance.Rules[ruleIdx].Backend.ExtendsHubStorage = r.isExtendingHubStorage(typeRef) + } +} + +func (r *Resolver) isExtendingHubStorage(ref TypeRefWithAdditionalRefs) bool { + // Check if it's a core Hub storage + if strings.HasPrefix(ref.Path, types.HubBackendParentNodeName) { + return true + } + + // if not, check if extends core Hub storage type + for _, ref := range ref.AdditionalRefs { + if ref == types.HubBackendParentNodeName { + return true + } + } + return false +} diff --git a/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go b/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go index 54e8caa74..9d012bf5d 100644 --- a/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go +++ b/pkg/engine/k8s/policy/metadata/metadata_resolver_test.go @@ -37,19 +37,26 @@ func TestResolveTypeInstanceMetadata(t *testing.T) { { Name: "Unresolved TypeRefs", Input: fixComplexPolicyWithoutTypeRef(), - HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 9}, + HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 12}, Expected: fixComplexPolicyWithTypeRef(), }, { - Name: "Partial result", - Input: fixComplexPolicyWithoutTypeRef(), - HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 9, IgnoreIDs: map[string]struct{}{"id2": {}, "id4": {}, "id8": {}}}, + Name: "Partial result", + Input: fixComplexPolicyWithoutTypeRef(), + HubCli: &fakeHub{ShouldRun: true, ExpectedIDLen: 12, IgnoreIDs: map[string]struct{}{ + "id2": {}, "id4": {}, // required + "id8": {}, // additional + "id9": {}, "id11": {}, // backend + }, + }, ExpectedErrMessage: ptr.String( heredoc.Doc(` - 3 errors occurred: + 5 errors occurred: * missing Type reference for RequiredTypeInstance (ID: "id2", description: "ID 2") * missing Type reference for RequiredTypeInstance (ID: "id4") - * missing Type reference for AdditionalTypeInstance (ID: "id8", name: "ID8")`, + * missing Type reference for AdditionalTypeInstance (ID: "id8", name: "ID8") + * missing Type reference for BackendTypeInstance (ID: "id9", description: "ID 9") + * missing Type reference for BackendTypeInstance (ID: "id11", description: "ID 11")`, ), ), }, diff --git a/pkg/engine/k8s/policy/predefined.go b/pkg/engine/k8s/policy/predefined.go index 8b6949420..16b49d002 100644 --- a/pkg/engine/k8s/policy/predefined.go +++ b/pkg/engine/k8s/policy/predefined.go @@ -15,7 +15,7 @@ func NewDenyAll() Policy { func NewAllowAll() Policy { return Policy{ Interface: InterfacePolicy{ - Rules: RulesList{ + Rules: InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.*", diff --git a/pkg/engine/k8s/policy/type_instance.go b/pkg/engine/k8s/policy/type_instance.go new file mode 100644 index 000000000..d081fe738 --- /dev/null +++ b/pkg/engine/k8s/policy/type_instance.go @@ -0,0 +1,121 @@ +package policy + +import ( + "fmt" + + "capact.io/capact/internal/ptr" + "capact.io/capact/pkg/sdk/apis/0.0.1/types" +) + +// maxBackendLookupForTypeRef defines maximum number of iteration to find a matching backend based on TypeRef path pattern. +const maxBackendLookupForTypeRef = 30 + +// TypeInstancePolicy holds the Policy for TypeInstance. +type TypeInstancePolicy struct { + Rules []RulesForTypeInstance `json:"rules"` +} + +// RulesForTypeInstance holds a single policy rule for a TypeInstance. +// +kubebuilder:object:generate=true +type RulesForTypeInstance struct { + TypeRef types.ManifestRefWithOptRevision `json:"typeRef"` + Backend TypeInstanceBackend `json:"backend"` +} + +// TypeInstanceBackend holds a Backend description to be used for storing a given TypeInstance. +// +kubebuilder:object:generate=true +type TypeInstanceBackend struct { + TypeInstanceReference `json:",inline"` +} + +// TypeInstanceBackendCollection knows which Backend should be used for a given TypeInstance based on the TypeRef. +type TypeInstanceBackendCollection struct { + byTypeRef map[string]TypeInstanceBackend + byAlias map[string]TypeInstanceBackend +} + +// SetByTypeRef associates a given TypeRef with a given storage backend instance. +func (t *TypeInstanceBackendCollection) SetByTypeRef(ref types.ManifestRefWithOptRevision, backend TypeInstanceBackend) { + if t.byTypeRef == nil { + t.byTypeRef = map[string]TypeInstanceBackend{} + } + t.byTypeRef[ref.String()] = backend +} + +// GetByTypeRef returns storage backend for a given TypeRef. +// If backend for an explicit TypeRef is not found, the pattern matching is used. +// +// For example, if TypeRef is `cap.type.capactio.examples.message:0.1.0`: +// - cap.type.capactio.examples.*:0.1.0 +// - cap.type.capactio.examples.* +// - cap.type.capactio.*:0.1.0 +// - cap.type.capactio.* +// - cap.type.*:0.1.0 +// - cap.type.* +// - cap.*:0.1.0 +// - cap.* +// +func (t TypeInstanceBackendCollection) GetByTypeRef(typeRef types.TypeRef) (TypeInstanceBackend, bool) { + // 1. Try the explicit TypeRef + key := types.ManifestRefWithOptRevision{ + Path: typeRef.Path, + Revision: ptr.String(typeRef.Revision), + } + backend, found := t.byTypeRef[key.String()] + if found { + return backend, true + } + + // 2. Try to find matching pattern for a given TypeRef. + var ( + subPath = typeRef.Path + iterations = 0 + ) + + for { + if fmt.Sprintf("%s.", subPath) == types.OCFPathPrefix || iterations > maxBackendLookupForTypeRef { + break + } + subPath = types.TrimLastNodeFromOCFPath(subPath) + + keyPatterns := []string{ + fmt.Sprintf("%s.*:%s", subPath, typeRef.Revision), // first try to match with revision + fmt.Sprintf("%s.*", subPath), // later check for path pattern only + } + for _, pattern := range keyPatterns { + backend, found := t.byTypeRef[pattern] + if found { + return backend, true + } + } + iterations++ + } + + return TypeInstanceBackend{}, false +} + +// SetByAlias associates a given alias with a given storage backend instance. +func (t *TypeInstanceBackendCollection) SetByAlias(name string, backend TypeInstanceBackend) { + if t.byAlias == nil { + t.byAlias = map[string]TypeInstanceBackend{} + } + t.byAlias[name] = backend +} + +// GetByAlias returns backend associated with a given alias. +func (t *TypeInstanceBackendCollection) GetByAlias(name string) (TypeInstanceBackend, bool) { + backend, found := t.byAlias[name] + return backend, found +} + +// GetAll returns all registered storage backends both for aliases and TypeRefs. +func (t *TypeInstanceBackendCollection) GetAll() map[string]TypeInstanceBackend { + out := map[string]TypeInstanceBackend{} + for k, v := range t.byAlias { + out[k] = v + } + for k, v := range t.byTypeRef { + out[k] = v + } + return out +} diff --git a/pkg/engine/k8s/policy/type_instance_test.go b/pkg/engine/k8s/policy/type_instance_test.go new file mode 100644 index 000000000..7040d287f --- /dev/null +++ b/pkg/engine/k8s/policy/type_instance_test.go @@ -0,0 +1,147 @@ +package policy_test + +import ( + "strings" + "testing" + + "capact.io/capact/internal/ptr" + "capact.io/capact/pkg/engine/k8s/policy" + "capact.io/capact/pkg/sdk/apis/0.0.1/types" + + "github.com/stretchr/testify/assert" +) + +func TestTypeInstanceBackendCollection_GetByTypeRef(t *testing.T) { + data := policy.TypeInstanceBackendCollection{} + + data.SetByTypeRef(fixTypeRef("cap.type.capactio.examples.message:0.1.0"), fixTypeInstanceBackend("ID1")) + data.SetByTypeRef(fixTypeRef("cap.type.capactio.examples.*"), fixTypeInstanceBackend("ID2")) + data.SetByTypeRef(fixTypeRef("cap.type.capactio.examples.*:0.2.0"), fixTypeInstanceBackend("ID3")) + data.SetByTypeRef(fixTypeRef("cap.*"), fixTypeInstanceBackend("ID4")) + data.SetByTypeRef(fixTypeRef("cap.*:0.1.0"), fixTypeInstanceBackend("ID5")) + + data.SetByAlias("aws-secret-manager", fixTypeInstanceBackend("ID333")) // ensure that Alias does not affect proper selection + + tests := map[string]struct { + givenTypeRef types.TypeRef + expBackend policy.TypeInstanceBackend + expFound bool + }{ + "Should match exact type ref": { + givenTypeRef: types.TypeRef{ + Path: "cap.type.capactio.examples.message", + Revision: "0.1.0", + }, + expFound: true, + expBackend: fixTypeInstanceBackend("ID1"), + }, + "Should match pattern cap.type.capactio.examples.*": { + givenTypeRef: types.TypeRef{ + Path: "cap.type.capactio.examples.other-that-message", + Revision: "0.1.0", + }, + expFound: true, + expBackend: fixTypeInstanceBackend("ID2"), + }, + "Should match pattern cap.type.capactio.examples.*:0.2.0": { + givenTypeRef: types.TypeRef{ + Path: "cap.type.capactio.examples.other-that-message", + Revision: "0.2.0", + }, + expFound: true, + expBackend: fixTypeInstanceBackend("ID3"), + }, + "Should match generic cap.* with revision": { + givenTypeRef: types.TypeRef{ + Path: "cap.type.aws.examples", + Revision: "0.1.0", + }, + expFound: true, + expBackend: fixTypeInstanceBackend("ID5"), + }, + "Should match generic cap.* with different revision": { + givenTypeRef: types.TypeRef{ + Path: "cap.type.aws.examples", + Revision: "0.2.0", + }, + expFound: true, + expBackend: fixTypeInstanceBackend("ID4"), + }, + + "Should not found backend for unknown path": { + givenTypeRef: types.TypeRef{ + Path: "some.not.registered.type.ref", + Revision: "0.2.0", + }, + expFound: false, + }, + } + for tn, tc := range tests { + t.Run(tn, func(t *testing.T) { + gotBackend, gotFound := data.GetByTypeRef(tc.givenTypeRef) + + assert.Equal(t, tc.expBackend, gotBackend) + assert.Equal(t, tc.expFound, gotFound) + }) + } +} + +func TestTypeInstanceBackendCollection_GetByAlias(t *testing.T) { + data := policy.TypeInstanceBackendCollection{} + + data.SetByTypeRef(fixTypeRef("cap.type.capactio.examples.message:0.1.0"), fixTypeInstanceBackend("ID1")) // ensure that TypeRef does not affect proper selection + data.SetByAlias("helm-storage", fixTypeInstanceBackend("ID2")) + data.SetByAlias("aws-secret-manager", fixTypeInstanceBackend("ID3")) + + tests := map[string]struct { + givenAlias string + + expFound bool + expBackend policy.TypeInstanceBackend + }{ + "Should match helm-storage": { + givenAlias: "helm-storage", + + expFound: true, + expBackend: fixTypeInstanceBackend("ID2"), + }, + "Should match aws-secret-manager": { + givenAlias: "aws-secret-manager", + + expFound: true, + expBackend: fixTypeInstanceBackend("ID3"), + }, + "Should not found valut": { + givenAlias: "vault", + + expFound: false, + }, + } + for tn, tc := range tests { + t.Run(tn, func(t *testing.T) { + gotBackend, found := data.GetByAlias(tc.givenAlias) + assert.Equal(t, tc.expFound, found) + assert.Equal(t, tc.expBackend, gotBackend) + }) + } +} + +func fixTypeRef(in string) types.ManifestRefWithOptRevision { + var rev *string + parts := strings.Split(in, ":") + if len(parts) > 1 { + rev = ptr.String(parts[1]) + } + return types.ManifestRefWithOptRevision{ + Path: parts[0], + Revision: rev, + } +} + +func fixTypeInstanceBackend(id string) policy.TypeInstanceBackend { + return policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: id, + }, + } +} diff --git a/pkg/engine/k8s/policy/types.go b/pkg/engine/k8s/policy/types.go index 8f0915e24..327059670 100644 --- a/pkg/engine/k8s/policy/types.go +++ b/pkg/engine/k8s/policy/types.go @@ -30,19 +30,20 @@ const ( // Policy holds the policy properties. type Policy struct { - Interface InterfacePolicy `json:"interface"` + Interface InterfacePolicy `json:"interface"` + TypeInstance TypeInstancePolicy `json:"typeInstance"` } // InterfacePolicy holds the Policy for Interfaces. type InterfacePolicy struct { - Rules RulesList `json:"rules"` + Rules InterfaceRulesList `json:"rules"` } // ActionPolicy holds the Policy injected during Action creation properties. type ActionPolicy Policy -// RulesList holds the list of the rules in the policy. -type RulesList []RulesForInterface +// InterfaceRulesList holds the list of the rules in the Interface policy. +type InterfaceRulesList []RulesForInterface // RulesForInterface holds a single policy rule for an Interface. // +kubebuilder:object:generate=true @@ -110,33 +111,24 @@ type ImplementationConstraints struct { // RequiredTypeInstanceToInject holds a RequiredTypeInstances to be injected to the Action. // +kubebuilder:object:generate=true type RequiredTypeInstanceToInject struct { - // RequiredTypeInstanceReference is a reference to TypeInstance provided by user. - RequiredTypeInstanceReference `json:",inline"` - - // TypeRef refers to a given Type. - TypeRef *types.ManifestRef `json:"typeRef"` + TypeInstanceReference `json:",inline"` } -// RequiredTypeInstanceReference is a reference to TypeInstance provided by user. +// TypeInstanceReference holds TypeInstance ID with TypeRef that is resolved in runtime. // +kubebuilder:object:generate=true -type RequiredTypeInstanceReference struct { +type TypeInstanceReference struct { // ID is the TypeInstance identifier. ID string `json:"id"` - // Description contains user's description for a given RequiredTypeInstanceToInject. + // Description contains user's description for a given TypeInstance. Description *string `json:"description,omitempty"` -} -// UnmarshalJSON unmarshalls RequiredTypeInstanceToInject from bytes. It ignores all fields apart from RequiredTypeInstanceReference files. -func (in *RequiredTypeInstanceToInject) UnmarshalJSON(bytes []byte) error { - var out RequiredTypeInstanceReference - if err := json.Unmarshal(bytes, &out); err != nil { - return err - } + // TypeRef refers to a given Type. Ignores if present in Policy as this is resolved only in runtime + // based on real data stored in Hub. + TypeRef *types.TypeRef `json:"-"` - in.RequiredTypeInstanceReference = out - - return nil + // ExtendsHubStorage must be set to `true` if TypeRef is a child of `cap.core.type.hub.storage` node. + ExtendsHubStorage bool `json:"-"` } // AdditionalTypeInstanceToInject is used to represent additional TypeInstance injection for a given Implementation. diff --git a/pkg/engine/k8s/policy/types_test.go b/pkg/engine/k8s/policy/types_test.go index 045c280f8..67e2f34ac 100644 --- a/pkg/engine/k8s/policy/types_test.go +++ b/pkg/engine/k8s/policy/types_test.go @@ -26,22 +26,22 @@ func TestRule_RequiredTypeInstancesToInject(t *testing.T) { Input: fixPolicyWithTypeRef().Interface.Rules[0].OneOf[0], Expected: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id", - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.sample", - Revision: "0.1.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.sample", + Revision: "0.1.0", + }, }, }, { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id2", Description: ptr.String("ID 2"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.sample2", - Revision: "0.2.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.sample2", + Revision: "0.2.0", + }, }, }, }, diff --git a/pkg/engine/k8s/policy/workflow_test.go b/pkg/engine/k8s/policy/workflow_test.go index f07d47776..0b160c8a7 100644 --- a/pkg/engine/k8s/policy/workflow_test.go +++ b/pkg/engine/k8s/policy/workflow_test.go @@ -54,7 +54,7 @@ func policyWithAdditionalInput(input map[string]interface{}) Policy { implementation := "cap.implementation.bitnami.postgresql.install" return Policy{ Interface: InterfacePolicy{ - Rules: RulesList{ + Rules: InterfaceRulesList{ RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", diff --git a/pkg/engine/k8s/policy/zz_generated.deepcopy.go b/pkg/engine/k8s/policy/zz_generated.deepcopy.go index b298356d1..e6c5d5e17 100644 --- a/pkg/engine/k8s/policy/zz_generated.deepcopy.go +++ b/pkg/engine/k8s/policy/zz_generated.deepcopy.go @@ -87,35 +87,10 @@ func (in *ImplementationConstraints) DeepCopy() *ImplementationConstraints { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RequiredTypeInstanceReference) DeepCopyInto(out *RequiredTypeInstanceReference) { - *out = *in - if in.Description != nil { - in, out := &in.Description, &out.Description - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequiredTypeInstanceReference. -func (in *RequiredTypeInstanceReference) DeepCopy() *RequiredTypeInstanceReference { - if in == nil { - return nil - } - out := new(RequiredTypeInstanceReference) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RequiredTypeInstanceToInject) DeepCopyInto(out *RequiredTypeInstanceToInject) { *out = *in - in.RequiredTypeInstanceReference.DeepCopyInto(&out.RequiredTypeInstanceReference) - if in.TypeRef != nil { - in, out := &in.TypeRef, &out.TypeRef - *out = new(types.ManifestRef) - **out = **in - } + in.TypeInstanceReference.DeepCopyInto(&out.TypeInstanceReference) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequiredTypeInstanceToInject. @@ -170,3 +145,61 @@ func (in *RulesForInterface) DeepCopy() *RulesForInterface { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RulesForTypeInstance) DeepCopyInto(out *RulesForTypeInstance) { + *out = *in + in.TypeRef.DeepCopyInto(&out.TypeRef) + in.Backend.DeepCopyInto(&out.Backend) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RulesForTypeInstance. +func (in *RulesForTypeInstance) DeepCopy() *RulesForTypeInstance { + if in == nil { + return nil + } + out := new(RulesForTypeInstance) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TypeInstanceBackend) DeepCopyInto(out *TypeInstanceBackend) { + *out = *in + in.TypeInstanceReference.DeepCopyInto(&out.TypeInstanceReference) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeInstanceBackend. +func (in *TypeInstanceBackend) DeepCopy() *TypeInstanceBackend { + if in == nil { + return nil + } + out := new(TypeInstanceBackend) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TypeInstanceReference) DeepCopyInto(out *TypeInstanceReference) { + *out = *in + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.TypeRef != nil { + in, out := &in.TypeRef, &out.TypeRef + *out = new(types.TypeRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeInstanceReference. +func (in *TypeInstanceReference) DeepCopy() *TypeInstanceReference { + if in == nil { + return nil + } + out := new(TypeInstanceReference) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/hub/api/graphql/local/models_gen.go b/pkg/hub/api/graphql/local/models_gen.go index 6936e66de..bf9d71857 100644 --- a/pkg/hub/api/graphql/local/models_gen.go +++ b/pkg/hub/api/graphql/local/models_gen.go @@ -32,6 +32,8 @@ type CreateTypeInstanceInput struct { TypeRef *TypeInstanceTypeReferenceInput `json:"typeRef"` Attributes []*AttributeReferenceInput `json:"attributes"` Value interface{} `json:"value"` + // If not provided, TypeInstance value is stored as static value in Local Hub core storage. + Backend *TypeInstanceBackendInput `json:"backend"` } type CreateTypeInstanceOutput struct { @@ -56,6 +58,7 @@ type TypeInstance struct { TypeRef *TypeInstanceTypeReference `json:"typeRef"` Uses []*TypeInstance `json:"uses"` UsedBy []*TypeInstance `json:"usedBy"` + Backend *TypeInstanceBackendReference `json:"backend"` LatestResourceVersion *TypeInstanceResourceVersion `json:"latestResourceVersion"` FirstResourceVersion *TypeInstanceResourceVersion `json:"firstResourceVersion"` PreviousResourceVersion *TypeInstanceResourceVersion `json:"previousResourceVersion"` @@ -63,6 +66,15 @@ type TypeInstance struct { ResourceVersions []*TypeInstanceResourceVersion `json:"resourceVersions"` } +type TypeInstanceBackendInput struct { + ID string `json:"id"` +} + +type TypeInstanceBackendReference struct { + ID string `json:"id"` + Abstract bool `json:"abstract"` +} + type TypeInstanceFilter struct { Attributes []*AttributeFilterInput `json:"attributes"` TypeRef *TypeRefFilterInput `json:"typeRef"` diff --git a/pkg/hub/api/graphql/local/schema_gen.go b/pkg/hub/api/graphql/local/schema_gen.go index d49131e73..93402c8f8 100644 --- a/pkg/hub/api/graphql/local/schema_gen.go +++ b/pkg/hub/api/graphql/local/schema_gen.go @@ -72,6 +72,7 @@ type ComplexityRoot struct { } TypeInstance struct { + Backend func(childComplexity int) int FirstResourceVersion func(childComplexity int) int ID func(childComplexity int) int LatestResourceVersion func(childComplexity int) int @@ -84,6 +85,11 @@ type ComplexityRoot struct { Uses func(childComplexity int) int } + TypeInstanceBackendReference struct { + Abstract func(childComplexity int) int + ID func(childComplexity int) int + } + TypeInstanceInstrumentation struct { Health func(childComplexity int) int Metrics func(childComplexity int) int @@ -279,6 +285,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.TypeInstances(childComplexity, args["filter"].(*TypeInstanceFilter)), true + case "TypeInstance.backend": + if e.complexity.TypeInstance.Backend == nil { + break + } + + return e.complexity.TypeInstance.Backend(childComplexity), true + case "TypeInstance.firstResourceVersion": if e.complexity.TypeInstance.FirstResourceVersion == nil { break @@ -354,6 +367,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TypeInstance.Uses(childComplexity), true + case "TypeInstanceBackendReference.abstract": + if e.complexity.TypeInstanceBackendReference.Abstract == nil { + break + } + + return e.complexity.TypeInstanceBackendReference.Abstract(childComplexity), true + + case "TypeInstanceBackendReference.id": + if e.complexity.TypeInstanceBackendReference.ID == nil { + break + } + + return e.complexity.TypeInstanceBackendReference.ID(childComplexity), true + case "TypeInstanceInstrumentation.health": if e.complexity.TypeInstanceInstrumentation.Health == nil { break @@ -590,6 +617,7 @@ type TypeInstance { @relation(name: "OF_TYPE", direction: "OUT") uses: [TypeInstance!]! @relation(name: "USES", direction: "OUT") usedBy: [TypeInstance!]! @relation(name: "USES", direction: "IN") + backend: TypeInstanceBackendReference! @relation(name: "STORED_IN", direction: "OUT") latestResourceVersion: TypeInstanceResourceVersion @cypher( @@ -641,6 +669,11 @@ type TypeInstanceResourceVersionSpec { @relation(name: "INSTRUMENTED_WITH", direction: "OUT") } +type TypeInstanceBackendReference { + id: String! + abstract: Boolean! +} + type TypeInstanceTypeReference { path: NodePath! revision: Version! @@ -746,6 +779,10 @@ input TypeInstanceTypeReferenceInput { revision: Version! } +input TypeInstanceBackendInput { + id: String! +} + input CreateTypeInstanceInput { """ Used to define the relationships, between the created TypeInstances @@ -756,6 +793,10 @@ input CreateTypeInstanceInput { typeRef: TypeInstanceTypeReferenceInput! attributes: [AttributeReferenceInput!] value: Any + """ + If not provided, TypeInstance value is stored as static value in Local Hub core storage. + """ + backend: TypeInstanceBackendInput } input TypeInstanceUsesRelationInput { @@ -888,17 +929,41 @@ type Mutation { createTypeInstance(in: CreateTypeInstanceInput!): TypeInstance! @cypher( statement: """ - WITH apoc.convert.toJson($in.value) as value - MERGE (typeRef:TypeInstanceTypeReference {path: $in.typeRef.path, revision: $in.typeRef.revision}) - CREATE (ti:TypeInstance {id: apoc.create.uuid()}) + + // Backend + WITH * + CALL apoc.do.when( + $in.backend.id IS NOT NULL, + ' + WITH false as abstract + RETURN $in.backend.id as id, abstract + ', + ' + // TODO(storage): this should be resolved by Local Hub server during the insertion, not in cypher. + WITH true as abstract + MATCH (backend:TypeInstance)-[:OF_TYPE]->(typeRef {path: "cap.core.type.hub.storage.neo4j"}) + RETURN backend.id as id, abstract + ', + {in: $in} + ) YIELD value as backend + MATCH (backendTI:TypeInstance {id: backend.id}) + CREATE (ti)-[:USES]->(backendTI) + // TODO(storage): It should be taken from the uses relation but we don't have access to the TypeRef.additionalRefs to check + // if a given type is a backend or not. Maybe we will introduce a dedicated property to distinguish them from others. + MERGE (storageRef:TypeInstanceBackendReference {abstract: backend.abstract, id: backendTI.id}) + CREATE (ti)-[:STORED_IN]->(storageRef) + + // TypeRef + MERGE (typeRef:TypeInstanceTypeReference {path: $in.typeRef.path, revision: $in.typeRef.revision}) CREATE (ti)-[:OF_TYPE]->(typeRef) + // Revision CREATE (tir: TypeInstanceResourceVersion {resourceVersion: 1, createdBy: $in.createdBy}) CREATE (ti)-[:CONTAINS]->(tir) CREATE (tir)-[:DESCRIBED_BY]->(metadata: TypeInstanceResourceVersionMetadata) - CREATE (tir)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec {value: value}) + CREATE (tir)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec {value: apoc.convert.toJson($in.value)}) FOREACH (attr in $in.attributes | MERGE (attrRef: AttributeReference {path: attr.path, revision: attr.revision}) @@ -1489,7 +1554,7 @@ func (ec *executionContext) _Mutation_createTypeInstance(ctx context.Context, fi return ec.resolvers.Mutation().CreateTypeInstance(rctx, args["in"].(CreateTypeInstanceInput)) } directive1 := func(ctx context.Context) (interface{}, error) { - statement, err := ec.unmarshalOString2ᚖstring(ctx, "WITH apoc.convert.toJson($in.value) as value\nMERGE (typeRef:TypeInstanceTypeReference {path: $in.typeRef.path, revision: $in.typeRef.revision})\n\nCREATE (ti:TypeInstance {id: apoc.create.uuid()})\nCREATE (ti)-[:OF_TYPE]->(typeRef)\n\nCREATE (tir: TypeInstanceResourceVersion {resourceVersion: 1, createdBy: $in.createdBy})\nCREATE (ti)-[:CONTAINS]->(tir)\n\nCREATE (tir)-[:DESCRIBED_BY]->(metadata: TypeInstanceResourceVersionMetadata)\nCREATE (tir)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec {value: value})\n\nFOREACH (attr in $in.attributes |\n MERGE (attrRef: AttributeReference {path: attr.path, revision: attr.revision})\n CREATE (metadata)-[:CHARACTERIZED_BY]->(attrRef)\n)\n\nRETURN ti") + statement, err := ec.unmarshalOString2ᚖstring(ctx, "CREATE (ti:TypeInstance {id: apoc.create.uuid()})\n\n// Backend\nWITH *\nCALL apoc.do.when(\n $in.backend.id IS NOT NULL,\n '\n WITH false as abstract\n RETURN $in.backend.id as id, abstract\n ',\n '\n // TODO(storage): this should be resolved by Local Hub server during the insertion, not in cypher.\n WITH true as abstract\n MATCH (backend:TypeInstance)-[:OF_TYPE]->(typeRef {path: \"cap.core.type.hub.storage.neo4j\"})\n RETURN backend.id as id, abstract\n ',\n {in: $in}\n) YIELD value as backend\nMATCH (backendTI:TypeInstance {id: backend.id})\nCREATE (ti)-[:USES]->(backendTI)\n// TODO(storage): It should be taken from the uses relation but we don't have access to the TypeRef.additionalRefs to check\n// if a given type is a backend or not. Maybe we will introduce a dedicated property to distinguish them from others.\nMERGE (storageRef:TypeInstanceBackendReference {abstract: backend.abstract, id: backendTI.id})\nCREATE (ti)-[:STORED_IN]->(storageRef)\n\n// TypeRef\nMERGE (typeRef:TypeInstanceTypeReference {path: $in.typeRef.path, revision: $in.typeRef.revision})\nCREATE (ti)-[:OF_TYPE]->(typeRef)\n\n// Revision\nCREATE (tir: TypeInstanceResourceVersion {resourceVersion: 1, createdBy: $in.createdBy})\nCREATE (ti)-[:CONTAINS]->(tir)\n\nCREATE (tir)-[:DESCRIBED_BY]->(metadata: TypeInstanceResourceVersionMetadata)\nCREATE (tir)-[:SPECIFIED_BY]->(spec: TypeInstanceResourceVersionSpec {value: apoc.convert.toJson($in.value)})\n\nFOREACH (attr in $in.attributes |\n MERGE (attrRef: AttributeReference {path: attr.path, revision: attr.revision})\n CREATE (metadata)-[:CHARACTERIZED_BY]->(attrRef)\n)\n\nRETURN ti") if err != nil { return nil, err } @@ -2194,6 +2259,69 @@ func (ec *executionContext) _TypeInstance_usedBy(ctx context.Context, field grap return ec.marshalNTypeInstance2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _TypeInstance_backend(ctx context.Context, field graphql.CollectedField, obj *TypeInstance) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "TypeInstance", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Backend, nil + } + directive1 := func(ctx context.Context) (interface{}, error) { + name, err := ec.unmarshalOString2ᚖstring(ctx, "STORED_IN") + if err != nil { + return nil, err + } + direction, err := ec.unmarshalOString2ᚖstring(ctx, "OUT") + if err != nil { + return nil, err + } + if ec.directives.Relation == nil { + return nil, errors.New("directive relation is not implemented") + } + return ec.directives.Relation(ctx, obj, directive0, name, direction, nil, nil) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*TypeInstanceBackendReference); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *capact.io/capact/pkg/hub/api/graphql/local.TypeInstanceBackendReference`, tmp) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*TypeInstanceBackendReference) + fc.Result = res + return ec.marshalNTypeInstanceBackendReference2ᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceBackendReference(ctx, field.Selections, res) +} + func (ec *executionContext) _TypeInstance_latestResourceVersion(ctx context.Context, field graphql.CollectedField, obj *TypeInstance) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2488,6 +2616,76 @@ func (ec *executionContext) _TypeInstance_resourceVersions(ctx context.Context, return ec.marshalNTypeInstanceResourceVersion2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceResourceVersionᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _TypeInstanceBackendReference_id(ctx context.Context, field graphql.CollectedField, obj *TypeInstanceBackendReference) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "TypeInstanceBackendReference", + 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.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _TypeInstanceBackendReference_abstract(ctx context.Context, field graphql.CollectedField, obj *TypeInstanceBackendReference) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "TypeInstanceBackendReference", + 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.Abstract, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _TypeInstanceInstrumentation_metrics(ctx context.Context, field graphql.CollectedField, obj *TypeInstanceInstrumentation) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4529,6 +4727,14 @@ func (ec *executionContext) unmarshalInputCreateTypeInstanceInput(ctx context.Co if err != nil { return it, err } + case "backend": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("backend")) + it.Backend, err = ec.unmarshalOTypeInstanceBackendInput2ᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceBackendInput(ctx, v) + if err != nil { + return it, err + } } } @@ -4591,6 +4797,26 @@ func (ec *executionContext) unmarshalInputLockTypeInstancesInput(ctx context.Con return it, nil } +func (ec *executionContext) unmarshalInputTypeInstanceBackendInput(ctx context.Context, obj interface{}) (TypeInstanceBackendInput, error) { + var it TypeInstanceBackendInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "id": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + it.ID, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputTypeInstanceFilter(ctx context.Context, obj interface{}) (TypeInstanceFilter, error) { var it TypeInstanceFilter var asMap = obj.(map[string]interface{}) @@ -5027,6 +5253,11 @@ func (ec *executionContext) _TypeInstance(ctx context.Context, sel ast.Selection if out.Values[i] == graphql.Null { invalids++ } + case "backend": + out.Values[i] = ec._TypeInstance_backend(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "latestResourceVersion": out.Values[i] = ec._TypeInstance_latestResourceVersion(ctx, field, obj) case "firstResourceVersion": @@ -5051,6 +5282,38 @@ func (ec *executionContext) _TypeInstance(ctx context.Context, sel ast.Selection return out } +var typeInstanceBackendReferenceImplementors = []string{"TypeInstanceBackendReference"} + +func (ec *executionContext) _TypeInstanceBackendReference(ctx context.Context, sel ast.SelectionSet, obj *TypeInstanceBackendReference) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, typeInstanceBackendReferenceImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("TypeInstanceBackendReference") + case "id": + out.Values[i] = ec._TypeInstanceBackendReference_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "abstract": + out.Values[i] = ec._TypeInstanceBackendReference_abstract(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var typeInstanceInstrumentationImplementors = []string{"TypeInstanceInstrumentation"} func (ec *executionContext) _TypeInstanceInstrumentation(ctx context.Context, sel ast.SelectionSet, obj *TypeInstanceInstrumentation) graphql.Marshaler { @@ -5827,6 +6090,16 @@ func (ec *executionContext) marshalNTypeInstance2ᚖcapactᚗioᚋcapactᚋpkg return ec._TypeInstance(ctx, sel, v) } +func (ec *executionContext) marshalNTypeInstanceBackendReference2ᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceBackendReference(ctx context.Context, sel ast.SelectionSet, v *TypeInstanceBackendReference) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._TypeInstanceBackendReference(ctx, sel, v) +} + func (ec *executionContext) marshalNTypeInstanceInstrumentationMetricsDashboard2ᚕᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceInstrumentationMetricsDashboardᚄ(ctx context.Context, sel ast.SelectionSet, v []*TypeInstanceInstrumentationMetricsDashboard) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -6470,6 +6743,14 @@ func (ec *executionContext) marshalOTypeInstance2ᚖcapactᚗioᚋcapactᚋpkg return ec._TypeInstance(ctx, sel, v) } +func (ec *executionContext) unmarshalOTypeInstanceBackendInput2ᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceBackendInput(ctx context.Context, v interface{}) (*TypeInstanceBackendInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputTypeInstanceBackendInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOTypeInstanceFilter2ᚖcapactᚗioᚋcapactᚋpkgᚋhubᚋapiᚋgraphqlᚋlocalᚐTypeInstanceFilter(ctx context.Context, v interface{}) (*TypeInstanceFilter, error) { if v == nil { return nil, nil diff --git a/pkg/hub/client/local/fields.go b/pkg/hub/client/local/fields.go index 6c4d3d939..b63c4dfd7 100644 --- a/pkg/hub/client/local/fields.go +++ b/pkg/hub/client/local/fields.go @@ -7,6 +7,7 @@ import ( var typeInstancesFieldsRegistry = map[TypeInstancesQueryFields]string{ TypeInstanceRootFields: rootFields, TypeInstanceTypeRefFields: typeRefFields, + TypeInstanceBackendFields: backendFields, TypeInstanceUsesIDField: usesIDField, TypeInstanceUsedByIDField: usedByIDField, TypeInstanceLatestResourceVersionField: latestResourceVersionField, @@ -27,6 +28,12 @@ var ( revision }` + backendFields = ` + backend { + id + abstract + }` + usedByIDField = ` usedBy { id @@ -57,6 +64,8 @@ var ( %s + %s + latestResourceVersion { %s } @@ -64,18 +73,18 @@ var ( firstResourceVersion { %s } - + previousResourceVersion { %s } - + resourceVersions { %s } - + resourceVersion(resourceVersion: 1) { %s - }`, rootFields, typeRefFields, + }`, rootFields, typeRefFields, backendFields, typeInstanceResourceVersion, typeInstanceResourceVersion, typeInstanceResourceVersion, typeInstanceResourceVersion, typeInstanceResourceVersion) ) diff --git a/pkg/hub/client/local/options.go b/pkg/hub/client/local/options.go index 33b6f39cd..358520473 100644 --- a/pkg/hub/client/local/options.go +++ b/pkg/hub/client/local/options.go @@ -4,7 +4,7 @@ import ( "strings" ) -// TypeInstancesQueryFields allows to configure which fields should be returned for TypeInstance query. +// TypeInstancesQueryFields allows configuring which fields should be returned for TypeInstance query. type TypeInstancesQueryFields uint64 const ( @@ -12,6 +12,8 @@ const ( TypeInstanceRootFields TypeInstancesQueryFields = 1 << iota // TypeInstanceTypeRefFields returns TypeInstance's TypeRef fields. TypeInstanceTypeRefFields + // TypeInstanceBackendFields returns TypeInstance's Backend fields. + TypeInstanceBackendFields // TypeInstanceUsedByIDField returns IDs for UsedBy field. TypeInstanceUsedByIDField // TypeInstanceUsesIDField returns IDs for Uses field. diff --git a/pkg/hub/client/policy_enforced_client.go b/pkg/hub/client/policy_enforced_client.go index ee9df9de7..ea5fe79e3 100644 --- a/pkg/hub/client/policy_enforced_client.go +++ b/pkg/hub/client/policy_enforced_client.go @@ -5,14 +5,13 @@ import ( "fmt" "sync" - "capact.io/capact/pkg/engine/k8s/policy/metadata" - "capact.io/capact/pkg/sdk/validation" - "capact.io/capact/pkg/engine/k8s/policy" + "capact.io/capact/pkg/engine/k8s/policy/metadata" hublocalgraphql "capact.io/capact/pkg/hub/api/graphql/local" hubpublicgraphql "capact.io/capact/pkg/hub/api/graphql/public" "capact.io/capact/pkg/hub/client/public" "capact.io/capact/pkg/sdk/apis/0.0.1/types" + "capact.io/capact/pkg/sdk/validation" "github.com/pkg/errors" "sigs.k8s.io/yaml" @@ -25,6 +24,7 @@ type HubClient interface { ListTypeInstancesTypeRef(ctx context.Context) ([]hublocalgraphql.TypeInstanceTypeReference, error) FindInterfaceRevision(ctx context.Context, ref hubpublicgraphql.InterfaceReference, opts ...public.InterfaceRevisionOption) (*hubpublicgraphql.InterfaceRevision, error) FindTypeInstancesTypeRef(ctx context.Context, ids []string) (map[string]hublocalgraphql.TypeInstanceTypeReference, error) + ListTypes(ctx context.Context, opts ...public.TypeOption) ([]*hubpublicgraphql.Type, error) } // PolicyIOValidator defines validator used for PolicyEnforcedClient. @@ -33,7 +33,7 @@ type PolicyIOValidator interface { ValidateTypeInstancesMetadata(in policy.Policy) validation.Result ValidateTypeInstancesMetadataForRule(in policy.Rule) validation.Result ValidateAdditionalTypeInstances(additionalTIsInPolicy []policy.AdditionalTypeInstanceToInject, implRev hubpublicgraphql.ImplementationRevision) validation.Result - IsTypeRefInjectableAndEqualToImplReq(typeRef *types.ManifestRef, reqItem *hubpublicgraphql.ImplementationRequirementItem) bool + IsTypeRefInjectableAndEqualToImplReq(typeRef *types.TypeRef, reqItem *hubpublicgraphql.ImplementationRequirementItem) bool LoadAdditionalInputParametersSchemas(context.Context, hubpublicgraphql.ImplementationRevision) (validation.SchemaCollection, error) ValidateAdditionalInputParameters(ctx context.Context, paramsSchemas validation.SchemaCollection, parameters types.ParametersCollection) (validation.Result, error) } @@ -104,9 +104,55 @@ func (e *PolicyEnforcedClient) ListImplementationRevisionForInterface(ctx contex return implementations, rule, nil } +// 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 { + 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 + inject, err := e.listRequiredTypeInstancesToInjectBasedOnPolicy(rule, implRev) + if err != nil { + return policy.TypeInstanceBackendCollection{}, err + } + + for alias, rule := range inject { + out.SetByAlias(alias, policy.TypeInstanceBackend(rule)) + } + + return out, nil +} + // ListRequiredTypeInstancesToInjectBasedOnPolicy returns the required TypeInstance references, // which have to be injected into the Action, based on the current policy rules. func (e *PolicyEnforcedClient) ListRequiredTypeInstancesToInjectBasedOnPolicy(policyRule policy.Rule, implRev hubpublicgraphql.ImplementationRevision) ([]types.InputTypeInstanceRef, error) { + var typeInstancesToInject []types.InputTypeInstanceRef + inject, err := e.listRequiredTypeInstancesToInjectBasedOnPolicy(policyRule, implRev) + if err != nil { + return nil, err + } + + for alias, typeInstance := range inject { + typeInstancesToInject = append(typeInstancesToInject, types.InputTypeInstanceRef{ + Name: alias, + ID: typeInstance.ID, + }) + } + + return typeInstancesToInject, nil +} + +// requiredTypeInstanceToInject holds required TypeInstances for injection indexed by alias. +type requiredTypeInstanceToInject map[string]policy.RequiredTypeInstanceToInject + +func (e *PolicyEnforcedClient) listRequiredTypeInstancesToInjectBasedOnPolicy(policyRule policy.Rule, implRev hubpublicgraphql.ImplementationRevision) (requiredTypeInstanceToInject, error) { requiredTIs := policyRule.RequiredTypeInstancesToInject() if len(requiredTIs) == 0 { return nil, nil @@ -116,20 +162,18 @@ func (e *PolicyEnforcedClient) ListRequiredTypeInstancesToInjectBasedOnPolicy(po return nil, e.wrapValidationResultError(res.ErrorOrNil(), "while validating Policy rule") } - var typeInstancesToInject []types.InputTypeInstanceRef + typeInstancesToInject := requiredTypeInstanceToInject{} for _, typeInstance := range requiredTIs { alias, found := e.findAliasForTypeInstance(typeInstance, implRev) if !found { // Implementation doesn't require such TypeInstance, skip injecting it continue } - - typeInstanceToInject := types.InputTypeInstanceRef{ - Name: alias, - ID: typeInstance.ID, + if _, found := typeInstancesToInject[alias]; found { + return nil, fmt.Errorf("found duplicated alias %q entry under requires property", alias) } - typeInstancesToInject = append(typeInstancesToInject, typeInstanceToInject) + typeInstancesToInject[alias] = typeInstance } return typeInstancesToInject, nil diff --git a/pkg/hub/client/policy_enforced_client_test.go b/pkg/hub/client/policy_enforced_client_test.go index b8469dafd..458a51402 100644 --- a/pkg/hub/client/policy_enforced_client_test.go +++ b/pkg/hub/client/policy_enforced_client_test.go @@ -79,13 +79,13 @@ func TestPolicyEnforcedClient_ListRequiredTypeInstancesToInjectBasedOnPolicy(t * Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "my-uuid", Description: ptr.String("My UUID"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", - Revision: "0.1.1", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.1", + }, }, }, }, @@ -110,7 +110,7 @@ func TestPolicyEnforcedClient_ListRequiredTypeInstancesToInjectBasedOnPolicy(t * Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "my-uuid", Description: ptr.String("My UUID"), }, @@ -142,13 +142,13 @@ func TestPolicyEnforcedClient_ListRequiredTypeInstancesToInjectBasedOnPolicy(t * Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "my-uuid", Description: ptr.String("My UUID"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", - Revision: "0.1.1", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.1", + }, }, }, }, @@ -469,6 +469,167 @@ func TestPolicyEnforcedClient_ListAdditionalInputToInjectBasedOnPolicy(t *testin } } +func TestPolicyEnforcedClient_ListTypeInstancesBackendsBasedOnPolicy(t *testing.T) { + tests := []struct { + name string + + implRev gqlpublicapi.ImplementationRevision + policyRule policy.Rule + expectedBackends map[string]policy.TypeInstanceBackend + expectedErrMessage *string + globalPolicy policy.Policy + }{ + { + name: "Empty inject in policy rule", + implRev: fixImplementationRevisionWithRequire(gqlpublicapi.ImplementationRequirement{ + AnyOf: []*gqlpublicapi.ImplementationRequirementItem{ + { + TypeRef: &gqlpublicapi.TypeReference{ + Path: "cap.type.gcp.sa", + Revision: "0.1.1", + }, + }, + }, + }), + policyRule: policy.Rule{ + Inject: &policy.InjectData{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{}, + }, + }, + globalPolicy: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.capactio.examples.message", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "ID1", + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.secret-manager.storage", + Revision: "0.1.0", + }, + ExtendsHubStorage: true, + }, + }, + }, + }, + }, + }, + expectedBackends: map[string]policy.TypeInstanceBackend{ + "cap.type.capactio.examples.message": { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "ID1", + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.secret-manager.storage", + Revision: "0.1.0", + }, + ExtendsHubStorage: true, + }}, + }, + }, + { + name: "Inject Helm storage", + implRev: fixImplementationRevisionWithRequire(gqlpublicapi.ImplementationRequirement{ + AllOf: []*gqlpublicapi.ImplementationRequirementItem{ + { + Alias: ptr.String("helm-storage"), + TypeRef: &gqlpublicapi.TypeReference{ + Path: "cap.type.helm.storage", + Revision: "0.1.1", + }, + }, + }, + }), + policyRule: policy.Rule{ + Inject: &policy.InjectData{ + RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ + { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "ID2", + Description: ptr.String("ID2"), + TypeRef: &types.TypeRef{ + Path: "cap.type.helm.storage", + Revision: "0.1.1", + }, + ExtendsHubStorage: true, + }, + }, + }, + }, + }, + globalPolicy: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.capactio.examples.message", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "ID1", + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.secret-manager.storage", + Revision: "0.1.0", + }, + ExtendsHubStorage: true, + }, + }, + }, + }, + }, + }, + expectedBackends: map[string]policy.TypeInstanceBackend{ + "cap.type.capactio.examples.message": { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "ID1", + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.secret-manager.storage", + Revision: "0.1.0", + }, + ExtendsHubStorage: true, + }, + }, + "helm-storage": { + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "ID2", + Description: ptr.String("ID2"), + TypeRef: &types.TypeRef{ + Path: "cap.type.helm.storage", + Revision: "0.1.1", + }, + ExtendsHubStorage: true, + }, + }, + }, + }, + } + for _, test := range tests { + tt := test + t.Run(tt.name, func(t *testing.T) { + // given + hubCli := &fake.FileSystemClient{} + + validator := policyvalidation.NewValidator(hubCli) + cli := client.NewPolicyEnforcedClient(hubCli, validator) + cli.SetGlobalPolicy(tt.globalPolicy) + + // when + actual, err := cli.ListTypeInstancesBackendsBasedOnPolicy(context.Background(), tt.policyRule, tt.implRev) + + // then + if tt.expectedErrMessage != nil { + require.Error(t, err) + assert.EqualError(t, err, *tt.expectedErrMessage) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedBackends, actual.GetAll()) + } + }) + } +} + func fixImplementationRevisionWithRequire(req gqlpublicapi.ImplementationRequirement) gqlpublicapi.ImplementationRevision { impl := fixImplementationRevision("impl", "0.0.1") impl.Spec.Requires = []*gqlpublicapi.ImplementationRequirement{ diff --git a/pkg/hub/client/policy_merger.go b/pkg/hub/client/policy_merger.go index 88e944754..986a1737b 100644 --- a/pkg/hub/client/policy_merger.go +++ b/pkg/hub/client/policy_merger.go @@ -4,7 +4,7 @@ import ( "reflect" "capact.io/capact/internal/maps" - + "capact.io/capact/internal/ptr" "capact.io/capact/pkg/engine/k8s/policy" ) @@ -15,10 +15,14 @@ func (e *PolicyEnforcedClient) mergePolicies() { switch p { case policy.Global: applyInterfacePolicy(¤tPolicy.Interface, e.globalPolicy.Interface) + applyTypeInstancePolicy(¤tPolicy.TypeInstance, e.globalPolicy.TypeInstance) case policy.Action: applyInterfacePolicy(¤tPolicy.Interface, e.actionPolicy.Interface) + applyTypeInstancePolicy(¤tPolicy.TypeInstance, e.actionPolicy.TypeInstance) case policy.Workflow: for _, wp := range e.workflowStepPolicies { + // ignore TypeInstance Policy on Workflow as it's not supported, + // see: policy.WorkflowPolicy type. applyInterfacePolicy(¤tPolicy.Interface, wp.Interface) } } @@ -31,7 +35,7 @@ func (e *PolicyEnforcedClient) mergePolicies() { // current policy is a higher priority policy func applyInterfacePolicy(currentPolicy *policy.InterfacePolicy, newPolicy policy.InterfacePolicy) { for _, newRuleForInterface := range newPolicy.Rules { - policyRuleIndex := getIndexOfPolicyRule(currentPolicy, newRuleForInterface) + policyRuleIndex := getIndexOfInterfacePolicyRule(currentPolicy, newRuleForInterface) if policyRuleIndex == -1 { newRuleForInterface := newRuleForInterface.DeepCopy() currentPolicy.Rules = append(currentPolicy.Rules, *newRuleForInterface) @@ -71,7 +75,7 @@ func mergeRules(rule *policy.Rule, newRule policy.Rule) { } } -func getIndexOfPolicyRule(p *policy.InterfacePolicy, rule policy.RulesForInterface) int { +func getIndexOfInterfacePolicyRule(p *policy.InterfacePolicy, rule policy.RulesForInterface) int { for i, ruleForInterface := range p.Rules { if isForSameInterface(ruleForInterface, rule) { return i @@ -162,3 +166,35 @@ func mergeAdditionalParameters(current, overwrite []policy.AdditionalParametersT } return out } + +func applyTypeInstancePolicy(currentPolicy *policy.TypeInstancePolicy, newPolicy policy.TypeInstancePolicy) { + for _, newRule := range newPolicy.Rules { + policyRuleIndex := getIndexOfTypeInstancePolicyRule(currentPolicy, newRule) + if policyRuleIndex == -1 { + newRuleForInterface := newRule.DeepCopy() + currentPolicy.Rules = append(currentPolicy.Rules, *newRuleForInterface) + continue + } + + // override + currentPolicy.Rules[policyRuleIndex] = newRule + } +} + +func getIndexOfTypeInstancePolicyRule(current *policy.TypeInstancePolicy, newRule policy.RulesForTypeInstance) int { + for i, currentRule := range current.Rules { + if currentRule.TypeRef.Path != newRule.TypeRef.Path { + continue + } + curRev := ptr.StringPtrToString(currentRule.TypeRef.Revision) + newRev := ptr.StringPtrToString(newRule.TypeRef.Revision) + + if curRev != newRev { + continue + } + + return i + } + + return -1 +} diff --git a/pkg/hub/client/policy_merger_test.go b/pkg/hub/client/policy_merger_test.go index fdb592c72..51d0b7def 100644 --- a/pkg/hub/client/policy_merger_test.go +++ b/pkg/hub/client/policy_merger_test.go @@ -28,7 +28,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { name: "only global policy", global: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -51,13 +51,13 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", - Revision: "0.1.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, }, }, }, @@ -83,7 +83,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { action: policy.ActionPolicy{}, expected: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -106,13 +106,13 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", - Revision: "0.1.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + Revision: "0.1.0", + }, }, }, }, @@ -141,7 +141,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { name: "only action policy", action: policy.ActionPolicy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -164,12 +164,12 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + }, }, }, }, @@ -195,7 +195,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { global: policy.Policy{}, expected: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -218,12 +218,12 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + }, }, }, }, @@ -252,7 +252,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { name: "action first then global for the same interface", action: policy.ActionPolicy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -276,12 +276,12 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123-111", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + }, }, }, }, @@ -306,7 +306,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, global: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -330,12 +330,12 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123-222", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + }, }, }, }, @@ -365,7 +365,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, expected: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -390,12 +390,12 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123-111", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + }, }, }, }, @@ -429,7 +429,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { name: "action first then global for different interfaces - only rules", action: policy.ActionPolicy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -447,7 +447,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, global: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: secondInterfacePath, @@ -465,7 +465,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, expected: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -499,7 +499,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { name: "merge type instances and additional input", action: policy.ActionPolicy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -522,12 +522,12 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123-111", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + }, }, }, }, @@ -552,7 +552,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, global: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -565,12 +565,12 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "123-321-123", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.x", + TypeRef: &types.TypeRef{ + Path: "cap.type.x", + }, }, }, }, @@ -595,7 +595,7 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { }, expected: policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ policy.RulesForInterface{ Interface: types.ManifestRefWithOptRevision{ Path: interfacePath, @@ -608,21 +608,21 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "123-321-123", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.x", + TypeRef: &types.TypeRef{ + Path: "cap.type.x", + }, }, }, { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "1314-142-123-111", Description: ptr.String("Sample TI"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.gcp.auth.service-account", + TypeRef: &types.TypeRef{ + Path: "cap.type.gcp.auth.service-account", + }, }, }, }, @@ -683,6 +683,384 @@ func TestPolicyEnforcedClient_mergePolicies(t *testing.T) { } } +func TestPolicyEnforcedClient_mergeTypeInstancePolicies(t *testing.T) { + tests := []struct { + name string + global policy.Policy + action policy.ActionPolicy + expected policy.Policy + order policy.MergeOrder + }{ + { + name: "only global policy", + global: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + }, + action: policy.ActionPolicy{}, + expected: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + }, + order: policy.MergeOrder{policy.Action, policy.Global}, + }, + { + name: "only action policy", + action: policy.ActionPolicy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + }, + global: policy.Policy{}, + expected: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + }, + order: policy.MergeOrder{policy.Action, policy.Global}, + }, + { + name: "action first then global for the same Types", + action: policy.ActionPolicy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + }, + global: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-1234-1234-1234-1234", + Description: ptr.String("Vault TI"), + }, + }, + }, + }, + }, + }, + expected: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "1234-1234-1234-1234-1234", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + }, + order: policy.MergeOrder{policy.Action, policy.Global}, + }, + { + name: "action first then global for different Types", + action: policy.ActionPolicy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + }, + global: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + }, + }, + }, + expected: policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + }, + }, + }, + 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.Policy()) + }) + } +} + func TestNestedWorkflowPolicy(t *testing.T) { w1 := workflowPolicyWithAdditionalInput(map[string]interface{}{"a": 1}) w2 := workflowPolicyWithAdditionalInput(map[string]interface{}{"a": 2, "b": 3}) diff --git a/pkg/hub/client/public/additional_refs.go b/pkg/hub/client/public/additional_refs.go new file mode 100644 index 000000000..12f0696d8 --- /dev/null +++ b/pkg/hub/client/public/additional_refs.go @@ -0,0 +1,71 @@ +package public + +import ( + "context" + + "capact.io/capact/internal/ptr" + "capact.io/capact/internal/regexutil" + gqlpublicapi "capact.io/capact/pkg/hub/api/graphql/public" + "capact.io/capact/pkg/sdk/apis/0.0.1/types" + + "github.com/pkg/errors" +) + +// listAdditionalRefsFields defines preset for response fields required for collection Type's spec.additionalRefs +const listAdditionalRefsFields = TypeRevisionRootFields | TypeRevisionSpecAdditionalRefsField + +// ListAdditionalRefsClient defines external Hub calls used by ListAdditionalRefs. +type ListAdditionalRefsClient interface { + ListTypes(ctx context.Context, opts ...TypeOption) ([]*gqlpublicapi.Type, error) +} + +// ListAdditionalRefsOutput holds Type's spec.additionalRefs entry indexed by TypeRef key. +type ListAdditionalRefsOutput map[types.TypeRef][]string + +// ListAdditionalRefs knows how to fetch Type's spec.additionalRefs entries for all revisions for all given Types in a bit efficient way: +// - uses OR to get all Types in a single call +// - requests only required fields to prevent over-fetching +// - uses map type to give O(1) access to returned responses. +func ListAdditionalRefs(ctx context.Context, cli ListAdditionalRefsClient, reqTypes []types.TypeRef) (ListAdditionalRefsOutput, error) { + filter := regexutil.OrStringSlice(mapToPaths(reqTypes)) + + opts := []TypeOption{ + WithTypeRevisions(listAdditionalRefsFields), + WithTypeFilter(gqlpublicapi.TypeFilter{ + PathPattern: ptr.String(filter), + }), + } + + res, err := cli.ListTypes(ctx, opts...) + if err != nil { + return ListAdditionalRefsOutput{}, errors.Wrap(err, "while fetching Types' additionalRefs for all revisions") + } + + out := ListAdditionalRefsOutput{} + for _, item := range res { + if item == nil { + continue + } + for _, rev := range item.Revisions { + if rev.Spec == nil { + continue + } + out[types.TypeRef{ + Path: item.Path, + Revision: rev.Revision, + }] = rev.Spec.AdditionalRefs + } + } + + return out, nil +} + +func mapToPaths(in []types.TypeRef) []string { + var paths []string + + for _, expType := range in { + paths = append(paths, expType.Path) + } + + return paths +} 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 0947f85b9..7f7c197ff 100644 --- a/pkg/sdk/apis/0.0.1/types/types.extend.go +++ b/pkg/sdk/apis/0.0.1/types/types.extend.go @@ -1,10 +1,17 @@ // Package types holds manually added types. package types -import "fmt" +import ( + "fmt" + "strings" +) -// OCFPathPrefix defines path prefix that all OCF manifest must have. -const OCFPathPrefix = "cap." +const ( + // OCFPathPrefix defines path prefix that all OCF manifest must have. + OCFPathPrefix = "cap." + // HubBackendParentNodeName define parent path for the core hub storage. + HubBackendParentNodeName = "cap.core.type.hub.storage" +) // InterfaceRef holds the full path and revision to the Interface type InterfaceRef ManifestRefWithOptRevision @@ -84,3 +91,14 @@ type ManifestMetadata struct { OCFVersion OCFVersion `yaml:"ocfVersion"` Kind ManifestKind `yaml:"kind"` } + +// TrimLastNodeFromOCFPath removes the last node name from the given OCF path. +// For example, for `cap.core.type.examples.name` returns `cap.core.type.examples`. +func TrimLastNodeFromOCFPath(in string) string { + idx := strings.LastIndex(in, ".") + if idx == -1 { + return in + } + + return in[:idx] +} diff --git a/pkg/sdk/apis/0.0.1/types/types.extend_test.go b/pkg/sdk/apis/0.0.1/types/types.extend_test.go new file mode 100644 index 000000000..7ddaae9bc --- /dev/null +++ b/pkg/sdk/apis/0.0.1/types/types.extend_test.go @@ -0,0 +1,33 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTrimLastNodeFromOCFPath(t *testing.T) { + tests := map[string]struct { + givenPath string + expPath string + }{ + "Should remove last node name": { + givenPath: "cap.core.type.examples.name", + expPath: "cap.core.type.examples", + }, + "Should trim trailing separator": { + givenPath: "cap.", + expPath: "cap", + }, + "Should return given path if no separator detected": { + givenPath: "cap", + expPath: "cap", + }, + } + for tn, tc := range tests { + t.Run(tn, func(t *testing.T) { + gotPath := TrimLastNodeFromOCFPath(tc.givenPath) + assert.Equal(t, tc.expPath, gotPath) + }) + } +} diff --git a/pkg/sdk/renderer/argo/dedicated_renderer.go b/pkg/sdk/renderer/argo/dedicated_renderer.go index d89669ec0..415026183 100644 --- a/pkg/sdk/renderer/argo/dedicated_renderer.go +++ b/pkg/sdk/renderer/argo/dedicated_renderer.go @@ -10,14 +10,15 @@ import ( "capact.io/capact/internal/ctxutil" "capact.io/capact/internal/k8s-engine/graphql/domain/action" - "capact.io/capact/internal/ptr" + "capact.io/capact/pkg/engine/k8s/policy" hubpublicapi "capact.io/capact/pkg/hub/api/graphql/public" "capact.io/capact/pkg/sdk/apis/0.0.1/types" "github.com/Knetic/govaluate" wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" "github.com/pkg/errors" + "go.uber.org/zap" apiv1 "k8s.io/api/core/v1" ) @@ -45,6 +46,7 @@ type dedicatedRenderer struct { typeInstancesToOutput *OutputTypeInstances typeInstancesToUpdate UpdateTypeInstances registeredOutputTypeInstanceNames []*string + log *zap.Logger } // InputArtifact is an Argo artifact with a reference to a Capact TypeInstance. @@ -54,8 +56,9 @@ type InputArtifact struct { typeInstanceReference *string } -func newDedicatedRenderer(maxDepth int, policyEnforcedCli PolicyEnforcedHubClient, typeInstanceHandler *TypeInstanceHandler, opts ...RendererOption) *dedicatedRenderer { +func newDedicatedRenderer(log *zap.Logger, maxDepth int, policyEnforcedCli PolicyEnforcedHubClient, typeInstanceHandler *TypeInstanceHandler, opts ...RendererOption) *dedicatedRenderer { r := &dedicatedRenderer{ + log: log, maxDepth: maxDepth, policyEnforcedCli: policyEnforcedCli, typeInstanceHandler: typeInstanceHandler, @@ -167,12 +170,20 @@ func (r *dedicatedRenderer) GetRootTemplates() []*Template { return r.processedTemplates } +// RootImplementation represents a root implementation against which steps are rendered. +// It's used to access the imports and requires sections used by steps. +// - The `capact-action` is selected based on `spec.imports[*].alias`. +// - The `capact-outputTypeInstances[*].backend` is selected based on `spec.requires[].alias`. +type RootImplementation struct { + Revision hubpublicapi.ImplementationRevision + Rule policy.Rule +} + // TODO Refactor it. It's too long // 1. Split it to smaller functions and leave only high level steps here // 2. Do not use global state, calling it multiple times seems not to work //nolint:gocyclo // This legacy function is complex but the team too busy to simplify it -func (r *dedicatedRenderer) RenderTemplateSteps(ctx context.Context, workflow *Workflow, importsCollection []*hubpublicapi.ImplementationImport, - typeInstances []types.InputTypeInstanceRef, prefix string) (map[string]*string, error) { +func (r *dedicatedRenderer) RenderTemplateSteps(ctx context.Context, workflow *Workflow, rootimpl RootImplementation, typeInstances []types.InputTypeInstanceRef, prefix string) (map[string]*string, error) { r.currentIteration++ if ctxutil.ShouldExit(ctx) { @@ -183,7 +194,15 @@ func (r *dedicatedRenderer) RenderTemplateSteps(ctx context.Context, workflow *W return nil, NewMaxDepthError(r.maxDepth) } - outputTypeInstances := map[string]*string{} + var ( + outputTypeInstances = map[string]*string{} + // importsCollection describes collection from the Implementation manifest for which we + // render steps which may uses `capact-action`. Because of the `alias` logic + // between `Implementation.spec.imports` and `Implementation.spec.action.args.workflow.templates[*].steps.capact-action` + // we need it. + // If we are in context of `capact-action` the new Implementation is selected which satisfy a given import Interface. + importsCollection = rootimpl.Revision.Spec.Imports + ) for _, tpl := range workflow.Templates { // 0. Aggregate processed templates @@ -318,7 +337,7 @@ func (r *dedicatedRenderer) RenderTemplateSteps(ctx context.Context, workflow *W r.InjectAdditionalInput(step, additionalParameters) for k, v := range newArtifactMappings { - artifactMappings[k] = v + artifactMappings[k] = v.Name } step.Template = importedWorkflow.Entrypoint @@ -329,13 +348,17 @@ func (r *dedicatedRenderer) RenderTemplateSteps(ctx context.Context, workflow *W // 3.9 Add TypeInstances to the upload graph inputArtifacts := r.tplInputArguments[step.Template] - if err := r.addOutputTypeInstancesToGraph(step, workflowPrefix, iface, &implementation, inputArtifacts); err != nil { + typeInstancesBackends, err := r.policyEnforcedCli.ListTypeInstancesBackendsBasedOnPolicy(ctx, rootimpl.Rule, rootimpl.Revision) + if err != nil { + return nil, errors.Wrap(err, "while resolving TypeInstance Backend based on Policy") + } + if err := r.addOutputTypeInstancesToGraph(step, workflowPrefix, iface, &implementation, inputArtifacts, typeInstancesBackends, newArtifactMappings); err != nil { return nil, errors.Wrap(err, "while adding TypeInstances to graph") } // 3.10 Render imported Workflow templates and add them to root templates // TODO(advanced-rendering): currently not supported. - actionOutputTypeInstances, err := r.RenderTemplateSteps(ctx, importedWorkflow, implementation.Spec.Imports, nil, workflowPrefix) + actionOutputTypeInstances, err := r.RenderTemplateSteps(ctx, importedWorkflow, RootImplementation{Revision: implementation, Rule: rule}, nil, workflowPrefix) if err != nil { return nil, err } @@ -395,7 +418,12 @@ func (r *dedicatedRenderer) ResolveRunnerInterface(impl hubpublicapi.Implementat return fullRef.Path, nil } -func (r *dedicatedRenderer) UnmarshalWorkflowFromImplementation(prefix string, implementation *hubpublicapi.ImplementationRevision) (*Workflow, map[string]string, error) { +type artefactNameWithBackend struct { + Name string + Backend *string +} + +func (r *dedicatedRenderer) UnmarshalWorkflowFromImplementation(prefix string, implementation *hubpublicapi.ImplementationRevision) (*Workflow, map[string]artefactNameWithBackend, error) { workflow, err := r.createWorkflow(implementation) if err != nil { return nil, nil, errors.Wrap(err, "while unmarshaling Argo Workflow from OCF Implementation") @@ -403,7 +431,7 @@ func (r *dedicatedRenderer) UnmarshalWorkflowFromImplementation(prefix string, i if workflow == nil || workflow.WorkflowSpec == nil || workflow.Entrypoint == "" { return nil, nil, errors.New("workflow and its entrypoint cannot be empty") } - artifactsNameMapping := map[string]string{} + artifactsNameMapping := map[string]artefactNameWithBackend{} for i := range workflow.Templates { tmpl := workflow.Templates[i] @@ -420,7 +448,9 @@ func (r *dedicatedRenderer) UnmarshalWorkflowFromImplementation(prefix string, i } newName := addPrefix(prefix, artifact.GlobalName) - artifactsNameMapping[artifact.GlobalName] = newName + artifactsNameMapping[artifact.GlobalName] = artefactNameWithBackend{ + Name: newName, + } artifact.GlobalName = newName } } @@ -436,17 +466,24 @@ func (r *dedicatedRenderer) UnmarshalWorkflowFromImplementation(prefix string, i step.Template = addPrefix(prefix, step.Template) } - typeInstances := make([]TypeInstanceDefinition, 0, len(step.CapactTypeInstanceOutputs)+len(step.CapactTypeInstanceUpdates)) + typeInstances := make([]CapactTypeInstanceOutputs, 0, len(step.CapactTypeInstanceOutputs)+len(step.CapactTypeInstanceUpdates)) typeInstances = append(typeInstances, step.CapactTypeInstanceOutputs...) - typeInstances = append(typeInstances, step.CapactTypeInstanceUpdates...) + for _, item := range step.CapactTypeInstanceUpdates { + typeInstances = append(typeInstances, CapactTypeInstanceOutputs{ + TypeInstanceDefinition: item, + }) + } for _, ti := range typeInstances { - tiStep, template, artifactMappings := r.getOutputTypeInstanceTemplate(step, ti, prefix) + tiStep, template, artifactMappings := r.getOutputTypeInstanceTemplate(step, ti.TypeInstanceDefinition, prefix) workflow.Templates = append(workflow.Templates, &template) tmpl.Steps = append(tmpl.Steps, ParallelSteps{&tiStep}) for k, v := range artifactMappings { - artifactsNameMapping[k] = v + artifactsNameMapping[k] = artefactNameWithBackend{ + Name: v, + Backend: ti.Backend, + } } } } @@ -927,17 +964,22 @@ func (r *dedicatedRenderer) registerTemplateInputArguments(step *WorkflowStep, a r.tplInputArguments[step.Template] = inputArtifacts } -func (r *dedicatedRenderer) addOutputTypeInstancesToGraph(step *WorkflowStep, prefix string, iface *hubpublicapi.InterfaceRevision, impl *hubpublicapi.ImplementationRevision, inputArtifacts []InputArtifact) error { +func (r *dedicatedRenderer) addOutputTypeInstancesToGraph(step *WorkflowStep, prefix string, iface *hubpublicapi.InterfaceRevision, impl *hubpublicapi.ImplementationRevision, inputArtifacts []InputArtifact, backends policy.TypeInstanceBackendCollection, mappings map[string]artefactNameWithBackend) error { artifactNamesMap := map[string]*string{} for _, artifact := range inputArtifacts { artifactNamesMap[artifact.artifact.Name] = artifact.typeInstanceReference } for _, item := range impl.Spec.OutputTypeInstanceRelations { - name := item.TypeInstanceName + var ( + name = item.TypeInstanceName + stepOutputBackendAlias = "" + ) + if step != nil { // we have to track the renaming based on capact-outputTypeInstances and prefix it if output := findOutputTypeInstance(step, item.TypeInstanceName); output != nil { + stepOutputBackendAlias = ptr.StringPtrToString(output.Backend) name = addPrefix(prefix, output.From) r.tryReplaceTypeInstanceName(output.Name, name) } else { @@ -955,16 +997,33 @@ func (r *dedicatedRenderer) addOutputTypeInstancesToGraph(step *WorkflowStep, pr artifactName := r.addTypeInstanceName(name) artifactNamesMap[item.TypeInstanceName] = artifactName + // select backend + upperLayerStepOutputBackendAlias := ptr.StringPtrToString(mappings[name].Backend) + backendAlias, err := r.selectBackendAlias(upperLayerStepOutputBackendAlias, stepOutputBackendAlias) + if err != nil { + return errors.Wrapf(err, "while resolving backend alias for %q", step.Name) + } + + log := r.log.With(zap.String("artifactName", *artifactName)) + log.Debug("Available TypeInstance Backend", zap.Any("backends", backends.GetAll())) + + backend, err := r.selectBackend(backendAlias, typeRef, backends) + if err != nil { + return errors.Wrapf(err, "while resolving backend ID for %q", name) + } + + log.Debug("Selected TypeInstance Backend", zap.Any("backend", backend)) + + // add output r.typeInstancesToOutput.typeInstances = append(r.typeInstancesToOutput.typeInstances, OutputTypeInstance{ ArtifactName: artifactName, + Backend: backend, TypeInstance: types.OutputTypeInstance{ - TypeRef: &types.TypeRef{ - Path: typeRef.Path, - Revision: typeRef.Revision, - }, + TypeRef: &typeRef, }, }) + // setup uses for _, uses := range item.Uses { usesArtifactName, ok := artifactNamesMap[uses] if !ok { @@ -981,6 +1040,36 @@ func (r *dedicatedRenderer) addOutputTypeInstancesToGraph(step *WorkflowStep, pr return nil } +func (*dedicatedRenderer) selectBackendAlias(upperStep, resolvedStep string) (*string, error) { + if upperStep != "" && resolvedStep != "" { + return nil, errors.Errorf("cannot override backend on capact-outputTypeInstances") + } + for _, alias := range []string{upperStep, resolvedStep} { + if alias != "" { + return &alias, nil + } + } + return nil, nil +} + +func (*dedicatedRenderer) selectBackend(alias *string, typeRef types.TypeRef, backends policy.TypeInstanceBackendCollection) (policy.TypeInstanceBackend, error) { + if alias == nil { // alias not set, get the Policy default based on TypeRef + backend, _ := backends.GetByTypeRef(typeRef) + return backend, nil + } + + // when alias is specified, required TypeInstance needs to be injected and be of Hub storage type + backend, found := backends.GetByAlias(*alias) + if !found { + return policy.TypeInstanceBackend{}, fmt.Errorf("cannot find backend storage for specified %q alias", *alias) + } + if !backend.ExtendsHubStorage { + return policy.TypeInstanceBackend{}, fmt.Errorf("TypeInstance with %q alias is not a Hub storage", *alias) + } + + return backend, nil +} + func (r *dedicatedRenderer) registerUpdatedTypeInstances(step *WorkflowStep, availableTypeInstances map[argoArtifactRef]*string, prefix string) error { for _, update := range step.CapactTypeInstanceUpdates { typeInstance, ok := availableTypeInstances[argoArtifactRef{ diff --git a/pkg/sdk/renderer/argo/dedicated_renderer_test.go b/pkg/sdk/renderer/argo/dedicated_renderer_test.go index cc0278687..636a5fddb 100644 --- a/pkg/sdk/renderer/argo/dedicated_renderer_test.go +++ b/pkg/sdk/renderer/argo/dedicated_renderer_test.go @@ -3,9 +3,11 @@ package argo import ( "testing" + "capact.io/capact/internal/logger" hubclient "capact.io/capact/pkg/hub/client" "capact.io/capact/pkg/hub/client/fake" policyvalidation "capact.io/capact/pkg/sdk/validation/policy" + "github.com/stretchr/testify/require" ) @@ -22,7 +24,7 @@ func createFakeDedicatedRendererObject(t *testing.T) *dedicatedRenderer { opts := []RendererOption{} maxDepth := 20 - return newDedicatedRenderer(maxDepth, policyEnforcedClient, typeInstanceHandler, opts...) + return newDedicatedRenderer(logger.Noop(), maxDepth, policyEnforcedClient, typeInstanceHandler, opts...) } func TestCapactWhenContainDashes(t *testing.T) { diff --git a/pkg/sdk/renderer/argo/fixtures_test.go b/pkg/sdk/renderer/argo/fixtures_test.go index 2fe1f1261..5b7f62c99 100644 --- a/pkg/sdk/renderer/argo/fixtures_test.go +++ b/pkg/sdk/renderer/argo/fixtures_test.go @@ -9,7 +9,7 @@ import ( func fixGCPGlobalPolicy() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -33,7 +33,7 @@ func fixGCPGlobalPolicy() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "c268d3f5-8834-434b-bea2-b677793611c5", Description: ptr.String("GCP SA"), }, @@ -66,7 +66,7 @@ func fixGCPGlobalPolicy() policy.Policy { func fixAWSGlobalPolicy(additionalParameters ...policy.AdditionalParametersToInject) policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -90,7 +90,7 @@ func fixAWSGlobalPolicy(additionalParameters ...policy.AdditionalParametersToInj Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "517cf827-233c-4bf1-8fc9-48534424dd58", Description: ptr.String("AWS Credentials"), }, @@ -119,7 +119,7 @@ func fixAWSGlobalPolicy(additionalParameters ...policy.AdditionalParametersToInj func fixGlobalPolicyForFallback() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -138,7 +138,7 @@ func fixGlobalPolicyForFallback() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "517cf827-233c-4bf1-8fc9-48534424dd58", Description: ptr.String("AWS Credentials"), }, @@ -187,7 +187,7 @@ func fixGlobalPolicyForFallback() policy.Policy { func fixTerraformPolicy() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -201,7 +201,7 @@ func fixTerraformPolicy() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "c268d3f5-8834-434b-bea2-b677793611c5", Description: ptr.String("GCP SA"), }, @@ -229,7 +229,7 @@ func fixTerraformPolicy() policy.Policy { func fixAWSRDSPolicy() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.database.postgresql.install", @@ -243,7 +243,7 @@ func fixAWSRDSPolicy() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "517cf827-233c-4bf1-8fc9-48534424dd58", Description: ptr.String("AWS SA"), }, @@ -279,7 +279,7 @@ func fixAWSRDSPolicy() policy.Policy { func fixExistingDBPolicy() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.interface.productivity.mattermost.install", diff --git a/pkg/sdk/renderer/argo/helpers.go b/pkg/sdk/renderer/argo/helpers.go index 8298f8ee0..503880236 100644 --- a/pkg/sdk/renderer/argo/helpers.go +++ b/pkg/sdk/renderer/argo/helpers.go @@ -34,9 +34,9 @@ func GetEntrypointWorkflowIndex(w *Workflow) (int, error) { return 0, NewEntrypointWorkflowIndexNotFoundError(w.Entrypoint) } -func findTypeInstanceTypeRef(typeInstanceName string, impl *hubpublicgraphql.ImplementationRevision, iface *hubpublicgraphql.InterfaceRevision) (*hubpublicgraphql.TypeReference, error) { +func findTypeInstanceTypeRef(typeInstanceName string, impl *hubpublicgraphql.ImplementationRevision, iface *hubpublicgraphql.InterfaceRevision) (types.TypeRef, error) { if iface == nil { - return nil, NewTypeReferenceNotFoundError(typeInstanceName) + return types.TypeRef{}, NewTypeReferenceNotFoundError(typeInstanceName) } var toSearch []*hubpublicgraphql.OutputTypeInstance @@ -51,15 +51,15 @@ func findTypeInstanceTypeRef(typeInstanceName string, impl *hubpublicgraphql.Imp for i := range toSearch { ti := toSearch[i] - if ti.Name == typeInstanceName { - return ti.TypeRef, nil + if ti.Name == typeInstanceName && ti.TypeRef != nil { + return types.TypeRef(*ti.TypeRef), nil } } - return nil, NewTypeReferenceNotFoundError(typeInstanceName) + return types.TypeRef{}, NewTypeReferenceNotFoundError(typeInstanceName) } -func findOutputTypeInstance(step *WorkflowStep, typeInstanceName string) *TypeInstanceDefinition { +func findOutputTypeInstance(step *WorkflowStep, typeInstanceName string) *CapactTypeInstanceOutputs { for _, output := range step.CapactTypeInstanceOutputs { if output.From == typeInstanceName { return &output diff --git a/pkg/sdk/renderer/argo/renderer.go b/pkg/sdk/renderer/argo/renderer.go index 831728fdd..43fd5c27e 100644 --- a/pkg/sdk/renderer/argo/renderer.go +++ b/pkg/sdk/renderer/argo/renderer.go @@ -5,6 +5,8 @@ import ( "encoding/json" "time" + "go.uber.org/zap" + "capact.io/capact/pkg/engine/k8s/policy" hubpublicapi "capact.io/capact/pkg/hub/api/graphql/public" hubclient "capact.io/capact/pkg/hub/client" @@ -15,8 +17,6 @@ import ( ) const ( - // UserInputName is exported so we can use that as a reference in `capact act create` validation process. - UserInputName = "input-parameters" runnerContext = "runner-context" ) @@ -27,6 +27,7 @@ type PolicyEnforcedHubClient interface { ListRequiredTypeInstancesToInjectBasedOnPolicy(policyRule policy.Rule, implRev hubpublicapi.ImplementationRevision) ([]types.InputTypeInstanceRef, error) ListAdditionalTypeInstancesToInjectBasedOnPolicy(policyRule policy.Rule, implRev hubpublicapi.ImplementationRevision) ([]types.InputTypeInstanceRef, error) ListAdditionalInputToInjectBasedOnPolicy(ctx context.Context, policyRule policy.Rule, implRev hubpublicapi.ImplementationRevision) (types.ParametersCollection, error) + ListTypeInstancesBackendsBasedOnPolicy(ctx context.Context, policyRule policy.Rule, implRev hubpublicapi.ImplementationRevision) (policy.TypeInstanceBackendCollection, error) SetGlobalPolicy(policy policy.Policy) SetActionPolicy(policy policy.ActionPolicy) PushWorkflowStepPolicy(policy policy.WorkflowPolicy) error @@ -48,16 +49,18 @@ type Renderer struct { typeInstanceHandler *TypeInstanceHandler wfValidator workflowValidator hubClient hubclient.HubClient + log *zap.Logger } // NewRenderer returns a new Renderer instance. -func NewRenderer(cfg renderer.Config, hubClient hubclient.HubClient, typeInstanceHandler *TypeInstanceHandler, validator workflowValidator) *Renderer { +func NewRenderer(log *zap.Logger, cfg renderer.Config, hubClient hubclient.HubClient, typeInstanceHandler *TypeInstanceHandler, validator workflowValidator) *Renderer { r := &Renderer{ typeInstanceHandler: typeInstanceHandler, maxDepth: cfg.MaxDepth, renderTimeout: cfg.RenderTimeout, hubClient: hubClient, wfValidator: validator, + log: log, } return r @@ -73,7 +76,7 @@ func (r *Renderer) Render(ctx context.Context, input *RenderInput) (*RenderOutpu policyEnforcedClient := hubclient.NewPolicyEnforcedClient(r.hubClient, r.wfValidator.PolicyValidator()) // 0. Populate render options - dedicatedRenderer := newDedicatedRenderer(r.maxDepth, policyEnforcedClient, r.typeInstanceHandler, input.Options...) + dedicatedRenderer := newDedicatedRenderer(r.log, r.maxDepth, policyEnforcedClient, r.typeInstanceHandler, input.Options...) ctxWithTimeout, cancel := context.WithTimeout(ctx, r.renderTimeout) defer cancel() @@ -110,7 +113,7 @@ func (r *Renderer) Render(ctx context.Context, input *RenderInput) (*RenderOutpu } // 3. Extract workflow from the root Implementation - rootWorkflow, _, err := dedicatedRenderer.UnmarshalWorkflowFromImplementation("", &implementation) + rootWorkflow, newArtifactMappings, err := dedicatedRenderer.UnmarshalWorkflowFromImplementation("", &implementation) if err != nil { return nil, errors.Wrap(err, "while creating root workflow") } @@ -179,12 +182,20 @@ func (r *Renderer) Render(ctx context.Context, input *RenderInput) (*RenderOutpu availableArtifacts := dedicatedRenderer.tplInputArguments[dedicatedRenderer.entrypointStep.Template] // 9. Register output TypeInstances - if err := dedicatedRenderer.addOutputTypeInstancesToGraph(nil, "", iface, &implementation, availableArtifacts); err != nil { + typeInstancesBackends, err := policyEnforcedClient.ListTypeInstancesBackendsBasedOnPolicy(ctx, rule, implementation) + if err != nil { + return nil, errors.Wrap(err, "while resolving TypeInstance backend based on Policy") + } + + if err := dedicatedRenderer.addOutputTypeInstancesToGraph(nil, "", iface, &implementation, availableArtifacts, typeInstancesBackends, newArtifactMappings); err != nil { return nil, errors.Wrap(err, "while noting output artifacts") } // 10. Render rootWorkflow templates - _, err = dedicatedRenderer.RenderTemplateSteps(ctxWithTimeout, rootWorkflow, implementation.Spec.Imports, dedicatedRenderer.inputTypeInstances, "") + _, err = dedicatedRenderer.RenderTemplateSteps(ctxWithTimeout, rootWorkflow, RootImplementation{ + Revision: implementation, + Rule: rule, + }, dedicatedRenderer.inputTypeInstances, "") if err != nil { return nil, err } diff --git a/pkg/sdk/renderer/argo/renderer_test.go b/pkg/sdk/renderer/argo/renderer_test.go index 7ec1a71f7..3e34de08c 100644 --- a/pkg/sdk/renderer/argo/renderer_test.go +++ b/pkg/sdk/renderer/argo/renderer_test.go @@ -7,13 +7,13 @@ import ( "testing" "time" - actionvalidation "capact.io/capact/pkg/sdk/validation/interfaceio" - policyvalidation "capact.io/capact/pkg/sdk/validation/policy" - + "capact.io/capact/internal/logger" "capact.io/capact/pkg/engine/k8s/policy" "capact.io/capact/pkg/hub/client/fake" "capact.io/capact/pkg/sdk/apis/0.0.1/types" "capact.io/capact/pkg/sdk/renderer" + actionvalidation "capact.io/capact/pkg/sdk/validation/interfaceio" + policyvalidation "capact.io/capact/pkg/sdk/validation/policy" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -44,8 +44,7 @@ func TestRenderHappyPath(t *testing.T) { interfaceIOValidator := actionvalidation.NewValidator(fakeCli) policyIOValidator := policyvalidation.NewValidator(fakeCli) wfValidator := renderer.NewWorkflowInputValidator(interfaceIOValidator, policyIOValidator) - - argoRenderer := NewRenderer(renderer.Config{ + argoRenderer := NewRenderer(logger.Noop(), renderer.Config{ RenderTimeout: time.Second, MaxDepth: 20, }, fakeCli, typeInstanceHandler, wfValidator) @@ -301,7 +300,7 @@ func TestRenderHappyPathWithCustomPolicies(t *testing.T) { policyIOValidator := policyvalidation.NewValidator(fakeCli) wfValidator := renderer.NewWorkflowInputValidator(interfaceIOValidator, policyIOValidator) - argoRenderer := NewRenderer(renderer.Config{ + argoRenderer := NewRenderer(logger.Noop(), renderer.Config{ RenderTimeout: time.Hour, MaxDepth: 50, }, fakeCli, typeInstanceHandler, wfValidator) @@ -348,7 +347,7 @@ func TestRendererMaxDepth(t *testing.T) { policyIOValidator := policyvalidation.NewValidator(fakeCli) wfValidator := renderer.NewWorkflowInputValidator(interfaceIOValidator, policyIOValidator) - argoRenderer := NewRenderer(renderer.Config{ + argoRenderer := NewRenderer(logger.Noop(), renderer.Config{ RenderTimeout: time.Second, MaxDepth: 3, }, fakeCli, typeInstanceHandler, wfValidator) @@ -387,7 +386,7 @@ func TestRendererDenyAllPolicy(t *testing.T) { policyIOValidator := policyvalidation.NewValidator(fakeCli) wfValidator := renderer.NewWorkflowInputValidator(interfaceIOValidator, policyIOValidator) - argoRenderer := NewRenderer(renderer.Config{ + argoRenderer := NewRenderer(logger.Noop(), renderer.Config{ RenderTimeout: time.Second, MaxDepth: 3, }, fakeCli, typeInstanceHandler, wfValidator) 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 b02d2fa35..d4a2cabcd 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 @@ -1117,6 +1117,7 @@ args: typeInstances: - alias: mattermost-config attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.productivity.mattermost.config @@ -1124,6 +1125,7 @@ args: value: null - alias: mattermost-install-install-db-postgresql attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.database.postgresql.config @@ -1131,6 +1133,7 @@ args: value: null - alias: mattermost-install-install-db-postgres-install-helm-install-helm-release attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.helm.chart.release @@ -1138,6 +1141,7 @@ args: value: null - alias: mattermost-install-create-user-user attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.database.postgresql.user @@ -1145,6 +1149,7 @@ args: value: null - alias: mattermost-install-create-db-database attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.database.postgresql.database @@ -1152,6 +1157,7 @@ args: value: null - alias: mattermost-install-helm-install-helm-release attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.helm.chart.release 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 23cf8e558..32777a5c2 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 @@ -183,6 +183,7 @@ args: typeInstances: - alias: postgresql attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.database.postgresql.config @@ -190,6 +191,7 @@ args: value: null - alias: postgres-install-helm-install-helm-release attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.helm.chart.release 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 c0614e990..40446f4b1 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 @@ -1081,6 +1081,7 @@ args: typeInstances: - alias: stack-install-install-shared-db-postgresql attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.database.postgresql.config @@ -1088,6 +1089,7 @@ args: value: null - alias: stack-install-install-shared-db-postgres-install-helm-install-helm-release attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.helm.chart.release @@ -1095,6 +1097,7 @@ args: value: null - alias: app1-install-app1-install-app1-config attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.productivity.app1.config @@ -1102,6 +1105,7 @@ args: value: null - alias: app1-install-app1-install-app1-install-create-db-database attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.database.postgresql.database @@ -1109,6 +1113,7 @@ args: value: null - alias: app1-install-app1-install-app1-install-helm-install-helm-release attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.helm.chart.release @@ -1116,6 +1121,7 @@ args: value: null - alias: app2-install-app2-install-app2-config attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.productivity.app2.config @@ -1123,6 +1129,7 @@ args: value: null - alias: app2-install-app2-install-main-create-db-database attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.database.postgresql.database @@ -1130,6 +1137,7 @@ args: value: null - alias: app2-install-app2-install-main-helm-install-helm-release attributes: [] + backend: null createdBy: default/action typeRef: path: cap.type.helm.chart.release 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 813c232af..dae24c648 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 @@ -197,6 +197,7 @@ args: typeInstances: - alias: postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -204,6 +205,7 @@ args: value: null - alias: postgres-install-cloudsql-run-cloudsql-instance attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.gcp.cloudsql.instance 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 1472aff02..9a4352851 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 @@ -1429,6 +1429,7 @@ args: typeInstances: - alias: mattermost-config attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.productivity.mattermost.config @@ -1436,6 +1437,7 @@ args: value: null - alias: mattermost-install-install-db-postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -1443,6 +1445,7 @@ args: value: null - alias: mattermost-install-install-db-rds-instance attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.aws.rds.instance @@ -1450,6 +1453,7 @@ args: value: null - alias: mattermost-install-install-db-rds-provision-terraform-apply-terraform-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.terraform.release @@ -1457,6 +1461,7 @@ args: value: null - alias: mattermost-install-install-db-rds-provision-terraform-apply-tfstate attributes: [] + backend: null createdBy: owner typeRef: path: cap.core.type.generic.value @@ -1464,6 +1469,7 @@ args: value: null - alias: mattermost-install-create-user-user attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.user @@ -1471,6 +1477,7 @@ args: value: null - alias: mattermost-install-create-db-database attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.database @@ -1478,6 +1485,7 @@ args: value: null - alias: mattermost-install-helm-install-helm-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.helm.chart.release 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 5c81fd2df..2907f6dcc 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 @@ -1148,6 +1148,7 @@ args: typeInstances: - alias: mattermost-config attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.productivity.mattermost.config @@ -1155,6 +1156,7 @@ args: value: null - alias: mattermost-install-install-db-postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -1162,6 +1164,7 @@ args: value: null - alias: mattermost-install-install-db-postgres-install-cloudsql-run-cloudsql-instance attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.gcp.cloudsql.instance @@ -1169,6 +1172,7 @@ args: value: null - alias: mattermost-install-create-user-user attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.user @@ -1176,6 +1180,7 @@ args: value: null - alias: mattermost-install-create-db-database attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.database @@ -1183,6 +1188,7 @@ args: value: null - alias: mattermost-install-helm-install-helm-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.helm.chart.release 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 bdb2d3e97..b97715d18 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 @@ -1211,6 +1211,7 @@ args: typeInstances: - alias: mattermost-config attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.productivity.mattermost.config @@ -1218,6 +1219,7 @@ args: value: null - alias: mattermost-install-install-db-postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -1225,6 +1227,7 @@ args: value: null - alias: mattermost-install-install-db-postgres-install-terraform-apply-terraform-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.terraform.release @@ -1232,6 +1235,7 @@ args: value: null - alias: mattermost-install-install-db-postgres-install-terraform-apply-tfstate attributes: [] + backend: null createdBy: owner typeRef: path: cap.core.type.generic.value @@ -1239,6 +1243,7 @@ args: value: null - alias: mattermost-install-create-user-user attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.user @@ -1246,6 +1251,7 @@ args: value: null - alias: mattermost-install-create-db-database attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.database @@ -1253,6 +1259,7 @@ args: value: null - alias: mattermost-install-helm-install-helm-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.helm.chart.release 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 7b4dba4b7..b8a07bdca 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 @@ -985,6 +985,7 @@ args: typeInstances: - alias: mattermost-config attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.productivity.mattermost.config @@ -992,6 +993,7 @@ args: value: null - alias: mattermost-install-create-user-user attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.user @@ -999,6 +1001,7 @@ args: value: null - alias: mattermost-install-create-db-database attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.database @@ -1006,6 +1009,7 @@ args: value: null - alias: mattermost-install-helm-install-helm-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.helm.chart.release 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 2519164eb..d63517542 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 @@ -482,6 +482,7 @@ args: typeInstances: - alias: postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -489,6 +490,7 @@ args: value: null - alias: rds-instance attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.aws.rds.instance @@ -496,6 +498,7 @@ args: value: null - alias: rds-provision-terraform-apply-terraform-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.terraform.release @@ -503,6 +506,7 @@ args: value: null - alias: rds-provision-terraform-apply-tfstate attributes: [] + backend: null createdBy: owner typeRef: path: cap.core.type.generic.value 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 737351536..21493476a 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 @@ -183,6 +183,7 @@ args: typeInstances: - alias: postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -190,6 +191,7 @@ args: value: null - alias: postgres-install-helm-install-helm-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.helm.chart.release 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 e272701e6..075634e6a 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 @@ -869,6 +869,7 @@ args: typeInstances: - alias: app1-config attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.productivity.app1.config @@ -876,6 +877,7 @@ args: value: null - alias: app1-install-install-db-postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -883,6 +885,7 @@ args: value: null - alias: app1-install-install-db-rds-instance attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.aws.rds.instance @@ -890,6 +893,7 @@ args: value: null - alias: app1-install-install-db-rds-provision-terraform-apply-terraform-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.terraform.release @@ -897,6 +901,7 @@ args: value: null - alias: app1-install-install-db-rds-provision-terraform-apply-tfstate attributes: [] + backend: null createdBy: owner typeRef: path: cap.core.type.generic.value @@ -904,6 +909,7 @@ args: value: null - alias: app1-install-create-db-database attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.database @@ -911,6 +917,7 @@ args: value: null - alias: app1-install-helm-install-helm-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.helm.chart.release 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 974d81526..8e0730076 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 @@ -869,6 +869,7 @@ args: typeInstances: - alias: app2-config attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.productivity.app2.config @@ -876,6 +877,7 @@ args: value: null - alias: main-install-db-postgresql attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.config @@ -883,6 +885,7 @@ args: value: null - alias: main-install-db-rds-instance attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.aws.rds.instance @@ -890,6 +893,7 @@ args: value: null - alias: main-install-db-rds-provision-terraform-apply-terraform-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.terraform.release @@ -897,6 +901,7 @@ args: value: null - alias: main-install-db-rds-provision-terraform-apply-tfstate attributes: [] + backend: null createdBy: owner typeRef: path: cap.core.type.generic.value @@ -904,6 +909,7 @@ args: value: null - alias: main-create-db-database attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.database.postgresql.database @@ -911,6 +917,7 @@ args: value: null - alias: main-helm-install-helm-release attributes: [] + backend: null createdBy: owner typeRef: path: cap.type.helm.chart.release diff --git a/pkg/sdk/renderer/argo/typeinstance_handler.go b/pkg/sdk/renderer/argo/typeinstance_handler.go index 43644d8ad..f7627408a 100644 --- a/pkg/sdk/renderer/argo/typeinstance_handler.go +++ b/pkg/sdk/renderer/argo/typeinstance_handler.go @@ -5,11 +5,12 @@ import ( "path" "strings" - "github.com/google/uuid" - + "capact.io/capact/pkg/engine/k8s/policy" graphqllocal "capact.io/capact/pkg/hub/api/graphql/local" "capact.io/capact/pkg/sdk/apis/0.0.1/types" + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/google/uuid" apiv1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" ) @@ -104,6 +105,7 @@ type OutputTypeInstanceRelation struct { type OutputTypeInstance struct { ArtifactName *string TypeInstance types.OutputTypeInstance + Backend policy.TypeInstanceBackend } // OutputTypeInstances holds information about the output TypeInstances @@ -135,7 +137,7 @@ func (r *TypeInstanceHandler) AddUploadTypeInstancesStep(rootWorkflow *Workflow, } for _, ti := range output.typeInstances { - payload.TypeInstances = append(payload.TypeInstances, &graphqllocal.CreateTypeInstanceInput{ + gqlTI := &graphqllocal.CreateTypeInstanceInput{ Alias: ti.ArtifactName, CreatedBy: &ownerID, TypeRef: &graphqllocal.TypeInstanceTypeReferenceInput{ @@ -143,7 +145,13 @@ func (r *TypeInstanceHandler) AddUploadTypeInstancesStep(rootWorkflow *Workflow, Revision: ti.TypeInstance.TypeRef.Revision, }, Attributes: []*graphqllocal.AttributeReferenceInput{}, - }) + } + if ti.Backend.ID != "" { + gqlTI.Backend = &graphqllocal.TypeInstanceBackendInput{ + ID: ti.Backend.ID, + } + } + payload.TypeInstances = append(payload.TypeInstances, gqlTI) artifacts = append(artifacts, wfv1.Artifact{ Name: *ti.ArtifactName, diff --git a/pkg/sdk/renderer/argo/types.go b/pkg/sdk/renderer/argo/types.go index 0b970d467..21858d9e1 100644 --- a/pkg/sdk/renderer/argo/types.go +++ b/pkg/sdk/renderer/argo/types.go @@ -29,16 +29,22 @@ type ParallelSteps []*WorkflowStep // It extends the Argo WorkflowStep and adds Capact specific properties. type WorkflowStep struct { *wfv1.WorkflowStep - CapactWhen *string `json:"capact-when,omitempty"` - CapactAction *string `json:"capact-action,omitempty"` - CapactPolicy *policy.WorkflowPolicy `json:"capact-policy,omitempty"` - CapactTypeInstanceOutputs []TypeInstanceDefinition `json:"capact-outputTypeInstances,omitempty"` - CapactTypeInstanceUpdates []TypeInstanceDefinition `json:"capact-updateTypeInstances,omitempty"` + CapactWhen *string `json:"capact-when,omitempty"` + CapactAction *string `json:"capact-action,omitempty"` + CapactPolicy *policy.WorkflowPolicy `json:"capact-policy,omitempty"` + CapactTypeInstanceOutputs []CapactTypeInstanceOutputs `json:"capact-outputTypeInstances,omitempty"` + CapactTypeInstanceUpdates []TypeInstanceDefinition `json:"capact-updateTypeInstances,omitempty"` // internal fields typeInstanceOutputs map[string]*string } +// CapactTypeInstanceOutputs holds data defined for `capact-outputTypeInstances` field on step level. +type CapactTypeInstanceOutputs struct { + TypeInstanceDefinition `json:",inline"` + Backend *string `json:"backend,omitempty"` +} + // TypeInstanceDefinition represents a TypeInstance, // which is created in a step. type TypeInstanceDefinition struct { diff --git a/pkg/sdk/validation/manifest/json_remote_implementation.go b/pkg/sdk/validation/manifest/json_remote_implementation.go index 998d7bdb9..68832bf42 100644 --- a/pkg/sdk/validation/manifest/json_remote_implementation.go +++ b/pkg/sdk/validation/manifest/json_remote_implementation.go @@ -6,8 +6,6 @@ import ( "fmt" "strings" - "capact.io/capact/internal/ptr" - "capact.io/capact/internal/regexutil" gqlpublicapi "capact.io/capact/pkg/hub/api/graphql/public" "capact.io/capact/pkg/hub/client/public" "capact.io/capact/pkg/sdk/apis/0.0.1/types" @@ -20,14 +18,10 @@ import ( "github.com/pkg/errors" ) -const ( - typeListQueryFields = public.TypeRevisionRootFields | public.TypeRevisionSpecAdditionalRefsField -) - // ParentNodesAssociation represents relations between parent node and associated other types. // - key holds the parent node path // - value holds list of associated Types -type ParentNodesAssociation map[string][]types.ManifestRef +type ParentNodesAssociation map[string][]types.TypeRef // RemoteImplementationValidator is a validator for Implementation manifest, which calls Hub in order to do validation checks. type RemoteImplementationValidator struct { @@ -254,7 +248,7 @@ func (v *RemoteImplementationValidator) resolveRequiresPath(parentPrefix string, allReqItems = append(allReqItems, reqItem.AnyOf...) for _, requiresSubItem := range allReqItems { - ref := types.ManifestRef{ + ref := types.TypeRef{ Path: strings.Join([]string{parentPrefix, requiresSubItem.Name}, "."), // default assumption Revision: requiresSubItem.Revision, } @@ -286,30 +280,12 @@ func (v *RemoteImplementationValidator) checkParentNodesAssociation(ctx context. var validationErrs []error for parentNode, expTypesRefs := range relations { - typesPath, expAttachedTypes := v.mapToPathAndPathRevIndex(expTypesRefs) - - filter := regexutil.OrStringSlice(typesPath) - res, err := v.hub.ListTypes(ctx, public.WithTypeRevisions(typeListQueryFields), public.WithTypeFilter(gqlpublicapi.TypeFilter{ - PathPattern: ptr.String(filter), - })) + gotAttachedTypes, err := public.ListAdditionalRefs(ctx, v.hub, expTypesRefs) if err != nil { return ValidationResult{}, errors.Wrap(err, "while fetching Types based on parent node") } - gotAttachedTypes := map[string][]string{} - for _, item := range res { - if item == nil { - continue - } - for _, rev := range item.Revisions { - if rev.Spec == nil { - continue - } - gotAttachedTypes[v.key(item.Path, rev.Revision)] = rev.Spec.AdditionalRefs - } - } - - missingEntries := v.detectMissingChildren(gotAttachedTypes, expAttachedTypes, parentNode) + missingEntries := v.detectMissingChildren(gotAttachedTypes, expTypesRefs, parentNode) if len(missingEntries) == 0 { continue } @@ -324,21 +300,7 @@ func (v *RemoteImplementationValidator) checkParentNodesAssociation(ctx context. return ValidationResult{Errors: validationErrs}, nil } -func (v *RemoteImplementationValidator) mapToPathAndPathRevIndex(in []types.ManifestRef) ([]string, []string) { - var ( - paths []string - pathsRevIdx []string - ) - - for _, expType := range in { - paths = append(paths, expType.Path) - pathsRevIdx = append(pathsRevIdx, v.key(expType.Path, expType.Revision)) - } - - return paths, pathsRevIdx -} - -func (v *RemoteImplementationValidator) detectMissingChildren(gotAttachedTypes map[string][]string, expAttachedTypes []string, expParent string) []string { +func (v *RemoteImplementationValidator) detectMissingChildren(gotAttachedTypes public.ListAdditionalRefsOutput, expAttachedTypes []types.TypeRef, expParent string) []string { var missingChildren []string for _, exp := range expAttachedTypes { @@ -352,7 +314,7 @@ func (v *RemoteImplementationValidator) detectMissingChildren(gotAttachedTypes m continue } - missingChildren = append(missingChildren, fmt.Sprintf("%q", exp)) + missingChildren = append(missingChildren, fmt.Sprintf(`"%s:%s"`, exp.Path, exp.Revision)) } return missingChildren @@ -366,7 +328,3 @@ func (v *RemoteImplementationValidator) stringSliceContains(slice []string, elem } return false } - -func (v *RemoteImplementationValidator) key(a, b string) string { - return fmt.Sprintf("%s:%s", a, b) -} diff --git a/pkg/sdk/validation/policy/fixtures_test.go b/pkg/sdk/validation/policy/fixtures_test.go index f34f2de7c..3e0d952a5 100644 --- a/pkg/sdk/validation/policy/fixtures_test.go +++ b/pkg/sdk/validation/policy/fixtures_test.go @@ -9,7 +9,7 @@ import ( func fixPolicyWithoutTypeRef() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.*", @@ -20,12 +20,12 @@ func fixPolicyWithoutTypeRef() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id", }, }, { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id2", Description: ptr.String("ID 2"), }, @@ -45,13 +45,108 @@ func fixPolicyWithoutTypeRef() policy.Policy { }, }, }, + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, + } +} + +func fixPolicyWithWrongBackendForTypeRef() policy.Policy { + return policy.Policy{ + TypeInstance: policy.TypeInstancePolicy{ + Rules: []policy.RulesForTypeInstance{ + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.auth.credentials", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + TypeRef: &types.TypeRef{ + Path: "cap.type.hashicorp.vault.storage", + Revision: "0.1.0", + }, + ID: "00fd161c-01bd-47a6-9872-47490e11f996", + Description: ptr.String("Vault TI"), + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.type.aws.*", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + TypeRef: &types.TypeRef{ + Path: "cap.type.aws.auth.secret-manager", + Revision: "0.1.0", + }, + ID: "31bb8355-10d7-49ce-a739-4554d8a40b63", + }, + }, + }, + { + TypeRef: types.ManifestRefWithOptRevision{ + Path: "cap.*", + Revision: ptr.String("0.1.0"), + }, + Backend: policy.TypeInstanceBackend{ + TypeInstanceReference: policy.TypeInstanceReference{ + TypeRef: &types.TypeRef{ + Path: "cap.core.type.hub.storage.postresql", + Revision: "0.1.0", + }, + ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", + Description: ptr.String("Default Capact PostgreSQL backend"), + }, + }, + }, + }, + }, } } func fixPolicyWithTypeRef() policy.Policy { return policy.Policy{ Interface: policy.InterfacePolicy{ - Rules: policy.RulesList{ + Rules: policy.InterfaceRulesList{ { Interface: types.ManifestRefWithOptRevision{ Path: "cap.*", @@ -62,22 +157,22 @@ func fixPolicyWithTypeRef() policy.Policy { Inject: &policy.InjectData{ RequiredTypeInstances: []policy.RequiredTypeInstanceToInject{ { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id", - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.sample", - Revision: "0.1.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.sample", + Revision: "0.1.0", + }, }, }, { - RequiredTypeInstanceReference: policy.RequiredTypeInstanceReference{ + TypeInstanceReference: policy.TypeInstanceReference{ ID: "id2", Description: ptr.String("ID 2"), - }, - TypeRef: &types.ManifestRef{ - Path: "cap.type.sample2", - Revision: "0.2.0", + TypeRef: &types.TypeRef{ + Path: "cap.type.sample2", + Revision: "0.2.0", + }, }, }, }, diff --git a/pkg/sdk/validation/policy/policy.go b/pkg/sdk/validation/policy/policy.go index 0dfd65e54..7fbd83422 100644 --- a/pkg/sdk/validation/policy/policy.go +++ b/pkg/sdk/validation/policy/policy.go @@ -49,7 +49,7 @@ func (v *Validator) LoadAdditionalInputParametersSchemas(ctx context.Context, im } // IsTypeRefInjectableAndEqualToImplReq returns boolean value if a given Type reference matches the one in Implementation requirement item and can be injected. -func (v *Validator) IsTypeRefInjectableAndEqualToImplReq(typeRef *types.ManifestRef, reqItem *gqlpublicapi.ImplementationRequirementItem) bool { +func (v *Validator) IsTypeRefInjectableAndEqualToImplReq(typeRef *types.TypeRef, reqItem *gqlpublicapi.ImplementationRequirementItem) bool { // check requirement item valid if reqItem == nil || reqItem.TypeRef == nil || reqItem.Alias == nil { return false @@ -96,13 +96,13 @@ func (v *Validator) ValidateAdditionalInputParameters(ctx context.Context, param // ValidateTypeInstancesMetadata validates that every TypeInstance has metadata resolved. func (v *Validator) ValidateTypeInstancesMetadata(in policy.Policy) validation.Result { unresolvedTypeInstances := metadata.TypeInstanceIDsWithUnresolvedMetadataForPolicy(in) - return v.validationResultForTIMetadata(unresolvedTypeInstances) + return v.validationResultForTIMetadata(unresolvedTypeInstances, in.TypeInstance.Rules) } // ValidateTypeInstancesMetadataForRule validates whether the TypeInstance injection metadata are resolved. func (v *Validator) ValidateTypeInstancesMetadataForRule(in policy.Rule) validation.Result { unresolvedTypeInstances := metadata.TypeInstanceIDsWithUnresolvedMetadataForRule(in) - return v.validationResultForTIMetadata(unresolvedTypeInstances) + return v.validationResultForTIMetadata(unresolvedTypeInstances, nil) } // AreTypeInstancesMetadataResolved returns whether every TypeInstance has metadata resolved. @@ -119,15 +119,28 @@ func (v *Validator) hasImplAdditionalInputParams(impl gqlpublicapi.Implementatio return true } -func (v *Validator) validationResultForTIMetadata(tis []metadata.TypeInstanceMetadata) validation.Result { - if len(tis) == 0 { - return validation.Result{} - } - +func (v *Validator) validationResultForTIMetadata(tis []metadata.TypeInstanceMetadata, rules []policy.RulesForTypeInstance) validation.Result { resultBldr := validation.NewResultBuilder("Metadata for") + unresolvedIDs := map[string]struct{}{} for _, ti := range tis { resultBldr.ReportIssue(string(ti.Kind), "missing Type reference for %s", ti.String(false)) + unresolvedIDs[ti.ID] = struct{}{} + } + + for _, rule := range rules { + if _, unresolved := unresolvedIDs[rule.Backend.ID]; unresolved { + continue // Type was not resolved, so `ExtendsHubStorage` has zero value + } + + if rule.Backend.ExtendsHubStorage { + continue + } + ref := metadata.TypeInstanceMetadata{ + ID: rule.Backend.ID, + Description: rule.Backend.Description, + } + resultBldr.ReportIssue("BackendTypeInstance", "Type reference %s is not a Hub storage", ref.String(false)) } return resultBldr.Result() diff --git a/pkg/sdk/validation/policy/policy_test.go b/pkg/sdk/validation/policy/policy_test.go index d63917524..9729bab30 100644 --- a/pkg/sdk/validation/policy/policy_test.go +++ b/pkg/sdk/validation/policy/policy_test.go @@ -34,18 +34,34 @@ func TestValidator_ValidateTypeInstancesMetadata(t *testing.T) { Input: fixPolicyWithTypeRef(), }, { - Name: "Invalid", + Name: "Policy Without TypeRefs", Input: fixPolicyWithoutTypeRef(), ExpectedErrMessage: ptr.String( heredoc.Docf(` - Metadata for "AdditionalTypeInstance": * missing Type reference for ID: "id3", name: "id-3" + - Metadata for "BackendTypeInstance": + * missing Type reference for ID: "00fd161c-01bd-47a6-9872-47490e11f996", description: "Vault TI" + * missing Type reference for ID: "31bb8355-10d7-49ce-a739-4554d8a40b63" + * missing Type reference for ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", description: "Default Capact PostgreSQL backend" - Metadata for "RequiredTypeInstance": * missing Type reference for ID: "id" * missing Type reference for ID: "id2", description: "ID 2"`, ), ), }, + { + Name: "Policy with wrong backends for Types", + Input: fixPolicyWithWrongBackendForTypeRef(), + ExpectedErrMessage: ptr.String( + heredoc.Docf(` + - Metadata for "BackendTypeInstance": + * Type reference ID: "00fd161c-01bd-47a6-9872-47490e11f996", description: "Vault TI" is not a Hub storage + * Type reference ID: "31bb8355-10d7-49ce-a739-4554d8a40b63" is not a Hub storage + * Type reference ID: "a36ed738-dfe7-45ec-acd1-8e44e8db893b", description: "Default Capact PostgreSQL backend" is not a Hub storage`, + ), + ), + }, } for _, testCase := range tests { @@ -73,7 +89,7 @@ func TestValidator_IsTypeRefInjectableAndEqualToImplReq(t *testing.T) { validator := policyvalidation.NewValidator(nil) tests := []struct { Name string - TypeRef *types.ManifestRef + TypeRef *types.TypeRef ReqItem *gqlpublicapi.ImplementationRequirementItem ExpectedResult bool }{ @@ -91,7 +107,7 @@ func TestValidator_IsTypeRefInjectableAndEqualToImplReq(t *testing.T) { }, { Name: "Empty ReqItem", - TypeRef: &types.ManifestRef{ + TypeRef: &types.TypeRef{ Path: "path", Revision: "revision", }, @@ -100,7 +116,7 @@ func TestValidator_IsTypeRefInjectableAndEqualToImplReq(t *testing.T) { }, { Name: "Different path", - TypeRef: &types.ManifestRef{ + TypeRef: &types.TypeRef{ Path: "path1", Revision: "1.0.0", }, @@ -115,7 +131,7 @@ func TestValidator_IsTypeRefInjectableAndEqualToImplReq(t *testing.T) { }, { Name: "Different revision", - TypeRef: &types.ManifestRef{ + TypeRef: &types.TypeRef{ Path: "path", Revision: "1.0.0", }, @@ -130,7 +146,7 @@ func TestValidator_IsTypeRefInjectableAndEqualToImplReq(t *testing.T) { }, { Name: "Equal but empty alias", - TypeRef: &types.ManifestRef{ + TypeRef: &types.TypeRef{ Path: "path", Revision: "revision", }, @@ -144,7 +160,7 @@ func TestValidator_IsTypeRefInjectableAndEqualToImplReq(t *testing.T) { }, { Name: "Equal", - TypeRef: &types.ManifestRef{ + TypeRef: &types.TypeRef{ Path: "path", Revision: "revision", }, diff --git a/test/e2e/action_test.go b/test/e2e/action_test.go index c3f403528..6e0b1f133 100644 --- a/test/e2e/action_test.go +++ b/test/e2e/action_test.go @@ -12,11 +12,13 @@ import ( "strings" "time" + "capact.io/capact/internal/cli/heredoc" "capact.io/capact/internal/ptr" enginegraphql "capact.io/capact/pkg/engine/api/graphql" engine "capact.io/capact/pkg/engine/client" hublocalgraphql "capact.io/capact/pkg/hub/api/graphql/local" hubclient "capact.io/capact/pkg/hub/client" + "capact.io/capact/pkg/hub/client/local" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" @@ -26,10 +28,14 @@ import ( ) const ( - actionPassingPath = "cap.interface.capactio.capact.validation.action.passing" + actionPassingInterfacePath = "cap.interface.capactio.capact.validation.action.passing" + uploadTypePath = "cap.type.capactio.capact.validation.upload" + builtinStorageTypePath = "cap.core.type.hub.storage.neo4j" + singleKeyTypePath = "cap.type.capactio.capact.validation.single-key" ) func getActionName() string { + rand.Seed(time.Now().UTC().UnixNano()) return fmt.Sprintf("e2e-test-%d-%s", GinkgoParallelNode(), strconv.Itoa(rand.Intn(10000))) } @@ -46,8 +52,8 @@ var _ = Describe("Action", func() { hubClient = getHubGraphQLClient() actionName = getActionName() - // Ensure Test Policy - updateGlobalPolicy(ctx, engineClient, nil) + // Ensure default Test Policy + setGlobalTestPolicy(ctx, engineClient) }) AfterEach(func() { @@ -57,14 +63,16 @@ var _ = Describe("Action", func() { }) Context("Action execution", func() { - It("should pick proper Implementation and inject TypeInstance based on cluster policy", func() { - actionPath := "cap.interface.capactio.capact.validation.action.passing" - testValue := "Implementation A" + It("should pick Implementation A", func() { + implIndicatorValue := "Implementation A" + + // 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(testValue) + download := getTypeInstanceInputForDownload(implIndicatorValue) downloadTI, downloadTICleanup := createTypeInstance(ctx, hubClient, download) defer downloadTICleanup() @@ -73,73 +81,126 @@ var _ = Describe("Action", func() { updateTI, updateTICleanup := createTypeInstance(ctx, hubClient, update) defer updateTICleanup() - typeInstances := []*enginegraphql.InputTypeInstanceData{ - {Name: "testUpdate", ID: updateTI.ID}, - {Name: "testInput", ID: downloadTI.ID}, - } + By("1.3 Creating TypeInstance that describes Helm storage") + helmStorage := fixHelmStorageTypeInstanceCreateInput() + helmStorageTI, helmStorageTICleanup := createTypeInstance(ctx, hubClient, helmStorage) + defer helmStorageTICleanup() inputData := &enginegraphql.ActionInputData{ - TypeInstances: typeInstances, + TypeInstances: []*enginegraphql.InputTypeInstanceData{ + {Name: "testInput", ID: downloadTI.ID}, + {Name: "testUpdate", ID: updateTI.ID}, + }, } - By("1.3 Create TypeInstance which is required for Implementation B to be picked based on Policy") - typeInstanceValue := getTypeInstanceInputForPolicy() - typeInstance, tiCleanupFn := createTypeInstance(ctx, hubClient, typeInstanceValue) - defer tiCleanupFn() - injectedTypeInstanceID := typeInstance.ID + builtinStorage := getBuiltinStorageTypeInstance(ctx, hubClient) + expUpdatedTIOutput := mapToOutputTypeInstanceDetails(updateTI, builtinStorage.Backend) - By("2. Expecting Implementation A is picked based on test policy and requirements met...") + By("2. Expecting Implementation A is picked and builtin storage is used...") - action := createActionAndWaitForReadyToRunPhase(ctx, engineClient, actionName, actionPath, inputData) - assertActionRenderedWorkflowContains(action, "echo 'Implementation A'") + action := createActionAndWaitForReadyToRunPhase(ctx, engineClient, actionName, actionPassingInterfacePath, inputData) + assertActionRenderedWorkflowContains(action, "echo '%s'", implIndicatorValue) runActionAndWaitForSucceeded(ctx, engineClient, actionName) - By("3. Check TypeInstances") By("3.1 Check uploaded TypeInstances") - assertUploadedTypeInstance(ctx, hubClient, testValue) - - assertOutputTypeInstancesInActionStatus(ctx, engineClient, action.Name, And(ContainElement( - &enginegraphql.OutputTypeInstanceDetails{ - ID: updateTI.ID, - TypeRef: &enginegraphql.ManifestReference{ - Path: updateTI.TypeRef.Path, - Revision: updateTI.TypeRef.Revision, - }, - }, - ), HaveLen(2))) + expUploadTIBackend := &hublocalgraphql.TypeInstanceBackendReference{ID: builtinStorage.ID, Abstract: true} + uploadedTI, cleanupUploaded := getUploadedTypeInstanceByValue(ctx, hubClient, implIndicatorValue) + Expect(uploadedTI.Backend).Should(Equal(expUploadTIBackend)) By("3.2 Check updated TypeInstances") - updateTI, err := hubClient.FindTypeInstance(ctx, updateTI.ID) - Expect(err).ToNot(HaveOccurred()) - Expect(updateTI).ToNot(BeNil()) + getTypeInstanceByIDAndValue(ctx, hubClient, updateTI.ID, implIndicatorValue) - _, err = getTypeInstanceWithValue([]hublocalgraphql.TypeInstance{*updateTI}, testValue) - Expect(err).ToNot(HaveOccurred()) + By("3.3 Check Action output TypeInstances") + uploadedTIOutput := mapToOutputTypeInstanceDetails(uploadedTI, expUploadTIBackend) + assertOutputTypeInstancesInActionStatus(ctx, engineClient, action.Name, And(ContainElements(expUpdatedTIOutput, uploadedTIOutput), HaveLen(2))) - By("3.3 Deleting Action...") - err = engineClient.DeleteAction(ctx, actionName) + By("4. Deleting Action...") + err := engineClient.DeleteAction(ctx, actionName) + cleanupUploaded() // We need to clean it up as it's not deleted when Action is deleted. Expect(err).ToNot(HaveOccurred()) - By("3.4 Waiting for Action deleted") + By("5. Waiting for Action deleted") waitForActionDeleted(ctx, engineClient, actionName) - By("4. Modifying Policy to make Implementation B picked for next run...") - globalPolicyRequiredTypeInstances := enginegraphql.RequiredTypeInstanceReferenceInput{ - ID: injectedTypeInstanceID, - Description: ptr.String("Test TypeInstance"), + By("6. Modifying Policy to change backend storage for uploaded TypeInstance via TypeRef...") + setGlobalTestPolicy(ctx, engineClient, withHelmBackendForUploadTypeRef(helmStorageTI.ID)) + + By("7. Expecting Implementation A is picked and the Helm storage is used for uploaded TypeInstance...") + action = createActionAndWaitForReadyToRunPhase(ctx, engineClient, actionName, actionPassingInterfacePath, inputData) + assertActionRenderedWorkflowContains(action, "echo '%s'", implIndicatorValue) + runActionAndWaitForSucceeded(ctx, engineClient, actionName) + + By("8.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("8.2 Check Action output TypeInstances") + uploadedTIOutput = mapToOutputTypeInstanceDetails(uploadedTI, expUploadTIBackend) + assertOutputTypeInstancesInActionStatus(ctx, engineClient, action.Name, And(ContainElements(expUpdatedTIOutput, uploadedTIOutput), HaveLen(2))) + }) + + It("should pick Implementation B", 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 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"), + }, } - updateGlobalPolicy(ctx, engineClient, &globalPolicyRequiredTypeInstances) + setGlobalTestPolicy(ctx, engineClient, prependInjectRuleForPassingActionInterface(globalPolicyRequiredTypeInstances)) - By("5. Expecting Implementation B is picked based on test policy...") - action = createActionAndWaitForReadyToRunPhase(ctx, engineClient, actionName, actionPath, inputData) - assertActionRenderedWorkflowContains(action, "echo 'Implementation B'") + 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("6. Check Uploaded TypeInstances") - assertUploadedTypeInstance(ctx, hubClient, testValue) + 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("7. Check output TypeInstances in Action status") - assertOutputTypeInstancesInActionStatus(ctx, engineClient, action.Name, HaveLen(1)) + 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() { @@ -245,6 +306,20 @@ var _ = Describe("Action", func() { }) }) +func mapToOutputTypeInstanceDetails(ti *hublocalgraphql.TypeInstance, backend *hublocalgraphql.TypeInstanceBackendReference) *enginegraphql.OutputTypeInstanceDetails { + return &enginegraphql.OutputTypeInstanceDetails{ + ID: ti.ID, + TypeRef: &enginegraphql.ManifestReference{ + Path: ti.TypeRef.Path, + Revision: ti.TypeRef.Revision, + }, + Backend: &enginegraphql.TypeInstanceBackendDetails{ + ID: backend.ID, + Abstract: backend.Abstract, + }, + } +} + func getActionStatusFunc(ctx context.Context, cl *engine.Client, name string) func() (enginegraphql.ActionStatusPhase, error) { return func() (enginegraphql.ActionStatusPhase, error) { action, err := cl.GetAction(ctx, name) @@ -272,7 +347,7 @@ func getActionFunc(ctx context.Context, cl *engine.Client, name string) func() ( func getTypeInstanceInputForPolicy() *hublocalgraphql.CreateTypeInstanceInput { return &hublocalgraphql.CreateTypeInstanceInput{ TypeRef: &hublocalgraphql.TypeInstanceTypeReferenceInput{ - Path: "cap.type.capactio.capact.validation.single-key", + Path: singleKeyTypePath, Revision: "0.1.0", }, Attributes: []*hublocalgraphql.AttributeReferenceInput{ @@ -319,6 +394,40 @@ func getTypeInstanceInputForUpdate() *hublocalgraphql.CreateTypeInstanceInput { } } +func fixHelmStorageTypeInstanceCreateInput() *hublocalgraphql.CreateTypeInstanceInput { + return &hublocalgraphql.CreateTypeInstanceInput{ + TypeRef: &hublocalgraphql.TypeInstanceTypeReferenceInput{ + Path: "cap.type.helm.storage", + Revision: "0.1.0", + }, + Attributes: []*hublocalgraphql.AttributeReferenceInput{}, + Value: map[string]interface{}{ + "url": "helm-release.default:50051", + "acceptValue": true, + "contextSchema": heredoc.Doc(` + { + "$id": "#/properties/contextSchema", + "type": "object", + "required": [ + "name", + "namespace" + ], + "properties": { + "name": { + "$id": "#/properties/contextSchema/properties/name", + "type": "string" + }, + "namespace": { + "$id": "#/properties/contextSchema/properties/namespace", + "type": "string" + } + }, + "additionalProperties": false + }`), + }, + } +} + func createActionAndWaitForReadyToRunPhase(ctx context.Context, engineClient *engine.Client, actionName, actionPath string, input *enginegraphql.ActionInputData) *enginegraphql.Action { _, err := engineClient.CreateAction(ctx, &enginegraphql.ActionDetailsInput{ Name: actionName, @@ -342,11 +451,11 @@ func createActionAndWaitForReadyToRunPhase(ctx context.Context, engineClient *en return action } -func assertActionRenderedWorkflowContains(action *enginegraphql.Action, stringToFind string) { +func assertActionRenderedWorkflowContains(action *enginegraphql.Action, toFindFormat string, toFindArgs ...interface{}) { jsonBytes, err := json.Marshal(action.RenderedAction) Expect(err).ToNot(HaveOccurred()) Expect( - strings.Contains(string(jsonBytes), stringToFind), + strings.Contains(string(jsonBytes), fmt.Sprintf(toFindFormat, toFindArgs...)), ).To(BeTrue()) } @@ -389,32 +498,106 @@ func createTypeInstance(ctx context.Context, hubClient *hubclient.Client, in *hu return createdTypeInstance, cleanupFn } -func updateGlobalPolicy(ctx context.Context, client *engine.Client, reqTypeInstance *enginegraphql.RequiredTypeInstanceReferenceInput) { - var reqInput []*enginegraphql.RequiredTypeInstanceReferenceInput - // nils element are not handled by GraphQL - if reqTypeInstance != nil { - reqInput = append(reqInput, reqTypeInstance) +type policyOption func(*enginegraphql.PolicyInput) + +func withHelmBackendForUploadTypeRef(backendID string) policyOption { + return func(policy *enginegraphql.PolicyInput) { + policy.TypeInstance = &enginegraphql.TypeInstancePolicyInput{ + Rules: []*enginegraphql.RulesForTypeInstanceInput{ + { + TypeRef: &enginegraphql.ManifestReferenceInput{ + Path: uploadTypePath, + Revision: ptr.String("0.1.0"), + }, + Backend: &enginegraphql.TypeInstanceBackendRuleInput{ + ID: backendID, + Description: ptr.String("Default Hub backend storage via TypeRef"), + }, + }, + }, + } + } +} + +func prependInjectRuleForPassingActionInterface(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"), + }, + Inject: &enginegraphql.PolicyRuleInjectDataInput{ + RequiredTypeInstances: reqInput, + }, + }, + }, policy.Interface.Rules[idx].OneOf...) + } + } +} + +func setGlobalTestPolicy(ctx context.Context, client *engine.Client, opts ...policyOption) { + p := fixGQLTestPolicyInput() + + for _, opt := range opts { + opt(p) } - p := PolicyInputTestFixture(reqInput) + _, err := client.UpdatePolicy(ctx, p) Expect(err).ToNot(HaveOccurred()) } -func assertUploadedTypeInstance(ctx context.Context, hubClient *hubclient.Client, testValue string) { +func getTypeInstanceByIDAndValue(ctx context.Context, hubClient *hubclient.Client, id, expValue string) *hublocalgraphql.TypeInstance { + updateTI, err := hubClient.FindTypeInstance(ctx, id) + Expect(err).ToNot(HaveOccurred()) + Expect(updateTI).ToNot(BeNil()) + _, err = getTypeInstanceWithValue([]hublocalgraphql.TypeInstance{*updateTI}, expValue) + Expect(err).ToNot(HaveOccurred()) + + return updateTI +} + +func getUploadedTypeInstanceByValue(ctx context.Context, hubClient *hubclient.Client, expValue string) (*hublocalgraphql.TypeInstance, func()) { uploaded, err := hubClient.ListTypeInstances(ctx, &hublocalgraphql.TypeInstanceFilter{ TypeRef: &hublocalgraphql.TypeRefFilterInput{ - Path: "cap.type.capactio.capact.validation.upload", + Path: uploadTypePath, Revision: ptr.String("0.1.0"), }, }) Expect(err).ToNot(HaveOccurred()) Expect(len(uploaded)).Should(BeNumerically(">", 0)) - ti, err := getTypeInstanceWithValue(uploaded, testValue) + ti, err := getTypeInstanceWithValue(uploaded, expValue) Expect(err).ToNot(HaveOccurred()) - err = hubClient.DeleteTypeInstance(ctx, ti.ID) + return ti, func() { + err = hubClient.DeleteTypeInstance(ctx, ti.ID) + Expect(err).ToNot(HaveOccurred()) + } +} + +func getBuiltinStorageTypeInstance(ctx context.Context, hubClient *hubclient.Client) hublocalgraphql.TypeInstance { + coreStorage, err := hubClient.ListTypeInstances(ctx, &hublocalgraphql.TypeInstanceFilter{ + TypeRef: &hublocalgraphql.TypeRefFilterInput{ + Path: builtinStorageTypePath, + Revision: ptr.String("0.1.0"), + }, + }, local.WithFields(local.TypeInstanceAllFields)) Expect(err).ToNot(HaveOccurred()) + Expect(coreStorage).Should(HaveLen(1)) + + return coreStorage[0] } func assertOutputTypeInstancesInActionStatus(ctx context.Context, engineClient *engine.Client, actionName string, @@ -461,12 +644,10 @@ func mapToInputParameters(params map[string]interface{}) (*enginegraphql.JSON, e return &res, nil } -func PolicyInputTestFixture(reqInput []*enginegraphql.RequiredTypeInstanceReferenceInput) *enginegraphql.PolicyInput { - manifestRef := func(path string) []*enginegraphql.ManifestReferenceInput { - return []*enginegraphql.ManifestReferenceInput{ - { - Path: path, - }, +func fixGQLTestPolicyInput() *enginegraphql.PolicyInput { + manifestRef := func(path string) *enginegraphql.ManifestReferenceInput { + return &enginegraphql.ManifestReferenceInput{ + Path: path, } } @@ -474,17 +655,8 @@ func PolicyInputTestFixture(reqInput []*enginegraphql.RequiredTypeInstanceRefere Interface: &enginegraphql.InterfacePolicyInput{ Rules: []*enginegraphql.RulesForInterfaceInput{ { - Interface: manifestRef(actionPassingPath)[0], + Interface: manifestRef(actionPassingInterfacePath), OneOf: []*enginegraphql.PolicyRuleInput{ - { - ImplementationConstraints: &enginegraphql.PolicyRuleImplementationConstraintsInput{ - Requires: manifestRef("cap.type.capactio.capact.validation.single-key"), - Attributes: manifestRef("cap.attribute.capactio.capact.validation.policy.most-preferred"), - }, - Inject: &enginegraphql.PolicyRuleInjectDataInput{ - RequiredTypeInstances: reqInput, - }, - }, { ImplementationConstraints: &enginegraphql.PolicyRuleImplementationConstraintsInput{ Path: ptr.String("cap.implementation.capactio.capact.validation.action.passing-a"), @@ -494,7 +666,7 @@ func PolicyInputTestFixture(reqInput []*enginegraphql.RequiredTypeInstanceRefere }, // allow all others { - Interface: manifestRef("cap.*")[0], + Interface: manifestRef("cap.*"), OneOf: []*enginegraphql.PolicyRuleInput{ { ImplementationConstraints: &enginegraphql.PolicyRuleImplementationConstraintsInput{}, diff --git a/test/e2e/hub_test.go b/test/e2e/hub_test.go index 2dc439de8..9b36348ed 100644 --- a/test/e2e/hub_test.go +++ b/test/e2e/hub_test.go @@ -246,6 +246,7 @@ var _ = Describe("GraphQL API", func() { Context("Local Hub", func() { It("should create, find and delete TypeInstance", func() { cli := getHubGraphQLClient() + builtinStorage := getBuiltinStorageTypeInstance(ctx, cli) // create TypeInstance createdTypeInstance, err := cli.CreateTypeInstance(ctx, &gqllocalapi.CreateTypeInstanceInput{ @@ -291,7 +292,11 @@ var _ = Describe("GraphQL API", func() { Path: "cap.type.capactio.capact.ti", Revision: "0.1.0", }, - Uses: []*gqllocalapi.TypeInstance{}, + Backend: &gqllocalapi.TypeInstanceBackendReference{ + ID: builtinStorage.ID, + Abstract: true, + }, + Uses: []*gqllocalapi.TypeInstance{&builtinStorage}, UsedBy: []*gqllocalapi.TypeInstance{}, LatestResourceVersion: rev, FirstResourceVersion: rev, @@ -311,6 +316,7 @@ var _ = Describe("GraphQL API", func() { It("creates multiple TypeInstances with uses relations", func() { cli := getHubGraphQLClient() + builtinStorage := getBuiltinStorageTypeInstance(ctx, cli) createdTypeInstanceIDs, err := cli.CreateTypeInstances(ctx, createTypeInstancesInput()) @@ -325,11 +331,11 @@ var _ = Describe("GraphQL API", func() { childTiID := findCreatedTypeInstanceID("child", createdTypeInstanceIDs) Expect(childTiID).ToNot(BeNil()) - expectedChild := expectedChildTypeInstance(*childTiID) - expectedParent := expectedParentTypeInstance(*parentTiID) - expectedChild.UsedBy = []*gqllocalapi.TypeInstance{expectedParentTypeInstance(*parentTiID)} - expectedChild.Uses = []*gqllocalapi.TypeInstance{} - expectedParent.Uses = []*gqllocalapi.TypeInstance{expectedChildTypeInstance(*childTiID)} + expectedChild := expectedChildTypeInstance(*childTiID, builtinStorage.ID) + expectedParent := expectedParentTypeInstance(*parentTiID, builtinStorage.ID) + expectedChild.UsedBy = []*gqllocalapi.TypeInstance{expectedParentTypeInstance(*parentTiID, builtinStorage.ID)} + expectedChild.Uses = []*gqllocalapi.TypeInstance{&builtinStorage} + expectedParent.Uses = []*gqllocalapi.TypeInstance{&builtinStorage, expectedChildTypeInstance(*childTiID, builtinStorage.ID)} expectedParent.UsedBy = []*gqllocalapi.TypeInstance{} assertTypeInstance(ctx, cli, *childTiID, expectedChild) @@ -731,7 +737,7 @@ func createTypeInstancesInput() *gqllocalapi.CreateTypeInstancesInput { } } -func expectedChildTypeInstance(ID string) *gqllocalapi.TypeInstance { +func expectedChildTypeInstance(tiID, backendID string) *gqllocalapi.TypeInstance { tiRev := &gqllocalapi.TypeInstanceResourceVersion{ ResourceVersion: 1, Metadata: &gqllocalapi.TypeInstanceResourceVersionMetadata{ @@ -750,11 +756,16 @@ func expectedChildTypeInstance(ID string) *gqllocalapi.TypeInstance { } return &gqllocalapi.TypeInstance{ - ID: ID, + ID: tiID, TypeRef: &gqllocalapi.TypeInstanceTypeReference{ Path: "com.child", Revision: "0.1.0", }, + + Backend: &gqllocalapi.TypeInstanceBackendReference{ + ID: backendID, + Abstract: true, + }, LatestResourceVersion: tiRev, FirstResourceVersion: tiRev, PreviousResourceVersion: nil, @@ -765,7 +776,7 @@ func expectedChildTypeInstance(ID string) *gqllocalapi.TypeInstance { } } -func expectedParentTypeInstance(ID string) *gqllocalapi.TypeInstance { +func expectedParentTypeInstance(tiID, backendID string) *gqllocalapi.TypeInstance { tiRev := &gqllocalapi.TypeInstanceResourceVersion{ ResourceVersion: 1, Metadata: &gqllocalapi.TypeInstanceResourceVersionMetadata{ @@ -784,11 +795,16 @@ func expectedParentTypeInstance(ID string) *gqllocalapi.TypeInstance { } return &gqllocalapi.TypeInstance{ - ID: ID, + ID: tiID, TypeRef: &gqllocalapi.TypeInstanceTypeReference{ Path: "com.parent", Revision: "0.1.0", }, + + Backend: &gqllocalapi.TypeInstanceBackendReference{ + ID: backendID, + Abstract: true, + }, LatestResourceVersion: tiRev, FirstResourceVersion: tiRev, PreviousResourceVersion: nil,