1
1
# function-patch-and-transform
2
+ [ ![ CI] ( https://github.com/crossplane-contrib/function-patch-and-transform/actions/workflows/ci.yml/badge.svg )] ( https://github.com/crossplane-contrib/function-patch-and-transform/actions/workflows/ci.yml ) ![ GitHub release (latest SemVer)] ( https://img.shields.io/github/release/crossplane-contrib/function-patch-and-transform )
2
3
3
- A [ Crossplane] Composition Function that implements P&T-style Composition.
4
+ This [ composition function] [ docs-functions ] does everything Crossplane's
5
+ built-in [ patch & transform] [ docs-pandt ] (P&T) composition does. Instead of
6
+ specifying ` spec.resources ` in your Composition, you can use this function.
4
7
5
- ``` yaml
6
- apiVersion : pkg.crossplane.io/v1beta1
7
- kind : Function
8
- metadata :
9
- name : function-patch-and-transform
10
- spec :
11
- package : xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.1.4
12
- ` ` `
13
-
14
- ## What is this?
15
-
16
- This [Composition Function][function-design] does everything Crossplane's built-in Patch &
17
- Transform Composition does. Instead of specifying ` spec.resources` in your
18
- Composition, you can use this Function.
19
-
20
- Note that this is a beta-style Function. It won't work with Crossplane v1.13 or
21
- earlier - it targets the [implementation of Functions][function-pr] coming with
22
- Crossplane v1.14 in late October.
23
-
24
- Take [this example][docs-composition] from https://docs.crossplane.io. Using
25
- this Function, it would look like this :
8
+ Using this function, P&T looks like this:
26
9
27
10
``` yaml
28
11
apiVersion : apiextensions.crossplane.io/v1
29
12
kind : Composition
30
13
metadata :
31
- name: dynamo-with-bucket
14
+ name : example
32
15
spec :
33
- compositeTypeRef:
34
- apiVersion: database.example.com/v1alpha1
35
- kind: NoSQL
16
+ # Omitted for brevity.
36
17
mode : Pipeline
37
18
pipeline :
38
19
- step : patch-and-transform
@@ -42,40 +23,14 @@ spec:
42
23
apiVersion : pt.fn.crossplane.io/v1beta1
43
24
kind : Resources
44
25
resources :
45
- - name: s3Bucket
26
+ - name : bucket
46
27
base :
47
28
apiVersion : s3.aws.upbound.io/v1beta1
48
29
kind : Bucket
49
- metadata:
50
- name: crossplane-quickstart-bucket
51
30
spec :
52
31
forProvider :
53
32
region : us-east-2
54
33
patches :
55
- - type: FromCompositeFieldPath
56
- fromFieldPath: "location"
57
- toFieldPath: "spec.forProvider.region"
58
- transforms:
59
- - type: map
60
- map:
61
- EU: "eu-north-1"
62
- US: "us-east-2"
63
- - name: dynamoDB
64
- base:
65
- apiVersion: dynamodb.aws.upbound.io/v1beta1
66
- kind: Table
67
- metadata:
68
- name: crossplane-quickstart-database
69
- spec:
70
- forProvider:
71
- region: "us-east-2"
72
- writeCapacity: 1
73
- readCapacity: 1
74
- attribute:
75
- - name: S3ID
76
- type: S
77
- hashKey: S3ID
78
- patches:
79
34
- type : FromCompositeFieldPath
80
35
fromFieldPath : " spec.location"
81
36
toFieldPath : " spec.forProvider.region"
@@ -86,168 +41,120 @@ spec:
86
41
US : " us-east-2"
87
42
` ` `
88
43
89
- Notice that it looks pretty much identical to the example from the Crossplane
90
- documentation. The key difference is that everything that used to be under
91
- ` spec.resources` is now nested a little deeper. Specifically, it's under
92
- ` spec.pipeline[0].input.resources` (i.e. in the Function's input).
44
+ Notice that it looks very similar to native P&T. The difference is that
45
+ everything is under ` spec.pipeline[0].input.resources`, not `spec.resources`.
46
+ This is the Function's input.
93
47
94
48
# # Okay, but why?
95
49
96
- I think there are a _lot_ of good reasons to implement P&T Composition as a
97
- Function. In fact, I would go so far as to propose that once Functions are a GA
98
- feature we deprecate support for 'native' P&T (i.e. `spec.resources`). We can't
99
- remove it - that would be a breaking change - but we can freeze its API and
100
- suggest folks use the P&T Function instead.
50
+ There are a lot of good reasons to use a function to use a function to do P&T
51
+ composition. In fact, it's so compelling that the Crossplane maintainers are
52
+ considering deprecating native P&T. See Crossplane issue [#4746] for details.
101
53
102
- # ## Run P&T anywhere in your pipeline
54
+ # ## Mix and match P&T with other functions
103
55
104
- Native P&T can only run before the Composition Function pipeline. In the [draft
105
- beta implementation of Functions][function-pr] Crossplane does all the patching
106
- and transforming first, then sends the results through the Function pipeline .
56
+ With this function you can use P&T with other functions. For example you can
57
+ create a desired resource using the [Go Templating][fn-go-templating] function,
58
+ then patch the result using this function .
107
59
108
- This is handy, but what if you wanted to run another Function (like rendering
109
- some Go templates) first, then pass the result of that Function to be patched
110
- and transformed? With this Function you can do that :
111
-
112
- ` ` ` yaml
113
- apiVersion: apiextensions.crossplane.io/v1
114
- kind: Composition
115
- metadata:
116
- name: dynamo-with-bucket
117
- spec:
118
- compositeTypeRef:
119
- apiVersion: database.example.com/v1alpha1
120
- kind: NoSQL
121
- # This pipeliene renders some Go templates, then passes them to P&T
122
- pipeline:
123
- - step: render-go-templates
124
- functionRef:
125
- name: function-go-templates
126
- input: {} # Omitted for brevity :)
127
- - step: patch-and-transform
128
- functionRef:
129
- name: function-patch-and-transform
130
- input:
131
- apiVersion: pt.fn.crossplane.io/v1beta1
132
- kind: Resources
133
- resources:
134
- # Notice that my-cool-bucket doesn't have a base template. As long as
135
- # the render-go-templates step above rendered a composed resource with
136
- # this name, this Function will patch it.
137
- - name: my-cool-bucket
138
- patches:
139
- - type: FromCompositeFieldPath
140
- fromFieldPath: "location"
141
- toFieldPath: "spec.forProvider.region"
142
- transforms:
143
- - type: map
144
- map:
145
- EU: "eu-north-1"
146
- US: "us-east-2"
147
- ` ` `
148
-
149
- It's not just patches either - you can use P&T to derive XR connection details
150
- from a resource produced by another Function too, or use it to determine whether
151
- a resource produced by another Function is ready
60
+ It's not just patches either. You can use P&T to derive composite resource
61
+ connection details from a resource produced by another function, or use it to
62
+ determine whether a resource produced by another function is ready
152
63
153
64
# ## Decouple P&T development from Crossplane core
154
65
155
- When P&T development happens in a Function, it's not coupled to the Crossplane
156
- release cycle. The Function developers could cut releases more frequently to add
157
- new features to P&T.
158
-
159
- Plus, because it's just a Function, it becomes easier to fork. You could fork
160
- this Function, add a new kind of transform and try it out for a few weeks in
161
- your development environment before sending a PR upstream. Or, if your new
162
- feature is controversial, it's now a lot less work to maintain your own fork
163
- long term.
66
+ When P&T development happens in a function, it's not coupled to the Crossplane
67
+ release cycle. The maintainers of this function can cut releases more frequently
68
+ to add new features to P&T.
164
69
165
- # ## Makes P&T code more portable
70
+ It also becomes easier to fork. You could fork this function, add a new kind of
71
+ transform and try it out for a few weeks before sending a PR upstream. Or, if
72
+ your new feature is controversial, it's now a lot less work to maintain your own
73
+ fork long term.
166
74
167
- A lot of building a better developer experience around Composition comes down to
168
- shifting left - letting you run and test your Compositions when you're
169
- developing them. Historically this has been tough. You need to spin up a `kind`
170
- cluster, install Crossplane, install providers, etc.
171
-
172
- ` ` ` bash
173
- $ xp composition render xr.yaml composition.yaml
174
- ` ` `
75
+ # ## Test P&T locally using the Crossplane CLI
175
76
176
- You could imagine a CLI tool like the above helping a lot. The problem with
177
- building tools like this in the past has been that they need to share
178
- Crossplane's Composition logic. We could make Composition a library, but then
179
- you'd need to make sure that the version of `xp` on your laptop used the same
180
- Composition library as your control planes, or you might see different results
181
- than you expected in production.
77
+ You can use the Crossplane CLI to run any function locally and see what composed
78
+ resources it would create. This only works with functions - not native P&T.
182
79
183
- When all Composition logic is encapsulated in Functions - i.e. versioned OCI
184
- containers with a standard RPC - building a tool like this becomes much easier.
185
- Just tell the CLI what Function versions you're using in production and it can
186
- pull them down and use them to render your Composition.
80
+ For example, using the files in the [example](example) directory :
187
81
188
- # ## Makes Crossplane's Composition implementation simpler
189
-
190
- If we can make the _native_ P&T implementation and Functions mutually exclusive,
191
- Crossplane's Composition implementation is dramatically less complex. This means
192
- it's easier to maintain and much less likely to be buggy.
82
+ ` ` ` shell
83
+ $ crossplane beta render xr.yaml composition.yaml functions.yaml
84
+ ` ` `
85
+ Produces the following output, showing what resources Crossplane would compose :
193
86
194
- Moving P&T inside a Function makes this possible - you can still use 'both' P&T
195
- and Functions, you'd just do it by... using Functions.
87
+ ` ` ` yaml
88
+ ---
89
+ apiVersion: example.crossplane.io/v1
90
+ kind: XR
91
+ metadata:
92
+ name: example-xr
93
+ ---
94
+ apiVersion: s3.aws.upbound.io/v1beta1
95
+ kind: Bucket
96
+ metadata:
97
+ annotations:
98
+ crossplane.io/composition-resource-name: bucket
99
+ generateName: example-xr-
100
+ labels:
101
+ crossplane.io/composite: example-xr
102
+ ownerReferences:
103
+ # Omitted for brevity
104
+ spec:
105
+ forProvider:
106
+ region: us-east-2
107
+ ` ` `
196
108
197
- Eventually, if enough P&T users switch to this Function we may be able to remove
198
- native support for P&T altogether .
109
+ See the [composition functions documentation][docs-functions] to learn how to
110
+ use `crossplane beta render` .
199
111
200
112
# # Differences from the native implementation
201
113
202
- This Function has a few small, intentional breaking changes compared to the
203
- native implementation. Making the below fields required makes P&T configuration
204
- a lot more explicit and less ambiguous.
205
-
206
- * `resources[i].name` is now a required field.
207
- * `resources[i].connectionDetails[i].name` is now a required field
208
- * `resources[i].connectionDetails[i].type` is now a required field
209
- * `resources[i].patches[i].transforms[i].string.type` is now a required field
210
- * `resources[i].patches[i].transforms[i].math.type` is now a required field
211
- * `resources[i].patches[i].policy.mergeOptions` is no longer supported
114
+ This function has a few small, intentional breaking changes compared to the
115
+ native implementation.
212
116
213
- Functions use Kubernetes server-side apply, not `mergeOptions`, to intelligently
214
- merge arrays and objects. This requires merge configuration to be specified at
215
- the composed resource schema level (i.e. in CRDs) per [#4617].
117
+ These fields are now required. This makes P&T configuration less ambiguous :
216
118
217
- # # Known issues
119
+ * `resources[i].name`
120
+ * `resources[i].connectionDetails[i].name`
121
+ * `resources[i].connectionDetails[i].type`
122
+ * `resources[i].patches[i].transforms[i].string.type`
123
+ * `resources[i].patches[i].transforms[i].math.type`
218
124
219
- The initial implementation has the following limitations :
125
+ Also, the `resources[i].patches[i].policy.mergeOptions` field is no longer
126
+ supported.
220
127
221
- * `EnvironmentConfig` and its associated patches aren't supported yet. This is
222
- just because Crossplane doesn't yet send the `EnvironmentConfig` along with
223
- the `RunFunctionRequest`. Once we do, these should be easy to (re)implement.
224
- Adding support at the Functions level is tracked in [#4632].
128
+ Composition functions use Kubernetes server-side apply to intelligently merge
129
+ arrays and objects. This requires merge configuration to be specified at the
130
+ composed resource schema level (i.e. in CRDs) per [#4617].
225
131
226
- # # Developing
132
+ # # Developing this function
227
133
228
- This Function doesn't use the typical Crossplane build submodule and Makefile,
229
- since we'd like Functions to have a less heavyweight developer experience.
230
- It mostly relies on regular old Go tools :
134
+ This function uses [Go][go], [Docker][docker], and the [Crossplane CLI][cli] to
135
+ build functions.
231
136
232
137
` ` ` shell
233
138
# Run code generation - see input/generate.go
234
139
$ go generate ./...
235
140
236
- # Run tests
237
- $ go test -cover ./...
238
- ? github.com/crossplane-contrib/function-patch-and-transform/input/v1beta1 [no test files]
239
- ok github.com/crossplane-contrib/function-patch-and-transform 0.021s coverage: 76.1% of statements
141
+ # Run tests - see fn_test.go
142
+ $ go test ./...
240
143
241
- # Lint the code
242
- $ docker run --rm -v $(pwd):/app -v ~/.cache/golangci-lint/v1.54.2:/root/.cache -w /app golangci/golangci-lint:v1.54.2 golangci-lint run
144
+ # Build the function's runtime image - see Dockerfile
145
+ $ docker build . --tag=runtime
243
146
244
- # Build a Docker image - see Dockerfile
245
- $ docker build .
147
+ # Build a function package - see package/crossplane.yaml
148
+ $ crossplane xpkg build -f package --embed-runtime-image=runtime
246
149
` ` `
247
150
248
151
[Crossplane] : https://crossplane.io
249
- [function-design] : https://github.com/crossplane/crossplane/blob/3996f20/design/design-doc-composition-functions.md
250
- [function-pr] : https://github.com/crossplane/crossplane/pull/4500
251
- [docs-composition] : https://docs.crossplane.io/v1.13/getting-started/provider-aws-part-2/#create-a-deployment-template
152
+ [docs-composition] : https://docs.crossplane.io/v1.14/getting-started/provider-aws-part-2/#create-a-deployment-template
153
+ [docs-functions] : https://docs.crossplane.io/v1.14/concepts/composition-functions/
154
+ [docs-pandt] : https://docs.crossplane.io/v1.14/concepts/patch-and-transform/
155
+ [fn-go-templating] : https://github.com/crossplane-contrib/function-go-templating
252
156
[#4617]: https://github.com/crossplane/crossplane/issues/4617
253
- [#4632]: https://github.com/crossplane/crossplane/pull/4632
157
+ [#4746]: https://github.com/crossplane/crossplane/issues/4746
158
+ [go] : https://go.dev
159
+ [docker] : https://www.docker.com
160
+ [cli] : https://docs.crossplane.io/latest/cli
0 commit comments