Skip to content

Commit 4f1efbc

Browse files
renovate[bot]iciclespider
authored andcommitted
Integrate render command with an active cluster
1 parent 4f53ce9 commit 4f1efbc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+667
-547
lines changed

README.md

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ kind: Function
5757
metadata:
5858
name: function-pythonic
5959
spec:
60-
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.3.1
60+
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.4.0
6161
```
6262
6363
### Crossplane V1
@@ -69,7 +69,7 @@ kind: Function
6969
metadata:
7070
name: function-pythonic
7171
spec:
72-
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.3.1
72+
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.4.0
7373
runtimeConfigRef:
7474
name: function-pythonic
7575
--
@@ -104,7 +104,7 @@ condition, the composition will be terminated or the observed value for that fie
104104
be used, depending on the `unknownsFatal` settings.
105105

106106
Take the following example:
107-
```yaml
107+
```python
108108
vpc = self.resources.VPC('ec2.aws.crossplane.io/v1beta1', 'VPC')
109109
vpc.spec.forProvider.region = 'us-east-1
110110
vpc.spec.forProvider.cidrBlock = '10.0.0.0/16'
@@ -126,12 +126,39 @@ overridden for all composed resource by setting the Composite `self.unknownsFata
126126
to False, or at the individual composed resource level by setting the
127127
`Resource.unknownsFatal` field to False.
128128

129+
## Explicit Dependencies
130+
131+
At times, the above implicit dependency handling does not account for all cases.
132+
Explicit dependencies can be configured using the resource `addDependency` method.
133+
The dependency's "ready" is used to determine when that dependency is available
134+
for use. The dependency's ready state can either be explictly set, or will be
135+
defaulted to it's auto-ready calculation.
136+
137+
Here is an example of specifying an explicit dependency:
138+
```yaml
139+
crd = self.resources.KarpenterCrdRelease('Release', 'helm.crossplane.io/v1beta1')
140+
crd.spec.deletionPolicy = 'Orphan'
141+
crd.spec.forProvider.chart.repository = 'oci://public.ecr.aws/karpenter'
142+
crd.spec.forProvider.chart.name = 'karpenter-crd'
143+
crd.spec.forProvider.chart.version = '1.8.6'
144+
crd.spec.forProvider.namespace = 'karpenter'
145+
crd.externalName = 'karpenter-crd'
146+
karpenter = self.resources.KarpenterRelease('Release', 'helm.crossplane.io/v1beta1')
147+
karpenter.addDependency(crd)
148+
karpenter.spec.deletionPolicy = 'Orphan'
149+
karpenter.spec.forProvider.chart.repository = 'oci://public.ecr.aws/karpenter'
150+
karpenter.spec.forProvider.chart.name = 'karpenter'
151+
karpenter.spec.forProvider.chart.version = '1.8.6'
152+
karpenter.spec.forProvider.namespace = 'karpenter'
153+
karpenter.externalName = 'karpenter'
154+
```
155+
129156
## Usage Dependencies
130157

