Operation¶
Group/Version/Kind: ops.vworkspace.io/v1alpha1 Operation
Scope: Namespaced
Status: Alpha
Last Updated: 2026-05-30
Overview¶
Operation is a single generic CRD for day-2 actions against an ApplicationInstance. It carries a type (the verb: Backup, Restore, Upgrade, Migration, RunCommand, Runbook), an engine (the executor: velero, workflow, job, helm, volsync, helmHookJob), a reference to the target application, an open parameters object, and optional approvals. The operator picks the engine, materializes the matching downstream resource (a velero.io/Backup, an argoproj.io/Workflow, a Kubernetes Job, ...), watches it, and aggregates conditions back.
The conceptual treatment is in ../concepts/day-2-operations.md. The reasoning behind a single generic CRD rather than one CRD per verb is in ../concepts/crds.md.
Example¶
apiVersion: ops.vworkspace.io/v1alpha1
kind: Operation
metadata:
name: nextcloud-myteam-backup-2026-05-28
namespace: org-myteam
labels:
app.vworkspace.io/managed-by: control-plane
app.vworkspace.io/cluster-id: cluster-prod-1
app.vworkspace.io/application-instance: nextcloud-myteam
spec:
targetRef:
apiVersion: apps.vworkspace.io/v1alpha1
kind: ApplicationInstance
name: nextcloud-myteam
type: Backup
engine: velero
parameters:
retention: "30d"
snapshotClassName: "csi-rbd"
status:
phase: Running
startedAt: "2026-05-28T10:00:00Z"
finishedAt: null
conditions: []
outputs:
backupName: "velero-backup-xyz"
spec¶
targetRef¶
| Field | Type | Required | Description |
|---|---|---|---|
targetRef.apiVersion |
string | Yes | The apiVersion of the target resource. Currently apps.vworkspace.io/v1alpha1. |
targetRef.kind |
string | Yes | The kind of the target resource. Currently ApplicationInstance. |
targetRef.name |
string | Yes | The name of the target ApplicationInstance. Must live in the same namespace as the Operation. |
The admission webhook rejects targetRef values that point at non-existent or cross-namespace targets.
type¶
The verb. Enum:
Backup— produce a point-in-time backup of the target.Restore— restore the target from a prior backup.Upgrade— change the chart version or values of the target's underlying release.Migration— run a structured migration (typically via Argo Workflows or a chart hook).RunCommand— execute a constrained command inside the target (typically via a KubernetesJob).Runbook— execute a curated multi-step runbook (typically via Argo Workflows).
engine¶
The executor for this operation. Enum:
velero— drive avelero.io/Backuporvelero.io/Restore.workflow— drive anargoproj.io/Workflow.job— drive a KubernetesJob.helm— drive an upgrade by patching the underlyingHelmRelease.volsync— drive avolsync.backube/ReplicationSourceorReplicationDestination.helmHookJob— invoke a named chart-provided hook by creating the chart's hookJobdirectly.
The combination of type and engine must be consistent with the target ApplicationInstance's capability annotations (ops.vworkspace.io/<capability>=<engine>). For example, type: Backup with engine: velero requires ops.vworkspace.io/backup=velero on the target. The admission webhook rejects mismatches.
parameters¶
An open object whose schema is determined by the catalog template that produced this Operation. Examples by engine:
velerobackup:retention,snapshotClassName,storageLocation,includeNamespaces.velerorestore:backupName,namespaceMapping,restorePVs.workflow:workflowTemplate,workflowTemplateParameters.job:image,command,args,env,serviceAccountName,timeoutSeconds.helm:targetVersion,valuesPatch.volsync:source,destination,schedule,copyMethod.helmHookJob:hookName.
The operator validates parameters against the engine's schema at apply time and produces a Blocked condition with an explicit reason if the parameters are missing or malformed.
approvals (optional)¶
| Field | Type | Required | Description |
|---|---|---|---|
approvals.required |
bool | No | Whether this operation requires an explicit approval claim before running. Catalog templates set this; admins can also set it explicitly. |
approvals.claim |
string | No | An opaque approval claim issued by Odoo (typically a short-lived signed token) attesting that the human approval workflow has been satisfied. The admission webhook verifies the claim and rejects unsigned or expired claims. |
approvals.approvedBy |
string | No | Human-readable identifier for the approver. Populated by Odoo at claim-issuance time and preserved for the audit log. |
approvals.approvedAt |
string | No | RFC 3339 timestamp of the approval. |
When approvals.required is true and approvals.claim is absent or invalid, the resource is admitted with a Blocked condition (reason AwaitingApproval) and the operator does not start the operation. Once a valid claim is supplied, the operator proceeds.
status¶
| Field | Type | Description |
|---|---|---|
status.phase |
enum | One of Pending, Running, Succeeded, Failed, Cancelled. A high-level summary; per-transition detail lives in status.conditions. |
status.startedAt |
string | RFC 3339 timestamp of the moment the operator started running the engine. Empty until the operation leaves Pending. |
status.finishedAt |
string | RFC 3339 timestamp of the terminal transition. Empty until the operation reaches Succeeded, Failed, or Cancelled. |
status.conditions[] |
[]Condition | Standard Kubernetes condition objects. The type vocabulary is Accepted, Running, Succeeded, Failed, Cancelled, Blocked; see conditions.md. |
status.outputs |
object | Engine-specific output references. Common keys: backupName, restoreName, workflowName, jobName, replicationName. The operator only writes keys relevant to the engine. |
status.logsRef |
object | Optional reference to where the engine's logs are persisted. Common shape: { kind, name, namespace } for a Workflow whose pod logs are addressable, or { url } for an external log store. |
Conditions¶
See conditions.md for the full list of condition types and reasons emitted on Operation.status.conditions[].
Admission rules¶
The operator ships a validating admission webhook (validate.operations.ops.vworkspace.io). The rules:
- Allowed
typeper namespace. Each namespace labeledmanaged-by=vworkspacecarries a configurable list of allowed operation types. The webhook rejects operations whosetypeis not allowed in the namespace. Typical examples:RestoreandMigrationare allowed only in non-production namespaces by default, with production namespaces opted in explicitly. - Engine/type consistency. The combination of
typeandenginemust match a catalog template the cluster knows about, and must match the targetApplicationInstance's capability annotation for that capability. - Target existence.
targetRef.namemust resolve to an existingApplicationInstancein the same namespace. Cross-namespace targets are not allowed. - Concurrency rules. Conflicting operations on the same target are rejected. The default conflict matrix:
- No two
Upgradeoperations on the same target. - No
Restorewhile anUpgradeis in progress. - No
Backupwhile aRestoreis in progress, and vice versa. RunCommandandRunbookoperations are concurrent-safe by default but can be marked exclusive in the catalog template if needed. Concurrency conflicts are admitted withBlocked=True(reasonConflictingOperation) rather than rejected outright, so the conflict is visible and the operation can proceed automatically once the conflicting one finishes.- Approvals. If the catalog template for
(type, engine, target catalog entry)requires approval,approvals.requiredmust be true andapprovals.claimmust be a valid Odoo-issued claim. The webhook verifies the claim's signature and expiry. - Immutability of identity.
spec.targetRef,spec.type, andspec.engineare immutable after creation. Editing any of them would change the meaning of an in-flight operation. The right pattern is to cancel the currentOperation(set an annotation or delete it) and create a new one.
The webhook errs on the side of admitting with a Blocked condition where possible, rather than rejecting outright, so the audit trail reflects what was attempted and why it did not proceed.