Skip to content

Commit f8e8f67

Browse files
committed
feat: Implement Required Resources
Signed-off-by: John Gardiner Myers <jgmyers@proofpoint.com>
1 parent bd4f15a commit f8e8f67

File tree

17 files changed

+792
-14
lines changed

17 files changed

+792
-14
lines changed

README.md

Lines changed: 160 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -529,16 +529,165 @@ spec:
529529
}
530530
```
531531

532-
### Extra resources
533-
By defining one or more special `ExtraResources`, you can ask Crossplane to retrieve additional resources from the local cluster
532+
### Required resources
533+
534+
By defining one or more "required resources", you can ask Crossplane to retrieve additional resources from the local cluster
534535
and make them available to your templates.
535-
See the [docs](https://github.com/crossplane/crossplane/blob/main/design/design-doc-composition-functions-extra-resources.md) for more information.
536+
See the [docs](https://docs.crossplane.io/latest/composition/compositions/#required-resources) for more information.
537+
538+
This feature only works with Crossplane v2. Crossplane v1 must use [Extra Resources](#extra-resources), described in the section below.
539+
540+
There are two ways to request required resources:
541+
542+
One, you can list the resources to retrieve in the `requirements.requiredResources` field
543+
of the pipeline step:
544+
545+
```yaml
546+
apiVersion: apiextensions.crossplane.io/v1
547+
kind: Composition
548+
metadata:
549+
name: example
550+
spec:
551+
compositeTypeRef:
552+
apiVersion: example.crossplane.io/v1beta1
553+
kind: XR
554+
mode: Pipeline
555+
pipeline:
556+
- step: basic
557+
functionRef:
558+
name: function-kcl
559+
requirements:
560+
requiredResources:
561+
- requirementName: foo
562+
apiVersion: example.com/v1beta1
563+
kind: Foo
564+
matchLabels:
565+
foo: bar
566+
- requirementName: bar
567+
apiVersion: example.com/v1beta1
568+
kind: Bar
569+
name: my-bar
570+
- requirementName: baz
571+
apiVersion: example.m.com/v1beta1
572+
kind: Bar
573+
name: my-bar
574+
namespace: my-baz-ns
575+
- requirementName: quux
576+
apiVersion: example.m.com/v1beta1
577+
kind: Quux
578+
matchLabels:
579+
baz: quux
580+
namespace: my-quux-ns
581+
input:
582+
apiVersion: krm.kcl.dev/v1alpha1
583+
kind: KCLInput
584+
spec:
585+
source: "..."
586+
```
587+
588+
Two, the composition can dynamically request resources by returning a special
589+
`RequiredResources` item:
590+
591+
```yaml
592+
apiVersion: krm.kcl.dev/v1alpha1
593+
kind: KCLInput
594+
spec:
595+
source: |
596+
# Omit other logic
597+
details = {
598+
apiVersion: "meta.krm.kcl.dev/v1alpha1"
599+
kind: "RequiredResources"
600+
requirements = {
601+
foo = {
602+
apiVersion: "example.com/v1beta1",
603+
kind: "Foo",
604+
matchLabels: {
605+
"foo": "bar"
606+
}
607+
},
608+
bar = {
609+
apiVersion: "example.com/v1beta1",
610+
kind: "Bar",
611+
name: "my-bar"
612+
},
613+
baz = {
614+
apiVersion: "example.m.com/v1beta1",
615+
kind: "Bar",
616+
name: "my-bar"
617+
namespace: "my-baz-ns"
618+
},
619+
quux = {
620+
apiVersion: "example.m.com/v1beta1",
621+
kind: "Quux",
622+
matchLabels: {
623+
"baz": "quux"
624+
}
625+
namespace: "my-quux-ns"
626+
}
627+
}
628+
}
629+
630+
# Omit other composite logics.
631+
items = [
632+
details
633+
# Omit other return resources.
634+
]
635+
```
636+
637+
Either way will result in Crossplane retrieving the requested resources and making them available with the following format:
638+
639+
```yaml
640+
foo:
641+
- Resource:
642+
apiVersion: example.com/v1beta1
643+
kind: Foo
644+
metadata:
645+
labels:
646+
foo: bar
647+
# Omitted for brevity
648+
- Resource:
649+
apiVersion: example.com/v1beta1
650+
kind: Foo
651+
metadata:
652+
labels:
653+
foo: bar
654+
# Omit for brevity
655+
bar:
656+
- Resource:
657+
apiVersion: example.com/v1beta1
658+
kind: Bar
659+
metadata:
660+
name: my-bar
661+
# Omitted for brevity
662+
```
663+
664+
You can access the retrieved resources in your code like this:
536665

537666
> [!NOTE]
538-
> With ExtraResources, you can fetch cluster-scoped resources, but not namespaced resources such as claims.
539-
> If you need to get a composite resource via its claim name you can use `matchLabels` with `crossplane.io/claim-name: <claimname>`.
540-
> Namespace scoped resources can be queried with the `matchNamespace` field.
541-
> Leaving the `matchNamespace` field empty or not defining it will query a cluster scoped resource.
667+
> Crossplane performs an additional reconciliation pass for dynamic required resources.
668+
> Consequently, during the initial execution, these resources might not be present.
669+
> It is essential to implement checks to handle this scenario.
670+
671+
```yaml
672+
apiVersion: krm.kcl.dev/v1alpha1
673+
kind: KCLInput
674+
spec:
675+
source: |
676+
er = option("params")?.requiredResources
677+
678+
if er?.bar:
679+
name = er?.bar[0]?.Resource?.metadata?.name or ""
680+
# Omit other logic
681+
```
682+
683+
### Extra resources
684+
685+
Extra resources are Crossplane v1's mechanism for retrieving additional resources from the local cluster.
686+
It is deprecated in Crossplane v2.
687+
688+
Unlike required resources, there is no mechanism for requesting extra resources in the
689+
pipeline step definition. They can only be requested dynamically, by returning a special
690+
`ExtraResources` item:
542691

543692
```yaml
544693
apiVersion: krm.kcl.dev/v1alpha1
@@ -587,7 +736,9 @@ spec:
587736
```
588737
You can retrieve the extra resources either via labels with `matchLabels` or via name with `matchName: somename`.
589738

590-
This will result in Crossplane receiving the requested resources and make them available with the following format.
739+
See the [docs](https://github.com/crossplane/crossplane/blob/main/design/design-doc-composition-functions-extra-resources.md) for more information.
740+
741+
This will result in Crossplane receiving the requested resources and making them available using the following format:
591742

592743
```yaml
593744
foo:
@@ -618,7 +769,7 @@ You can access the retrieved resources in your code like this:
618769