131158
function-pythonic can be configured to automatically create
132159
[Crossplane Usages](https://docs.crossplane.io/latest/managed-resources/usages/)
133160
dependencies between resources. Modifying the above VPC example with:
134-
```yaml
161+
```python
135162
self.usages = True
136163
137164
vpc = self.resources.VPC('ec2.aws.crossplane.io/v1beta1', 'VPC')
@@ -236,8 +263,8 @@ The BaseComposite class provides the following fields for manipulating the Compo
236263
| self.conditions | Conditions | The composite desired and observed conditions, read from observed if not in desired |
237264
| self.results | Results | Returned results applied to the Composite and optionally on the Claim |
238265
| self.connectionSecret | Map | The name, namespace, and resourceName to use when generating the connection secret in Crossplane v2 |
239-
| self.connection | Map | The composite desired connection detials |
240-
| self.connection.observed | Map | The composite observed connection detials |
266+
| self.connection | Map | The composite desired connection details |
267+
| self.connection.observed | Map | The composite observed connection details |
241268
| self.ready | Boolean | The composite desired ready state |
242269

243270
The BaseComposite also provides access to the following Crossplane Function level features:
@@ -254,9 +281,9 @@ The BaseComposite also provides access to the following Crossplane Function leve
254281
| self.environment | Map | The response environment, initialized from the request context environment |
255282
| self.requireds | Requireds | Request and read additional local Kubernetes resources |
256283
| self.resources | Resources | Define and process composed resources |
257-
| self.unknownsFatal | Boolean | Terminate the composition if already created resources are assigned unknown values, default True |
258284
| self.usages| Boolean | Generate Crossplane Usages for resource dependencies, default False |
259285
| self.autoReady | Boolean | Perform auto ready processing on all composed resources, default True |
286+
| self.unknownsFatal | Boolean | Terminate the composition if already created resources are assigned unknown values, default False |
260287

261288
### Composed Resources
262289

@@ -281,9 +308,11 @@ Resource class:
281308
| Resource.conditions | Conditions | The resource conditions |
282309
| Resource.connection | Map | The resource observed connection details |
283310
| Resource.ready | Boolean | The resource ready state |
284-
| Resource.unknownsFatal | Boolean | Terminate the composition if this resource has been created and is assigned unknown values, default is Composite.unknownsFatal |
311+
| Resource.addDependency | Method | Add another composed resource as a dependency |
312+
| Resource.setReadyCondition | Method | Set Resource.ready to the Ready Condition status |
285313
| Resource.usages | Boolean | Generate Crossplane Usages for this resource, default is Composite.autoReady |
286314
| Resource.autoReady | Boolean | Perform auto ready processing on this resource, default is Composite.autoReady |
315+
| Resource.unknownsFatal | Boolean | Terminate the composition if this resource has been created and is assigned unknown values, default is Composite.unknownsFatal |
287316

288317
### Required Resources
289318

@@ -382,28 +411,34 @@ $ pip install crossplane-function-pythonic
382411
Then to render function-pythonic Compositions, use the `function-pythonic render ...`
383412
command.
384413
```shell
385-
$ function-pythonic render -h
386-
usage: Crossplane Function Pythonic render [-h] [--debug] [--log-name-width WIDTH] [--python-path DIRECTORY] [--render-unknowns]
387-
[--allow-oversize-protos] [--context-files KEY=PATH] [--context-values KEY=VALUE]
388-
[--observed-resources PATH] [--required-resources PATH] [--secret-store PATH] [--include-full-xr]
389-
[--include-connection-xr] [--include-function-results] [--include-context]
390-
PATH [PATH/CLASS]
414+
$ function-pythonic render --help
415+
usage: Crossplane Function Pythonic render [-h] [--debug] [--log-name-width WIDTH] [--logger-level LOGGER=LEVEL] [--python-path DIRECTORY]
416+
[--render-unknowns] [--allow-oversize-protos] [--crossplane-v1] [--kube-context CONTEXT]
417+
[--context-files KEY=PATH] [--context-values KEY=VALUE] [--observed-resources PATH]
418+
[--required-resources PATH] [--secret-store PATH] [--include-full-xr] [--include-connection-xr]
419+
[--include-function-results] [--include-context]
420+
COMPOSITE [COMPOSITION]
391421
392422
positional arguments:
393-
PATH A YAML file containing the Composite resource to render.
394-
PATH/CLASS A YAML file containing the Composition resource or the complete path of a function=-pythonic BaseComposite subclass.
423+
COMPOSITE A YAML file containing the Composite resource to render, or kind:apiVersion:namespace:name of cluster Composite.
424+
COMPOSITION A YAML file containing the Composition resource, or the complete path of a function-pythonic BaseComposite subclass.
395425
396426
options:
397427
-h, --help show this help message and exit
398428
--debug, -d Emit debug logs.
399429
--log-name-width WIDTH
400430
Width of the logger name in the log output, default 40.
431+
--logger-level LOGGER=LEVEL
432+
Logger level, for example: botocore.hooks=INFO
401433
--python-path DIRECTORY
402434
Filing system directories to add to the python path.
403435
--render-unknowns, -u
404436
Render resources with unknowns, useful during local development.
405437
--allow-oversize-protos
406438
Allow oversized protobuf messages
439+
--crossplane-v1 Enable Crossplane V1 compatibility mode
440+
--kube-context, -k CONTEXT
441+
The kubectl context to use to obtain external resources from, such as required resources, connections, etc.
407442
--context-files KEY=PATH
408443
Context key-value pairs to pass to the Function pipeline. Values must be files containing YAML/JSON.
409444
--context-values KEY=VALUE
@@ -419,7 +454,7 @@ options:
419454
--include-connection-xr
420455
Include the Composite connection values in the rendered output as a resource of kind: Connection.
421456
--include-function-results, -r
422-
Include informational and warning messages from Functions in the rendered output as resources of kind: Result..
457+
Include informational and warning messages from Functions in the rendered output as resources of kind: Result.
423458
--include-context, -c
424459
Include the context in the rendered output as a resource of kind: Context.
425460
```
@@ -555,7 +590,7 @@ kind: Function
555590
metadata:
556591
name: function-pythonic
557592
spec:
558-
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.3.1
593+
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.4.0
559594
runtimeConfigRef:
560595
name: function-pythonic
561596
---

crossplane/pythonic/auto_ready.py

Lines changed: 96 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
11

22

3-
def process(composite):
4-
for name, resource in composite.resources:
5-
if resource.observed:
6-
if resource.autoReady or (resource.autoReady is None and composite.autoReady):
7-
if resource.ready is None:
8-
if _checks.get((resource.apiVersion, resource.kind), _check_default).ready(resource):
9-
resource.ready = True
3+
#def process_compsite(composite):
4+
# for name, resource in composite.resources:
5+
# resource.ready
6+
# if resource.autoReady or (resource.autoReady is None and composite.autoReady):
7+
# if resource.observed:
8+
# if resource.ready is None:
9+
# if _checks.get((resource.observed.apiVersion, resource.observed.kind), _check_default).ready(resource):
10+
# resource.ready = True
1011

1112

12-
class ConditionReady:
13+
def resource_ready(resource):
14+
if not resource.observed:
15+
return None
16+
return _checks.get((resource.observed.apiVersion, resource.observed.kind), _check_default).ready(resource)
17+
18+
19+
class ReadyCondition:
1320
def ready(self, resource):
14-
return bool(resource.conditions.Ready.status)
21+
ready = resource.conditions.Ready
22+
if not ready._find_condition():
23+
return None
24+
if ready.status:
25+
return resource.observed.metadata.name
26+
if ready.reason:
27+
return resource.status.notReadyCondition[ready.reason]
28+
return resource.status.notReadyCondition
1529

1630
_checks = {}
17-
_check_default = ConditionReady()
31+
_check_default = ReadyCondition()
1832

1933
class Check:
2034
@classmethod
@@ -28,7 +42,7 @@ def ready(self, resource):
2842

2943
class AlwaysReady(Check):
3044
def ready(self, resource):
31-
return True
45+
return resource.observed.metadata.name
3246

3347

3448
class ClusterRole(AlwaysReady):
@@ -44,85 +58,116 @@ class CronJob(Check):
4458
apiVersion = 'batch/v1'
4559
def ready(self, resource):
4660
if resource.observed.spec.suspend and len(resource.observed.spec.suspend):
47-
return True
61+
return resource.observed.metadata.name
4862
if not resource.status.lastScheduleTime:
49-
return False
63+
return resource.status.lastScheduleTime
5064
if resource.status.active:
51-
return True
65+
return resource.observed.metadata.name
5266
if not resource.status.lastSuccessfulTime:
53-
return False
54-
return str(resource.status.lastSuccessfulTime) >= str(resource.status.lastScheduleTime)
67+
return resource.status.lastSuccessfulTime
68+
if str(resource.status.lastSuccessfulTime) < str(resource.status.lastScheduleTime):
69+
return resource.status.successfulBeforeSchedule
70+
return resource.observed.metadata.name
5571

5672
class DaemonSet(Check):
5773
apiVersion = 'apps/v1'
5874
def ready(self, resource):
59-
if not resource.status.desiredNumberScheduled:
60-
return False
6175
scheduled = resource.status.desiredNumberScheduled
62-
return (scheduled == resource.status.numberReady and
63-
scheduled == resource.status.updatedNumberScheduled and
64-
scheduled == resource.status.numberAvailable
65-
)
76+
if not scheduled:
77+
return scheduled
78+
for field in ('numberReady', 'updatedNumberScheduled', 'numberAvailable'):
79+
value = resource.status[field]
80+
if not value:
81+
return value
82+
if scheduled != value:
83+
return resource.status[f"{field}NotScheduled"]
84+
return resource.observed.metadata.name
6685

6786
class Deployment(Check):
6887
apiVersion = 'apps/v1'
6988
def ready(self, resource):
7089
replicas = resource.observed.spec.replicas or 1
71-
if resource.status.updatedReplicas != replicas or resource.status.availableReplicas != replicas:
72-
return False
73-
return bool(resource.conditions.Available.status)
90+
for field in ('updatedReplicas', 'availableReplicas'):
91+
value = resource.status[field]
92+
if not value:
93+
return value
94+
if replicas != value:
95+
return resource.status[F"{field}NotReplicas"]
96+
available = resource.conditions.Available
97+
if not available:
98+
return resource.status.notAvailable
99+
if not available.status:
100+
if available.reason:
101+
return resource.status.notAvailable[available.reason]
102+
return resource.status.notAvailable
103+
return resource.observed.metadata.name
74104

75105
class HorizontalPodAutoscaler(Check):
76106
apiVersion = 'autoscaling/v2'
77107
def ready(self, resource):
78108
for type in ('FailedGetScale', 'FailedUpdateScale', 'FailedGetResourceMetric', 'InvalidSelector'):
79109
if resource.conditions[type].status:
80-
return False
110+
return resource.status[f"is{type}"]
81111
for type in ('ScalingActive', 'ScalingLimited'):
82112
if resource.conditions[type].status:
83-
return True
84-
return False
113+
return resource.observed.metadata.name
114+
return resource.status.notScalingActiveOrLimiited
85115

86116
class Ingress(Check):
87117
apiVersion = 'networking.k8s.io/v1'
88118
def ready(self, resource):
89-
return len(resource.status.loadBalancer.ingress) > 0
119+
if not len(resource.status.loadBalancer.ingress):
120+
return resource.status.noLoadBalanceIngresses
121+
return resource.observed.metadata.name
90122

91123
class Job(Check):
92124
apiVersion = 'batch/v1'
93125
def ready(self, resource):
94126
for type in ('Failed', 'Suspended'):
95127
if resource.conditions[type].status:
96-
return False
97-
return bool(resource.conditions.Complete.status)
128+
return resource.status[f"is{type}"]
129+
complete = resource.conditions.Complete
130+
if not complete:
131+
return resource.status.notComplete
132+
if not complete.status:
133+
if complete.reason:
134+
return resource.status.notComplete[complete.reason]
135+
return resource.status.notComplete
136+
return resource.observed.metadata.name
98137

99138
class Namespace(AlwaysReady):
100139
apiVersion = 'v1'
101140

102141
class PersistentVolumeClaim(Check):
103142
apiVersion = 'v1'
104143
def ready(self, resource):
105-
return resource.status.phase == 'Bound'
144+
if resource.status.phase != 'Bound':
145+
return resource.status.phaseNotBound
146+
return resource.observed.metadata.name
106147

107148
class Pod(Check):
108149
apiVersion = 'v1'
109150
def ready(self, resource):
110151
if resource.status.phase == 'Succeeded':
111-
return True
152+
return resource.observed.metadata.name
112153
if resource.status.phase == 'Running':
113154
if resource.observed.spec.restartPolicy == 'Always':
114155
if resource.conditions.Ready.status:
115-
return True
116-
return False
156+
return resource.observed.metadata.name
157+
return resource.status.notSucceededOrRunning
117158

118159
class ReplicaSet(Check):
119160
apiVersion = 'v1'
120161
def ready(self, resource):
121162
if int(resource.status.observedGeneration) < int(resource.observed.metadata.generation):
122-
return False
163+
return resource.status.priorObservedGeneration
123164
if resource.conditions.ReplicaFailure.status:
124-
return False
125-
return int(resource.status.availableReplicas) >= int(resource.observed.spec.replicas or 1)
165+
if resource.conditions.ReplicaFailure.reason:
166+
return resource.status.isReplicaFailure[resource.conditions.ReplicaFailure.reason]
167+
return resource.status.isReplicaFailure
168+
if int(resource.status.availableReplicas) < int(resource.observed.spec.replicas or 1):
169+
return resource.status.tooFewavailableReplicas
170+
return resource.observed.metadata.name
126171

127172
class Role(AlwaysReady):
128173
apiVersion = 'rbac.authorization.k8s.io/v1'
@@ -137,8 +182,10 @@ class Service(Check):
137182
apiVersion = 'v1'
138183
def ready(self, resource):
139184
if resource.observed.spec.type != 'LoadBalancer':
140-
return True
141-
return len(resource.status.loadBalancer.ingress) > 0
185+
return resource.observed.metadata.name
186+
if not len(resource.status.loadBalancer.ingress):
187+
return resource.status.noLoadBalancerIngresses
188+
return resource.observed.metadata.name
142189

143190
class ServiceAccount(AlwaysReady):
144191
apiVersion = 'v1'
@@ -147,7 +194,12 @@ class StatefulSet(Check):
147194
apiVersion = 'apps/v1'
148195
def ready(self, resource):
149196
replicas = resource.observed.spec.replicas or 1
150-
return (resource.status.readyReplicas == replicas and
151-
resource.status.currentReplicas == replicas and
152-
resource.status.currentRevision == resource.status.updateRevision
153-
)
197+
for field in ('readyReplicas', 'currentReplicas'):
198+
value = resource.status[field]
199+
if not value:
200+
return value
201+
if replicas != value:
202+
return resource.status[F"{field}NotReplicas"]
203+
if resource.status.currentRevision != resource.status.updateRevision:
204+
return resource.status.currentRevisionNotUpdateReivsion
205+
return resource.observed.metadata.name

0 commit comments

Comments
 (0)