Skip to content

Commit 8a160bd

Browse files
authored
Merge pull request #160 from kaessert/tk/v2-standard-kube-resources
Add resource-specific health checks for standard Kubernetes resources
2 parents 6f30151 + 39fadec commit 8a160bd

35 files changed

+2456
-9
lines changed

README.md

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,67 @@ resources that are ready. It considers a composed resource ready if:
66

77
* Another function added the composed resource to the desired state.
88
* The composed resource appears in the observed state (i.e. it exists).
9-
* The composed resource has the status condition `type: Ready`, `status: True`.
9+
* **For standard Kubernetes resources** with health check implementations (see list below), the resource passes resource-specific health checks.
10+
* **For all other resources** (Crossplane managed resources, custom resources, etc.), the composed resource has the status condition `type: Ready`, `status: True`.
1011

1112
Crossplane considers a composite resource (XR) to be ready when all of its
1213
desired composed resources are ready.
1314

15+
## Health Checks
16+
17+
This function implements resource-specific health checks for standard Kubernetes resources. The following table shows the current implementation status:
18+
19+
### Core (core/v1)
20+
- [x] Pod - Succeeded, or Running with Ready condition (RestartPolicy: Always)
21+
- [x] Service - ClusterIP/NodePort: immediately ready; LoadBalancer: requires ingress assignment
22+
- [x] Namespace - Always ready if it exists
23+
- [ ] Node
24+
- [x] ConfigMap - Always ready if it exists
25+
- [x] Secret - Always ready if it exists
26+
- [x] ServiceAccount - Always ready if it exists
27+
- [ ] Endpoints
28+
- [ ] PersistentVolume
29+
- [x] PersistentVolumeClaim - Phase is Bound
30+
- [ ] ReplicationController
31+
- [ ] ResourceQuota
32+
- [ ] LimitRange
33+
- [ ] Event
34+
35+
### Apps (apps/v1)
36+
- [x] Deployment - `spec.replicas == status.availableReplicas`, all replicas updated, `Available` condition is `True`
37+
- [x] StatefulSet - `spec.replicas == status.readyReplicas`, all replicas at current revision
38+
- [x] DaemonSet - All desired pods are scheduled, ready, updated, and available
39+
- [x] ReplicaSet - Observed generation matches, available replicas match desired, no replica failures
40+
41+
### Batch (batch/v1)
42+
- [x] Job - Complete condition is True (not Failed or Suspended)
43+
- [x] CronJob - Suspended, has active jobs, or last execution succeeded
44+
45+
### Autoscaling (autoscaling/v2)
46+
- [x] HorizontalPodAutoscaler - ScalingActive or ScalingLimited, no failed conditions
47+
48+
### Networking (networking.k8s.io/v1)
49+
- [x] Ingress - Load balancer ingress is assigned
50+
- [ ] IngressClass
51+
- [ ] NetworkPolicy
52+
53+
### RBAC (rbac.authorization.k8s.io/v1)
54+
- [ ] Role
55+
- [ ] ClusterRole
56+
- [ ] RoleBinding
57+
- [ ] ClusterRoleBinding
58+
59+
### Storage (storage.k8s.io/v1)
60+
- [ ] StorageClass
61+
- [ ] VolumeAttachment
62+
- [ ] CSIDriver
63+
- [ ] CSINode
64+
65+
### Policy (policy/v1)
66+
- [ ] PodDisruptionBudget
67+
68+
For all other resource types (Crossplane managed resources, custom resources, etc.), the function falls back to checking the standard Ready status condition.
69+
1470
In this example, the [Go Templating][fn-go-templating] function is used to add
1571
a desired composed resource - an Amazon Web Services S3 Bucket. Once Crossplane
1672
has created the Bucket, the Auto Ready function will let Crossplane know when it
@@ -54,11 +110,17 @@ See the [example](example) directory for an example you can run locally using
54110
the Crossplane CLI:
55111
56112
```shell
57-
$ crossplane beta render xr.yaml composition.yaml functions.yaml
113+
$ crossplane render xr.yaml composition.yaml functions.yaml
114+
```
115+
116+
To test with observed resources (simulating resources that already exist):
117+
118+
```shell
119+
$ crossplane render xr.yaml composition-k8s.yaml functions.yaml -o observed-k8s.yaml
58120
```
59121

