Skip to content

Commit fc5b31e

Browse files
author
Patrick J. McNerthney
committed
Refactor to use the crossplane.pythonic package.
Upgrade to use crossplane-sdk-python v0.9.0. Implement being able to specify the full path to the Composite class.
1 parent 377704a commit fc5b31e

28 files changed

+529
-129
lines changed

.github/workflows/ci.yaml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,13 @@ jobs:
127127
build-args:
128128
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
129129
outputs: type=docker,dest=runtime-${{ matrix.arch }}.tar
130-
130+
131131
- name: Setup the Crossplane CLI
132-
run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh"
132+
run: "mkdir bin && cd bin && curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh"
133133

134134
- name: Build Package
135-
run: ./crossplane xpkg build --package-file=${{ matrix.arch }}.xpkg --package-root=package/ --embed-runtime-image-tarball=runtime-${{ matrix.arch }}.tar
136-
135+
run: bin/crossplane xpkg build --package-file=${{ matrix.arch }}.xpkg --package-root=package/ --embed-runtime-image-tarball=runtime-${{ matrix.arch }}.tar
136+
137137
- name: Upload Single-Platform Package
138138
uses: actions/upload-artifact@v4
139139
with:
@@ -165,7 +165,15 @@ jobs:
165165
merge-multiple: true
166166

167167
- name: Setup the Crossplane CLI
168-
run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh"
168+
run: "mkdir bin && cd bin && curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh"
169+
170+
# If a version wasn't explicitly passed as a workflow_dispatch input we
171+
# default to version v0.0.0-<git-commit-date>-<git-short-sha>, for example
172+
# v0.0.0-20231101115142-1091066df799. This is a simple implementation of
173+
# Go's pseudo-versions: https://go.dev/ref/mod#pseudo-versions.
174+
- name: Set Default Multi-Platform Package Version
175+
if: env.XPKG_VERSION == ''
176+
run: echo "XPKG_VERSION=v0.0.0-$(date -d@$(git show -s --format=%ct) +%Y%m%d%H%M%S)-$(git rev-parse --short=12 HEAD)" >> $GITHUB_ENV
169177

170178
# - name: Login to Upbound
171179
# uses: docker/login-action@v3
@@ -175,17 +183,9 @@ jobs:
175183
# username: ${{ secrets.XPKG_ACCESS_ID }}
176184
# password: ${{ secrets.XPKG_TOKEN }}
177185

178-
# If a version wasn't explicitly passed as a workflow_dispatch input we
179-
# default to version v0.0.0-<git-commit-date>-<git-short-sha>, for example
180-
# v0.0.0-20231101115142-1091066df799. This is a simple implementation of
181-
# Go's pseudo-versions: https://go.dev/ref/mod#pseudo-versions.
182-
# - name: Set Default Multi-Platform Package Version
183-
# if: env.XPKG_VERSION == ''
184-
# run: echo "XPKG_VERSION=v0.0.0-$(date -d@$(git show -s --format=%ct) +%Y%m%d%H%M%S)-$(git rev-parse --short=12 HEAD)" >> $GITHUB_ENV
185-
186186
# - name: Push Multi-Platform Package to Upbound
187187
# if: env.XPKG_ACCESS_ID != ''
188-
# run: "./crossplane --verbose xpkg push --package-files $(echo *.xpkg|tr ' ' ,) ${{ env.XPKG }}:${{ env.XPKG_VERSION }}"
188+
# run: "bin/crossplane --verbose xpkg push --package-files $(echo *.xpkg|tr ' ' ,) ${{ env.XPKG }}:${{ env.XPKG_VERSION }}"
189189

190190
- name: Login to GHCR
191191
uses: docker/login-action@v3
@@ -195,4 +195,4 @@ jobs:
195195
password: ${{ secrets.GITHUB_TOKEN }}
196196

197197
- name: Push Multi-Platform Package to GHCR
198-
run: "./crossplane --verbose xpkg push --package-files $(echo *.xpkg|tr ' ' ,) ${{ env.CROSSPLANE_REGORG }}:${{ env.XPKG_VERSION }}"
198+
run: "bin/crossplane --verbose xpkg push --package-files $(echo *.xpkg|tr ' ' ,) ${{ env.CROSSPLANE_REGORG }}:${{ env.XPKG_VERSION }}"

CloudOps.groovy

Lines changed: 0 additions & 25 deletions
This file was deleted.

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ RUN \
2727
&& /venv/build/bin/pip install hatch \
2828
&& /venv/build/bin/hatch build -t wheel /whl
2929

