Skip to content

Commit 5bfddc4

Browse files
author
Patrick J. McNerthney
committed
Implement unit test framework with initial unit tests.
1 parent e3bf500 commit 5bfddc4

File tree

15 files changed

+512
-174
lines changed

15 files changed

+512
-174
lines changed

.github/workflows/ci.yaml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,27 @@ jobs:
4848
# python-version: ${{ env.PYTHON_VERSION }}
4949

5050
# - name: Setup Hatch
51-
# run: pipx install hatch==1.7.0
51+
# run: pipx install hatch==1.14.1
5252

5353
# - name: Lint
5454
# run: hatch run lint:check
5555

56-
# unit-test:
57-
# runs-on: ubuntu-24.04
58-
# steps:
59-
# - name: Checkout
60-
# uses: actions/checkout@v4
56+
unit-test:
57+
runs-on: ubuntu-24.04
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v4
6161

62-
# - name: Setup Python
63-
# uses: actions/setup-python@v5
64-
# with:
65-
# python-version: ${{ env.PYTHON_VERSION }}
62+
- name: Setup Python
63+
uses: actions/setup-python@v5
64+
with:
65+
python-version: ${{ env.PYTHON_VERSION }}
6666

67-
# - name: Setup Hatch
68-
# run: pipx install hatch==1.7.0
67+
- name: Setup Hatch
68+
run: pipx install hatch==1.14.1
6969

70-
# - name: Run Unit Tests
71-
# run: hatch run test:unit
70+
- name: Run Unit Tests
71+
run: hatch run test:unit
7272

7373
# We want to build most packages for the amd64 and arm64 architectures. To
7474
# speed this up we build single-platform packages in parallel. We then upload

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ The following functions are provided to create Protobuf structures:
116116
| List | Create a new Protobuf list |
117117
| Yaml | Create a new Protobuf structure from a yaml string |
118118
| Json | Create a new Protobuf structure from a json string |
119+
| Base64Encode | Encode a string into base 64 |
120+
| Base64Decode | Decode a string from base 64 |
119121

120122
The following items are supported in all the Protobuf Message wrapper classes: `bool`,
121123
`len`, `contains`, `iter`, `hash`, `==`, `str`, `format`

examples/function-go-templating/inline/composition.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ spec:
2323
user = f"test-user-{ix}"
2424
r = self.resources[user]('iam.aws.upbound.io/v1beta1', 'User')
2525
r.metadata.labels['testing.upbound.io/example-name'] = user
26-
r.metadata.labels.dummy = r.observed.resource.metadata.labels.dummy or random.choice(['foo', 'bar', 'baz'])
26+
r.metadata.labels.dummy = r.observed.metadata.labels.dummy or random.choice(['foo', 'bar', 'baz'])
2727
r = self.resources[f"sample-access-key-{ix}"]('iam.aws.upbound.io/v1beta1', 'AccessKey')
2828
r.spec.forProvider.userSelector.matchLabels['testing.upbound.io/example-name'] = user
2929
r.spec.writeConnectionSecretToRef.namespace = 'crossplane.system'

examples/helm-copy-secret/composition.yaml

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,42 @@ spec:
1515
apiVersion: pythonic.fn.fortra.com/v1alpha1
1616
kind: Composite
1717
composite: |
18+
def argcd_secret_config(secret):
19+
config = Map()
20+
config.tlsClientConfig.insecure = True
21+
config.tlsClientConfig.caData = B64Decode(secret.data['certificate-authority'])
22+
config.tlsClientConfig.certData = B64Decode(secret.data['client-certificate'])
23+
config.tlsClientConfig.keyData = B64Decode(secret.data['client-key'])
24+
return config
25+
1826
class Composite(BaseComposite):
1927
def compose(self):
20-
argocd = self.resources.Release('helm.crossplane.io/v1beta1', 'Release')
21-
argocd.externalName('argocd')
22-
argocd.spec.forProvider.namespace = 'argocd'
23-
argocd.spec.forProvider.chart.repository = 'https://argoproj.github.io/argo-helm'
24-
argocd.spec.forProvider.chart.name = 'argo-cd'
25-
argocd.spec.forProvider.chart.version = '8.0.7'
28+
name = self.metadata.name
29+
namespace = name
30+
31+
release = self.resources.release('helm.crossplane.io/v1beta1', 'Release', name=name)
32+
release.spec.providerConfigRef.name = 'helm-provider'
33+
release.spec.rollbackLimit = 1
34+
release.spec.forProvider.chart.repository = 'https://charts.loft.sh'
35+
release.spec.forProvider.chart.name = 'vcluster'
36+
release.spec.forProvider.chart.version = '0.26.0'
37+
release.spec.forProvider.namespace = namespace
38+
release.spec.forProvider.values.controlPlane.proxy.extraSANs[0] = f'{name}.{namespace}'
2639
27-
# This will work once crossplane-sdk-python is updated to the V2 function api
28-
#secret = self.requireds.Secret('v1', 'Secret', 'argocd', 'argocd-secret')[0]
29-
secret = self.requireds.Secret('v1', 'Secret', labels={'app.kubernetes.io/name':'argocd-secret'})[0]
30-
self.resources.Secret('v1', 'Secret', 'default', 'argocd-secret').data = secret.data
40+
secret_name = f'vc-{name}'
41+
# This will work once crossplane-sdk-python is updated to the v2 function api
42+
#vcluster_secrets = self.requireds.Secret('v1', 'Secret', namespace, secret_name)
43+
vcluster_secrets = self.requireds.Secret('v1', 'Secret', labels={'vcluster-name':name})
44+
for secret in vcluster_secrets:
45+
if secret.metadata.name != secret_name:
46+
continue
47+
argocd_secret = self.resources.secret('v1', 'Secret', 'argocd', secret_name)
48+
argocd_secret.metadata.labels['argocd.argoproj.io/secret-type'] = 'cluster'
49+
argocd_secret.type = 'Opaque'
50+
argocd_secret.data.name = B64Encode(name)
51+
argocd_secret.data.server = B64Encode(f'https://{name}.{namespace}:443')
52+
argocd_secret.data.config = B64Encode(format(argcd_secret_config(secret), 'json'))
53+
argocd_secret.ready = argocd_secret.observed.data
54+
break
55+
else:
56+
self.ready = False