60122
See the [composition functions documentation][docs-functions] to learn more
61-
about `crossplane beta render`.
123+
about `crossplane render`.
62124

63125
## Developing this function
64126

example/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Example: Testing function-auto-ready with Kubernetes Resources
2+
3+
This example demonstrates how the function-auto-ready composition function automatically detects readiness for standard Kubernetes resources using resource-specific health checks.
4+
5+
## Files
6+
7+
- **composition-k8s.yaml**: A composition that creates three Kubernetes resources (Service, Deployment, ConfigMap) and uses function-auto-ready to detect their readiness
8+
- **xr.yaml**: A simple composite resource (XR) instance to render the composition
9+
- **functions.yaml**: Function definitions required by the Crossplane CLI
10+
- **observed-k8s.yaml**: Simulated observed resources with status fields, used to test readiness detection with resources that already exist
11+
12+
## What This Example Demonstrates
13+
14+
The composition creates three Kubernetes resources:
15+
- **Service** (ClusterIP type) - Ready immediately since ClusterIP services don't require external provisioning
16+
- **Deployment** (3 replicas, nginx) - Ready when all replicas are available and the Available condition is True
17+
- **ConfigMap** - Ready immediately if it exists (no status conditions to check)
18+
19+
The function-auto-ready function automatically applies resource-specific health checks to determine when each resource is ready.
20+
21+
## Testing Locally
22+
23+
### Basic Rendering (Without Observed Resources)
24+
25+
To see what resources the composition creates:
26+
27+
```shell
28+
crossplane render xr.yaml composition-k8s.yaml functions.yaml
29+
```
30+
31+
This renders the desired state. Resources will not be marked as ready because they don't exist in the observed state yet.
32+
33+
### Rendering with Observed Resources
34+
35+
To simulate resources that already exist in a cluster with their status fields populated:
36+
37+
```shell
38+
crossplane render xr.yaml composition-k8s.yaml functions.yaml -o observed-k8s.yaml
39+
```
40+
41+
This simulates the function behavior after Kubernetes has created the resources. The observed-k8s.yaml file contains:
42+
- Service with empty status (ready immediately)
43+
- Deployment with full status showing 3/3 replicas available and Available condition True (ready)
44+
- ConfigMap with no status (ready immediately)
45+
46+
### Expected Output
47+
48+
When rendered with observed resources, you should see:
49+
- All three composed resources in the desired state
50+
- Each resource marked with `Ready: True` in their annotations
51+
- The composite resource (XR) marked as ready because all composed resources are ready
52+
53+
## See Also
54+
55+
For a simpler example using Crossplane managed resources (AWS S3 Bucket), see the main README.md file in the repository root.

example/composition-k8s.yaml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
apiVersion: apiextensions.crossplane.io/v1
2+
kind: Composition
3+
metadata:
4+
name: example-k8s-resources
5+
spec:
6+
compositeTypeRef:
7+
apiVersion: example.crossplane.io/v1
8+
kind: XR
9+
mode: Pipeline
10+
pipeline:
11+
- step: create-k8s-resources
12+
functionRef:
13+
name: function-go-templating
14+
input:
15+
apiVersion: gotemplating.fn.crossplane.io/v1beta1
16+
kind: GoTemplate
17+
source: Inline
18+
inline:
19+
template: |
20+
---
21+
apiVersion: v1
22+
kind: Service
23+
metadata:
24+
name: example-service
25+
annotations:
26+
gotemplating.fn.crossplane.io/composition-resource-name: service
27+
spec:
28+
type: ClusterIP
29+
selector:
30+
app: example
31+
ports:
32+
- port: 80
33+
targetPort: 8080
34+
---
35+
apiVersion: apps/v1
36+
kind: Deployment
37+
metadata:
38+
name: example-deployment
39+
annotations:
40+
gotemplating.fn.crossplane.io/composition-resource-name: deployment
41+
spec:
42+
replicas: 3
43+
selector:
44+
matchLabels:
45+
app: example
46+
template:
47+
metadata:
48+
labels:
49+
app: example
50+
spec:
51+
containers:
52+
- name: app
53+
image: nginx:latest
54+
ports:
55+
- containerPort: 8080
56+
---
57+
apiVersion: v1
58+
kind: ConfigMap
59+
metadata:
60+
name: example-config
61+
annotations:
62+
gotemplating.fn.crossplane.io/composition-resource-name: config
63+
data:
64+
config.yaml: |
65+
key: value
66+
# This function will automatically detect readiness using resource-specific health checks
67+
- step: automatically-detect-ready-composed-resources
68+
functionRef:
69+
name: function-auto-ready

