This is an alpha feature. Crossplane may change or drop this feature at any time.

This feature was introduced in v2.

For more information read the Crossplane feature lifecycle.

This document is for an unreleased version of Crossplane.

This document applies to the Crossplane master branch and not to the latest release v1.20.

This guide shows how to use Crossplane Operations to automate day-two operational tasks. You create an Operation that checks SSL certificate expiry for a website.

Crossplane calls this Operations. Operations run function pipelines to perform tasks that don’t fit the typical resource creation pattern - like certificate monitoring, rolling upgrades, or scheduled maintenance.

An Operation looks like this:

 1apiVersion: ops.crossplane.io/v1alpha1
 2kind: Operation
 3metadata:
 4  name: check-cert-expiry
 5spec:
 6  mode: Pipeline
 7  pipeline:
 8  - step: check-certificate
 9    functionRef:
10      name: crossplane-contrib-function-python
11    input:
12      apiVersion: python.fn.crossplane.io/v1beta1
13      kind: Script
14      script: |
15        import ssl
16        import socket
17        from datetime import datetime
18
19        from crossplane.function import request, response
20
21        def operate(req, rsp):
22            hostname = "google.com"
23            port = 443
24
25            # Get SSL certificate info
26            context = ssl.create_default_context()
27            with socket.create_connection((hostname, port)) as sock:
28                with context.wrap_socket(sock, server_hostname=hostname) as ssock:
29                    cert = ssock.getpeercert()
30
31            # Parse expiration date
32            expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
33            days_until_expiry = (expiry_date - datetime.now()).days
34
35            # Return results in operation output
36            response.set_output(rsp, {
37                "hostname": hostname,
38                "certificateExpires": cert['notAfter'],
39                "daysUntilExpiry": days_until_expiry,
40                "status": "warning" if days_until_expiry < 30 else "ok"
41            })

The Operation runs once to completion, like a Kubernetes Job.

When you create the Operation, Crossplane runs the function pipeline. The function checks SSL certificate expiry for google.com and returns the results in the operation’s output.

This basic example shows the concept. In the walkthrough below, you create a more realistic Operation that reads Kubernetes Ingress resources and annotates them with certificate expiry information for monitoring tools.

Prerequisites

This guide requires:

Tip

Enable Operations by adding --enable-operations to Crossplane’s startup arguments. If using Helm:

1helm upgrade --install crossplane crossplane-stable/crossplane \
2  --namespace crossplane-system \
3  --set args='{"--enable-operations"}'

Create an operation

Follow these steps to create your first Operation:

  1. Create a sample Ingress for certificate checking
  2. Install the function you want to use for the operation
  3. Create the Operation that checks the Ingress
  4. Check the Operation as it runs

Create a sample Ingress

Create an Ingress that references a real hostname but doesn’t route actual traffic:

 1apiVersion: networking.k8s.io/v1
 2kind: Ingress
 3metadata:
 4  name: example-app
 5  namespace: default
 6spec:
 7  rules:
 8  - host: google.com
 9    http:
10      paths:
11      - path: /
12        pathType: Prefix
13        backend:
14          service:
15            name: nonexistent-service
16            port:
17              number: 80

Save as ingress.yaml and apply it:

1kubectl apply -f ingress.yaml

Grant Ingress permissions

Operations need permission to access and change Ingresses. Create a ClusterRole that grants Crossplane access to Ingresses:

 1apiVersion: rbac.authorization.k8s.io/v1
 2kind: ClusterRole
 3metadata:
 4  name: operations-ingress-access
 5  labels:
 6    rbac.crossplane.io/aggregate-to-crossplane: "true"
 7rules:
 8- apiGroups: ["networking.k8s.io"]
 9  resources: ["ingresses"]
10  verbs: ["get", "list", "watch", "patch", "update"]

Save as ingress-rbac.yaml and apply it:

1kubectl apply -f ingress-rbac.yaml

Install the function

Operations use operation functions to implement their logic. Use the Python function, which supports both composition and operations.

Create this function to install Python support:

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: crossplane-contrib-function-python
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0

Save the function as function.yaml and apply it:

1kubectl apply -f function.yaml

Check that Crossplane installed the function:

1kubectl get -f function.yaml
2NAME                                 INSTALLED   HEALTHY   PACKAGE                                                        AGE
3crossplane-contrib-function-python   True        True      xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0   12s

Create the operation

Create this Operation that monitors the Ingress certificate:

 1apiVersion: ops.crossplane.io/v1alpha1
 2kind: Operation
 3metadata:
 4  name: ingress-cert-monitor
 5spec:
 6  mode: Pipeline
 7  pipeline:
 8  - step: check-ingress-certificate
 9    functionRef:
