Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.

Commit 18cfae3

Browse files
author
Patrick J. McNerthney
committed
Major refactor to support Usages
1 parent 5891000 commit 18cfae3

23 files changed

Lines changed: 1075 additions & 431 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ jobs:
217217
# pushes them as a multi-platform package. We only push the package it the
218218
# XPKG_ACCESS_ID and XPKG_TOKEN secrets were provided.
219219
push-xpkg:
220-
# Don't publish unless we were run with an explicit version.
221-
if: ${{ inputs.version != '' }}
220+
# Don't publish unless the main branch or we were run with an explicit version
221+
if: ${{ github.ref == 'refs/heads/main' || inputs.version != '' }}
222222
needs:
223223
- build-xpkg
224224
runs-on: ubuntu-24.04

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ overridden for all composed resource by setting the Composite `self.unknownsFata
9595
to False, or at the individual composed resource level by setting the
9696
`Resource.unknownsFatal` field to False.
9797

98+
## Usage Dependencies
99+
100+
function-pythonic can be configured to automatically create
101+
[Crossplane Usages](https://docs.crossplane.io/latest/managed-resources/usages/)
102+
dependencies between resources. Modifying the above VPC example with:
103+
```yaml
104+
self.usages = True
105+
106+
vpc = self.resources.VPC('ec2.aws.crossplane.io/v1beta1', 'VPC')
107+
vpc.spec.forProvider.region = 'us-east-1
108+
vpc.spec.forProvider.cidrBlock = '10.0.0.0/16'
109+
110+
subnet = self.resources.SubnetA('ec2.aws.crossplane.io/v1beta1', 'Subnet')
111+
subnet.spec.forProvider.region = 'us-east-1'
112+
subnet.spec.forProvider.vpcId = vpc.status.atProvider.vpcId
113+
subnet.spec.forProvider.availabilityZone = 'us-east-1a'
114+
subnet.spec.forProvider.cidrBlock = '10.0.0.0/20'
115+
```
116+
Will generate the appropriate Crossplane Usage resource.
117+
98118
## Pythonic access of Protobuf Messages
99119

100120
All Protobuf messages are wrapped by a set of python classes which enable using
@@ -201,6 +221,7 @@ The BaseComposite also provides access to the following Crossplane Function leve
201221
| self.requireds | Requireds | Request and read additional local Kubernetes resources |
202222
| self.resources | Resources | Define and process composed resources |
203223
| self.unknownsFatal | Boolean | Terminate the composition if already created resources are assigned unknown values, default True |
224+
| self.usages| Boolean | Generate Crossplane Usages for resource dependencies, default False |
204225
| self.autoReady | Boolean | Perform auto ready processing on all composed resources, default True |
205226

206227
### Composed Resources
@@ -227,6 +248,7 @@ Resource class:
227248
| Resource.connection | Connection | The resource connection details |
228249
| Resource.ready | Boolean | The resource ready state |
229250
| Resource.unknownsFatal | Boolean | Terminate the composition if this resource has been created and is assigned unknown values, default is Composite.unknownsFatal |
251+
| Resource.usages | Boolean | Generate Crossplane Usages for this resource, default is Composite.autoReady |
230252
| Resource.autoReady | Boolean | Perform auto ready processing on this resource, default is Composite.autoReady |
231253

232254
### Required Resources (AKA Extra Resources)

crossplane/pythonic/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11

2-
import base64
32

43
from .composite import BaseComposite
5-
from .protobuf import append, Map, List, Unknown, Yaml, Json
6-
B64Encode = lambda s: base64.b64encode(s.encode('utf-8')).decode('utf-8')
7-
B64Decode = lambda s: base64.b64decode(s.encode('utf-8')).decode('utf-8')
4+
from .protobuf import append, Map, List, Unknown, Yaml, Json, B64Encode, B64Decode
85

96
__all__ = [
107
'BaseComposite',

crossplane/pythonic/composite.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __init__(self, request, logger):
3131
self.resources = Resources(self)
3232
self.unknownsFatal = True
3333
self.autoReady = True
34+
self.usages = False
3435

3536
observed = self.request.observed.composite
3637
desired = self.response.desired.composite
@@ -49,7 +50,7 @@ def __init__(self, request, logger):
4950
def ttl(self):
5051
if self.response.meta.ttl.nanos:
5152
return float(self.response.meta.ttl.seconds) + (float(self.response.meta.ttl.nanos) / 1000000000.0)
52-
return self.response.meta.ttl.seconds
53+
return int(self.response.meta.ttl.seconds)
5354

5455
@ttl.setter
5556
def ttl(self, ttl):
@@ -61,7 +62,7 @@ def ttl(self, ttl):
6162
if ttl.is_integer():
6263
self.response.meta.ttl.nanos = 0
6364
else:
64-
self.response.meta.ttl.nanos = int((ttl - self.response.meta.ttl.seconds) * 1000000000)
65+
self.response.meta.ttl.nanos = int((ttl - int(self.response.meta.ttl.seconds)) * 1000000000)
6566
else:
6667
raise ValueError('ttl must be an int or float')
6768

@@ -78,7 +79,7 @@ def ready(self):
7879
def ready(self, ready):
7980
if ready:
8081
ready = fnv1.Ready.READY_TRUE
81-
elif ready == None or (isinstance(ready, protobuf.Values) and ready._isUnknown):
82+
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
8283
ready = fnv1.Ready.READY_UNSPECIFIED
8384
else:
8485
ready = fnv1.Ready.READY_FALSE
@@ -184,6 +185,7 @@ def __init__(self, composite, name):
184185
self.connection = Connection(observed)
185186
self.unknownsFatal = None
186187
self.autoReady = None
188+
self.usages = None
187189

188190
def __call__(self, apiVersion=_notset, kind=_notset, namespace=_notset, name=_notset):
189191
self.desired()
@@ -199,15 +201,15 @@ def __call__(self, apiVersion=_notset, kind=_notset, namespace=_notset, name=_no
199201

200202
@property
201203
def apiVersion(self):
202-
return self.observed.apiVersion
204+
return self.desired.apiVersion
203205

204206
@apiVersion.setter
205207
def apiVersion(self, apiVersion):
206208
self.desired.apiVersion = apiVersion
207209

208210
@property
209211
def kind(self):
210-
return self.observed.kind
212+
return self.desired.kind
211213

212214
@kind.setter
213215
def kind(self, kind):
@@ -265,7 +267,7 @@ def ready(self):
265267
def ready(self, ready):
266268
if ready:
267269
ready = fnv1.Ready.READY_TRUE
268-
elif ready == None or (isinstance(ready, protobuf.Values) and ready._isUnknown):
270+
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
269271
ready = fnv1.Ready.READY_UNSPECIFIED
270272
else:
271273
ready = fnv1.Ready.READY_FALSE
@@ -376,8 +378,8 @@ def matchLabels(self, labels):
376378
elif isinstance(entry, (list, tuple)):
377379
self._selector.match_labels.labels[entry[0]] = entry[1]
378380

379-
def __getitem__(self, key):
380-
return RequiredResource(self.name, self._resources.items[key])
381+
def __getitem__(self, ix):
382+
return RequiredResource(self.name, ix, self._resources.items[ix])
381383

382384
def __bool__(self):
383385
return bool(self._resources.items)
@@ -391,8 +393,9 @@ def __iter__(self):
391393

392394

393395
class RequiredResource:
394-
def __init__(self, name, resource):
396+
def __init__(self, name, ix, resource):
395397
self.name = name
398+
self.ix = ix
396399
self.observed = resource.resource
397400
self.apiVersion = self.observed.apiVersion
398401
self.kind = self.observed.kind
@@ -487,7 +490,7 @@ def status(self, status):
487490
condition.status = fnv1.Status.STATUS_CONDITION_TRUE
488491
elif status == None:
489492
condition.status = fnv1.Status.STATUS_CONDITION_UNKNOWN
490-
elif isinstance(status, protobuf.Values) and status._isUnknown:
493+
elif isinstance(status, protobuf.Value) and status._isUnknown:
491494
condition.status = fnv1.Status.STATUS_CONDITION_UNSPECIFIED
492495
else:
493496
condition.status = fnv1.Status.STATUS_CONDITION_FALSE
@@ -521,7 +524,7 @@ def lastTransitionTime(self):
521524
if observed.type == self.type:
522525
time = observed.lastTransitionTime
523526
if time:
524-
return datetime.datetime.fromisoformat(time)
527+
return datetime.datetime.fromisoformat(str(time))
525528
return None
526529

527530
@property
@@ -534,7 +537,7 @@ def claim(self, claim):
534537
condition = self._find_condition(True)
535538
if claim:
536539
condition.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
537-
elif claim == None or (isinstance(claim, protobuf.Values) and claim._isUnknown):
540+
elif claim == None or (isinstance(claim, protobuf.Value) and claim._isUnknown):
538541
condition.target = fnv1.Target.TARGET_UNSPECIFIED
539542
else:
540543
condition.target = fnv1.Target.TARGET_COMPOSITE
@@ -711,7 +714,7 @@ def claim(self, claim):
711714
if bool(self):
712715
if claim:
713716
self._result.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
714-
elif claim == None or (isinstance(claim, protobuf.Values) and claim._isUnknown):
717+
elif claim == None or (isinstance(claim, protobuf.Value) and claim._isUnknown):
715718
self._result.target = fnv1.Target.TARGET_UNSPECIFIED
716719
else:
717720
self._result.target = fnv1.Target.TARGET_COMPOSITE

0 commit comments

Comments
 (0)