function/composite.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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._type == function.protobuf.Values.Type.UNKNOWN):
57+
elif ready == None or (isinstance(ready, function.protobuf.Values) and ready._isUnknown):
5858
ready = fnv1.Ready.READY_UNSPECIFIED
5959
else:
6060
ready = fnv1.Ready.READY_FALSE
@@ -209,7 +209,7 @@ def ready(self):
209209
def ready(self, ready):
210210
if ready:
211211
ready = fnv1.Ready.READY_TRUE
212-
elif ready == None or (isinstance(ready, function.protobuf.Values) and ready._type == function.protobuf.Values.Type.UNKNOWN):
212+
elif ready == None or (isinstance(ready, function.protobuf.Values) and ready._isUnknown):
213213
ready = fnv1.Ready.READY_UNSPECIFIED
214214
else:
215215
ready = fnv1.Ready.READY_FALSE
@@ -415,7 +415,7 @@ def claim(self, claim):
415415
if bool(self):
416416
if claim:
417417
self._result.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
418-
elif claim == None or (isinstance(claim, function.protobuf.Values) and claim._type == function.protobuf.Values.Type.UNKNOWN):
418+
elif claim == None or (isinstance(claim, function.protobuf.Values) and claim._isUnknown):
419419
self._result.target = fnv1.Target.TARGET_UNSPECIFIED
420420
else:
421421
self._result.target = fnv1.Target.TARGET_COMPOSITE
@@ -502,7 +502,7 @@ def status(self, status):
502502
condition.status = fnv1.Status.STATUS_CONDITION_TRUE
503503
elif status == None:
504504
condition.status = fnv1.Status.STATUS_CONDITION_UNKNOWN
505-
elif isinstance(ready, function.protobuf.Values) and ready._type == function.protobuf.Values.Type.UNKNOWN:
505+
elif isinstance(status, function.protobuf.Values) and status._isUnknown:
506506
condition.status = fnv1.Status.STATUS_CONDITION_UNSPECIFIED
507507
else:
508508
condition.status = fnv1.Status.STATUS_CONDITION_FALSE
@@ -549,7 +549,7 @@ def claim(self, claim):
549549
condition = self._find_condition(True)
550550
if claim:
551551
condition.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
552-
elif claim == None or (isinstance(claim, function.protobuf.Values) and claim._type == function.protobuf.Values.Type.UNKNOWN):
552+
elif claim == None or (isinstance(claim, function.protobuf.Values) and claim._isUnknown):
553553
condition.target = fnv1.Target.TARGET_UNSPECIFIED
554554
else:
555555
condition.target = fnv1.Target.TARGET_COMPOSITE

function/fn.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""A Crossplane composition function."""
22

33
import asyncio
4+
import base64
45
import inspect
56

67
import grpc
@@ -21,15 +22,8 @@ def __init__(self):
2122
self.modules = {}
2223

2324
async def RunFunction(
24-
self, request: fnv1.RunFunctionRequest, context: grpc.aio.ServicerContext
25+
self, request: fnv1.RunFunctionRequest, _: grpc.aio.ServicerContext
2526
) -> fnv1.RunFunctionResponse:
26-
try:
27-
return await self.run(request, context)
28-
except Exception as e:
29-
self.logger.exception('Error during RunFuction')
30-
raise
31-
32-
async def run(self, request, context):
3327
composite = request.observed.composite.resource
3428
logger = self.logger.bind(
3529
apiVersion=composite['apiVersion'],
@@ -114,3 +108,5 @@ def __init__(self):
114108
self.List = function.protobuf.List
115109
self.Yaml = function.protobuf.Yaml
116110
self.Json = function.protobuf.Json
111+
self.B64Encode = lambda s: base64.b64encode(s.encode('utf-8')).decode('utf-8')
112+
self.B64Decode = lambda s: base64.b64decode(s.encode('utf-8')).decode('utf-8')

0 commit comments

Comments
 (0)