From bfff85f700e4fb776b1cd6051d3b77d2941ceabe Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Wed, 6 Aug 2025 13:48:18 -0700 Subject: [PATCH 01/15] Add ops to "What's Crossplane?" Feels like it belongs here as a top-level tool, despite being alpha. Signed-off-by: Nic Cope --- content/master/whats-crossplane/_index.md | 61 ++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/content/master/whats-crossplane/_index.md b/content/master/whats-crossplane/_index.md index 78952bb1..f60de526 100644 --- a/content/master/whats-crossplane/_index.md +++ b/content/master/whats-crossplane/_index.md @@ -46,13 +46,14 @@ involved in writing a controller. ## Crossplane components -Crossplane has three major components: +Crossplane has four major components: * [Composition](#composition) * [Managed resources](#managed-resources) +* [Operations](#operations) * [Package manager](#package-manager) -You can use all three components to build your control plane, or pick only the +You can use all four components to build your control plane, or pick only the ones you need. ### Composition @@ -223,6 +224,62 @@ GCP, Terraform, Helm, GitHub, etc to support Crossplane v2 soon. {{}} +### Operations + +Operations let you run operational tasks using function pipelines. + +While composition and managed resources focus on creating and managing +infrastructure, operations handle tasks that don't fit the typical resource +creation pattern - like certificate monitoring, rolling upgrades, or scheduled +maintenance. + +**Operations run function pipelines to completion like a Kubernetes Job.** +Instead of continuously managing resources, they perform specific tasks and +report the results. + +```mermaid +flowchart TD +user(User) + +subgraph control [Control Plane] + operation(SSL Monitor Operation API) + + subgraph crossplane [Operation Engine] + fn(Python Function) + end + + ingress(Ingress API) +end + +subgraph ext [External System] + cert(SSL Certificate) +end + +user -- create --> operation +crossplane watch@<-- watch --> operation +crossplane -- read --> ingress +crossplane -- check --> cert +crossplane -- annotate --> ingress + +watch@{animate: true} +``` + +Operations support three modes: + +* **Operation** - Run once to completion +* **CronOperation** - Run on a scheduled basis +* **WatchOperation** - Run when resources change + +You can use operations alongside composition and managed resources to build +complete operational workflows for your control plane. + +Follow [Get Started with Operations]({{}}) +to see how operations work. + +{{}} +Operations are an alpha feature available in Crossplane v2. +{{}} + ### Package manager The Crossplane package manager lets you install new managed resources and From bb454e8c5a82b797740c8731f0f47a68dd7c2093 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Wed, 6 Aug 2025 13:52:07 -0700 Subject: [PATCH 02/15] Add Operations to "What's new" page. Signed-off-by: Nic Cope --- content/master/whats-new/_index.md | 45 ++++++++++++++++++- .../styles/Crossplane/spelling-exceptions.txt | 1 + 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/content/master/whats-new/_index.md b/content/master/whats-new/_index.md index 8237297f..149e92da 100644 --- a/content/master/whats-new/_index.md +++ b/content/master/whats-new/_index.md @@ -6,11 +6,12 @@ description: Learn what's new in the Crossplane v2 preview **Crossplane v2 makes Crossplane more useful, more intuitive, and less opinionated.** -Crossplane v2 makes three major changes: +Crossplane v2 makes four major changes: * **Composite resources are now namespaced** * **Managed resources are now namespaced** * **Composition supports any Kubernetes resource** +* **Operations enable operational workflows** **Crossplane v2 is better suited to building control planes for applications, not just infrastructure.** It removes the need for awkward abstractions like @@ -214,6 +215,48 @@ resources like MRs or XRs. Read to learn how to grant Crossplane access. {{}} +## Operations enable operational workflows + +Crossplane v2 introduces Operations - a new way to run operational tasks using +function pipelines. + +Operations handle tasks that don't fit the typical resource creation pattern. +Things like certificate monitoring, rolling upgrades, scheduled maintenance, or +responding to resource changes. + +**Operations run function pipelines to completion, like a Kubernetes Job.** +Instead of continuously managing resources, they perform specific tasks and +report the results. + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: CronOperation +metadata: + name: cert-monitor +spec: + schedule: "0 6 * * *" # Daily at 6 AM + mode: Pipeline + pipeline: + - step: check-certificates + functionRef: + name: crossplane-contrib-function-python + # function checks SSL certificates and reports status +``` + +Operations support three modes: + +* **Operation** - Run once to completion +* **CronOperation** - Run on a scheduled basis +* **WatchOperation** - Run when resources change + +Operations can read existing resources and optionally change them. This enables +workflows like annotating resources with operational data, triggering +maintenance tasks, or implementing custom operational policies. + +{{}} +Operations are an alpha feature in Crossplane v2. +{{}} + ## Backward compatibility Crossplane v2 makes the following breaking changes: diff --git a/utils/vale/styles/Crossplane/spelling-exceptions.txt b/utils/vale/styles/Crossplane/spelling-exceptions.txt index 7e5f4dfc..7fa28941 100644 --- a/utils/vale/styles/Crossplane/spelling-exceptions.txt +++ b/utils/vale/styles/Crossplane/spelling-exceptions.txt @@ -56,4 +56,5 @@ UpperCamelCased user-defined v2 version-specific +Job. backporting \ No newline at end of file From 4c36f9049ccdcca091127cc12d00b7f462fea772 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Tue, 5 Aug 2025 23:36:11 -0700 Subject: [PATCH 03/15] First pass at Operations docs Signed-off-by: Nic Cope --- content/master/_index.md | 2 + .../get-started-with-operations.md | 367 ++++++++++++ content/master/operations/_index.md | 7 + content/master/operations/cronoperation.md | 345 +++++++++++ content/master/operations/operation.md | 500 +++++++++++++++ content/master/operations/watchoperation.md | 567 ++++++++++++++++++ .../vale/styles/Crossplane/allowed-jargon.txt | 6 + .../styles/Crossplane/crossplane-words.txt | 12 + .../styles/Crossplane/spelling-exceptions.txt | 27 + 9 files changed, 1833 insertions(+) create mode 100644 content/master/get-started/get-started-with-operations.md create mode 100644 content/master/operations/_index.md create mode 100644 content/master/operations/cronoperation.md create mode 100644 content/master/operations/operation.md create mode 100644 content/master/operations/watchoperation.md diff --git a/content/master/_index.md b/content/master/_index.md index 00a6c950..7d6d54a1 100644 --- a/content/master/_index.md +++ b/content/master/_index.md @@ -23,6 +23,8 @@ Crossplane organizes its documentation into the following sections: * [Composition]({{}}) covers the key concepts of composition. +* [Operations]({{}}) covers the key concepts of operations. + * [Managed Resources]({{}}) covers the key concepts of managed resources. diff --git a/content/master/get-started/get-started-with-operations.md b/content/master/get-started/get-started-with-operations.md new file mode 100644 index 00000000..cf3d953d --- /dev/null +++ b/content/master/get-started/get-started-with-operations.md @@ -0,0 +1,367 @@ +--- +title: Get Started With Operations +weight: 300 +state: alpha +alphaVersion: 2.0 +--- + +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: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: check-cert-expiry +spec: + mode: Pipeline + pipeline: + - step: check-certificate + functionRef: + name: crossplane-contrib-function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + import ssl + import socket + from datetime import datetime + + from crossplane.function import request, response + + def operate(req, rsp): + hostname = "google.com" + port = 443 + + # Get SSL certificate info + context = ssl.create_default_context() + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + cert = ssock.getpeercert() + + # Parse expiration date + expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') + days_until_expiry = (expiry_date - datetime.now()).days + + # Return results in operation output + response.set_output(rsp, { + "hostname": hostname, + "certificateExpires": cert['notAfter'], + "daysUntilExpiry": days_until_expiry, + "status": "warning" if days_until_expiry < 30 else "ok" + }) +``` + + +**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: + +* A Kubernetes cluster with at least 2 GB of RAM +* The Crossplane v2 preview [installed on the Kubernetes cluster]({{}}) with Operations enabled + +{{}} +Enable Operations by adding `--enable-operations` to Crossplane's startup +arguments. If using Helm: + +```shell +helm upgrade --install crossplane crossplane-stable/crossplane \ + --namespace crossplane-system \ + --set args='{"--enable-operations"}' +``` +{{}} + +## Create an operation + +Follow these steps to create your first Operation: + +1. [Create a sample Ingress](#create-a-sample-ingress) for certificate checking +1. [Install the function](#install-the-function) you want to use for the + operation +1. [Create the Operation](#create-the-operation) that checks the Ingress +1. [Check the Operation](#check-the-operation) as it runs + +### Create a sample Ingress + +Create an Ingress that references a real hostname but doesn't route actual +traffic: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example-app + namespace: default +spec: + rules: + - host: google.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nonexistent-service + port: + number: 80 +``` + +Save as `ingress.yaml` and apply it: + +```shell +kubectl apply -f ingress.yaml +``` + +### Grant Ingress permissions + +Operations need permission to access and change Ingresses. Create a ClusterRole +that grants Crossplane access to Ingresses: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operations-ingress-access + labels: + rbac.crossplane.io/aggregate-to-crossplane: "true" +rules: +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "patch", "update"] +``` + +Save as `ingress-rbac.yaml` and apply it: + +```shell +kubectl 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: + +```yaml +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 +``` + +Save the function as `function.yaml` and apply it: + +```shell +kubectl apply -f function.yaml +``` + +Check that Crossplane installed the function: + +```shell {copy-lines="1"} +kubectl get -f function.yaml +NAME INSTALLED HEALTHY PACKAGE AGE +crossplane-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: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: ingress-cert-monitor +spec: + mode: Pipeline + pipeline: + - step: check-ingress-certificate + functionRef: + name: crossplane-contrib-function-python + requirements: + requiredResources: + - requirementName: ingress + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: example-app + namespace: default + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + import ssl + import socket + from datetime import datetime + + from crossplane.function import request, response + + def operate(req, rsp): + # Get the Ingress resource + ingress = request.get_required_resource(req, "ingress") + if not ingress: + response.set_output(rsp, {"error": "No ingress resource found"}) + return + + # Extract hostname from Ingress rules + hostname = ingress["spec"]["rules"][0]["host"] + port = 443 + + # Get SSL certificate info + context = ssl.create_default_context() + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + cert = ssock.getpeercert() + + # Parse expiration date + expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') + days_until_expiry = (expiry_date - datetime.now()).days + + # Add warning if certificate expires soon + if days_until_expiry < 30: + response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") + + # Annotate the Ingress with certificate expiry info + rsp.desired.resources["ingress"].resource.update({ + "apiVersion": "networking.k8s.io/v1", + "kind": "Ingress", + "metadata": { + "name": ingress["metadata"]["name"], + "namespace": ingress["metadata"]["namespace"], + "annotations": { + "cert-monitor.crossplane.io/expires": cert['notAfter'], + "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), + "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" + } + } + }) + + # Return results in operation output for monitoring + response.set_output(rsp, { + "ingressName": ingress["metadata"]["name"], + "hostname": hostname, + "certificateExpires": cert['notAfter'], + "daysUntilExpiry": days_until_expiry, + "status": "warning" if days_until_expiry < 30 else "ok" + }) +``` + + +Save the operation as `operation.yaml` and apply it: + +```shell +kubectl apply -f operation.yaml +``` + +### Check the operation + +Check that the Operation runs successfully: + +```shell {copy-lines="1"} +kubectl get -f operation.yaml +NAME SYNCED SUCCEEDED AGE +ingress-cert-monitor True True 15s +``` + +{{}} +Operations show `SUCCEEDED=True` when they complete successfully. +{{}} + +Check the Operation's detailed status: + +```shell {copy-lines="1"} +kubectl describe operation ingress-cert-monitor +# ... metadata ... +Status: + Conditions: + Last Transition Time: 2024-01-15T10:30:15Z + Reason: PipelineSuccess + Status: True + Type: Succeeded + Last Transition Time: 2024-01-15T10:30:15Z + Reason: ValidPipeline + Status: True + Type: ValidPipeline + Pipeline: + Output: + Certificate Expires: Sep 29 08:34:02 2025 GMT + Days Until Expiry: 54 + Hostname: google.com + Ingress Name: example-app + Status: ok + Step: check-ingress-certificate +``` + +{{}} +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: + +```shell {copy-lines="1"} +kubectl get ingress example-app -o yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-monitor.crossplane.io/days-until-expiry: "54" + cert-monitor.crossplane.io/expires: Sep 29 08:34:02 2025 GMT + cert-monitor.crossplane.io/status: ok + name: example-app + namespace: default +spec: + # ... ingress spec ... +``` + +{{}} +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: + +```shell +kubectl delete -f operation.yaml +kubectl delete -f ingress.yaml +kubectl delete -f ingress-rbac.yaml +kubectl delete -f function.yaml +``` + +## Next steps + +Operations are powerful building blocks for operational workflows. Learn more +about: + +* [**Operation concepts**]({{}}) - Core + Operation features and best practices +* [**CronOperation**]({{}}) - Schedule + operations to run automatically +* [**WatchOperation**]({{}}) - Trigger + operations when resources change + +Explore the complete [Operations documentation]({{}}) for +advanced features and examples. diff --git a/content/master/operations/_index.md b/content/master/operations/_index.md new file mode 100644 index 00000000..a69146ca --- /dev/null +++ b/content/master/operations/_index.md @@ -0,0 +1,7 @@ +--- +title: Operations +weight: 52 +state: alpha +alphaVersion: v2.0-preview +description: Understand Crossplane's Operations feature +--- diff --git a/content/master/operations/cronoperation.md b/content/master/operations/cronoperation.md new file mode 100644 index 00000000..47de4c6d --- /dev/null +++ b/content/master/operations/cronoperation.md @@ -0,0 +1,345 @@ +--- +title: CronOperation +weight: 120 +state: alpha +alphaVersion: 2.0 +description: CronOperations create Operations on a schedule for recurring tasks +--- + +A `CronOperation` creates [Operations]({{}}) on a schedule, +like Kubernetes CronJobs. Use CronOperations for recurring operational tasks +such as database backups, certificate rotation, or periodic maintenance. + + +## How CronOperations work + + +CronOperations contain a template for an Operation and create new Operations +based on a cron schedule. Each scheduled run creates a new Operation that +executes once to completion. + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: CronOperation +metadata: + name: daily-backup +spec: + schedule: "0 2 * * *" # Daily at 2 AM + concurrencyPolicy: Forbid + successfulHistoryLimit: 5 + failedHistoryLimit: 3 + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: backup + functionRef: + name: function-database-backup + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: DatabaseBackupInput + retentionDays: 7 +``` + +{{}} +CronOperations are an alpha feature. You must enable Operations by adding +`--enable-operations` to Crossplane's arguments. +{{}} + +## Key features + +- **Standard cron scheduling syntax** - Uses the same format as Kubernetes CronJobs +- **Configurable concurrency policies** (Allow, Forbid, Replace) +- **Automatic cleanup of old Operations** - Maintains history limits +- **Tracks run history and running operations** - Provides visibility into scheduled runs + +## Scheduling + +CronOperations use standard cron syntax: + +``` +┌───────────── minute (0 - 59) +│ ┌───────────── hour (0 - 23) +│ │ ┌───────────── day of the month (1 - 31) +│ │ │ ┌───────────── month (1 - 12) +│ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday) +│ │ │ │ │ +│ │ │ │ │ +* * * * * +``` + +**Common schedule examples:** +- `"0 2 * * *"` - Every day at 2:00 AM +- `"0 0 * * 0"` - Every Sunday at midnight +- `"0 0 1 * *"` - Every month on the first at midnight +- `"*/15 * * * *"` - Every 15 minutes + +## Concurrency policies + +CronOperations support three concurrency policies: + +- **Allow (default)**: Multiple Operations can run simultaneously. Use this + when operations don't interfere with each other. +- **Forbid**: New Operations don't start if previous ones are still running. + Use this for operations that can't run concurrently. +- **Replace**: New Operations stop running ones before starting. Use this + when you always want the latest operation to run. + +## History management + +Control the number of completed Operations to keep: + +```yaml +spec: + successfulHistoryLimit: 5 # Keep 5 successful operations + failedHistoryLimit: 3 # Keep 3 failed operations for debugging +``` + +This helps balance debugging capabilities with resource usage. + +## Common use cases + +{{}} +The following examples use hypothetical functions for illustration. At launch, +only function-python supports operations. +{{}} + +### Scheduled database backups + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: CronOperation +metadata: + name: postgres-backup +spec: + schedule: "0 3 * * *" # Daily at 3 AM + concurrencyPolicy: Forbid # Don't allow overlapping backups + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: backup + functionRef: + name: function-postgres-backup + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: PostgresBackupInput + instance: production-db + s3Bucket: db-backups +``` + +### Scheduled maintenance + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: CronOperation +metadata: + name: weekly-maintenance +spec: + schedule: "0 3 * * 0" # Weekly on Sunday at 3 AM + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: cleanup-logs + functionRef: + name: function-log-cleanup + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: LogCleanupInput + retentionDays: 30 + - step: update-certificates + functionRef: + name: function-cert-renewal +``` + +### Periodic health checks + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: CronOperation +metadata: + name: health-check +spec: + schedule: "*/30 * * * *" # Every 30 minutes + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: check-cluster-health + functionRef: + name: function-health-check + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: HealthCheckInput + alertThreshold: 80 +``` + +## Advanced configuration + +### Complex scheduling patterns + +Advanced cron schedule examples for specific use cases: + +```yaml +# Weekdays only at 9 AM (Monday-Friday) +schedule: "0 9 * * 1-5" + +# Every 4 hours during business days +schedule: "0 8,12,16 * * 1-5" + +# First and last day of each month +schedule: "0 2 1,L * *" + +# Every quarter (1st of Jan, Apr, Jul, Oct) +schedule: "0 2 1 1,4,7,10 *" + +# Business hours only, every 2 hours +schedule: "0 9-17/2 * * 1-5" +``` + +### Starting deadline + +CronOperations support a `startingDeadlineSeconds` field that controls how +long to wait after the scheduled time before considering it too late to +create the Operation: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: CronOperation +metadata: + name: deadline-example +spec: + schedule: "0 9 * * 1-5" # Weekdays at 9 AM + startingDeadlineSeconds: 900 # 15 minutes + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: morning-tasks + functionRef: + name: function-morning-tasks +``` + +If the Operation can't start in 15 minutes of 9 AM (due to +controller downtime, resource constraints, etc.), the scheduled run is +skipped. + +Skip operations for: +- **Time-sensitive operations** - Skip operations that become meaningless if delayed +- **Resource protection** - Prevent backup Operations piling up during outages +- **SLA compliance** - Ensure operations run in acceptable time windows + +### Time zone considerations + +{{}} +CronOperations use the cluster's local time zone, same as Kubernetes CronJobs. +To ensure consistent scheduling across different environments, consider: + +1. **Standardize cluster time zones** - Use UTC in production clusters +2. **Document time zone assumptions** - Note expected time zone in comments +3. **Account for DST changes** - Be aware that some schedules may skip or repeat during transitions +{{}} + +## Status and monitoring + +CronOperations provide status information about scheduling: + +```yaml +status: + conditions: + - type: Synced + status: "True" + reason: ReconcileSuccess + - type: Scheduling + status: "True" + reason: ScheduleActive + lastScheduleTime: "2024-01-15T10:00:00Z" + lastSuccessfulTime: "2024-01-15T10:02:30Z" + runningOperationRefs: + - name: daily-backup-1705305600 +``` + +**Key status fields:** +- **Conditions**: Standard Crossplane conditions (Synced) and CronOperation-specific conditions: + - **Scheduling**: `True` when the CronOperation is actively scheduling operations, `False` when paused or has incorrect schedule syntax +- **`lastScheduleTime`**: When the CronOperation last created an Operation +- **`lastSuccessfulTime`**: When an Operation last completed successfully +- **`runningOperationRefs`**: Running Operations + +### Events + +CronOperations emit events for important activities: +- `CreateOperation` (Warning) - Scheduled operation creation failures +- `GarbageCollectOperations` (Warning) - Garbage collection failures +- `ReplaceRunningOperation` (Warning) - Running operation deletion failures +- `InvalidSchedule` (Warning) - Cron schedule parsing errors + + +### Monitoring + + + +Monitor CronOperations using: + + +```shell +# Check CronOperation status +kubectl get cronoperation my-cronop + +# View recent Operations created by the CronOperation +kubectl get operations -l crossplane.io/cronoperation=my-cronop + +# Check events +kubectl get events --field-selector involvedObject.name=my-cronop +``` + +## Best practices + +### Scheduling considerations + +1. **Consider time zones** - CronOperations use the host's local time + (same as Kubernetes CronJobs) +1. **Plan for long-running operations** - Ensure operations complete before + next scheduled run +1. **Set reasonable history limits** - Balance debugging needs with cluster + resource usage + +### Concurrency policies + +1. **Choose appropriate concurrency policies**: + - **Forbid** for backups, maintenance, or operations that must complete + alone + - **Replace** for health checks or monitoring where latest data is most + important + - **Allow** for independent tasks that can run simultaneously + +For general Operations best practices including function development and +operational considerations, see [Operation best practices]({{}}). + +## Troubleshooting + + +### CronOperation not creating Operations + + +1. Check the cron schedule syntax +1. Verify the CronOperation has `Synced=True` condition +1. Look for events indicating schedule parsing errors + +### Operations failing often + +1. Check Operation events and logs +1. Verify function capabilities include `operation` +1. Review retry limits and adjust as needed + +### Resource cleanup issues + +1. Verify you set history limits appropriately +1. Check for events about garbage collection failures + +## Next steps + +- Learn about [Operation]({{}}) for one-time operational tasks +- Learn about [WatchOperation]({{}}) for reactive operations +- [Get started with Operations]({{}}) to try scheduling your first operation \ No newline at end of file diff --git a/content/master/operations/operation.md b/content/master/operations/operation.md new file mode 100644 index 00000000..6c615b2f --- /dev/null +++ b/content/master/operations/operation.md @@ -0,0 +1,500 @@ +--- +title: Operation +weight: 110 +state: alpha +alphaVersion: 2.0 +description: Operations run function pipelines once to completion for operational tasks +--- + +An `Operation` runs a function pipeline once to completion to perform operational +tasks that don't fit the typical resource creation pattern. Unlike compositions +that continuously reconcile desired state, Operations focus on tasks like +backups, rolling upgrades, configuration validation, and scheduled maintenance. + +## How operations work + +Operations are like Kubernetes Jobs - they run once to completion rather than +continuously reconciling. Like compositions, Operations use function pipelines +to implement their logic, but they're designed for operational workflows +instead of resource composition. + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: backup-database +spec: + mode: Pipeline + pipeline: + - step: create-backup + functionRef: + name: function-database-backup + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: DatabaseBackupInput + database: production-db + retentionDays: 30 +``` + +When you create this Operation, Crossplane: + +1. **Validates** the operation and its function dependencies +2. **Executes** the function pipeline step by step +3. **Applies** any resources the functions create or change +4. **Updates** the Operation status with results and completion state + +{{}} +Operations are an alpha feature. You must enable them by adding +`--enable-operations` to Crossplane's arguments. +{{}} + +## Key characteristics + +- **Runs once to completion** (like Kubernetes Jobs) +- **Uses function pipelines** (like Compositions) +- **Can create or change any Kubernetes resources** +- **Provides detailed status and output from each step** +- **Supports retry on failure with configurable limits** + +## Operation functions vs composition functions + +Operations and compositions both use function pipelines, but with important +differences: + +**Composition Functions:** +- **Purpose**: Create and maintain resources +- **Lifecycle**: Continuous reconciliation +- **Input**: Observed composite resources +- **Output**: Desired composed resources +- **Ownership**: Creates owner references + +**Operation Functions:** +- **Purpose**: Perform operational tasks +- **Lifecycle**: Run once to completion +- **Input**: Required resources only +- **Output**: Any Kubernetes resources +- **Ownership**: Force applies without owners + +Functions can support both modes by declaring the appropriate capabilities in +their package metadata. Function authors declare this in the `crossplane.yaml` +file when building the function package: + +```yaml +apiVersion: meta.pkg.crossplane.io/v1 +kind: Function +metadata: + name: my-function +spec: + capabilities: + - composition + - operation +``` + +This allows Crossplane to know which modes the function supports and avoid +trying to use a composition-only function for operations. + +## Common use cases + +{{}} +The following examples use hypothetical functions for illustration. At launch, +only function-python supports operations. +{{}} + +### Rolling upgrades + +Use Operations for controlled rolling upgrades: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: cluster-upgrade +spec: + mode: Pipeline + pipeline: + - step: rolling-upgrade + functionRef: + name: function-cluster-upgrade + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: ClusterUpgradeInput + targetVersion: "1.28" + batches: [0.25, 0.5, 1.0] # 25%, 50%, then 100% + healthChecks: [Synced, Ready] +``` + +### One-time maintenance + +Use Operations for specific maintenance tasks: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: certificate-rotation +spec: + mode: Pipeline + pipeline: + - step: rotate-certificates + functionRef: + name: function-cert-rotation + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: CertRotationInput + targetCertificates: + matchLabels: + rotate: "true" +``` + +## Advanced configuration + +### Retry behavior + +Operations automatically retry when they fail. Configure the retry limit to +control how often attempts occur: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: resilient-operation +spec: + retryLimit: 10 # Try up to 10 times before giving up (default: 5) + mode: Pipeline + pipeline: + - step: flaky-task + functionRef: + name: function-flaky-task + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: FlakyTaskInput + # Task that might fail due to temporary issues + timeout: "30s" +``` + +**Retry behavior:** +- Each retry resets the entire pipeline - if step 2 of 3 fails, the retry + starts from step 1 +- Operations use exponential backoff: 1 s, 2 s, 4 s, 8 s, 16 s, 32 s, then 60 s + max +- Operations track the number of failures in `status.failures` +- After reaching `retryLimit`, the Operation becomes + `Succeeded=False` + +### Credentials + +Operations can provide credentials to functions through Secrets: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: secure-backup +spec: + mode: Pipeline + pipeline: + - step: backup-with-credentials + functionRef: + name: function-backup + credentials: + - name: backup-creds + source: Secret + secretRef: + namespace: crossplane-system + name: backup-credentials + key: api-key + - name: database-creds + source: Secret + secretRef: + namespace: crossplane-system + name: database-credentials + key: connection-string + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: BackupInput + destination: s3://my-backup-bucket +``` + +### Multiple pipeline steps + +Complex operations can use multiple pipeline steps: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: multi-step-deployment +spec: + mode: Pipeline + pipeline: + - step: validate-config + functionRef: + name: function-validator + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: ValidatorInput + configName: app-config + - step: backup-current + functionRef: + name: function-backup + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: BackupInput + target: current-deployment + - step: deploy-new-version + functionRef: + name: function-deploy + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: DeployInput + image: myapp:v2.0.0 + strategy: rollingUpdate + - step: verify-health + functionRef: + name: function-health-check + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: HealthCheckInput + timeout: 300s + healthEndpoint: /health +``` + +### RBAC permissions + +If your Operation needs to access resources that Crossplane doesn't have +permissions for by default, create a ClusterRole that aggregates to +Crossplane: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operation-additional-permissions + labels: + rbac.crossplane.io/aggregate-to-crossplane: "true" +rules: +# Additional permissions for Operations +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "patch", "update"] +- apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list"] +# Add other resources your Operations need to access +``` + +This ClusterRole is automatically aggregated to Crossplane's main +ClusterRole, giving the Crossplane service account the permissions needed +for your Operations. + +{{}} +The [RBAC manager]({{}}) automatically +grants Crossplane access to Crossplane resources (MRs, XRs, etc.). You +only need to create more ClusterRoles for other Kubernetes resources +that your Operations need to access. + +For more details on RBAC configuration, see the +[Compositions RBAC documentation]({{}}). +{{}} + +### Required resources + +Operations can preload resources for functions to access: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: resource-aware-operation +spec: + mode: Pipeline + pipeline: + - step: process-deployment + functionRef: + name: function-processor + requirements: + requiredResources: + - requirementName: app-deployment + apiVersion: apps/v1 + kind: Deployment + name: my-app + namespace: production + - requirementName: app-service + apiVersion: v1 + kind: Service + name: my-app-service + namespace: production + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: ProcessorInput + action: upgrade +``` + +Functions access these resources through the standard request structure: + +```python +from crossplane.function import request, response + +def operate(req, rsp): + # Access required resources + deployment = request.get_required_resource(req, "app-deployment") + service = request.get_required_resource(req, "app-service") + + if not deployment or not service: + response.set_output(rsp, {"error": "Required resources not found"}) + return + + # Process the resources + new_replicas = deployment["spec"]["replicas"] * 2 + + # Return updated resources with full GVK and metadata for server-side apply + rsp.desired.resources["app-deployment"].resource.update({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": deployment["metadata"]["name"], + "namespace": deployment["metadata"]["namespace"] + }, + "spec": {"replicas": new_replicas} + }) +``` + +## Status and monitoring + +Operations provide rich status information: + +```yaml +status: + conditions: + - type: Synced + status: "True" + reason: ReconcileSuccess + - type: Succeeded + status: "True" + reason: PipelineSuccess + - type: ValidPipeline + status: "True" + reason: ValidPipeline + failures: 1 # Number of retry attempts + pipeline: + - step: create-backup + output: + backupId: "backup-20240115-103000" + size: "2.3GB" + appliedResourceRefs: + - apiVersion: "v1" + kind: "Secret" + namespace: "production" + name: "backup-secret" + - apiVersion: "apps/v1" + kind: "Deployment" + name: "updated-deployment" +``` + +**Key status fields:** +- **Conditions**: Standard Crossplane conditions (Synced) and Operation-specific conditions: + - **Succeeded**: `True` when the operation completed successfully, `False` when it failed + - **ValidPipeline**: `True` when all functions have the required `operation` capability +- **Failures**: Number of times the operation has failed and retried +- **Pipeline**: Output from each function step for tracking progress +- **`AppliedResourceRefs`**: References to all resources the Operation created or modified + +### Events + +Operations emit Kubernetes events for important activities: +- Function run results and warnings +- Resource apply failures +- Operation lifecycle events (creation, completion, failure) + +### Troubleshooting operations + +**Select operation status:** + +```shell +kubectl get operation my-operation -o wide +kubectl describe operation my-operation +``` + +**Common failure scenarios:** + +1. **ValidPipeline condition is False** - Function doesn't support operations: + ```yaml + conditions: + - type: ValidPipeline + status: "False" + reason: InvalidFunctionCapability + message: "Function function-name doesn't support operations" + ``` + *Solution*: use a function that declares `operation` capability. + +2. **Succeeded condition is False** - Function run failed: + ```yaml + conditions: + - type: Succeeded + status: "False" + reason: PipelineFailure + message: "Function returned error: connection timeout" + ``` + *Solution*: view function logs and fix the underlying issue. + +3. **Resource apply failures** - View events for details: + ```shell + kubectl get events --field-selector involvedObject.name=my-operation + ``` + +**Debug function runs:** + +```shell +# View function logs +kubectl logs -n crossplane-system deployment/function-python + +# Check operation events +kubectl get events --field-selector involvedObject.kind=Operation + +# Inspect operation status in detail +kubectl get operation my-operation -o jsonpath='{.status.pipeline}' | jq '.' +``` + +## Resource management + +Operations can create or change any Kubernetes resources using server-side +apply with force ownership. This means: + +**What Operations can do:** +- Create new resources of any kind +- Change existing resources by taking ownership of specific fields +- Apply changes that may conflict with other controllers + +**What Operations can't do:** +- Delete resources (current limitation of alpha implementation) +- Establish owner references (resources aren't garbage collected) +- Continuously maintain desired state (they run once) + +{{}} +Use caution with Operations that change resources managed by other controllers. +Operations force ownership when applying changes, which can cause conflicts. +{{}} + +## Best practices + +### Operation-specific practices + +1. **Plan for rollback** - Design operations to be reversible when possible, + because Operations don't auto rollback like Compositions +1. **Make operations idempotent** - Operations should be safe to retry if they + fail partway through +1. **Use required resources** - Prepopulate functions with needed resources for + efficiency rather than requesting them during running + +### Function development + +1. **Declare capabilities** - Explicitly declare `operation` capability in + function metadata to enable Operations support +1. **Return meaningful output** - Use the output field to track what the + operation accomplished for monitoring and debugging + +## Next steps + +- [Get started with Operations]({{}}) to create your first Operation +- Learn about [CronOperation]({{}}) for scheduled operations +- Learn about [WatchOperation]({{}}) for reactive operations diff --git a/content/master/operations/watchoperation.md b/content/master/operations/watchoperation.md new file mode 100644 index 00000000..6a6298b1 --- /dev/null +++ b/content/master/operations/watchoperation.md @@ -0,0 +1,567 @@ +--- +title: WatchOperation +weight: 130 +state: alpha +alphaVersion: 2.0 +description: WatchOperations create Operations when watched resources change +--- + +A `WatchOperation` creates [Operations]({{}}) when watched +Kubernetes resources change. Use WatchOperations for reactive operational +workflows such as backing up databases before deletion, validating +configurations after updates, or triggering alerts when resources fail. + + +## How WatchOperations work + + +WatchOperations watch specific Kubernetes resources and create new Operations +whenever those resources change. The changed resource is automatically injected +into the Operation for the function to process. + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: config-validator +spec: + watch: + apiVersion: v1 + kind: ConfigMap + matchLabels: + validate: "true" + concurrencyPolicy: Allow + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: validate + functionRef: + name: function-config-validator + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: ConfigValidatorInput + rules: + - required: ["database.url", "database.port"] + - format: "email" + field: "notification.email" + - step: notify + functionRef: + name: function-slack-notifier + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: SlackNotifierInput + channel: "#alerts" + severity: "warning" +``` + +{{}} +WatchOperations are an alpha feature. You must enable Operations by adding +`--enable-operations` to Crossplane's arguments. +{{}} + +## Key features + +- **Watches any Kubernetes resource type** - Not limited to Crossplane resources +- **Supports namespace and label filtering** - Target specific resources +- **Automatically injects changed resources** - Functions receive the triggering resource +- **Configurable concurrency policies** - Control operation creation + +## Resource watching + +WatchOperations can watch any Kubernetes resource with flexible filtering: + +### Watch all resources of a type + +```yaml +spec: + watch: + apiVersion: apps/v1 + kind: Deployment +``` + +### Watch resources in a specific namespace + +```yaml +spec: + watch: + apiVersion: v1 + kind: ConfigMap + namespace: production +``` + +### Watch resources with specific labels + +```yaml +spec: + watch: + apiVersion: example.org/v1 + kind: Database + matchLabels: + backup: "enabled" + environment: "production" +``` + +### Watch cluster-scoped resources + +```yaml +spec: + watch: + apiVersion: v1 + kind: Node + matchLabels: + node-role.kubernetes.io/worker: "" +``` + +## Resource injection + +When a WatchOperation creates an Operation, it automatically injects the +changed + +resource using the special requirement name +`ops.crossplane.io/watched-resource`. + +Functions can access this resource without explicitly requesting it. + +For example, when a ConfigMap with label `validate: "true"` changes, the +WatchOperation creates an Operation like this: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: config-validator-abc123 +spec: + mode: Pipeline + pipeline: + - step: validate + functionRef: + name: function-config-validator + requirements: + requiredResources: + - requirementName: ops.crossplane.io/watched-resource + apiVersion: v1 + kind: ConfigMap + name: my-config + namespace: default + # ... other pipeline steps from operationTemplate +``` + +The watched resource is automatically available to functions in +`req.required_resources` under the special name +`ops.crossplane.io/watched-resource`. + +## Concurrency policies + +WatchOperations support the same concurrency policies as CronOperations: + +- **Allow (default)**: Multiple Operations can run simultaneously. Use this + when operations don't interfere with each other. +- **Forbid**: New Operations don't start if previous ones are still running. + Use this for operations that can't run concurrently. +- **Replace**: New Operations stop running ones before starting. Use this + when you always want the latest operation to run. + +## Common use cases + +{{}} +The following examples use hypothetical functions for illustration. At launch, +only function-python supports operations. +{{}} + +### Configuration validation + +Validate ConfigMaps when they change: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: config-validator +spec: + watch: + apiVersion: v1 + kind: ConfigMap + matchLabels: + validate: "true" + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: validate-config + functionRef: + name: function-config-validator + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: ConfigValidatorInput + rules: + - required: ["database.host", "database.port"] + - format: "email" + field: "notification.email" +``` + +### Database backup on deletion + +Backup databases before they're deleted: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: backup-on-deletion +spec: + watch: + apiVersion: rds.aws.crossplane.io/v1alpha1 + kind: Instance + # Note: Watching for deletion requires function logic + # to check deletion timestamp + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: create-backup + functionRef: + name: function-rds-backup + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: RDSBackupInput + retentionDays: 30 +``` + +### Resource failure alerting + +Alert when resources enter a failed state: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: failure-alerts +spec: + watch: + apiVersion: example.org/v1 + kind: App + matchLabels: + alert: "enabled" + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: check-status + functionRef: + name: function-status-checker + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: StatusCheckerInput + alertConditions: + - type: "Ready" + status: "False" + - step: send-alert + functionRef: + name: function-alertmanager + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: AlertInput + severity: "critical" +``` + +## Advanced configuration + +### Advanced watch patterns + +Complex resource watching with multiple conditions: + +```yaml +# Watch Deployments in specific namespaces with multiple label conditions +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: multi-condition-watcher +spec: + watch: + apiVersion: apps/v1 + kind: Deployment + namespace: production # Only production namespace + matchLabels: + app.kubernetes.io/managed-by: "crossplane" + environment: "prod" + backup-required: "true" + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: backup-deployment + functionRef: + name: function-deployment-backup +``` + +```yaml +# Watch custom resources across all namespaces +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: database-lifecycle-manager +spec: + watch: + apiVersion: database.example.io/v1 + kind: PostgreSQLInstance + # No namespace specified = watch all namespaces + matchLabels: + lifecycle-management: "enabled" + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: lifecycle-check + functionRef: + name: function-database-lifecycle + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: DatabaseLifecycleInput + checkDeletionTimestamp: true + autoBackup: true +``` + + +### Cross-resource workflows + +WatchOperations can watch one resource type and dynamically fetch related +resources. Here's a WatchOperation that watches Ingresses and manages +certificates: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: ingress-certificate-manager +spec: + watch: + apiVersion: networking.k8s.io/v1 + kind: Ingress + matchLabels: + auto-cert: "enabled" + operationTemplate: + spec: + mode: Pipeline + pipeline: + - step: manage-certificates + functionRef: + name: function-cert-manager + input: + apiVersion: fn.crossplane.io/v1beta1 + kind: CertManagerInput + issuer: "letsencrypt-prod" + renewBefore: "720h" # 30 days +``` + +The function examines the watched Ingress and dynamically requests related resources: + +```python +from crossplane.function import request, response + +def operate(req, rsp): + # Access the watched Ingress resource + ingress = request.get_required_resource(req, "ops.crossplane.io/watched-resource") + if not ingress: + response.set_output(rsp, {"error": "No watched resource found"}) + return + + # Extract the service name from the Ingress backend + rules = ingress.get("spec", {}).get("rules", []) + if not rules: + return + + backend = rules[0].get("http", {}).get("paths", [{}])[0].get("backend", {}) + service_name = backend.get("service", {}).get("name") + if not service_name: + return + + ingress_namespace = ingress.get("metadata", {}).get("namespace", "default") + + # Always declare what resources we need (requirements must be stable across calls) + rsp.requirements.resources["related-service"].api_version = "v1" + rsp.requirements.resources["related-service"].kind = "Service" + rsp.requirements.resources["related-service"].match_name = service_name + rsp.requirements.resources["related-service"].namespace = ingress_namespace + + # Process the service if Crossplane fetched it after our previous response + service = request.get_required_resource(req, "related-service") + if service: + create_certificate_for_service(ingress, service, rsp) +``` + +This pattern allows functions to: +1. Examine the watched resource +2. Dynamically determine what other resources you need +3. Request those resources in the next response +4. Process the more resources in next calls + +## Status and monitoring + +WatchOperations provide status information about watching: + +```yaml +status: + conditions: + - type: Synced + status: "True" + reason: ReconcileSuccess + - type: Watching + status: "True" + reason: WatchActive + watchingResources: 12 + runningOperationRefs: + - name: config-validator-anjda + - name: config-validator-f0d92 +``` + +**Key status fields:** +- **Conditions**: Standard Crossplane conditions (Synced) and WatchOperation-specific conditions: + - **Watching**: `True` when the WatchOperation is actively watching resources, `False` when paused or failed +- **`watchingResources`**: Number of resources under watch +- **`runningOperationRefs`**: Running Operations created by this WatchOperation + +### Events + +WatchOperations emit events for important activities: +- `EstablishWatched` (Warning) - Watch establishment failures +- `TerminateWatched` (Warning) - Watch termination failures +- `GarbageCollectOperations` (Warning) - Operation cleanup failures +- `CreateOperation` (Warning) - Operation creation failures +- `ReplaceRunningOperation` (Warning) - Operation replacement failures + + +### Monitoring + + + +Monitor WatchOperations using: + + +```shell +# Check WatchOperation status +kubectl get watchoperation my-watchop + +# View recent Operations created by the WatchOperation +kubectl get operations -l crossplane.io/watchoperation=my-watchop + +# Check watched resource count +kubectl describe watchoperation my-watchop + +# Check events +kubectl get events --field-selector involvedObject.name=my-watchop +``` + + +## Best practices + +### Resource selection + +1. **Use specific label selectors** - Prevent unnecessary Operations with + precise filtering +1. **Avoid high-churn resources** - Be careful watching frequently changing + resources +1. **Start small** - Begin with narrow selectors and expand as needed + +### Event handling + +1. **Implement event filtering** - Check generation, deletion timestamp, + and status conditions + to avoid processing irrelevant changes + +1. **Monitor operation volume** - Popular resources can create numerous + Operations + + +### Concurrency policies + +1. **Choose appropriate concurrency policies**: + - **Allow** for independent processing that can run in parallel + - **Forbid** for operations that must complete before processing new + changes + - **Replace** for status-checking or monitoring where only latest state + matters + +### History management + +Like CronOperations, WatchOperations automatically clean up completed Operations: + +```yaml +apiVersion: ops.crossplane.io/v1alpha1 +kind: WatchOperation +metadata: + name: config-validator +spec: + watch: + apiVersion: v1 + kind: ConfigMap + successfulHistoryLimit: 10 # Keep 10 successful Operations (default: 3) + failedHistoryLimit: 5 # Keep 5 failed Operations (default: 1) + operationTemplate: + # Operation template here +``` + +### Watched resource injection + + +WatchOperations automatically inject the changed resource into the created +Operation using a special requirement name +`ops.crossplane.io/watched-resource`: + + +```python +from crossplane.function import request, response + +def operate(req, rsp): + # Access the resource that triggered this Operation + watched_resource = request.get_required_resource(req, "ops.crossplane.io/watched-resource") + if not watched_resource: + response.set_output(rsp, {"error": "No watched resource found"}) + return + + # Process based on the watched resource + if watched_resource["kind"] == "ConfigMap": + config_data = watched_resource["data"] + # Validate configuration... +``` + +The watched resource is available in the function's `required_resources` map +without needing to declare it in the Operation template. + +For general Operations best practices including function development and +operational considerations, see [Operation best practices]({{}}). + +## Troubleshooting + + +### WatchOperation not creating Operations + + +1. Verify the WatchOperation has `Watching=True` condition +1. Check that watched resources exist and match the selector +1. Ensure resources are actually changing +1. Look for events indicating watch establishment failures + + + +### Too many Operations created + + + +1. Refine label selectors to match fewer resources +1. Consider using `Forbid` or `Replace` concurrency policy +1. Check if resources are changing more frequently than expected +1. Review function logic to ensure it's not causing resource updates + +### Operations failing to process watched resources + +1. Verify function capabilities include `operation` +1. Check that functions handle the `ops.crossplane.io/watched-resource` +1. Review function logs for processing errors +1. Ensure functions can handle the specific resource types under watch + +## Next steps + +- Learn about [Operation]({{}}) for one-time operational tasks +- Learn about [CronOperation]({{}}) for scheduled operations +- [Get started with Operations]({{}}) to create your first reactive operation \ No newline at end of file diff --git a/utils/vale/styles/Crossplane/allowed-jargon.txt b/utils/vale/styles/Crossplane/allowed-jargon.txt index 16b990f0..97602bd2 100644 --- a/utils/vale/styles/Crossplane/allowed-jargon.txt +++ b/utils/vale/styles/Crossplane/allowed-jargon.txt @@ -20,7 +20,10 @@ ClusterRole ClusterRoles command-line ConfigMap +ConfigMaps CRD +cron +CronJobs crt CSS CUE @@ -39,6 +42,7 @@ float64 GitOps Go gRPC +hostname IAM imagePullSecret init.sh @@ -64,6 +68,7 @@ OIDC PersistentVolumeClaim Pre-releases pre-releases +Prepopulate PriorityClass proselint protobuf @@ -98,6 +103,7 @@ Substrings syscall templated TLS +walkthrough tolerations UI VM diff --git a/utils/vale/styles/Crossplane/crossplane-words.txt b/utils/vale/styles/Crossplane/crossplane-words.txt index ae015516..60f2c74c 100644 --- a/utils/vale/styles/Crossplane/crossplane-words.txt +++ b/utils/vale/styles/Crossplane/crossplane-words.txt @@ -13,12 +13,17 @@ CompositeResourceDefinition CompositeResourceDefinitions CompositionRevision CompositionRevisions +composition-only config Configs CONTRIBUTING.md ControllerConfig ControllerConfigs CRDs +CronJobs +CronOperation +CronOperation-specific +CronOperations CRs Crossplane crossplane-admin @@ -45,6 +50,7 @@ function-environment-configs function-extra-resources function-go-templating function-patch-and-transform +function-python function-template-python HealthyPackageRevision Helm-like @@ -63,9 +69,11 @@ ProviderConfig ProviderConfigs ProviderRevision RunFunctionRequest +Operation-specific RunFunctionResponse Sigstore StoreConfig +SSL StoreConfigs ToCompositeFieldPath ToEnvironmentFieldPath @@ -74,6 +82,10 @@ TrimPrefix TrimSuffix UnhealthyPackageRevision UnknownPackageRevisionHealth +ValidPipeline +WatchOperation +WatchOperation-specific +WatchOperations XCluster XNetwork xpkg diff --git a/utils/vale/styles/Crossplane/spelling-exceptions.txt b/utils/vale/styles/Crossplane/spelling-exceptions.txt index 7fa28941..a88a2791 100644 --- a/utils/vale/styles/Crossplane/spelling-exceptions.txt +++ b/utils/vale/styles/Crossplane/spelling-exceptions.txt @@ -3,6 +3,7 @@ backporting built-in call-outs +google.com ClusterRoles` comma-separated conformant @@ -10,6 +11,7 @@ cross-reference Cross-resource cross-resource datastore +day-two double-check double-checks dry-run @@ -23,12 +25,16 @@ how-to in-depth in-memory left-hand +Long-running +low-traffic multi-cluster multi-region multi-tenant multi-tenancy non-empty non-Kubernetes +one-time +One-time per-element per-object per-resource @@ -57,4 +63,25 @@ user-defined v2 version-specific Job. +e.g. +high-churn +idempotency +least-privilege +long-running +Multi-step +namespace-scoped +non-production +preload +event-driven +hardcode +low-risk +Operation-level +performant +resource-intensive +status-checking +System-level +Time-sensitive +user-provided +validators +webhook-based backporting \ No newline at end of file From 91edda5d3fba39da992a4e7b9e1c59ea11072295 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 01:17:53 -0700 Subject: [PATCH 04/15] Document crossplane alpha render op command Add Test an operation section to Operations concept documentation showing how to preview Operations locally using the CLI. Follows same pattern as composition render documentation with usage examples and output format. Signed-off-by: Nic Cope --- content/master/operations/operation.md | 80 ++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/content/master/operations/operation.md b/content/master/operations/operation.md index 6c615b2f..4af835ee 100644 --- a/content/master/operations/operation.md +++ b/content/master/operations/operation.md @@ -475,6 +475,86 @@ Use caution with Operations that change resources managed by other controllers. Operations force ownership when applying changes, which can cause conflicts. {{}} +## Test an operation + +You can preview the output of any Operation using the Crossplane CLI. You +don't need a Crossplane control plane to do this. The Crossplane CLI uses Docker +Engine to run functions. + +{{}} +See the [Crossplane CLI docs]({{}}) to +learn how to install and use the Crossplane CLI. +{{< /hint >}} + +{{}} +Running `crossplane alpha render op` requires [Docker](https://www.docker.com). +{{< /hint >}} + +Provide an operation, composition functions, and any required resources to render +the output locally. + +```shell +crossplane alpha render op operation.yaml functions.yaml --required-resources=ingress.yaml +``` + +`crossplane alpha render op` prints the Operation status and any resources the +operation functions created or modified. It shows what would happen if you +applied the Operation to a cluster. + +```yaml +--- +# Operation status showing function results +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: ingress-cert-monitor +status: + conditions: + - type: Succeeded + status: "True" + reason: PipelineSuccess + pipeline: + - step: check-ingress-certificate + output: + certificateExpires: "Sep 29 08:34:02 2025 GMT" + daysUntilExpiry: 53 + hostname: google.com + ingressName: example-app + status: ok +--- +# Modified Ingress resource with certificate annotations +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-monitor.crossplane.io/expires: Sep 29 08:34:02 2025 GMT + cert-monitor.crossplane.io/days-until-expiry: "53" + cert-monitor.crossplane.io/status: ok + name: example-app + namespace: default +spec: + # ... ingress spec unchanged +``` + +Use `--required-resources` to provide resources that your operation functions +need access to. You can specify multiple files or use glob patterns: + +```shell +# Multiple specific files +crossplane alpha render op operation.yaml functions.yaml \ + --required-resources=deployment.yaml,service.yaml,configmap.yaml + +# Glob pattern for all YAML files in a directory +crossplane alpha render op operation.yaml functions.yaml \ + --required-resources="resources/*.yaml" +``` + +{{}} +Use the `crossplane alpha render op` command to test your Operations locally +before deploying them to a cluster. The command helps validate function logic +and required resource access patterns. +{{}} + ## Best practices ### Operation-specific practices From ea1ab2506f3693f3bd92b4e39f6273a4f2360531 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 12:45:49 -0700 Subject: [PATCH 05/15] Document requirement stabilization pattern for dynamic resource discovery Add critical guidance for WatchOperation functions that request resources dynamically. Functions must return consistent requirements across iterations for proper stabilization. Include working example with error handling. Signed-off-by: Nic Cope --- content/master/operations/watchoperation.md | 59 ++++++++++++++++----- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/content/master/operations/watchoperation.md b/content/master/operations/watchoperation.md index 6a6298b1..6aeb5875 100644 --- a/content/master/operations/watchoperation.md +++ b/content/master/operations/watchoperation.md @@ -354,7 +354,8 @@ spec: renewBefore: "720h" # 30 days ``` -The function examines the watched Ingress and dynamically requests related resources: +The function examines the watched Ingress and dynamically requests related +resources: ```python from crossplane.function import request, response @@ -363,38 +364,70 @@ def operate(req, rsp): # Access the watched Ingress resource ingress = request.get_required_resource(req, "ops.crossplane.io/watched-resource") if not ingress: - response.set_output(rsp, {"error": "No watched resource found"}) + response.fatal(rsp, "No watched resource found") return # Extract the service name from the Ingress backend rules = ingress.get("spec", {}).get("rules", []) if not rules: + response.fatal(rsp, "Could not extract service name from ingress") return backend = rules[0].get("http", {}).get("paths", [{}])[0].get("backend", {}) service_name = backend.get("service", {}).get("name") if not service_name: + response.fatal(rsp, "Could not extract service name from ingress") return ingress_namespace = ingress.get("metadata", {}).get("namespace", "default") - # Always declare what resources we need (requirements must be stable across calls) - rsp.requirements.resources["related-service"].api_version = "v1" - rsp.requirements.resources["related-service"].kind = "Service" - rsp.requirements.resources["related-service"].match_name = service_name - rsp.requirements.resources["related-service"].namespace = ingress_namespace + # CRITICAL: Always request the same resources to ensure requirement + # stabilization. Crossplane calls the function repeatedly until + # requirements don't change. + response.require_resources( + rsp, + name="related-service", + api_version="v1", + kind="Service", + match_name=service_name, + namespace=ingress_namespace + ) - # Process the service if Crossplane fetched it after our previous response + # Check if the service is available and process accordingly service = request.get_required_resource(req, "related-service") if service: - create_certificate_for_service(ingress, service, rsp) + # Success: Both resources available + response.set_output(rsp, { + "status": "success", + "message": "Certificate management completed", + "ingress_host": ingress.get("spec", {}).get("rules", [{}])[0].get("host"), + "service_name": service.get("metadata", {}).get("name") + }) + return + + # Waiting: Service not available yet + response.set_output(rsp, { + "status": "waiting", + "message": f"Waiting for service '{service_name}' to be available" + }) ``` +{{}} +**Critical resource stabilization pattern**: functions must return the **same +requirements** in each iteration to signal completion. The function in the +preceding example always calls `response.require_resources()` regardless of +whether the service exists. This ensures Crossplane knows when to stop calling +the function. + +Common mistake: only requesting resources when missing breaks the stabilization +contract and causes timeout errors. +{{}} + This pattern allows functions to: -1. Examine the watched resource -2. Dynamically determine what other resources you need -3. Request those resources in the next response -4. Process the more resources in next calls +1. Examine the watched resource (injected automatically) +2. Dynamically determine what other resources the function needs +3. Request those resources consistently using `response.require_resources()` +4. Process all resources when available, or provide status when waiting ## Status and monitoring From 7240b42d833e8365d8059dd3ef238766ed94302c Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 13:26:56 -0700 Subject: [PATCH 06/15] Fix code block rendering for cron syntax diagram Change from plain code block to console with disabled line numbers and copy functionality per docs engineer feedback. Improves visual presentation of the cron schedule diagram. Signed-off-by: Nic Cope --- content/master/operations/cronoperation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/master/operations/cronoperation.md b/content/master/operations/cronoperation.md index 47de4c6d..fbfe08b5 100644 --- a/content/master/operations/cronoperation.md +++ b/content/master/operations/cronoperation.md @@ -57,7 +57,7 @@ CronOperations are an alpha feature. You must enable Operations by adding CronOperations use standard cron syntax: -``` +```console {linenos=false,copy-lines="none"} ┌───────────── minute (0 - 59) │ ┌───────────── hour (0 - 23) │ │ ┌───────────── day of the month (1 - 31) From 02df3dacb7ee229cc70c95b1702281b209593f09 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 18:03:42 -0700 Subject: [PATCH 07/15] Fix alphaVersion rendering issue in operations/_index.md Change alphaVersion from 'v2.0-preview' to '2.0' to avoid double 'v' rendering in the documentation. Signed-off-by: Nic Cope --- content/master/operations/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/master/operations/_index.md b/content/master/operations/_index.md index a69146ca..9fa0fea8 100644 --- a/content/master/operations/_index.md +++ b/content/master/operations/_index.md @@ -2,6 +2,6 @@ title: Operations weight: 52 state: alpha -alphaVersion: v2.0-preview +alphaVersion: 2.0 description: Understand Crossplane's Operations feature --- From 40f768e298edc89ad812d15e7375ccec0c3fe8f5 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 18:05:10 -0700 Subject: [PATCH 08/15] Add consistent backticks for Kubernetes resource types Apply backticks to Kubernetes resource types (Operation, Ingress, ClusterRole, Job) to follow documentation style conventions for API objects per Jared's feedback in PR #956. Signed-off-by: Nic Cope --- .../get-started-with-operations.md | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/content/master/get-started/get-started-with-operations.md b/content/master/get-started/get-started-with-operations.md index cf3d953d..4d81f3cc 100644 --- a/content/master/get-started/get-started-with-operations.md +++ b/content/master/get-started/get-started-with-operations.md @@ -6,14 +6,14 @@ alphaVersion: 2.0 --- This guide shows how to use Crossplane Operations to automate day-two -operational tasks. You create an Operation that checks SSL certificate +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: +An `Operation` looks like this: ```yaml apiVersion: ops.crossplane.io/v1alpha1 @@ -60,15 +60,15 @@ spec: ``` -**The Operation runs once to completion, like a Kubernetes Job.** +**The `Operation` runs once to completion, like a Kubernetes `Job`.** -When you create the Operation, Crossplane runs the function pipeline. The +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 +a more realistic `Operation` that reads Kubernetes `Ingress` resources and annotates them with certificate expiry information for monitoring tools. ## Prerequisites @@ -91,17 +91,17 @@ helm upgrade --install crossplane crossplane-stable/crossplane \ ## Create an operation -Follow these steps to create your first Operation: +Follow these steps to create your first `Operation`: 1. [Create a sample Ingress](#create-a-sample-ingress) for certificate checking 1. [Install the function](#install-the-function) you want to use for the operation -1. [Create the Operation](#create-the-operation) that checks the Ingress +1. [Create the Operation](#create-the-operation) that checks the `Ingress` 1. [Check the Operation](#check-the-operation) as it runs ### Create a sample Ingress -Create an Ingress that references a real hostname but doesn't route actual +Create an `Ingress` that references a real hostname but doesn't route actual traffic: ```yaml @@ -132,8 +132,8 @@ kubectl apply -f ingress.yaml ### Grant Ingress permissions -Operations need permission to access and change Ingresses. Create a ClusterRole -that grants Crossplane access to Ingresses: +`Operations` need permission to access and change `Ingresses`. Create a `ClusterRole` +that grants Crossplane access to `Ingresses`: ```yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -186,7 +186,7 @@ crossplane-contrib-function-python True True xpkg.crossplane.io/cr ### Create the operation -Create this Operation that monitors the Ingress certificate: +Create this `Operation` that monitors the `Ingress` certificate: ```yaml apiVersion: ops.crossplane.io/v1alpha1 @@ -275,7 +275,7 @@ kubectl apply -f operation.yaml ### Check the operation -Check that the Operation runs successfully: +Check that the `Operation` runs successfully: ```shell {copy-lines="1"} kubectl get -f operation.yaml @@ -284,10 +284,10 @@ ingress-cert-monitor True True 15s ``` {{}} -Operations show `SUCCEEDED=True` when they complete successfully. +`Operations` show `SUCCEEDED=True` when they complete successfully. {{}} -Check the Operation's detailed status: +Check the `Operation`'s detailed status: ```shell {copy-lines="1"} kubectl describe operation ingress-cert-monitor @@ -317,7 +317,7 @@ 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: +Check that the `Operation` annotated the `Ingress` with certificate information: ```shell {copy-lines="1"} kubectl get ingress example-app -o yaml @@ -335,8 +335,8 @@ spec: ``` {{}} -This pattern shows how Operations can both read and change existing Kubernetes -resources. The Operation annotated the Ingress with certificate expiry +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. {{}} @@ -353,14 +353,14 @@ kubectl delete -f function.yaml ## Next steps -Operations are powerful building blocks for operational workflows. Learn more +`Operations` are powerful building blocks for operational workflows. Learn more about: -* [**Operation concepts**]({{}}) - Core - Operation features and best practices -* [**CronOperation**]({{}}) - Schedule +* [**`Operation` concepts**]({{}}) - Core + `Operation` features and best practices +* [**`CronOperation`**]({{}}) - Schedule operations to run automatically -* [**WatchOperation**]({{}}) - Trigger +* [**`WatchOperation`**]({{}}) - Trigger operations when resources change Explore the complete [Operations documentation]({{}}) for From 5f729d95a7ef7585c2e02382992e9fdafd1020ed Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 20:49:47 -0700 Subject: [PATCH 09/15] Fix consistent code blocks in status field list Add backticks to all status field names (Conditions, Succeeded, ValidPipeline, Failures, Pipeline) to match AppliedResourceRefs per Jared's feedback in PR #956. Signed-off-by: Nic Cope --- content/master/operations/operation.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/master/operations/operation.md b/content/master/operations/operation.md index 4af835ee..1b1c83c1 100644 --- a/content/master/operations/operation.md +++ b/content/master/operations/operation.md @@ -392,11 +392,11 @@ status: ``` **Key status fields:** -- **Conditions**: Standard Crossplane conditions (Synced) and Operation-specific conditions: - - **Succeeded**: `True` when the operation completed successfully, `False` when it failed - - **ValidPipeline**: `True` when all functions have the required `operation` capability -- **Failures**: Number of times the operation has failed and retried -- **Pipeline**: Output from each function step for tracking progress +- **`Conditions`**: Standard Crossplane conditions (Synced) and Operation-specific conditions: + - **`Succeeded`**: `True` when the operation completed successfully, `False` when it failed + - **`ValidPipeline`**: `True` when all functions have the required `operation` capability +- **`Failures`**: Number of times the operation has failed and retried +- **`Pipeline`**: Output from each function step for tracking progress - **`AppliedResourceRefs`**: References to all resources the Operation created or modified ### Events From 178a42c50ba444e7165eb8e9073d59937905bd92 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 20:56:39 -0700 Subject: [PATCH 10/15] Fix truncated mermaid diagram text Changed 'SSL Monitor Operation API' to 'SSL Monitor Operation' in the mermaid diagram to prevent text truncation that was showing as 'SSL Monitor Operation AP' in the rendered output. Signed-off-by: Nic Cope --- content/master/whats-crossplane/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/master/whats-crossplane/_index.md b/content/master/whats-crossplane/_index.md index f60de526..f6d3ba58 100644 --- a/content/master/whats-crossplane/_index.md +++ b/content/master/whats-crossplane/_index.md @@ -242,7 +242,7 @@ flowchart TD user(User) subgraph control [Control Plane] - operation(SSL Monitor Operation API) + operation(SSL Monitor Operation) subgraph crossplane [Operation Engine] fn(Python Function) From 4efb03c1fef322cd2471200ae1eaee85828c1624 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 20:58:28 -0700 Subject: [PATCH 11/15] Alphabetize all Vale dictionary files Sorted all dictionary files in utils/vale/styles/Crossplane/ alphabetically: - allowed-jargon.txt - brands.txt - crossplane-words.txt - provider-words.txt - spelling-exceptions.txt This removes duplicate entries and ensures consistent ordering for easier maintenance and Vale linting compliance. Signed-off-by: Nic Cope --- .../vale/styles/Crossplane/allowed-jargon.txt | 18 +++--- utils/vale/styles/Crossplane/brands.txt | 2 +- .../styles/Crossplane/crossplane-words.txt | 20 +++---- .../vale/styles/Crossplane/provider-words.txt | 8 +-- .../styles/Crossplane/spelling-exceptions.txt | 55 +++++++++---------- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/utils/vale/styles/Crossplane/allowed-jargon.txt b/utils/vale/styles/Crossplane/allowed-jargon.txt index 97602bd2..b1ee0f02 100644 --- a/utils/vale/styles/Crossplane/allowed-jargon.txt +++ b/utils/vale/styles/Crossplane/allowed-jargon.txt @@ -14,10 +14,10 @@ CEL CI CLI cloud-native -cluster-scoped -cluster-wide ClusterRole ClusterRoles +cluster-scoped +cluster-wide command-line ConfigMap ConfigMaps @@ -50,12 +50,12 @@ IRSA JSONPath key-pair key-value -KV -kv kube-apiserver -kube-controller-manager kubeconfig +kube-controller-manager kubectl +kv +KV metrics-server minikube multi-platform @@ -66,9 +66,9 @@ NOTES.txt OCI OIDC PersistentVolumeClaim -Pre-releases -pre-releases Prepopulate +pre-releases +Pre-releases PriorityClass proselint protobuf @@ -103,9 +103,9 @@ Substrings syscall templated TLS -walkthrough tolerations UI VM +walkthrough webhooks.enabled -YAML \ No newline at end of file +YAML diff --git a/utils/vale/styles/Crossplane/brands.txt b/utils/vale/styles/Crossplane/brands.txt index e9631553..e41b43db 100644 --- a/utils/vale/styles/Crossplane/brands.txt +++ b/utils/vale/styles/Crossplane/brands.txt @@ -39,4 +39,4 @@ Velero VSCode Webpack write-good -Zendesk \ No newline at end of file +Zendesk diff --git a/utils/vale/styles/Crossplane/crossplane-words.txt b/utils/vale/styles/Crossplane/crossplane-words.txt index 60f2c74c..50a59d28 100644 --- a/utils/vale/styles/Crossplane/crossplane-words.txt +++ b/utils/vale/styles/Crossplane/crossplane-words.txt @@ -8,12 +8,12 @@ CombineFromComposite CombineFromEnvironment CombineToComposite CombineToEnvironment -composition.yaml CompositeResourceDefinition CompositeResourceDefinitions +composition-only CompositionRevision CompositionRevisions -composition-only +composition.yaml config Configs CONTRIBUTING.md @@ -22,16 +22,16 @@ ControllerConfigs CRDs CronJobs CronOperation -CronOperation-specific CronOperations -CRs +CronOperation-specific Crossplane crossplane-admin crossplane-browse crossplane-edit +Crossplane's crossplane-view crossplane.yaml -Crossplane's +CRs CUE definition.yaml deletionPolicy @@ -63,17 +63,17 @@ LateInitialize managementPolicies MR MRs +Operation-specific PatchSet PatchSets ProviderConfig ProviderConfigs ProviderRevision RunFunctionRequest -Operation-specific RunFunctionResponse Sigstore -StoreConfig SSL +StoreConfig StoreConfigs ToCompositeFieldPath ToEnvironmentFieldPath @@ -84,17 +84,17 @@ UnhealthyPackageRevision UnknownPackageRevisionHealth ValidPipeline WatchOperation -WatchOperation-specific WatchOperations +WatchOperation-specific XCluster XNetwork xpkg xpkg.crossplane.io xpkg.upbound.io XR -XR's XRC XRD XRD's XRDs -XRs \ No newline at end of file +XR's +XRs diff --git a/utils/vale/styles/Crossplane/provider-words.txt b/utils/vale/styles/Crossplane/provider-words.txt index 4f427cd4..2fb8fbad 100644 --- a/utils/vale/styles/Crossplane/provider-words.txt +++ b/utils/vale/styles/Crossplane/provider-words.txt @@ -8,19 +8,19 @@ europe-central2 GCP GCP's GKE -provider-upjet-aws -provider-upjet-gcp -provider-upjet-azure provider-aws provider-aws-iam provider-aws-s3 provider-gcp provider-helm provider-kubernetes +provider-upjet-aws +provider-upjet-azure +provider-upjet-gcp Pub/Sub PubSub S3 us-central1 us-east-2 VPC -xpkg.crossplane.io \ No newline at end of file +xpkg.crossplane.io diff --git a/utils/vale/styles/Crossplane/spelling-exceptions.txt b/utils/vale/styles/Crossplane/spelling-exceptions.txt index a88a2791..a86c0320 100644 --- a/utils/vale/styles/Crossplane/spelling-exceptions.txt +++ b/utils/vale/styles/Crossplane/spelling-exceptions.txt @@ -1,87 +1,86 @@ -/tab -/tabs backporting built-in call-outs -google.com ClusterRoles` comma-separated conformant cross-reference -Cross-resource cross-resource +Cross-resource datastore day-two double-check double-checks dry-run dual-pushes +e.g. end-points end-to-end +event-driven free-form function-based +google.com hands-on +hardcode +high-churn how-to +idempotency in-depth in-memory +Job. +least-privilege left-hand +long-running Long-running +low-risk low-traffic multi-cluster multi-region -multi-tenant +Multi-step multi-tenancy +multi-tenant +namespace-scoped non-empty non-Kubernetes +non-production one-time One-time +Operation-level per-element +performant per-object per-resource poll-interval pre-existing +preload pre-provisioned pre-release race-conditions read-only ready-made +resource-intensive resource-specific right-hand run-time -self-signed self-service +self-signed space-delimited +status-checking step-by-step subresources +System-level +/tab +/tabs third-party +Time-sensitive top-level unpause untrusted UpperCamelCase UpperCamelCased user-defined -v2 -version-specific -Job. -e.g. -high-churn -idempotency -least-privilege -long-running -Multi-step -namespace-scoped -non-production -preload -event-driven -hardcode -low-risk -Operation-level -performant -resource-intensive -status-checking -System-level -Time-sensitive user-provided +v2 validators +version-specific webhook-based -backporting \ No newline at end of file From fcba82541c624392e8c6511b897da35630b9068a Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 20:55:46 -0700 Subject: [PATCH 12/15] Add troubleshooting item about --enable-operations flag Added a new troubleshooting item to operation.md documenting the common error when Operations feature is not enabled and how to resolve it by adding the --enable-operations flag to Crossplane's startup arguments. Updated numbering for subsequent troubleshooting items accordingly. Signed-off-by: Nic Cope --- content/master/operations/operation.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/content/master/operations/operation.md b/content/master/operations/operation.md index 1b1c83c1..afae5a30 100644 --- a/content/master/operations/operation.md +++ b/content/master/operations/operation.md @@ -417,7 +417,14 @@ kubectl describe operation my-operation **Common failure scenarios:** -1. **ValidPipeline condition is False** - Function doesn't support operations: +1. **Operations do nothing** - Operations feature not enabled: + ```yaml + # Operation exists but has no status conditions and never progresses + status: {} + ``` + *Solution*: enable Operations by adding `--enable-operations` to Crossplane's startup arguments. + +2. **ValidPipeline condition is False** - Function doesn't support operations: ```yaml conditions: - type: ValidPipeline @@ -427,7 +434,7 @@ kubectl describe operation my-operation ``` *Solution*: use a function that declares `operation` capability. -2. **Succeeded condition is False** - Function run failed: +3. **Succeeded condition is False** - Function run failed: ```yaml conditions: - type: Succeeded @@ -437,7 +444,7 @@ kubectl describe operation my-operation ``` *Solution*: view function logs and fix the underlying issue. -3. **Resource apply failures** - View events for details: +4. **Resource apply failures** - View events for details: ```shell kubectl get events --field-selector involvedObject.name=my-operation ``` From 1460b626340272956ef6923eb5299522a542cccb Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 21:06:07 -0700 Subject: [PATCH 13/15] Fix status field capitalization to match YAML Updated status field names in operation.md to match actual YAML capitalization: conditions, failures, pipeline, appliedResourceRefs instead of the capitalized versions. Signed-off-by: Nic Cope --- content/master/operations/operation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/master/operations/operation.md b/content/master/operations/operation.md index afae5a30..305d1f54 100644 --- a/content/master/operations/operation.md +++ b/content/master/operations/operation.md @@ -392,12 +392,12 @@ status: ``` **Key status fields:** -- **`Conditions`**: Standard Crossplane conditions (Synced) and Operation-specific conditions: +- **`conditions`**: Standard Crossplane conditions (Synced) and Operation-specific conditions: - **`Succeeded`**: `True` when the operation completed successfully, `False` when it failed - **`ValidPipeline`**: `True` when all functions have the required `operation` capability -- **`Failures`**: Number of times the operation has failed and retried -- **`Pipeline`**: Output from each function step for tracking progress -- **`AppliedResourceRefs`**: References to all resources the Operation created or modified +- **`failures`**: Number of times the operation has failed and retried +- **`pipeline`**: Output from each function step for tracking progress +- **`appliedResourceRefs`**: References to all resources the Operation created or modified ### Events From e604ae53994242d07fb25c25aad382ffb8d54aeb Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 20:54:26 -0700 Subject: [PATCH 14/15] Fix HTML comment causing ordered list numbering reset Fixed Vale linter disable comment that was interrupting the ordered list in the "Event handling" section of watchoperation.md. Moved the Vale disable comments to wrap around the entire list instead of interrupting it, which resolves the ordering numbering reset issue. Signed-off-by: Nic Cope --- content/master/operations/operation.md | 7 ++++++- content/master/operations/watchoperation.md | 13 ++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/content/master/operations/operation.md b/content/master/operations/operation.md index 305d1f54..051a180a 100644 --- a/content/master/operations/operation.md +++ b/content/master/operations/operation.md @@ -408,10 +408,15 @@ Operations emit Kubernetes events for important activities: ### Troubleshooting operations -**Select operation status:** +**Check operation status:** ```shell kubectl get operation my-operation -o wide +``` + +**View detailed information:** + +```shell kubectl describe operation my-operation ``` diff --git a/content/master/operations/watchoperation.md b/content/master/operations/watchoperation.md index 6aeb5875..71db9064 100644 --- a/content/master/operations/watchoperation.md +++ b/content/master/operations/watchoperation.md @@ -115,13 +115,12 @@ spec: ## Resource injection -When a WatchOperation creates an Operation, it automatically injects the -changed -resource using the special requirement name -`ops.crossplane.io/watched-resource`. +When a WatchOperation creates an Operation, it automatically injects the changed +resource using the special requirement name +`ops.crossplane.io/watched-resource`. Functions can access this resource without +explicitly requesting it. -Functions can access this resource without explicitly requesting it. For example, when a ConfigMap with label `validate: "true"` changes, the WatchOperation creates an Operation like this: @@ -498,10 +497,10 @@ kubectl get events --field-selector involvedObject.name=my-watchop ### Event handling + 1. **Implement event filtering** - Check generation, deletion timestamp, and status conditions to avoid processing irrelevant changes - 1. **Monitor operation volume** - Popular resources can create numerous Operations @@ -597,4 +596,4 @@ operational considerations, see [Operation best practices]({{}}) for one-time operational tasks - Learn about [CronOperation]({{}}) for scheduled operations -- [Get started with Operations]({{}}) to create your first reactive operation \ No newline at end of file +- [Get started with Operations]({{}}) to create your first reactive operation From 1c911d861e6201a5c533799b7a7b926724aad33b Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 7 Aug 2025 21:25:01 -0700 Subject: [PATCH 15/15] Add context before Operations mermaid diagram Added introductory sentence before Operations diagram to match the pattern used for other diagrams. Explains the SSL certificate monitoring scenario that the diagram illustrates, connecting the use case to the visual representation. Signed-off-by: Nic Cope --- .claude/settings.local.json | 55 +++++ CLAUDE.md | 272 ++++++++++++++++++++++ content/master/whats-crossplane/_index.md | 6 + 3 files changed, 333 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 CLAUDE.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..7b353f21 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "permissions": { + "allow": [ + "Bash(find:*)", + "Bash(nix-shell:*)", + "Bash(vale:*)", + "Bash(grep:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(for file in /home/negz/control/crossplane/docs/content/v1.{18,19,20}/learn/feature-lifecycle.md)", + "Bash(do sed -i 's/won''''t be/aren''''t/g; s/will provide/provide/g' \"$file\")", + "Bash(done)", + "Bash(for file in /home/negz/control/crossplane/docs/content/v1.{18,19,20}/learn/release-cycle.md)", + "Bash(do sed -i 's/will be/are/g; s/won''''t be/aren''''t/g; s/will /\\0/g' \"$file\")", + "Bash(sed:*)", + "Bash(for version in v1.18 v1.19 v1.20)", + "Bash(do)", + "Bash(for file in /home/negz/control/crossplane/docs/content/$version/learn/release-cycle.md)", + "Bash(if [[ -f \"$file\" ]])", + "Bash(then)", + "Bash(fi)", + "Bash(for file in /home/negz/control/crossplane/docs/content/$version/guides/vault-injection.md)", + "Bash(for:*)", + "Bash(# Fix specific line-by-line issues in remaining files\nsed -i ''132s/will assign/assigns/'' /home/negz/control/crossplane/docs/content/v1.19/concepts/composition-revisions.md\nsed -i ''329s/will also/also/'' /home/negz/control/crossplane/docs/content/v1.19/concepts/composition-revisions.md\nsed -i ''294s/will automatically/automatically/'' /home/negz/control/crossplane/docs/content/v1.19/concepts/packages.md\nsed -i ''317s/will automatically/automatically/'' /home/negz/control/crossplane/docs/content/v1.19/concepts/providers.md\n\nsed -i ''132s/will assign/assigns/'' /home/negz/control/crossplane/docs/content/v1.20/concepts/composition-revisions.md\nsed -i ''329s/will also/also/'' /home/negz/control/crossplane/docs/content/v1.20/concepts/composition-revisions.md\nsed -i ''84s/will select/selects/'' /home/negz/control/crossplane/docs/content/v1.20/concepts/composition-revisions.md\nsed -i ''19s/will enforce/enforces/'' /home/negz/control/crossplane/docs/content/v1.20/guides/change-logs.md\n\nsed -i ''84s/will select/selects/'' /home/negz/control/crossplane/docs/content/v2.0-preview/composition/composition-revisions.md\nsed -i ''128s/will assign/assigns/'' /home/negz/control/crossplane/docs/content/v2.0-preview/composition/composition-revisions.md\nsed -i ''314s/will also/also/'' /home/negz/control/crossplane/docs/content/v2.0-preview/composition/composition-revisions.md\nsed -i ''293s/will automatically/automatically/'' /home/negz/control/crossplane/docs/content/v2.0-preview/packages/configurations.md\nsed -i ''317s/will automatically/automatically/'' /home/negz/control/crossplane/docs/content/v2.0-preview/packages/providers.md)", + "Bash(rg:*)", + "Bash(do sed -i 's/a large number of CRDs/many CRDs/g' \"content/$file/guides/crossplane-with-argo-cd.md\")", + "Bash(/dev/null)", + "Bash(hugo:*)", + "Bash(jq:*)", + "Bash(gh pr view:*)", + "Bash(gh pr diff:*)", + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(htmltest:*)", + "WebFetch(domain:protobuf.dev)", + "WebFetch(domain:googleapis.dev)", + "Bash(mkdir:*)", + "Bash(mv:*)", + "Bash(true)", + "Bash(crossplane alpha render:*)", + "Bash(md5sum:*)", + "Bash(cp:*)", + "Bash(kubectl apply:*)", + "Bash(kubectl get:*)", + "Bash(kubectl describe:*)", + "Bash(kubectl delete:*)", + "Bash(/tmp/crank-fixed:*)", + "WebFetch(domain:github.com)", + "Bash(gh api:*)" + ], + "additionalDirectories": [ + "/home/negz/control/crossplane/crossplane" + ] + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..15dd314b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,272 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with +code in this repository. + +## Project Overview + +This is the repository for the [Crossplane documentation](https://docs.crossplane.io). +The documentation site is built using [Hugo](https://gohugo.io/) and hosted on +Netlify. The site provides comprehensive documentation for Crossplane, a cloud +native control plane framework. + +## Development Commands + +### Local Development +- `hugo server` - Start local development server on http://localhost:1313 +- `hugo server --minify` - Start server with minified output +- `hugo --minify` - Build static site with minified output + +### Prerequisites +- Hugo Extended version (required for SCSS/CSS processing) +- Node.js and npm (for PostCSS processing) +- Git (for content management) +- Vale (for style linting) + +### Nix Development Environment +For Nix users, a `shell.nix` file is provided with all necessary dependencies: + +```bash +nix-shell # Enter development environment with Hugo, Vale, Node.js, and utilities +``` + +The Nix shell includes: +- Hugo (extended version) +- Vale prose linter +- Node.js 20 with npm +- HTML validation tools +- Image processing utilities (ImageMagick) +- JSON/YAML processing tools (jq, yq) + +### Building and Deployment +- Site automatically builds on Netlify using `netlify_build.sh` +- Uses Hugo version 0.119.0 (specified in netlify.toml) +- Production URL: https://docs.crossplane.io/ +- Preview deployments available for PRs + +## Repository Structure + +### Content Organization +- `content/` - All documentation content organized by version + - `v1.18/`, `v1.19/`, `v1.20/` - Version-specific documentation + - `master/` - Next release documentation + - `v2.0-preview/` - Preview documentation for v2.0 + - `contribute/` - Contributing guidelines and style guides +- `static/` - Static assets (images, icons, etc.) +- `themes/geekboot/` - Custom Hugo theme based on Geekdoc and Bootstrap +- `utils/` - Development utilities (Vale style checking, webpack config) + +### Key Configuration Files +- `config.yaml` - Hugo site configuration +- `netlify.toml` - Netlify build and deployment configuration +- `netlify_build.sh` - Custom build script for version management +- `package.json` - PostCSS dependencies for CSS optimization + +## Documentation Architecture + +### Version Management +- Each Crossplane version has its own content directory +- Latest version (currently 1.20) is copied to `/latest` during build +- Version dropdown menu allows switching between versions +- Automatic redirects for EOL versions + +### Content Types +- **Getting Started** - Installation and basic usage guides +- **Concepts** - Core Crossplane concepts and architecture +- **Guides** - How-to guides and advanced usage patterns +- **API Reference** - CRD documentation generated from YAML +- **CLI Reference** - Command reference documentation + +### Hugo Features Used +- Custom shortcodes for enhanced functionality (tabs, hints, code highlighting) +- Front matter for metadata (version, weight, state, descriptions) +- Table of contents generation +- Syntax highlighting with line numbers +- Image optimization and processing +- RSS feeds for sections + +## Writing Guidelines + +### Style Guide Essentials +- Use active voice, avoid passive voice +- Present tense, avoid "will" +- Sentence-case headings +- Wrap lines at 80 characters +- Spell out numbers less than 10 +- Use contractions (don't, can't, isn't) +- No Oxford commas +- U.S. English spelling and grammar +- Capitalize "Crossplane" and "Kubernetes" (never "k8s") + +### Content Structure +- Each page requires front matter with `title` and `weight` +- Use `state: alpha` or `state: beta` for feature lifecycle +- Include `alphaVersion` and `betaVersion` for feature tracking +- Use descriptive link text, avoid "click here" +- Order brand names alphabetically (AWS, Azure, GCP) + +### Code and Technical Content +- Use inline code style (backticks) for files, directories, paths +- Use angle brackets for placeholders (``) +- Kubernetes objects: use UpperCamelCase for Kinds, kebab-case for names +- Use hover shortcodes to relate explanations to code examples + +## Development Workflow + +### Contributing Process +1. Clone the repository: `git clone https://github.com/crossplane/docs.git` +2. Set up development environment: + - **With Nix**: Run `nix-shell` to enter development environment + - **Without Nix**: Install Hugo Extended, Vale, and Node.js manually +3. Run `hugo server` for local development +4. Make changes to appropriate version directory in `/content` +5. Test locally at http://localhost:1313 +6. Run `vale content/` to check style compliance +7. Submit PR for review + +### Content Management +- Create new content as markdown files in appropriate version directories +- Use `_index.md` for section landing pages +- Include proper front matter for all pages +- Test with Vale linter for style compliance +- Images should be optimized and placed in appropriate directories + +### Quality Assurance +- Vale linter enforces style guide compliance +- HTML validation with htmltest +- Automated Netlify preview deployments for all PRs +- Manual review process for content accuracy + +## Build System Details + +### Hugo Configuration +- Uses custom "geekboot" theme (based on Geekdoc + Bootstrap) +- Goldmark renderer with unsafe HTML enabled +- Syntax highlighting with line numbers and anchor links +- Module mounts for content and asset processing +- Table of contents generation (levels 1-9) + +### CSS and Assets +- PostCSS with PurgeCSS for optimization +- Custom SCSS in theme directory +- Responsive design with Bootstrap framework +- Font loading for Avenir and Consolas +- Icon system with SVG assets + +### Netlify Integration +- Environment-specific base URLs +- Automatic redirects for moved/deprecated content +- Build optimization with writeStats for PurgeCSS +- Deploy preview URLs for testing + +## Common Tasks + +### Adding New Documentation +1. Create markdown file in appropriate version directory +2. Add front matter with title, weight, and optional state +3. Follow style guide for writing +4. Add to multiple versions if needed +5. Test locally with Hugo server + +### Version Management +- Copy content between version directories as needed +- Update version references in netlify_build.sh +- Ensure redirects are configured for moved content +- Test version switching functionality + +### Style and Linting +- Run Vale linter: `vale content/` +- Check for style guide compliance +- Validate HTML structure +- Ensure proper image optimization + +## Important Files + +- `config.yaml` - Hugo site configuration and parameters +- `netlify_build.sh` - Build script with version management logic +- `shell.nix` - Nix development environment with all dependencies +- `content/contribute/` - Comprehensive contributing guidelines +- `themes/geekboot/layouts/` - Hugo templates and partials +- `utils/vale/` - Vale style checking configuration + +## Vale Linting Guidelines + +**CRITICAL: The documentation uses Vale for strict style enforcement. ALL errors and warnings MUST be fixed before merging. Writing Vale-compliant content from the start saves significant time - fixing linting issues after writing is much more time-consuming than avoiding them initially.** + +Here are common issues to avoid: + +### Common Vale Errors + +**Spelling Issues:** +- **API field names**: Put in backticks (`lastScheduleTime`) rather than adding to dictionaries +- **Technical terms**: Add Crossplane-specific terms to `utils/vale/styles/Crossplane/crossplane-words.txt` +- **General tech terms**: Add to `utils/vale/styles/Crossplane/allowed-jargon.txt` +- **Hyphenated terms**: Add to `utils/vale/styles/Crossplane/spelling-exceptions.txt` +- **Resource kinds**: When referring to Kubernetes resource kinds (Operation, CronOperation), these are correct - use Vale disable comments for false positives + +### Common Vale Warnings + +**Headings:** +- Use sentence-case, not title-case: "How operations work" not "How Operations Work" +- Exception: Technical terms like CronOperation in headings need disable comments +- Use `` around technical headings + +**Word Choice Issues:** +- **Weasel words**: Avoid "many", "various", "numerous" → use "several", "multiple", "some" +- **Too wordy**: "terminate" → "stop", "monitor" → "check" (unless monitoring is the correct technical term) +- **Future tense**: "won't start" → "don't start", avoid "will" → use present tense + +**Passive Voice:** +- "Operations are designed for" → "Operations focus on" +- "may be terminated" → "may stop" +- "being watched" → "under watch" +- "is needed" → "you need" + +**Other Issues:** +- **Ordinal numbers**: "1st" → "first" +- **Adverbs**: Remove "gracefully", "correctly", "properly", "repeatedly" +- **Contractions**: Use "can't" instead of "cannot" + +### Vale Disable Comments + +Use disable comments for legitimate technical terms that trigger false positives: + +```markdown + +### CronOperation + + + +Monitor resource usage carefully. + +``` + +### Dictionary Management + +- **`crossplane-words.txt`**: Crossplane-specific terms only (CronOperation, XRD, etc.) +- **`allowed-jargon.txt`**: General technical terms (kubectl, ConfigMap, etc.) +- **`spelling-exceptions.txt`**: Hyphenated terms (day-two, self-signed, etc.) +- Keep all dictionaries sorted alphabetically + +### Testing Vale + +**ALWAYS run Vale before considering documentation complete:** + +```bash +# Check only warnings and errors (ignore suggestions) +vale --minAlertLevel=warning content/ + +# Get structured output for analysis +vale --output=JSON content/ | jq '.[][] | select(.Severity == "warning")' +``` + +**Remember: Writing documentation that follows these guidelines from the start is much faster than writing first and fixing Vale issues later. The time investment in learning these patterns pays off immediately.** + +## Session Management + +- **Pre-Compaction Analysis**: Before compacting chat history, provide a + structured session summary including: + - Documentation updates made and their impact + - Important learnings about Hugo, documentation patterns, or writing guidelines + - Potential updates to CLAUDE.md based on new documentation features or workflows + - Any recurring style or technical issues encountered \ No newline at end of file diff --git a/content/master/whats-crossplane/_index.md b/content/master/whats-crossplane/_index.md index f6d3ba58..a558bd2c 100644 --- a/content/master/whats-crossplane/_index.md +++ b/content/master/whats-crossplane/_index.md @@ -237,6 +237,12 @@ maintenance. Instead of continuously managing resources, they perform specific tasks and report the results. + +Say you want your control plane to watch SSL certificates on Kubernetes +`Ingress` resources. When someone creates an Operation, the control plane +should check the certificate and annotate the `Ingress` with expiry information. + + ```mermaid flowchart TD user(User)