10      name: crossplane-contrib-function-python
11    requirements:
12      requiredResources:
13      - requirementName: ingress
14        apiVersion: networking.k8s.io/v1
15        kind: Ingress
16        name: example-app
17        namespace: default
18    input:
19      apiVersion: python.fn.crossplane.io/v1beta1
20      kind: Script
21      script: |
22        import ssl
23        import socket
24        from datetime import datetime
25
26        from crossplane.function import request, response
27
28        def operate(req, rsp):
29            # Get the Ingress resource
30            ingress = request.get_required_resource(req, "ingress")
31            if not ingress:
32                response.set_output(rsp, {"error": "No ingress resource found"})
33                return
34
35            # Extract hostname from Ingress rules
36            hostname = ingress["spec"]["rules"][0]["host"]
37            port = 443
38
39            # Get SSL certificate info
40            context = ssl.create_default_context()
41            with socket.create_connection((hostname, port)) as sock:
42                with context.wrap_socket(sock, server_hostname=hostname) as ssock:
43                    cert = ssock.getpeercert()
44
45            # Parse expiration date
46            expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
47            days_until_expiry = (expiry_date - datetime.now()).days
48
49            # Add warning if certificate expires soon
50            if days_until_expiry < 30:
51                response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days")
52
53            # Annotate the Ingress with certificate expiry info
54            rsp.desired.resources["ingress"].resource.update({
55                "apiVersion": "networking.k8s.io/v1",
56                "kind": "Ingress",
57                "metadata": {
58                    "name": ingress["metadata"]["name"],
59                    "namespace": ingress["metadata"]["namespace"],
60                    "annotations": {
61                        "cert-monitor.crossplane.io/expires": cert['notAfter'],
62                        "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry),
63                        "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok"
64                    }
65                }
66            })
67
68            # Return results in operation output for monitoring
69            response.set_output(rsp, {
70                "ingressName": ingress["metadata"]["name"],
71                "hostname": hostname,
72                "certificateExpires": cert['notAfter'],
73                "daysUntilExpiry": days_until_expiry,
74                "status": "warning" if days_until_expiry < 30 else "ok"
75            })

Save the operation as operation.yaml and apply it:

1kubectl apply -f operation.yaml

Check the operation

Check that the Operation runs successfully:

1kubectl get -f operation.yaml
2NAME                   SYNCED   SUCCEEDED   AGE
3ingress-cert-monitor   True     True        15s
Tip
Operations show SUCCEEDED=True when they complete successfully.

Check the Operation’s detailed status:

 1kubectl describe operation ingress-cert-monitor
 2# ... metadata ...
 3Status:
 4  Conditions:
 5    Last Transition Time:  2024-01-15T10:30:15Z
 6    Reason:                PipelineSuccess
 7    Status:                True
 8    Type:                  Succeeded
 9    Last Transition Time:  2024-01-15T10:30:15Z
10    Reason:                ValidPipeline
11    Status:                True
12    Type:                  ValidPipeline
13  Pipeline:
14    Output:
15      Certificate Expires:   Sep 29 08:34:02 2025 GMT
16      Days Until Expiry:     54
17      Hostname:              google.com
18      Ingress Name:          example-app
19      Status:                ok
20    Step:                    check-ingress-certificate
Tip
The status.pipeline field shows the output returned by each function step. Use this field for tracking what the operation accomplished.

Check that the Operation annotated the Ingress with certificate information:

 1kubectl get ingress example-app -o yaml
 2apiVersion: networking.k8s.io/v1
 3kind: Ingress
 4metadata:
 5  annotations:
 6    cert-monitor.crossplane.io/days-until-expiry: "54"
 7    cert-monitor.crossplane.io/expires: Sep 29 08:34:02 2025 GMT
 8    cert-monitor.crossplane.io/status: ok
 9  name: example-app
10  namespace: default
11spec:
12  # ... ingress spec ...
Tip
This pattern shows how Operations can both read and change existing Kubernetes resources. The Operation annotated the Ingress with certificate expiry information that other tools can use for monitoring and alerting.

Clean up

Delete the resources you created:

1kubectl delete -f operation.yaml
2kubectl delete -f ingress.yaml
3kubectl delete -f ingress-rbac.yaml
4kubectl delete -f function.yaml

Next steps

Operations are powerful building blocks for operational workflows. Learn more about:

Explore the complete Operations documentation for advanced features and examples.