diff --git a/README.md b/README.md index fc831d0..2ee5450 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ spec: parent: apiVersion: glueoperator.sample/v1 # watches all the custom resource of type WebPage kind: WebPage - resources: + childResources: - name: htmlconfigmap resource: apiVersion: v1 @@ -109,7 +109,7 @@ spec: ``` There are multiple aspects to see here. The four related resources will be templated -and applied to the cluster if such a resource is created. The reconciliation will be triggered if anything changes in the custom or managed resources. +and applied to the cluster if such a resource is created. The reconciliation will be triggered if anything changes in the custom or child resources. Note also the `condition` part for `Ingress` resource contains multiple types of conditions, `JSCondition` is used in this example, which allows writing conditions in Javascript. The `Ingress` will be created if the `.spec.exposed` property @@ -137,7 +137,7 @@ kind: Glue metadata: name: mutation-webhook-deployment spec: - resources: + childResources: - name: service resource: apiVersion: v1 diff --git a/docs/reference.md b/docs/reference.md index d745f7c..654ed97 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -9,22 +9,23 @@ Although it is limited only to Kubernetes resources it makes it very easy to use ## [Glue resource](https://github.com/csviri/kubernetes-glue-operator/releases/latest/download/glues.glue-v1.yml) `Glue` is the heart of the operator. Note that `GlueOperator` controller just creates a new `Glue` with a related resource, -for each parent custom resource. `Glue` defines `resources` (sometimes referred to as managed resources) and `related resources`: +for each parent custom resource. `Glue` defines `childResources` (sometimes referred to as managed resources) and `related resources`: -### Managed resources +### Child Resources #### Attributes -The `resources` section is a list of resources to be reconciled. It has several attributes: +The `childResources` section is a list of resources to be reconciled (created, updated, deleted by controller). +It has several attributes: - **`name`** - is a mandatory unique (unique also regarding related resources) attribute. The resource is referenced by this name from other places, typically other resource templates and `JSCondition`. If it is used in a `JSCondition` the `name` must be a valid JavaScript variable name. - **`resource`** - is the desired state of the resource applied by default using Server Side Apply. The resource is templated using [qute templating engine](https://quarkus.io/guides/qute-reference), other resources can be referenced from the templates, see below. - There is a restriction, that the managed resource is namespaced, and the namespace is always the same as the namespace of the `Glue` + There is a restriction, that the child resource is namespaced, and the namespace is always the same as the namespace of the `Glue` (and/or parent for `GlueOperator`), so the `namespace` field in resource **metadata should not be specified**. -- **`dependsOn`** - is a list of names of other managed resources (not related resources). The resource is not reconciled until all the resources +- **`dependsOn`** - is a list of names of other child resources (not related resources). The resource is not reconciled until all the resources which it depends on are not reconciled and ready (if there is a `readyPostCondition` present). Note that during the cleanup phase (when a `Glue` is deleted) resources are cleaned up in reverse order. - **`condition`** - a condition to specify if the resource should be there or not, thus even if the condition is evaluated to be `true` @@ -39,17 +40,17 @@ At the moment there are two types of built-in conditions provided: - **`ReadyCondition`** - check if a resource is up and running. Use it only as a `readyPostCondition`. See sample usage [here](https://github.com/csviri/kubernetes-glue-operator/blob/main/src/test/resources/sample/mutation/mutation.glue.yaml#L24-L25). - **`JSCondition`** - a generic condition, that allows writing conditions in JavaScript. As input, all the resources are available which - are either managed or related. The script should return a boolean value. + are either child or related. The script should return a boolean value. See accessing the related resource in [WebPage sample](https://github.com/csviri/kubernetes-glue-operator/blob/main/src/test/resources/sample/webpage/webpage.operator.yaml#L62-L64), and cross-referencing resources [here](https://github.com/csviri/kubernetes-glue-operator/blob/main/src/test/resources/glue/TwoResourcesAndCondition.yaml#L23-L28). ### Related resources -Related resources are resources that are not managed (not created, updated, or deleted) during reconciliation, but serve as an input for it. +Related resources are resources that are not reconciled (not created, updated, or deleted) during reconciliation, but serve as an input for it. See sample usage within `Glue` [here](https://github.com/csviri/kubernetes-glue-operator/blob/main/src/test/resources/glue/RelatedResourceSimpleWithCondition.yaml) The following attributes can be defined for a related resource: -- **`name`** - same as for managed resource, unique identifier, used to reference the resource. +- **`name`** - same as for child resource, unique identifier, used to reference the resource. - **`apiVersion`** - Kubernetes resource API Version of the resource - **`kind`** - Kubernetes kind property of the resource - **`resourceNames`** - list of string of the resource names within the same namespace as `Glue`. @@ -71,9 +72,9 @@ In addition to that in `GlueOperator` the **`parent`** attribute can be used to ### Reconciliation notes -The reconciliation is triggered either on a change of the `Glue` or any managed or related resources. +The reconciliation is triggered either on a change of the `Glue` or any child or related resources. -On every reconciliation, each managed resource is reconciled, and if a resource is updated, it is added to a cache, so it is available for templating +On every reconciliation, each child resource is reconciled, and if a resource is updated, it is added to a cache, so it is available for templating for a resource that depends on it. The `DependentResource` implementation of JOSDK makes all kinds of optimizations on the reconciliation which are utilized (or will be also here). @@ -98,7 +99,7 @@ While we will provide more options, users are encouraged to enhance/adjust this Since the project is a meta-controller, it needs to have access rights to all the resources it manages. When creating specialized roles for a deployment, roles should contain the union of required access rights -for all the managed resources, specifically: `["list", "watch", "create", "patch", "delete"]` +for all the child resources, specifically: `["list", "watch", "create", "patch", "delete"]` and `["list", "watch"]` for related resources. The project is mainly tested with cluster-scoped deployment, however, QOSDK namespace-scoped deployments are also supported. diff --git a/src/main/java/io/csviri/operator/glue/Utils.java b/src/main/java/io/csviri/operator/glue/Utils.java index 133c3ad..dc57817 100644 --- a/src/main/java/io/csviri/operator/glue/Utils.java +++ b/src/main/java/io/csviri/operator/glue/Utils.java @@ -30,7 +30,7 @@ public static Map getActualResourcesByNameInW var secondaryResources = context.getSecondaryResources(GenericKubernetesResource.class); Map res = new HashMap<>(); secondaryResources.forEach(sr -> { - var dependentSpec = glue.getSpec().getResources().stream() + var dependentSpec = glue.getSpec().getChildResources().stream() .filter(r -> Utils.getApiVersion(r).equals(sr.getApiVersion()) && Utils.getKind(r).equals(sr.getKind()) // comparing the name from annotation since the resource name might be templated in spec @@ -139,8 +139,8 @@ public static String getKindFromTemplate(String resourceTemplate) { public static Set leafResourceNames(Glue glue) { Set result = new HashSet<>(); - glue.getSpec().getResources().forEach(r -> result.add(r.getName())); - glue.getSpec().getResources().forEach(r -> { + glue.getSpec().getChildResources().forEach(r -> result.add(r.getName())); + glue.getSpec().getChildResources().forEach(r -> { r.getDependsOn().forEach(result::remove); }); return result; diff --git a/src/main/java/io/csviri/operator/glue/customresource/glue/GlueSpec.java b/src/main/java/io/csviri/operator/glue/customresource/glue/GlueSpec.java index 5b9fa98..9fb2bc2 100644 --- a/src/main/java/io/csviri/operator/glue/customresource/glue/GlueSpec.java +++ b/src/main/java/io/csviri/operator/glue/customresource/glue/GlueSpec.java @@ -6,16 +6,16 @@ public class GlueSpec { - private List resources = new ArrayList<>(); + private List childResources = new ArrayList<>(); private List relatedResources = new ArrayList<>(); - public List getResources() { - return resources; + public List getChildResources() { + return childResources; } - public void setResources(List resources) { - this.resources = resources; + public void setChildResources(List childResources) { + this.childResources = childResources; } public List getRelatedResources() { @@ -30,7 +30,7 @@ public GlueSpec setRelatedResources(List relatedResources) @Override public String toString() { return "GlueSpec{" + - "resources=" + resources + + "resources=" + childResources + '}'; } @@ -41,11 +41,11 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; GlueSpec that = (GlueSpec) o; - return Objects.equals(resources, that.resources); + return Objects.equals(childResources, that.childResources); } @Override public int hashCode() { - return Objects.hash(resources); + return Objects.hash(childResources); } } diff --git a/src/main/java/io/csviri/operator/glue/reconciler/ValidationAndErrorHandler.java b/src/main/java/io/csviri/operator/glue/reconciler/ValidationAndErrorHandler.java index c8a1339..306aafa 100644 --- a/src/main/java/io/csviri/operator/glue/reconciler/ValidationAndErrorHandler.java +++ b/src/main/java/io/csviri/operator/glue/reconciler/ValidationAndErrorHandler.java @@ -53,7 +53,7 @@ public void checkIfNamesAreUnique(GlueSpec glueSpec) { seen.add(n); } }; - glueSpec.getResources().stream().map(DependentResourceSpec::getName).forEach(deduplicate); + glueSpec.getChildResources().stream().map(DependentResourceSpec::getName).forEach(deduplicate); glueSpec.getRelatedResources().stream().map(RelatedResourceSpec::getName).forEach(deduplicate); if (!duplicates.isEmpty()) { diff --git a/src/main/java/io/csviri/operator/glue/reconciler/glue/GlueReconciler.java b/src/main/java/io/csviri/operator/glue/reconciler/glue/GlueReconciler.java index b1cd064..3fad9ff 100644 --- a/src/main/java/io/csviri/operator/glue/reconciler/glue/GlueReconciler.java +++ b/src/main/java/io/csviri/operator/glue/reconciler/glue/GlueReconciler.java @@ -126,7 +126,7 @@ private void cleanupRemovedResourcesFromWorkflow(Context context, context.getSecondaryResources(GenericKubernetesResource.class).forEach(r -> { String dependentName = r.getMetadata().getAnnotations().get(DEPENDENT_NAME_ANNOTATION_KEY); // dependent name is null for related resources - if (dependentName != null && primary.getSpec().getResources().stream() + if (dependentName != null && primary.getSpec().getChildResources().stream() .filter(pr -> pr.getName().equals(dependentName)).findAny().isEmpty()) { try { log.debug("Deleting resource with name: {}", dependentName + "for resource flow: {} " @@ -146,7 +146,7 @@ private io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow Set leafDependentNames = Utils.leafResourceNames(primary); Map genericDependentResourceMap = new HashMap<>(); - primary.getSpec().getResources().forEach(spec -> createAndAddDependentToWorkflow(primary, + primary.getSpec().getChildResources().forEach(spec -> createAndAddDependentToWorkflow(primary, context, spec, genericDependentResourceMap, builder, leafDependentNames.contains(spec.getName()))); diff --git a/src/main/java/io/csviri/operator/glue/reconciler/glue/InformerRegister.java b/src/main/java/io/csviri/operator/glue/reconciler/glue/InformerRegister.java index a2b4c1b..181dbd7 100644 --- a/src/main/java/io/csviri/operator/glue/reconciler/glue/InformerRegister.java +++ b/src/main/java/io/csviri/operator/glue/reconciler/glue/InformerRegister.java @@ -33,7 +33,7 @@ public synchronized void deRegisterInformerOnResourceFlowChange(Context co var registeredGVKSet = new HashSet<>(glueToInformerGVK.get(primary.getMetadata().getName())); - var currentGVKSet = primary.getSpec().getResources().stream() + var currentGVKSet = primary.getSpec().getChildResources().stream() .map(Utils::getGVK) .collect(Collectors.toSet()); diff --git a/src/main/java/io/csviri/operator/glue/reconciler/operator/GlueOperatorReconciler.java b/src/main/java/io/csviri/operator/glue/reconciler/operator/GlueOperatorReconciler.java index 2b6378d..9651dfc 100644 --- a/src/main/java/io/csviri/operator/glue/reconciler/operator/GlueOperatorReconciler.java +++ b/src/main/java/io/csviri/operator/glue/reconciler/operator/GlueOperatorReconciler.java @@ -99,7 +99,7 @@ private Glue createResourceFlow(GenericKubernetesResource targetParentResource, private GlueSpec toWorkflowSpec(GlueOperatorSpec spec) { var res = new GlueSpec(); - res.setResources(new ArrayList<>(spec.getResources())); + res.setChildResources(new ArrayList<>(spec.getChildResources())); res.setRelatedResources(new ArrayList<>(spec.getRelatedResources())); return res; } diff --git a/src/test/java/io/csviri/operator/glue/GlueOperatorTest.java b/src/test/java/io/csviri/operator/glue/GlueOperatorTest.java index aed156f..1a8731f 100644 --- a/src/test/java/io/csviri/operator/glue/GlueOperatorTest.java +++ b/src/test/java/io/csviri/operator/glue/GlueOperatorTest.java @@ -197,9 +197,9 @@ GlueOperator testWorkflowOperator() { wo.setSpec(spec); spec.setParent(new Parent(CR_GROUP + "/v1", TestCustomResource.class.getSimpleName())); - spec.setResources(new ArrayList<>()); + spec.setChildResources(new ArrayList<>()); DependentResourceSpec drs = new DependentResourceSpec(); - spec.getResources().add(drs); + spec.getChildResources().add(drs); drs.setResource(TestUtils.load("/ConfigMap.yaml")); drs.setName("configMap1"); return wo; diff --git a/src/test/java/io/csviri/operator/glue/GlueTest.java b/src/test/java/io/csviri/operator/glue/GlueTest.java index 649c132..e94a2a6 100644 --- a/src/test/java/io/csviri/operator/glue/GlueTest.java +++ b/src/test/java/io/csviri/operator/glue/GlueTest.java @@ -39,7 +39,7 @@ void simpleTemplating() { assertThat(cm2.getData().get("valueFromCM1")).isEqualTo("value1"); }); - ((Map) glue.getSpec().getResources().get(0).getResource() + ((Map) glue.getSpec().getChildResources().get(0).getResource() .getAdditionalProperties().get("data")) .put("key", CHANGED_VALUE); @@ -77,7 +77,7 @@ void crossReferenceResource() { }); var resourceTemplate = - glue.getSpec().getResources().stream().filter(r -> r.getName().equals("configMap1")) + glue.getSpec().getChildResources().stream().filter(r -> r.getName().equals("configMap1")) .findAny().orElseThrow().getResource(); // set new value ((Map) resourceTemplate.getAdditionalProperties().get("data")).put("key", @@ -113,7 +113,7 @@ void javaScriptCondition() { assertThat(cm2).isNull(); }); - Map map = (Map) glue.getSpec().getResources() + Map map = (Map) glue.getSpec().getChildResources() .get(0).getResource().getAdditionalProperties().get("data"); map.put("createOther", "true"); @@ -197,8 +197,8 @@ void changingWorkflow() { assertThat(cm2).isNotNull(); }); - glue.getSpec().getResources().remove(1); - glue.getSpec().getResources().add(new DependentResourceSpec() + glue.getSpec().getChildResources().remove(1); + glue.getSpec().getChildResources().add(new DependentResourceSpec() .setName("secret") .setResource(TestUtils.load("/Secret.yaml"))); diff --git a/src/test/java/io/csviri/operator/glue/JavaScripConditionTest.java b/src/test/java/io/csviri/operator/glue/JavaScripConditionTest.java index 89ec9c9..98f52c4 100644 --- a/src/test/java/io/csviri/operator/glue/JavaScripConditionTest.java +++ b/src/test/java/io/csviri/operator/glue/JavaScripConditionTest.java @@ -73,11 +73,11 @@ void injectsSecondaryResourcesResource() { Glue glue = new Glue(); glue.setSpec(new GlueSpec()); - glue.getSpec().setResources(new ArrayList<>()); + glue.getSpec().setChildResources(new ArrayList<>()); var drSpec = new DependentResourceSpec(); drSpec.setName(DR_NAME); drSpec.setResource(configMap()); - glue.getSpec().getResources().add(drSpec); + glue.getSpec().getChildResources().add(drSpec); var condition = new JavaScripCondition(""" secondary.data.key1 == "val1"; diff --git a/src/test/resources/glue/ChanginResources.yaml b/src/test/resources/glue/ChanginResources.yaml index cf3c8c6..009d47c 100644 --- a/src/test/resources/glue/ChanginResources.yaml +++ b/src/test/resources/glue/ChanginResources.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: rf-to-change spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/ClusterScopeResource.yaml b/src/test/resources/glue/ClusterScopeResource.yaml index 03fea20..6d16d2f 100644 --- a/src/test/resources/glue/ClusterScopeResource.yaml +++ b/src/test/resources/glue/ClusterScopeResource.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: "glue1" spec: - resources: + childResources: - name: cluster-scoped-resource resource: apiVersion: io.csviri.operator.glue/v1 diff --git a/src/test/resources/glue/CrossReferenceResource.yaml b/src/test/resources/glue/CrossReferenceResource.yaml index 54946e0..a6e34ec 100644 --- a/src/test/resources/glue/CrossReferenceResource.yaml +++ b/src/test/resources/glue/CrossReferenceResource.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: "crossreference" spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/MultiNameRelatedResource.yaml b/src/test/resources/glue/MultiNameRelatedResource.yaml index 3e3e2a4..6d4ee77 100644 --- a/src/test/resources/glue/MultiNameRelatedResource.yaml +++ b/src/test/resources/glue/MultiNameRelatedResource.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: related-resource-test1 spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/NonUniqueName.yaml b/src/test/resources/glue/NonUniqueName.yaml index 5063044..c76b37c 100644 --- a/src/test/resources/glue/NonUniqueName.yaml +++ b/src/test/resources/glue/NonUniqueName.yaml @@ -4,7 +4,7 @@ kind: Glue metadata: name: templating-sample spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/RelatedResourceSimpleWithCondition.yaml b/src/test/resources/glue/RelatedResourceSimpleWithCondition.yaml index 4f1e2ba..ee87dcc 100644 --- a/src/test/resources/glue/RelatedResourceSimpleWithCondition.yaml +++ b/src/test/resources/glue/RelatedResourceSimpleWithCondition.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: related-resource-test1 spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/RelatesResourceSameType.yaml b/src/test/resources/glue/RelatesResourceSameType.yaml index 77e4eb3..c97c116 100644 --- a/src/test/resources/glue/RelatesResourceSameType.yaml +++ b/src/test/resources/glue/RelatesResourceSameType.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: related-resource-test1 spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/ResourceInDifferentNamespace.yaml b/src/test/resources/glue/ResourceInDifferentNamespace.yaml index 20fd602..145a622 100644 --- a/src/test/resources/glue/ResourceInDifferentNamespace.yaml +++ b/src/test/resources/glue/ResourceInDifferentNamespace.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: "testglue" spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/ResourceTemplate.yaml b/src/test/resources/glue/ResourceTemplate.yaml index ed0c81b..69be71a 100644 --- a/src/test/resources/glue/ResourceTemplate.yaml +++ b/src/test/resources/glue/ResourceTemplate.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: resource-templating-sample spec: - resources: + childResources: - name: configMap1 resourceTemplate: | apiVersion: v1 diff --git a/src/test/resources/glue/TemplateForConcurrency.yaml b/src/test/resources/glue/TemplateForConcurrency.yaml index b011785..76ccb57 100644 --- a/src/test/resources/glue/TemplateForConcurrency.yaml +++ b/src/test/resources/glue/TemplateForConcurrency.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: "concurrencysample" spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/Templating.yaml b/src/test/resources/glue/Templating.yaml index 851e81f..75e12ef 100644 --- a/src/test/resources/glue/Templating.yaml +++ b/src/test/resources/glue/Templating.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: templating-sample spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glue/TwoResourcesAndCondition.yaml b/src/test/resources/glue/TwoResourcesAndCondition.yaml index 78e10d6..6138f9a 100644 --- a/src/test/resources/glue/TwoResourcesAndCondition.yaml +++ b/src/test/resources/glue/TwoResourcesAndCondition.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: two-resource-cross-condition spec: - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glueoperator/Concurrency.yaml b/src/test/resources/glueoperator/Concurrency.yaml index 6c4b1f1..1b8a782 100644 --- a/src/test/resources/glueoperator/Concurrency.yaml +++ b/src/test/resources/glueoperator/Concurrency.yaml @@ -6,7 +6,7 @@ spec: parent: apiVersion: io.csviri.operator.glue/v1 kind: TestCustomResource - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glueoperator/Concurrency2.yaml b/src/test/resources/glueoperator/Concurrency2.yaml index c31fb40..8b245cd 100644 --- a/src/test/resources/glueoperator/Concurrency2.yaml +++ b/src/test/resources/glueoperator/Concurrency2.yaml @@ -6,7 +6,7 @@ spec: parent: apiVersion: io.csviri.operator.glue/v1 kind: TestCustomResource2 - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glueoperator/NonUniqueName.yaml b/src/test/resources/glueoperator/NonUniqueName.yaml index 346fac3..46eab9a 100644 --- a/src/test/resources/glueoperator/NonUniqueName.yaml +++ b/src/test/resources/glueoperator/NonUniqueName.yaml @@ -6,7 +6,7 @@ spec: parent: apiVersion: io.csviri.operator.glue/v1 kind: TestCustomResource - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/glueoperator/Templating.yaml b/src/test/resources/glueoperator/Templating.yaml index d36daa4..7eb3852 100644 --- a/src/test/resources/glueoperator/Templating.yaml +++ b/src/test/resources/glueoperator/Templating.yaml @@ -6,7 +6,7 @@ spec: parent: apiVersion: io.csviri.operator.glue/v1 kind: TestCustomResource - resources: + childResources: - name: configMap1 resource: apiVersion: v1 diff --git a/src/test/resources/sample/mutation/mutation.glue.yaml b/src/test/resources/sample/mutation/mutation.glue.yaml index 2011021..e2de1bb 100644 --- a/src/test/resources/sample/mutation/mutation.glue.yaml +++ b/src/test/resources/sample/mutation/mutation.glue.yaml @@ -3,7 +3,7 @@ kind: Glue metadata: name: mutation-webhook-deployment spec: - resources: + childResources: - name: service resource: apiVersion: v1 diff --git a/src/test/resources/sample/webpage/webpage.operator.yaml b/src/test/resources/sample/webpage/webpage.operator.yaml index 9d365a5..64cc25b 100644 --- a/src/test/resources/sample/webpage/webpage.operator.yaml +++ b/src/test/resources/sample/webpage/webpage.operator.yaml @@ -6,7 +6,7 @@ spec: parent: apiVersion: glueoperator.sample/v1 kind: WebPage - resources: + childResources: - name: htmlconfigmap resource: apiVersion: v1