30-
# Create a fresh venv and install only the function wheel into it.
30+
# Create a fresh venv and install only the pythonic wheel into it.
3131
#RUN --mount=type=cache,target=/root/.cache/pip \
3232
RUN \
3333
python3 -m venv /venv/fn \
3434
&& /venv/fn/bin/pip install /whl/*.whl
3535

36-
# Copy the function venv to our runtime stage. It's important that the path be
36+
# Copy the pythonic venv to our runtime stage. It's important that the path be
3737
# the same as in the build stage, to avoid shebang paths and symlinks breaking.
3838
FROM gcr.io/distroless/python3-debian12 AS image
3939
WORKDIR /
4040
USER nonroot:nonroot
4141
COPY --from=build --chown=nonroot:nonroot /venv/fn /venv/fn
4242
EXPOSE 9443
43-
ENTRYPOINT ["/venv/fn/bin/function"]
43+
ENTRYPOINT ["/venv/fn/bin/pythonic"]

README.md

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ spec:
3535
vpc.spec.forProvider.cidrBlock = self.spec.cidr
3636
self.status.vpcId = vpc.status.atProvider.vpcId
3737
```
38+
In addtion to an inline script, the python implementation can be specified
39+
as the complete path to a python class. See [Filing system Composites](#filing-system-composites).
3840
3941
## Examples
4042
@@ -43,6 +45,17 @@ function-go-templating examples implemented using function-pythonic.
4345
The [eks-cluster](./examples/eks-cluster/composition.yaml) example is a good
4446
complex example creating the entire vpc structure needed for an EKS cluster.
4547
48+
## Installing function-pythonic
49+
50+
```yaml
51+
apiVersion: pkg.crossplane.io/v1
52+
kind: Function
53+
metadata:
54+
name: function-pythonic
55+
spec:
56+
package: ghcr.io/fortra/function-pythonic:v0.0.3
57+
```
58+
4659
## Managed Resource Dependencies
4760
4861
function-pythonic automatically handles dependencies between managed resources.
@@ -117,8 +130,8 @@ The following functions are provided to create Protobuf structures:
117130
| Unknown | Create a new Protobuf unknown placeholder |
118131
| Yaml | Create a new Protobuf structure from a yaml string |
119132
| Json | Create a new Protobuf structure from a json string |
120-
| Base64Encode | Encode a string into base 64 |
121-
| Base64Decode | Decode a string from base 64 |
133+
| B64Encode | Encode a string into base 64 |
134+
| B64Decode | Decode a string from base 64 |
122135

123136
The following items are supported in all the Protobuf Message wrapper classes: `bool`,
124137
`len`, `contains`, `iter`, `hash`, `==`, `str`, `format`
@@ -259,10 +272,73 @@ spec:
259272
self.status.composite = 'Hello, World!'
260273
```
261274
262-
## Installing Python Packages
275+
## Filing system Composites
276+
277+
Composition Composite implementations can be coded in a stand alone python files
278+
by configuring the function-pythonic deployment with the code mounted into
279+
the package-runtime container, and then adding the mount point to the python
280+
path using the --python-path command line option. For example:
281+
```yaml
282+
apiVersion: v1
283+
kind: ConfigMap
284+
metadata:
285+
namespace: crossplane-system
286+
name: pythonic-composites
287+
data:
288+
bucket.py: |
289+
class BucketComposite(BaseComposite):
290+
def compose(self):
291+
self.resources.bucket.apiVersion = 's3.aws.upbound.io/v1beta2'
292+
self.resources.bucket.kind = 'Bucket'
293+
self.resources.bucket.spec.forProvider.region = 'us-east-1'
294+
```
295+
```yaml
296+
apiVersion: pkg.crossplane.io/v1beta1
297+
kind: DeploymentRuntimeConfig
298+
metadata:
299+
name: function-pythonic
300+
spec:
301+
deploymentTemplate:
302+
spec:
303+
template:
304+
spec:
305+
containers:
306+
- name: package-runtime
307+
args:
308+
- --debug
309+
- --python-path
310+
- /mnt/composites
311+
volumeMounts:
312+
- name: composites
313+
mountPath: /mnt/composites
314+
volumes:
315+
- name: composites
316+
configMap:
317+
name: pythonic-composites
318+
```
319+
```yaml
320+
apiVersion: apiextensions.crossplane.io/v1
321+
kind: Composition
322+
metadata:
323+
name: buckets.example.pythonic.io/v1
324+
spec:
325+
compositeTypeRef:
326+
apiVersion: example.pythonic.io/v1
327+
kind: Bucket
328+
mode: Pipeline
329+
pipeline:
330+
- step: create-bucket
331+
functionRef:
332+
name: function-pythonic
333+
input:
334+
apiVersion: pythonic.fn.fortra.com/v1alpha1
335+
kind: Composite
336+
composite: bucket.BucketComposite
337+
```
338+
## Install Additional Python Packages
263339
264340
function-pythonic supports a `--pip-install` command line option which will run pip install
265-
with the configured pip install command. For example, the following DeploymentRuntimeConfig:
341+
with the configured pip install command. For example:
266342
```yaml
267343
apiVersion: pkg.crossplane.io/v1beta1
268344
kind: DeploymentRuntimeConfig
@@ -280,3 +356,27 @@ spec:
280356
- --pip-install
281357
- --quiet aiobotocore==2.23.2
282358
```
359+
360+
## Enable Oversize Protos
361+
362+
The Protobuf python package used by function-pythonic limits the depth of yaml
363+
elements and the total size of yaml parsed. This results in a limit of approximately
364+
30 levels of nested yaml fields. This check can be disabled using the `--allow-oversize-protos`
365+
command line option. For example:
366+
367+
```yaml
368+
apiVersion: pkg.crossplane.io/v1beta1
369+
kind: DeploymentRuntimeConfig
370+
metadata:
371+
name: function-pythonic
372+
spec:
373+
deploymentTemplate:
374+
spec:
375+
template:
376+
spec:
377+
containers:
378+
- name: package-runtime
379+
args:
380+
- --debug
381+
- --allow-oversize-protos
382+
```

crossplane/pythonic/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import base64
2+
3+
from .composite import BaseComposite
4+
from .protobuf import Map, List, Unknown, Yaml, Json
5+
B64Encode = lambda s: base64.b64encode(s.encode('utf-8')).decode('utf-8')
6+
B64Decode = lambda s: base64.b64decode(s.encode('utf-8')).decode('utf-8')
7+
8+
__all__ = [
9+
'BaseComposite',
10+
'Map',
11+
'List',
12+
'Unknown',
13+
'Yaml',
14+
'Json',
15+
'B64Encode',
16+
'B64Decode',
17+
]
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
import datetime
33
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
44

5-
import function.protobuf
5+
from . import protobuf
66

77

88
_notset = object()
99

1010

1111
class BaseComposite:
1212
def __init__(self, request, response, logger):
13-
self.request = function.protobuf.Message(None, None, request.DESCRIPTOR, request, 'Function Request')
14-
self.response = function.protobuf.Message(None, None, response.DESCRIPTOR, response)
13+
self.request = protobuf.Message(None, None, request.DESCRIPTOR, request, 'Function Request')
14+
self.response = protobuf.Message(None, None, response.DESCRIPTOR, response)
1515
self.logger = logger
1616
self.autoReady = True
1717
self.credentials = Credentials(self.request)
@@ -54,7 +54,7 @@ def ready(self):
5454
def ready(self, ready):
5555
if ready:
5656
ready = fnv1.Ready.READY_TRUE
57-
elif ready == None or (isinstance(ready, function.protobuf.Values) and ready._isUnknown):
57+
elif ready == None or (isinstance(ready, protobuf.Values) and ready._isUnknown):
5858
ready = fnv1.Ready.READY_UNSPECIFIED
5959
else:
6060
ready = fnv1.Ready.READY_FALSE
@@ -115,7 +115,6 @@ def __setattr__(self, key, resource):
115115
self[key] = resource
116116

117117
def __setitem__(self, key, resource):
118-
print('SETITEM', key, resource)
119118
self._composite.response.desired.resources[key].resource = resource
120119

121120
def __delattr__(self, key):
@@ -216,7 +215,7 @@ def ready(self):
216215
def ready(self, ready):
217216
if ready:
218217
ready = fnv1.Ready.READY_TRUE
219-
elif ready == None or (isinstance(ready, function.protobuf.Values) and ready._isUnknown):
218+
elif ready == None or (isinstance(ready, protobuf.Values) and ready._isUnknown):
220219
ready = fnv1.Ready.READY_UNSPECIFIED
221220
else:
222221
ready = fnv1.Ready.READY_FALSE
@@ -422,7 +421,7 @@ def claim(self, claim):
422421
if bool(self):
423422
if claim:
424423
self._result.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
425-
elif claim == None or (isinstance(claim, function.protobuf.Values) and claim._isUnknown):
424+
elif claim == None or (isinstance(claim, protobuf.Values) and claim._isUnknown):
426425
self._result.target = fnv1.Target.TARGET_UNSPECIFIED
427426
else:
428427
self._result.target = fnv1.Target.TARGET_COMPOSITE
@@ -461,7 +460,7 @@ def __getitem__(self, type):
461460
return Condition(self, type)
462461

463462

464-
class Condition(function.protobuf.ProtobufValue):
463+
class Condition(protobuf.ProtobufValue):
465464
def __init__(self, conditions, type):
466465
self._conditions = conditions
467466
self.type = type
@@ -509,7 +508,7 @@ def status(self, status):
509508
condition.status = fnv1.Status.STATUS_CONDITION_TRUE
510509
elif status == None:
511510
condition.status = fnv1.Status.STATUS_CONDITION_UNKNOWN
512-
elif isinstance(status, function.protobuf.Values) and status._isUnknown:
511+
elif isinstance(status, protobuf.Values) and status._isUnknown:
513512
condition.status = fnv1.Status.STATUS_CONDITION_UNSPECIFIED
514513
else:
515514
condition.status = fnv1.Status.STATUS_CONDITION_FALSE
@@ -556,7 +555,7 @@ def claim(self, claim):
556555
condition = self._find_condition(True)
557556
if claim:
558557
condition.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
559-
elif claim == None or (isinstance(claim, function.protobuf.Values) and claim._isUnknown):
558+
elif claim == None or (isinstance(claim, protobuf.Values) and claim._isUnknown):
560559
condition.target = fnv1.Target.TARGET_UNSPECIFIED
561560
else:
562561
condition.target = fnv1.Target.TARGET_COMPOSITE

0 commit comments

Comments
 (0)