mirror of https://github.com/crossplane/docs.git
Merge pull request #956 from negz/whats-op-doc
First pass at Operations docs
This commit is contained in:
commit
704d5dfe38
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 (`<placeholder_name>`)
|
||||||
|
- 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 `<!-- vale Google.Headings = NO -->` 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
|
||||||
|
<!-- vale Google.Headings = NO -->
|
||||||
|
### CronOperation
|
||||||
|
<!-- vale Google.Headings = YES -->
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
Monitor resource usage carefully.
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
@ -23,6 +23,8 @@ Crossplane organizes its documentation into the following sections:
|
||||||
|
|
||||||
* [Composition]({{<ref "composition">}}) covers the key concepts of composition.
|
* [Composition]({{<ref "composition">}}) covers the key concepts of composition.
|
||||||
|
|
||||||
|
* [Operations]({{<ref "operations">}}) covers the key concepts of operations.
|
||||||
|
|
||||||
* [Managed Resources]({{<ref "managed-resources">}}) covers the key concepts of
|
* [Managed Resources]({{<ref "managed-resources">}}) covers the key concepts of
|
||||||
managed resources.
|
managed resources.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- vale Crossplane.Spelling = NO -->
|
||||||
|
**The `Operation` runs once to completion, like a Kubernetes `Job`.**
|
||||||
|
<!-- vale Crossplane.Spelling = YES -->
|
||||||
|
|
||||||
|
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]({{<ref "install">}}) with Operations enabled
|
||||||
|
|
||||||
|
{{<hint "tip">}}
|
||||||
|
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"}'
|
||||||
|
```
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
{{<hint "tip">}}
|
||||||
|
`Operations` show `SUCCEEDED=True` when they complete successfully.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
{{<hint "tip">}}
|
||||||
|
The `status.pipeline` field shows the output returned by each function step.
|
||||||
|
Use this field for tracking what the operation accomplished.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
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 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
{{<hint "tip">}}
|
||||||
|
This pattern shows how `Operations` can both read and change existing Kubernetes
|
||||||
|
resources. The `Operation` annotated the `Ingress` with certificate expiry
|
||||||
|
information that other tools can use for monitoring and alerting.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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**]({{<ref "../operations/operation">}}) - Core
|
||||||
|
`Operation` features and best practices
|
||||||
|
* [**`CronOperation`**]({{<ref "../operations/cronoperation">}}) - Schedule
|
||||||
|
operations to run automatically
|
||||||
|
* [**`WatchOperation`**]({{<ref "../operations/watchoperation">}}) - Trigger
|
||||||
|
operations when resources change
|
||||||
|
|
||||||
|
Explore the complete [Operations documentation]({{<ref "../operations">}}) for
|
||||||
|
advanced features and examples.
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: Operations
|
||||||
|
weight: 52
|
||||||
|
state: alpha
|
||||||
|
alphaVersion: 2.0
|
||||||
|
description: Understand Crossplane's Operations feature
|
||||||
|
---
|
||||||
|
|
@ -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]({{<ref "operation">}}) on a schedule,
|
||||||
|
like Kubernetes CronJobs. Use CronOperations for recurring operational tasks
|
||||||
|
such as database backups, certificate rotation, or periodic maintenance.
|
||||||
|
|
||||||
|
<!-- vale Google.Headings = NO -->
|
||||||
|
## How CronOperations work
|
||||||
|
<!-- vale Google.Headings = YES -->
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
{{<hint "important">}}
|
||||||
|
CronOperations are an alpha feature. You must enable Operations by adding
|
||||||
|
`--enable-operations` to Crossplane's arguments.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
```console {linenos=false,copy-lines="none"}
|
||||||
|
┌───────────── 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
|
||||||
|
|
||||||
|
{{<hint "note">}}
|
||||||
|
The following examples use hypothetical functions for illustration. At launch,
|
||||||
|
only function-python supports operations.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
{{<hint "important">}}
|
||||||
|
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
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
### Monitoring
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
Monitor CronOperations using:
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
|
||||||
|
```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]({{<ref "operation#best-practices">}}).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
<!-- vale Google.Headings = NO -->
|
||||||
|
### CronOperation not creating Operations
|
||||||
|
<!-- vale Google.Headings = YES -->
|
||||||
|
|
||||||
|
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]({{<ref "operation">}}) for one-time operational tasks
|
||||||
|
- Learn about [WatchOperation]({{<ref "watchoperation">}}) for reactive operations
|
||||||
|
- [Get started with Operations]({{<ref "../get-started/get-started-with-operations">}}) to try scheduling your first operation
|
||||||
|
|
@ -0,0 +1,592 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
{{<hint "important">}}
|
||||||
|
Operations are an alpha feature. You must enable them by adding
|
||||||
|
`--enable-operations` to Crossplane's arguments.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
{{<hint "note">}}
|
||||||
|
The following examples use hypothetical functions for illustration. At launch,
|
||||||
|
only function-python supports operations.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
{{<hint "note">}}
|
||||||
|
The [RBAC manager]({{<ref "../guides/pods#rbac-manager-pod">}}) 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]({{<ref "../composition/compositions#grant-access-to-composed-resources">}}).
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
**Check operation status:**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl get operation my-operation -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
**View detailed information:**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl describe operation my-operation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common failure scenarios:**
|
||||||
|
|
||||||
|
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
|
||||||
|
status: "False"
|
||||||
|
reason: InvalidFunctionCapability
|
||||||
|
message: "Function function-name doesn't support operations"
|
||||||
|
```
|
||||||
|
*Solution*: use a function that declares `operation` capability.
|
||||||
|
|
||||||
|
3. **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.
|
||||||
|
|
||||||
|
4. **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)
|
||||||
|
|
||||||
|
{{<hint "important">}}
|
||||||
|
Use caution with Operations that change resources managed by other controllers.
|
||||||
|
Operations force ownership when applying changes, which can cause conflicts.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
{{<hint "tip">}}
|
||||||
|
See the [Crossplane CLI docs]({{<ref "../cli">}}) to
|
||||||
|
learn how to install and use the Crossplane CLI.
|
||||||
|
{{< /hint >}}
|
||||||
|
|
||||||
|
{{<hint "important">}}
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
|
{{<hint "tip">}}
|
||||||
|
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.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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]({{<ref "../get-started/get-started-with-operations">}}) to create your first Operation
|
||||||
|
- Learn about [CronOperation]({{<ref "cronoperation">}}) for scheduled operations
|
||||||
|
- Learn about [WatchOperation]({{<ref "watchoperation">}}) for reactive operations
|
||||||
|
|
@ -0,0 +1,599 @@
|
||||||
|
---
|
||||||
|
title: WatchOperation
|
||||||
|
weight: 130
|
||||||
|
state: alpha
|
||||||
|
alphaVersion: 2.0
|
||||||
|
description: WatchOperations create Operations when watched resources change
|
||||||
|
---
|
||||||
|
|
||||||
|
A `WatchOperation` creates [Operations]({{<ref "operation">}}) 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.
|
||||||
|
|
||||||
|
<!-- vale Google.Headings = NO -->
|
||||||
|
## How WatchOperations work
|
||||||
|
<!-- vale Google.Headings = YES -->
|
||||||
|
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
|
{{<hint "important">}}
|
||||||
|
WatchOperations are an alpha feature. You must enable Operations by adding
|
||||||
|
`--enable-operations` to Crossplane's arguments.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
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.
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
{{<hint "note">}}
|
||||||
|
The following examples use hypothetical functions for illustration. At launch,
|
||||||
|
only function-python supports operations.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
### 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.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")
|
||||||
|
|
||||||
|
# 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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if the service is available and process accordingly
|
||||||
|
service = request.get_required_resource(req, "related-service")
|
||||||
|
if service:
|
||||||
|
# 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"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
{{<hint "important">}}
|
||||||
|
**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.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
|
This pattern allows functions to:
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
### Monitoring
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
Monitor WatchOperations using:
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
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
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
<!-- vale write-good.TooWordy = NO -->
|
||||||
|
WatchOperations automatically inject the changed resource into the created
|
||||||
|
Operation using a special requirement name
|
||||||
|
`ops.crossplane.io/watched-resource`:
|
||||||
|
<!-- vale write-good.TooWordy = YES -->
|
||||||
|
|
||||||
|
```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]({{<ref "operation#best-practices">}}).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
<!-- vale Google.Headings = NO -->
|
||||||
|
### WatchOperation not creating Operations
|
||||||
|
<!-- vale Google.Headings = YES -->
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<!-- vale Google.Headings = NO -->
|
||||||
|
<!-- vale write-good.Weasel = NO -->
|
||||||
|
### Too many Operations created
|
||||||
|
<!-- vale write-good.Weasel = YES -->
|
||||||
|
<!-- vale Google.Headings = YES -->
|
||||||
|
|
||||||
|
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]({{<ref "operation">}}) for one-time operational tasks
|
||||||
|
- Learn about [CronOperation]({{<ref "cronoperation">}}) for scheduled operations
|
||||||
|
- [Get started with Operations]({{<ref "../get-started/get-started-with-operations">}}) to create your first reactive operation
|
||||||
|
|
@ -46,13 +46,14 @@ involved in writing a controller.
|
||||||
|
|
||||||
## Crossplane components
|
## Crossplane components
|
||||||
|
|
||||||
Crossplane has three major components:
|
Crossplane has four major components:
|
||||||
|
|
||||||
* [Composition](#composition)
|
* [Composition](#composition)
|
||||||
* [Managed resources](#managed-resources)
|
* [Managed resources](#managed-resources)
|
||||||
|
* [Operations](#operations)
|
||||||
* [Package manager](#package-manager)
|
* [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.
|
ones you need.
|
||||||
|
|
||||||
### Composition
|
### Composition
|
||||||
|
|
@ -223,6 +224,68 @@ GCP, Terraform, Helm, GitHub, etc to support Crossplane v2 soon.
|
||||||
<!-- vale gitlab.FutureTense = YES -->
|
<!-- vale gitlab.FutureTense = YES -->
|
||||||
{{</hint>}}
|
{{</hint>}}
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
<!-- vale Google.WordList = NO -->
|
||||||
|
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.
|
||||||
|
<!-- vale Google.WordList = YES -->
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
user(User)
|
||||||
|
|
||||||
|
subgraph control [Control Plane]
|
||||||
|
operation(SSL Monitor Operation)
|
||||||
|
|
||||||
|
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]({{<ref "../get-started/get-started-with-operations">}})
|
||||||
|
to see how operations work.
|
||||||
|
|
||||||
|
{{<hint "note">}}
|
||||||
|
Operations are an alpha feature available in Crossplane v2.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
### Package manager
|
### Package manager
|
||||||
|
|
||||||
The Crossplane package manager lets you install new managed resources and
|
The Crossplane package manager lets you install new managed resources and
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ description: Learn what's new in the Crossplane v2 preview
|
||||||
**Crossplane v2 makes Crossplane more useful, more intuitive, and less
|
**Crossplane v2 makes Crossplane more useful, more intuitive, and less
|
||||||
opinionated.**
|
opinionated.**
|
||||||
|
|
||||||
Crossplane v2 makes three major changes:
|
Crossplane v2 makes four major changes:
|
||||||
|
|
||||||
* **Composite resources are now namespaced**
|
* **Composite resources are now namespaced**
|
||||||
* **Managed resources are now namespaced**
|
* **Managed resources are now namespaced**
|
||||||
* **Composition supports any Kubernetes resource**
|
* **Composition supports any Kubernetes resource**
|
||||||
|
* **Operations enable operational workflows**
|
||||||
|
|
||||||
**Crossplane v2 is better suited to building control planes for applications,
|
**Crossplane v2 is better suited to building control planes for applications,
|
||||||
not just infrastructure.** It removes the need for awkward abstractions like
|
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.
|
to learn how to grant Crossplane access.
|
||||||
{{</hint>}}
|
{{</hint>}}
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
{{<hint "note">}}
|
||||||
|
Operations are an alpha feature in Crossplane v2.
|
||||||
|
{{</hint>}}
|
||||||
|
|
||||||
## Backward compatibility
|
## Backward compatibility
|
||||||
|
|
||||||
Crossplane v2 makes the following breaking changes:
|
Crossplane v2 makes the following breaking changes:
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,16 @@ CEL
|
||||||
CI
|
CI
|
||||||
CLI
|
CLI
|
||||||
cloud-native
|
cloud-native
|
||||||
cluster-scoped
|
|
||||||
cluster-wide
|
|
||||||
ClusterRole
|
ClusterRole
|
||||||
ClusterRoles
|
ClusterRoles
|
||||||
|
cluster-scoped
|
||||||
|
cluster-wide
|
||||||
command-line
|
command-line
|
||||||
ConfigMap
|
ConfigMap
|
||||||
|
ConfigMaps
|
||||||
CRD
|
CRD
|
||||||
|
cron
|
||||||
|
CronJobs
|
||||||
crt
|
crt
|
||||||
CSS
|
CSS
|
||||||
CUE
|
CUE
|
||||||
|
|
@ -39,6 +42,7 @@ float64
|
||||||
GitOps
|
GitOps
|
||||||
Go
|
Go
|
||||||
gRPC
|
gRPC
|
||||||
|
hostname
|
||||||
IAM
|
IAM
|
||||||
imagePullSecret
|
imagePullSecret
|
||||||
init.sh
|
init.sh
|
||||||
|
|
@ -46,12 +50,12 @@ IRSA
|
||||||
JSONPath
|
JSONPath
|
||||||
key-pair
|
key-pair
|
||||||
key-value
|
key-value
|
||||||
KV
|
|
||||||
kv
|
|
||||||
kube-apiserver
|
kube-apiserver
|
||||||
kube-controller-manager
|
|
||||||
kubeconfig
|
kubeconfig
|
||||||
|
kube-controller-manager
|
||||||
kubectl
|
kubectl
|
||||||
|
kv
|
||||||
|
KV
|
||||||
metrics-server
|
metrics-server
|
||||||
minikube
|
minikube
|
||||||
multi-platform
|
multi-platform
|
||||||
|
|
@ -62,8 +66,9 @@ NOTES.txt
|
||||||
OCI
|
OCI
|
||||||
OIDC
|
OIDC
|
||||||
PersistentVolumeClaim
|
PersistentVolumeClaim
|
||||||
Pre-releases
|
Prepopulate
|
||||||
pre-releases
|
pre-releases
|
||||||
|
Pre-releases
|
||||||
PriorityClass
|
PriorityClass
|
||||||
proselint
|
proselint
|
||||||
protobuf
|
protobuf
|
||||||
|
|
@ -101,5 +106,6 @@ TLS
|
||||||
tolerations
|
tolerations
|
||||||
UI
|
UI
|
||||||
VM
|
VM
|
||||||
|
walkthrough
|
||||||
webhooks.enabled
|
webhooks.enabled
|
||||||
YAML
|
YAML
|
||||||
|
|
@ -8,25 +8,30 @@ CombineFromComposite
|
||||||
CombineFromEnvironment
|
CombineFromEnvironment
|
||||||
CombineToComposite
|
CombineToComposite
|
||||||
CombineToEnvironment
|
CombineToEnvironment
|
||||||
composition.yaml
|
|
||||||
CompositeResourceDefinition
|
CompositeResourceDefinition
|
||||||
CompositeResourceDefinitions
|
CompositeResourceDefinitions
|
||||||
|
composition-only
|
||||||
CompositionRevision
|
CompositionRevision
|
||||||
CompositionRevisions
|
CompositionRevisions
|
||||||
|
composition.yaml
|
||||||
config
|
config
|
||||||
Configs
|
Configs
|
||||||
CONTRIBUTING.md
|
CONTRIBUTING.md
|
||||||
ControllerConfig
|
ControllerConfig
|
||||||
ControllerConfigs
|
ControllerConfigs
|
||||||
CRDs
|
CRDs
|
||||||
CRs
|
CronJobs
|
||||||
|
CronOperation
|
||||||
|
CronOperations
|
||||||
|
CronOperation-specific
|
||||||
Crossplane
|
Crossplane
|
||||||
crossplane-admin
|
crossplane-admin
|
||||||
crossplane-browse
|
crossplane-browse
|
||||||
crossplane-edit
|
crossplane-edit
|
||||||
|
Crossplane's
|
||||||
crossplane-view
|
crossplane-view
|
||||||
crossplane.yaml
|
crossplane.yaml
|
||||||
Crossplane's
|
CRs
|
||||||
CUE
|
CUE
|
||||||
definition.yaml
|
definition.yaml
|
||||||
deletionPolicy
|
deletionPolicy
|
||||||
|
|
@ -45,6 +50,7 @@ function-environment-configs
|
||||||
function-extra-resources
|
function-extra-resources
|
||||||
function-go-templating
|
function-go-templating
|
||||||
function-patch-and-transform
|
function-patch-and-transform
|
||||||
|
function-python
|
||||||
function-template-python
|
function-template-python
|
||||||
HealthyPackageRevision
|
HealthyPackageRevision
|
||||||
Helm-like
|
Helm-like
|
||||||
|
|
@ -57,6 +63,7 @@ LateInitialize
|
||||||
managementPolicies
|
managementPolicies
|
||||||
MR
|
MR
|
||||||
MRs
|
MRs
|
||||||
|
Operation-specific
|
||||||
PatchSet
|
PatchSet
|
||||||
PatchSets
|
PatchSets
|
||||||
ProviderConfig
|
ProviderConfig
|
||||||
|
|
@ -65,6 +72,7 @@ ProviderRevision
|
||||||
RunFunctionRequest
|
RunFunctionRequest
|
||||||
RunFunctionResponse
|
RunFunctionResponse
|
||||||
Sigstore
|
Sigstore
|
||||||
|
SSL
|
||||||
StoreConfig
|
StoreConfig
|
||||||
StoreConfigs
|
StoreConfigs
|
||||||
ToCompositeFieldPath
|
ToCompositeFieldPath
|
||||||
|
|
@ -74,15 +82,19 @@ TrimPrefix
|
||||||
TrimSuffix
|
TrimSuffix
|
||||||
UnhealthyPackageRevision
|
UnhealthyPackageRevision
|
||||||
UnknownPackageRevisionHealth
|
UnknownPackageRevisionHealth
|
||||||
|
ValidPipeline
|
||||||
|
WatchOperation
|
||||||
|
WatchOperations
|
||||||
|
WatchOperation-specific
|
||||||
XCluster
|
XCluster
|
||||||
XNetwork
|
XNetwork
|
||||||
xpkg
|
xpkg
|
||||||
xpkg.crossplane.io
|
xpkg.crossplane.io
|
||||||
xpkg.upbound.io
|
xpkg.upbound.io
|
||||||
XR
|
XR
|
||||||
XR's
|
|
||||||
XRC
|
XRC
|
||||||
XRD
|
XRD
|
||||||
XRD's
|
XRD's
|
||||||
XRDs
|
XRDs
|
||||||
|
XR's
|
||||||
XRs
|
XRs
|
||||||
|
|
@ -8,15 +8,15 @@ europe-central2
|
||||||
GCP
|
GCP
|
||||||
GCP's
|
GCP's
|
||||||
GKE
|
GKE
|
||||||
provider-upjet-aws
|
|
||||||
provider-upjet-gcp
|
|
||||||
provider-upjet-azure
|
|
||||||
provider-aws
|
provider-aws
|
||||||
provider-aws-iam
|
provider-aws-iam
|
||||||
provider-aws-s3
|
provider-aws-s3
|
||||||
provider-gcp
|
provider-gcp
|
||||||
provider-helm
|
provider-helm
|
||||||
provider-kubernetes
|
provider-kubernetes
|
||||||
|
provider-upjet-aws
|
||||||
|
provider-upjet-azure
|
||||||
|
provider-upjet-gcp
|
||||||
Pub/Sub
|
Pub/Sub
|
||||||
PubSub
|
PubSub
|
||||||
S3
|
S3
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
/tab
|
|
||||||
/tabs
|
|
||||||
backporting
|
backporting
|
||||||
built-in
|
built-in
|
||||||
call-outs
|
call-outs
|
||||||
|
|
@ -7,53 +5,82 @@ ClusterRoles`
|
||||||
comma-separated
|
comma-separated
|
||||||
conformant
|
conformant
|
||||||
cross-reference
|
cross-reference
|
||||||
Cross-resource
|
|
||||||
cross-resource
|
cross-resource
|
||||||
|
Cross-resource
|
||||||
datastore
|
datastore
|
||||||
|
day-two
|
||||||
double-check
|
double-check
|
||||||
double-checks
|
double-checks
|
||||||
dry-run
|
dry-run
|
||||||
dual-pushes
|
dual-pushes
|
||||||
|
e.g.
|
||||||
end-points
|
end-points
|
||||||
end-to-end
|
end-to-end
|
||||||
|
event-driven
|
||||||
free-form
|
free-form
|
||||||
function-based
|
function-based
|
||||||
|
google.com
|
||||||
hands-on
|
hands-on
|
||||||
|
hardcode
|
||||||
|
high-churn
|
||||||
how-to
|
how-to
|
||||||
|
idempotency
|
||||||
in-depth
|
in-depth
|
||||||
in-memory
|
in-memory
|
||||||
|
Job.
|
||||||
|
least-privilege
|
||||||
left-hand
|
left-hand
|
||||||
|
long-running
|
||||||
|
Long-running
|
||||||
|
low-risk
|
||||||
|
low-traffic
|
||||||
multi-cluster
|
multi-cluster
|
||||||
multi-region
|
multi-region
|
||||||
multi-tenant
|
Multi-step
|
||||||
multi-tenancy
|
multi-tenancy
|
||||||
|
multi-tenant
|
||||||
|
namespace-scoped
|
||||||
non-empty
|
non-empty
|
||||||
non-Kubernetes
|
non-Kubernetes
|
||||||
|
non-production
|
||||||
|
one-time
|
||||||
|
One-time
|
||||||
|
Operation-level
|
||||||
per-element
|
per-element
|
||||||
|
performant
|
||||||
per-object
|
per-object
|
||||||
per-resource
|
per-resource
|
||||||
poll-interval
|
poll-interval
|
||||||
pre-existing
|
pre-existing
|
||||||
|
preload
|
||||||
pre-provisioned
|
pre-provisioned
|
||||||
pre-release
|
pre-release
|
||||||
race-conditions
|
race-conditions
|
||||||
read-only
|
read-only
|
||||||
ready-made
|
ready-made
|
||||||
|
resource-intensive
|
||||||
resource-specific
|
resource-specific
|
||||||
right-hand
|
right-hand
|
||||||
run-time
|
run-time
|
||||||
self-signed
|
|
||||||
self-service
|
self-service
|
||||||
|
self-signed
|
||||||
space-delimited
|
space-delimited
|
||||||
|
status-checking
|
||||||
step-by-step
|
step-by-step
|
||||||
subresources
|
subresources
|
||||||
|
System-level
|
||||||
|
/tab
|
||||||
|
/tabs
|
||||||
third-party
|
third-party
|
||||||
|
Time-sensitive
|
||||||
top-level
|
top-level
|
||||||
unpause
|
unpause
|
||||||
untrusted
|
untrusted
|
||||||
UpperCamelCase
|
UpperCamelCase
|
||||||
UpperCamelCased
|
UpperCamelCased
|
||||||
user-defined
|
user-defined
|
||||||
|
user-provided
|
||||||
v2
|
v2
|
||||||
|
validators
|
||||||
version-specific
|
version-specific
|
||||||
backporting
|
webhook-based
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue