Skip to content

Commit 5d36fa0

Browse files
authored
docs: Document sync waves, hooks and delete policies (#526)
* document sync waves, hooks and delete policies * retitle
1 parent 1abbf00 commit 5d36fa0

File tree

4 files changed

+191
-5
lines changed

4 files changed

+191
-5
lines changed

generated/routes.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
},
1414
"/overview/management-api-reference": {
1515
"relPath": "/overview/management-api-reference.md",
16-
"lastmod": "2025-08-11T08:06:59.000Z"
16+
"lastmod": "2025-10-15T00:53:52.000Z"
1717
},
1818
"/overview/agent-api-reference": {
1919
"relPath": "/overview/agent-api-reference.md",
20-
"lastmod": "2025-04-18T01:44:02.000Z"
20+
"lastmod": "2025-10-15T00:53:52.000Z"
2121
},
2222
"/getting-started": {
2323
"relPath": "/getting-started/index.md",
@@ -119,6 +119,10 @@
119119
"relPath": "/plural-features/continuous-deployment/helm-service.md",
120120
"lastmod": "2025-06-09T19:18:57.000Z"
121121
},
122+
"/plural-features/continuous-deployment/resource-application-logic": {
123+
"relPath": "/plural-features/continuous-deployment/resource-application-logic.md",
124+
"lastmod": "2025-10-15T13:54:02.028Z"
125+
},
122126
"/plural-features/continuous-deployment/lua": {
123127
"relPath": "/plural-features/continuous-deployment/lua.md",
124128
"lastmod": "2025-07-15T12:44:58.000Z"
@@ -149,7 +153,7 @@
149153
},
150154
"/plural-features/k8s-upgrade-assistant/addon-compatibilities": {
151155
"relPath": "/plural-features/k8s-upgrade-assistant/addon-compatibilities.md",
152-
"lastmod": "2025-08-15T02:39:28.000Z"
156+
"lastmod": "2025-08-15T02:54:41.000Z"
153157
},
154158
"/plural-features/k8s-upgrade-assistant/cluster-drain": {
155159
"relPath": "/plural-features/k8s-upgrade-assistant/cluster-drain.md",
@@ -225,7 +229,7 @@
225229
},
226230
"/plural-features/plural-ai/multi-model-configuration": {
227231
"relPath": "/plural-features/plural-ai/multi-model-configuration.md",
228-
"lastmod": "2025-10-14T19:52:44.000Z"
232+
"lastmod": "2025-10-15T00:52:41.000Z"
229233
},
230234
"/plural-features/flows": {
231235
"relPath": "/plural-features/flows/index.md",
@@ -385,7 +389,7 @@
385389
},
386390
"/getting-started/agent-api-reference": {
387391
"relPath": "/overview/agent-api-reference.md",
388-
"lastmod": "2025-04-18T01:44:02.000Z"
392+
"lastmod": "2025-10-15T00:53:52.000Z"
389393
},
390394
"/getting-started/readme": {
391395
"relPath": "/getting-started/index.md",
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
title: Resource Application Logic
3+
description: Control deploy ordering with sync waves, run lifecycle hooks, and clean them up via hook delete policies
4+
---
5+
6+
## Overview
7+
8+
Plural’s deployment operator supports sync controls on any Kubernetes manifest you manage with Plural. You can:
9+
10+
- Order resource application using sync waves
11+
- Run lifecycle hooks at specific phases of a sync
12+
- Automatically clean up hook resources with delete policies
13+
14+
This lets you do things like run database migrations before an app rollout, seed data after,
15+
and strictly order dependencies across services.
16+
17+
## Sync waves
18+
19+
Sync waves allow you to define the order within a sync phase in which resources are applied. Lower waves run first.
20+
21+
You can use sync waves on any Kubernetes resource by simply adding an `deployment.plural.sh/sync-wave` annotation.
22+
We also support the Argo CD `argocd.argoproj.io/sync-wave` and Helm `helm.sh/hook-weight` annotations for compatibility.
23+
The value is an integer (as a quoted string) and can be negative.
24+
25+
Annotations are checked in that order of precedence, with `deployment.plural.sh/sync-wave`
26+
taking the highest precedence, then `argocd.argoproj.io/sync-wave`, then `helm.sh/hook-weight`. If none is set,
27+
then the default ordering will be used.
28+
29+
Default wave ordering is:
30+
- 0 - Non-namespaced resources (namespaces, CRDs, persistent volumes, cluster roles, etc.).
31+
- 1 - Core namespaced configuration resources (config maps, secrets, roles etc.).
32+
- 2 - Core namespaced workload resources (deployments, daemon sets, jobs, pods, etc.).
33+
- 3 - Core namespaced networking resources (services, ingresses, etc.).
34+
- 4 - All other resources.
35+
36+
The operator applies all resources in ascending wave order.
37+
38+
### Example
39+
40+
```yaml
41+
# Create a config map in wave -1, before the deployment in wave 6 and other resources in default waves
42+
apiVersion: v1
43+
kind: ConfigMap
44+
metadata:
45+
name: my-config
46+
annotations:
47+
deployment.plural.sh/sync-wave: "-1"
48+
---
49+
# Create the deployment in wave 6, after the config map and other resources in default waves
50+
apiVersion: apps/v1
51+
kind: Deployment
52+
metadata:
53+
name: my-app
54+
annotations:
55+
deployment.plural.sh/sync-wave: "6"
56+
```
57+
58+
## Sync hooks (phases)
59+
60+
Hook resources are created and monitored at specific phases of a sync.
61+
62+
Use the `deployment.plural.sh/sync-hook` annotation to designate a manifest as a hook. Alternatively,
63+
you can use Helm `helm.sh/hook` for compatibility. Commonly, jobs are used for hooks.
64+
65+
Supported phases are:
66+
- `pre-sync`: Run before `sync`. Use for DB migrations, pre-flight checks, or draining traffic.
67+
- `sync`: The default phase if none is specified.
68+
- `post-sync`: Run after `sync`. Useful for seeding data, smoke tests, or notifications.
69+
- `sync-fail`: Run only if the `sync` phase fails. Useful for rollbacks, alerts, or diagnostics.
70+
- `skip`: Do not apply this resource. Useful for temporarily disabling a resource without deleting it.
71+
72+
For `helm.sh/hook` we support the following values:
73+
- `pre-install` and `pre-upgrade`: equivalent to `pre-sync`.
74+
- `post-install` and `post-upgrade`: equivalent to `post-sync`.
75+
76+
A resource may belong to multiple phases, for example `pre-sync,post-sync`.
77+
78+
A resource can be both a hook and have a sync wave. This allows you to control the ordering of hooks relative
79+
to other hooks and resources.
80+
81+
The operator executes hooks starting in the described order and proceeds to the next phase only when all hooks
82+
in the current phase will apply and achieve a healthy state.
83+
84+
### Example
85+
86+
```yaml
87+
# Create a pre-sync migration job in wave -1
88+
apiVersion: batch/v1
89+
kind: Job
90+
metadata:
91+
name: migrate-db
92+
annotations:
93+
deployment.plural.sh/sync-hook: pre-sync
94+
deployment.plural.sh/sync-wave: "-1"
95+
spec:
96+
template:
97+
spec:
98+
restartPolicy: Never
99+
containers:
100+
- name: migrate
101+
image: myrepo/migrate:latest
102+
args: ["up"]
103+
```
104+
105+
## Hook delete policies (cleanup)
106+
107+
By default, hook resources are left in the cluster after they run. You can opt into automatic cleanup with
108+
`deployment.plural.sh/sync-hook-delete-policy` annotation. You can also use Helm `helm.sh/hook-delete-policy`
109+
annotation. Multiple policies can be comma-separated.
110+
111+
Use `hook-succeeded` and/or `hook-failed` to delete the resource after it completes successfully or fails, respectively.
112+
If no delete policy is set, the resource will be kept.
113+
114+
{% callout severity="warning" %}
115+
When a manifest of a hook is updated, the operator will reapply the resource even if it has already completed.
116+
{% /callout %}
117+
118+
### Example
119+
120+
```yaml
121+
# Create a pre-sync migration job that is deleted on success
122+
apiVersion: batch/v1
123+
kind: Job
124+
metadata:
125+
name: migrate-db
126+
annotations:
127+
deployment.plural.sh/sync-hook: pre-sync
128+
deployment.plural.sh/sync-hook-delete-policy: hook-succeeded
129+
spec:
130+
template:
131+
spec:
132+
restartPolicy: Never
133+
containers:
134+
- name: migrate
135+
image: myrepo/migrate:latest
136+
args: ["up"]
137+
```
138+
139+
## FAQ
140+
- Do I need a separate tool for these annotations to work?
141+
No. The operator understands these annotations and applies them as described.
142+
143+
- What happens if I don’t set a delete policy on a hook?
144+
The hook resource will be kept in the cluster so you can inspect logs and status. Add `hook-succeeded` and/or
145+
`hook-failed` to enable automatic cleanup.
146+
147+
- Can I control ordering between different hooks?
148+
Yes. Combine `deployment.plural.sh/sync-hook` with `deployment.plural.sh/sync-wave` on each hook.
149+
150+
- Are negative waves allowed?
151+
Yes. Negative, zero, and positive integers are supported. Lower numbers run first.

src/generated/graphql.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type Account = {
2929
billingAddress?: Maybe<Address>;
3030
billingCustomerId?: Maybe<Scalars['String']['output']>;
3131
clusterCount?: Maybe<Scalars['String']['output']>;
32+
consumerEmailDomains?: Maybe<Array<Maybe<Scalars['String']['output']>>>;
3233
delinquentAt?: Maybe<Scalars['DateTime']['output']>;
3334
domainMappings?: Maybe<Array<Maybe<DomainMapping>>>;
3435
grandfatheredUntil?: Maybe<Scalars['DateTime']['output']>;
@@ -519,11 +520,29 @@ export type ClusterInformationAttributes = {
519520
version?: InputMaybe<Scalars['String']['input']>;
520521
};
521522

523+
export type ClusterPingAttributes = {
524+
/** the cluster to ping */
525+
cluster: ClusterAttributes;
526+
/** the usage of the cluster */
527+
usage?: InputMaybe<ClusterUsageAttributes>;
528+
};
529+
530+
export type ClusterUsageAttributes = {
531+
/** the number of bytes ingested by the cluster */
532+
bytesIngested?: InputMaybe<Scalars['Int']['input']>;
533+
/** the number of clusters in the cluster */
534+
clusters?: InputMaybe<Scalars['Int']['input']>;
535+
/** the number of services deployed on the cluster */
536+
services?: InputMaybe<Scalars['Int']['input']>;
537+
};
538+
522539
/** A record of the utilization in a given cluster */
523540
export type ClusterUsageHistory = {
524541
__typename?: 'ClusterUsageHistory';
525542
account?: Maybe<Account>;
543+
bytesIngested?: Maybe<Scalars['Int']['output']>;
526544
cluster?: Maybe<Cluster>;
545+
clusters?: Maybe<Scalars['Int']['output']>;
527546
cpu?: Maybe<Scalars['Int']['output']>;
528547
insertedAt?: Maybe<Scalars['DateTime']['output']>;
529548
memory?: Maybe<Scalars['Int']['output']>;
@@ -574,6 +593,8 @@ export type ConsoleInstance = {
574593
console?: Maybe<Cluster>;
575594
/** the time this instance was deleted on */
576595
deletedAt?: Maybe<Scalars['DateTime']['output']>;
596+
/** the domain of this instance */
597+
domain?: Maybe<Scalars['String']['output']>;
577598
id: Scalars['ID']['output'];
578599
insertedAt?: Maybe<Scalars['DateTime']['output']>;
579600
/** the name of this instance (globally unique) */
@@ -2945,6 +2966,7 @@ export type RootMutationType = {
29452966
oauthCallback?: Maybe<User>;
29462967
oauthConsent?: Maybe<OauthResponse>;
29472968
passwordlessLogin?: Maybe<User>;
2969+
pingCluster?: Maybe<Cluster>;
29482970
pingWebhook?: Maybe<WebhookResponse>;
29492971
/** moves up the upgrade waterline for a user */
29502972
promote?: Maybe<User>;
@@ -3516,6 +3538,11 @@ export type RootMutationTypePasswordlessLoginArgs = {
35163538
};
35173539

35183540

3541+
export type RootMutationTypePingClusterArgs = {
3542+
attributes: ClusterPingAttributes;
3543+
};
3544+
3545+
35193546
export type RootMutationTypePingWebhookArgs = {
35203547
id: Scalars['ID']['input'];
35213548
message?: InputMaybe<Scalars['String']['input']>;

src/routing/docs-structure.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export const docsStructure: DocSection[] = [
8282
{ path: 'deployment-operator', title: 'The deployment operator' },
8383
{ path: 'git-service', title: 'Git-sourced services' },
8484
{ path: 'helm-service', title: 'Helm-sourced services' },
85+
{
86+
path: 'resource-application-logic',
87+
title: 'Resource Application Logic',
88+
},
8589
{ path: 'lua', title: 'Dynamic Helm Configuration with Lua Scripts' },
8690
{ path: 'global-service', title: 'Global services' },
8791
{

0 commit comments

Comments
 (0)