example/observed-k8s.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# This file simulates observed resources as they would appear in a running cluster
2+
# Use this with: crossplane render xr.yaml composition-k8s.yaml functions.yaml -o observed-k8s.yaml
3+
---
4+
apiVersion: v1
5+
kind: Service
6+
metadata:
7+
name: example-service
8+
annotations:
9+
crossplane.io/composition-resource-name: service
10+
spec:
11+
type: ClusterIP
12+
selector:
13+
app: example
14+
ports:
15+
- port: 80
16+
targetPort: 8080
17+
status: {}
18+
---
19+
apiVersion: apps/v1
20+
kind: Deployment
21+
metadata:
22+
name: example-deployment
23+
annotations:
24+
crossplane.io/composition-resource-name: deployment
25+
spec:
26+
replicas: 3
27+
selector:
28+
matchLabels:
29+
app: example
30+
template:
31+
metadata:
32+
labels:
33+
app: example
34+
spec:
35+
containers:
36+
- name: app
37+
image: nginx:latest
38+
ports:
39+
- containerPort: 8080
40+
status:
41+
replicas: 3
42+
updatedReplicas: 3
43+
availableReplicas: 3
44+
readyReplicas: 3
45+
conditions:
46+
- type: Available
47+
status: "True"
48+
reason: MinimumReplicasAvailable
49+
message: Deployment has minimum availability
50+
- type: Progressing
51+
status: "True"
52+
reason: NewReplicaSetAvailable
53+
message: ReplicaSet has successfully progressed
54+
---
55+
apiVersion: v1
56+
kind: ConfigMap
57+
metadata:
58+
name: example-config
59+
annotations:
60+
crossplane.io/composition-resource-name: config
61+
data:
62+
config.yaml: |
63+
key: value

fn.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/crossplane/function-sdk-go/response"
1414

1515
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
16+
17+
"github.com/crossplane/function-auto-ready/healthchecks"
1618
)
1719

1820
// Function returns whatever response you ask it to.
@@ -53,8 +55,35 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
5355

5456
f.log.Debug("Found desired resources", "count", len(desired))
5557

56-
// Our goal here is to automatically determine (from the Ready status
57-
// condition) whether existing composed resources are ready.
58+
// First, mark standard Kubernetes resources as ready using resource-specific health checks
59+
for name, dr := range desired {
60+
log := log.WithValues("composed-resource-name", name)
61+
62+
// Skip if resource doesn't exist yet
63+
or, ok := observed[name]
64+
if !ok {
65+
continue
66+
}
67+
68+
// Skip if readiness already explicitly set
69+
if dr.Ready != resource.ReadyUnspecified {
70+
continue
71+
}
72+
73+
// Check if this resource type has a registered health check
74+
// Get GVK from the unstructured object (apiVersion and kind fields)
75+
// composed.Unstructured embeds unstructured.Unstructured, so we can use it directly
76+
gvk := or.Resource.GroupVersionKind()
77+
if healthCheck := healthchecks.GetHealthCheck(gvk); healthCheck != nil {
78+
log.Debug("Using resource-specific health check", "gvk", gvk.String())
79+
if healthCheck(&or.Resource.Unstructured) {
80+
log.Info("Marked resource as ready via resource-specific health check", "gvk", gvk.String())
81+
dr.Ready = resource.ReadyTrue
82+
}
83+
}
84+
}
85+
86+
// Second, check remaining resources using the Ready status condition
5887
for name, dr := range desired {
5988
log := log.WithValues("composed-resource-name", name)
6089

0 commit comments

Comments
 (0)