619770
> [!NOTE]
620771
> Crossplane performs an additional reconciliation pass for extra resources.
621-
> Consequently, during the initial execution, these resources may be uninitialized.
772+
> Consequently, during the initial execution, these resources might not be present.
622773
> It is essential to implement checks to handle this scenario.
623774

624775
```yaml
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
run:
2+
crossplane render --verbose xr.yaml composition.yaml functions.yaml -r --required-resources required_resources.yaml
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Example Manifests
2+
3+
You can run your function locally and test it using `crossplane render`
4+
with these example manifests.
5+
6+
```shell
7+
# Run the function locally
8+
$ go run . --insecure --debug
9+
```
10+
11+
```shell
12+
# Then, in another terminal, call it with these example manifests
13+
$ crossplane render --verbose xr.yaml composition.yaml functions.yaml -r --required-resources required_resources.yaml
14+
---
15+
---
16+
apiVersion: example.crossplane.io/v1beta1
17+
kind: XR
18+
metadata:
19+
name: example
20+
status:
21+
conditions:
22+
- lastTransitionTime: "2024-01-01T00:00:00Z"
23+
message: 'Unready resources: another-awesome-dev-bucket, my-awesome-dev-bucket'
24+
reason: Creating
25+
status: "False"
26+
type: Ready
27+
---
28+
apiVersion: example/v1alpha1
29+
kind: Foo
30+
metadata:
31+
annotations:
32+
crossplane.io/composition-resource-name: another-awesome-dev-bucket
33+
generateName: example-
34+
labels:
35+
crossplane.io/composite: example
36+
name: another-awesome-dev-bucket
37+
ownerReferences:
38+
- apiVersion: example.crossplane.io/v1beta1
39+
blockOwnerDeletion: true
40+
controller: true
41+
kind: XR
42+
name: example
43+
uid: ""
44+
---
45+
apiVersion: example/v1alpha1
46+
kind: Foo
47+
metadata:
48+
annotations:
49+
crossplane.io/composition-resource-name: my-awesome-dev-bucket
50+
generateName: example-
51+
labels:
52+
crossplane.io/composite: example
53+
name: my-awesome-dev-bucket
54+
ownerReferences:
55+
- apiVersion: example.crossplane.io/v1beta1
56+
blockOwnerDeletion: true
57+
controller: true
58+
kind: XR
59+
name: example
60+
uid: ""
61+
62+
```
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
apiVersion: apiextensions.crossplane.io/v1
2+
kind: Composition
3+
metadata:
4+
name: kcl-function
5+
spec:
6+
compositeTypeRef:
7+
apiVersion: example.crossplane.io/v1beta1
8+
kind: XR
9+
mode: Pipeline
10+
pipeline:
11+
- step: normal
12+
functionRef:
13+
name: kcl-function
14+
input:
15+
apiVersion: krm.kcl.dev/v1alpha1
16+
kind: KCLInput
17+
metadata:
18+
annotations:
19+
"krm.kcl.dev/default_ready": "True"
20+
spec:
21+
source: |
22+
oxr = option("params").oxr
23+
rr = option("params")?.requiredResources
24+
25+
foo = [{
26+
apiVersion: "example/v1alpha1"
27+
kind: "Foo"
28+
metadata = {
29+
name: k.Resource.metadata.name
30+
}
31+
} for k in rr?.bucket] if rr?.bucket else []
32+
33+
dxr = {
34+
**oxr
35+
}
36+
37+
details = {
38+
apiVersion: "meta.krm.kcl.dev/v1alpha1"
39+
kind: "RequiredResources"
40+
requirements = {
41+
bucket = {
42+
apiVersion: "s3.aws.upbound.io/v1beta1",
43+
kind: "Bucket",
44+
matchLabels: {
45+
"foo": "bar"
46+
}
47+
}
48+
}
49+
}
50+
items = [
51+
details
52+
dxr
53+
] + foo
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: pkg.crossplane.io/v1beta1
2+
kind: Function
3+
metadata:
4+
name: kcl-function
5+
annotations:
6+
# This tells crossplane render to connect to the function locally.
7+
render.crossplane.io/runtime: Development
8+
spec:
9+
package: xpkg.upbound.io/crossplane-contrib/function-kcl:latest
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
apiVersion: s3.aws.upbound.io/v1beta1
2+
kind: Bucket
3+
metadata:
4+
annotations:
5+
crossplane.io/external-name: my-awesome-dev-bucket
6+
labels:
7+
foo: bar
8+
name: my-awesome-dev-bucket
9+
spec:
10+
forProvider:
11+
region: us-west-1
12+
status:
13+
atProvider:
14+
id: random-bucket-id
15+
---
16+
apiVersion: s3.aws.upbound.io/v1beta1
17+
kind: Bucket
18+
metadata:
19+
annotations:
20+
crossplane.io/external-name: my-awesome-dev-bucket
21+
labels:
22+
foo: bar
23+
name: another-awesome-dev-bucket
24+
spec:
25+
forProvider:
26+
region: us-west-1
27+
status:
28+
atProvider:
29+
id: random-bucket-id
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: example.crossplane.io/v1beta1
2+
kind: XR
3+
metadata:
4+
name: example
5+
spec:
6+
count: 1
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
run:
2+
crossplane render --verbose xr.yaml composition.yaml functions.yaml -r --required-resources required_resources_namespaced.yaml

0 commit comments

Comments
 (0)