Skip to content

Commit 3c0cf4e

Browse files
committed
Rework Crossplane v2 handling
1 parent d2c28d1 commit 3c0cf4e

File tree

71 files changed

+2168
-135
lines changed

Some content is hidden

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

71 files changed

+2168
-135
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,4 @@ crossplane/pythonic/__version__.py
215215
pocs/
216216
pythonic-packages/
217217
tests/protobuf/pytest_pb2*
218+
scripts/.aws-credentials

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,39 @@ kind: Function
5757
metadata:
5858
name: function-pythonic
5959
spec:
60-
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.2.1
60+
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.3.0
6161
```
62+
63+
### Crossplane V1
64+
When running function-pythonic in Crossplane V1, the `--crossplane-v1` command line
65+
option should be specified. This requires using a Crossplane DeploymentRuntimeConfig.
66+
```yaml
67+
apiVersion: pkg.crossplane.io/v1
68+
kind: Function
69+
metadata:
70+
name: function-pythonic
71+
spec:
72+
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.3.0
73+
runtimeConfigRef:
74+
name: function-pythonic
75+
--
76+
apiVersion: pkg.crossplane.io/v1beta1
77+
kind: DeploymentRuntimeConfig
78+
metadata:
79+
name: function-pythonic
80+
spec:
81+
deploymentTemplate:
82+
spec:
83+
selector: {}
84+
template:
85+
spec:
86+
containers:
87+
- name: package-runtime
88+
args:
89+
- --debug
90+
- --crossplane-v1
91+
```
92+
6293
## Composed Resource Dependencies
6394

6495
function-pythonic automatically handles dependencies between composed resources.
@@ -204,9 +235,10 @@ The BaseComposite class provides the following fields for manipulating the Compo
204235
| self.status | Map | The composite desired and observed status, read from observed if not in desired |
205236
| self.conditions | Conditions | The composite desired and observed conditions, read from observed if not in desired |
206237
| self.results | Results | Returned results applied to the Composite and optionally on the Claim |
238+
| self.connectionSecret | Map | The name, namespace, and resourceName to use when generating the connection secret in Crossplane v2 |
207239
| self.connection | Map | The composite desired connection detials |
240+
| self.connection.observed | Map | The composite observed connection detials |
208241
| self.ready | Boolean | The composite desired ready state |
209-
| self.observed.connection | Map | The composite observed connection detials |
210242

211243
The BaseComposite also provides access to the following Crossplane Function level features:
212244

@@ -253,7 +285,7 @@ Resource class:
253285
| Resource.usages | Boolean | Generate Crossplane Usages for this resource, default is Composite.autoReady |
254286
| Resource.autoReady | Boolean | Perform auto ready processing on this resource, default is Composite.autoReady |
255287

256-
### Required Resources (AKA Extra Resources)
288+
### Required Resources
257289

258290
Creating and accessing required resources is performed using the `BaseComposite.requireds` field.
259291
`BaseComposite.requireds` is a dictionary of the required resources whose key is the required
@@ -523,7 +555,7 @@ kind: Function
523555
metadata:
524556
name: function-pythonic
525557
spec:
526-
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.2.1
558+
package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.3.0
527559
runtimeConfigRef:
528560
name: function-pythonic
529561
---

crossplane/pythonic/auto_ready.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
2+
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
10+
11+
12+
class ConditionReady:
13+
def ready(self, resource):
14+
return bool(resource.conditions.Ready.status)
15+
16+
_checks = {}
17+
_check_default = ConditionReady()
18+
19+
class Check:
20+
@classmethod
21+
def __init_subclass__(cls, **kwargs):
22+
super().__init_subclass__(**kwargs)
23+
if hasattr(cls, 'apiVersion'):
24+
_checks[(cls.apiVersion, cls.__name__)] = cls()
25+
26+
def ready(self, resource):
27+
raise NotImplementedError()
28+
29+
class AlwaysReady(Check):
30+
def ready(self, resource):
31+
return True
32+
33+
34+
class ClusterRole(AlwaysReady):
35+
apiVersion = 'rbac.authorization.k8s.io/v1'
36+
37+
class ClusterRoleBinding(AlwaysReady):
38+
apiVersion = 'rbac.authorization.k8s.io/v1'
39+
40+
class ConfigMap(AlwaysReady):
41+
apiVersion = 'v1'
42+
43+
class CronJob(Check):
44+
apiVersion = 'batch/v1'
45+
def ready(self, resource):
46+
if resource.observed.spec.suspend and len(resource.observed.spec.suspend):
47+
return True
48+
if not resource.status.lastScheduleTime:
49+
return False
50+
if resource.status.active:
51+
return True
52+
if not resource.status.lastSuccessfulTime:
53+
return False
54+
return str(resource.status.lastSuccessfulTime) >= str(resource.status.lastScheduleTime)
55+
56+
class DaemonSet(Check):
57+
apiVersion = 'apps/v1'
58+
def ready(self, resource):
59+
if not resource.status.desiredNumberScheduled:
60+
return False
61+
scheduled = resource.status.desiredNumberScheduled
62+
return (scheduled == resource.status.numberReady and
63+
scheduled == resource.status.updatedNumberScheduled and
64+
scheduled == resource.status.numberAvailable
65+
)
66+
67+
class Deployment(Check):
68+
apiVersion = 'apps/v1'
69+
def ready(self, resource):
70+
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)
74+
75+
class HorizontalPodAutoscaler(Check):
76+
apiVersion = 'autoscaling/v2'
77+
def ready(self, resource):
78+
for type in ('FailedGetScale', 'FailedUpdateScale', 'FailedGetResourceMetric', 'InvalidSelector'):
79+
if resource.conditions[type].status:
80+
return False
81+
for type in ('ScalingActive', 'ScalingLimited'):
82+
if resource.conditions[type].status:
83+
return True
84+
return False
85+
86+
class Ingress(Check):
87+
apiVersion = 'networking.k8s.io/v1'
88+
def ready(self, resource):
89+
return len(resource.status.loadBalancer.ingress) > 0
90+
91+
class Job(Check):
92+
apiVersion = 'batch/v1'
93+
def ready(self, resource):
94+
for type in ('Failed', 'Suspended'):
95+
if resource.conditions[type].status:
96+
return False
97+
return bool(resource.conditions.Complete.status)
98+
99+
class Namespace(AlwaysReady):
100+
apiVersion = 'v1'
101+
102+
class PersistentVolumeClaim(Check):
103+
apiVersion = 'v1'
104+
def ready(self, resource):
105+
return resource.status.phase == 'Bound'
106+
107+
class Pod(Check):
108+
apiVersion = 'v1'
109+
def ready(self, resource):
110+
if resource.status.phase == 'Succeeded':
111+
return True
112+
if resource.status.phase == 'Running':
113+
if resource.observed.spec.restartPolicy == 'Always':
114+
if resource.conditions.Ready.status:
115+
return True
116+
return False
117+
118+
class ReplicaSet(Check):
119+
apiVersion = 'v1'
120+
def ready(self, resource):
121+
if int(resource.status.observedGeneration) < int(resource.observed.metadata.generation):
122+
return False
123+
if resource.conditions.ReplicaFailure.status:
124+
return False
125+
return int(resource.status.availableReplicas) >= int(resource.observed.spec.replicas or 1)
126+
127+
class Role(AlwaysReady):
128+
apiVersion = 'rbac.authorization.k8s.io/v1'
129+
130+
class RoleBinding(AlwaysReady):
131+
apiVersion = 'rbac.authorization.k8s.io/v1'
132+
133+
class Secret(AlwaysReady):
134+
apiVersion = 'v1'
135+
136+
class Service(Check):
137+
apiVersion = 'v1'
138+
def ready(self, resource):
139+
if resource.observed.spec.type != 'LoadBalancer':
140+
return True
141+
return len(resource.status.loadBalancer.ingress) > 0
142+
143+
class ServiceAccount(AlwaysReady):
144+
apiVersion = 'v1'
145+
146+
class StatefulSet(Check):
147+
apiVersion = 'apps/v1'
148+
def ready(self, resource):
149+
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+
)

crossplane/pythonic/command.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ def add_function_arguments(cls, parser):
5050
action='store_true',
5151
help='Allow oversized protobuf messages',
5252
)
53+
parser.add_argument(
54+
'--crossplane-v1',
55+
action='store_true',
56+
help='Enable Crossplane V1 compatibility mode',
57+
)
5358

5459
def __init__(self, args):
5560
self.args = args

0 commit comments

Comments
 (0)