@intentius/chant-lexicon-k8s 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/integrity.json +14 -6
- package/dist/manifest.json +1 -1
- package/dist/meta.json +216 -0
- package/dist/rules/argo-appset-single-project.ts +66 -0
- package/dist/rules/argo-ast.ts +121 -0
- package/dist/rules/argo-automated-prune.ts +75 -0
- package/dist/rules/argo-helpers.ts +49 -0
- package/dist/rules/argo002.ts +47 -0
- package/dist/rules/argo003.ts +80 -0
- package/dist/rules/argo005.ts +59 -0
- package/dist/rules/wk8301.ts +11 -3
- package/dist/skills/chant-k8s-argo.md +176 -0
- package/dist/types/index.d.ts +34 -0
- package/package.json +1 -1
- package/src/codegen/docs.ts +14 -1
- package/src/codegen/versions.ts +8 -5
- package/src/composites/argo-app.ts +380 -0
- package/src/composites/composites.test.ts +136 -0
- package/src/composites/index.ts +12 -0
- package/src/crd/crd-sources.ts +18 -0
- package/src/crd/loader.ts +4 -5
- package/src/crd/parser.test.ts +61 -0
- package/src/crd/parser.ts +37 -2
- package/src/describe-resources.ts +8 -1
- package/src/export-resources-io.test.ts +72 -0
- package/src/export-resources.ts +60 -0
- package/src/generated/index.d.ts +34 -0
- package/src/generated/index.ts +25 -0
- package/src/generated/lexicon-k8s.json +216 -0
- package/src/import/live-export.test.ts +114 -0
- package/src/import/live-export.ts +89 -0
- package/src/index.ts +5 -0
- package/src/lifecycle-integration.test.ts +111 -0
- package/src/lint/post-synth/argo-helpers.ts +49 -0
- package/src/lint/post-synth/argo002.ts +47 -0
- package/src/lint/post-synth/argo003.ts +80 -0
- package/src/lint/post-synth/argo005.ts +59 -0
- package/src/lint/post-synth/post-synth.test.ts +146 -2
- package/src/lint/post-synth/wk8301.ts +11 -3
- package/src/lint/rules/argo-appset-single-project.ts +66 -0
- package/src/lint/rules/argo-ast.ts +121 -0
- package/src/lint/rules/argo-automated-prune.ts +75 -0
- package/src/lint/rules/rules.test.ts +109 -0
- package/src/plugin.test.ts +6 -1
- package/src/plugin.ts +44 -1
- package/src/serializer-ownership.test.ts +44 -0
- package/src/serializer.test.ts +25 -0
- package/src/serializer.ts +9 -4
- package/src/skills/chant-k8s-argo.md +176 -0
package/src/plugin.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { k8sSerializer } from "./serializer";
|
|
|
15
15
|
import { hardcodedNamespaceRule } from "./lint/rules/hardcoded-namespace";
|
|
16
16
|
import { latestImageTagRule } from "./lint/rules/latest-image-tag";
|
|
17
17
|
import { missingResourceLimitsRule } from "./lint/rules/missing-resource-limits";
|
|
18
|
+
import { argoAutomatedPruneRule } from "./lint/rules/argo-automated-prune";
|
|
19
|
+
import { argoAppSetSingleProjectRule } from "./lint/rules/argo-appset-single-project";
|
|
18
20
|
import { k8sCompletions } from "./lsp/completions";
|
|
19
21
|
import { k8sHover } from "./lsp/hover";
|
|
20
22
|
import { K8sParser } from "./import/parser";
|
|
@@ -25,7 +27,13 @@ export const k8sPlugin: LexiconPlugin = {
|
|
|
25
27
|
serializer: k8sSerializer,
|
|
26
28
|
|
|
27
29
|
lintRules(): LintRule[] {
|
|
28
|
-
return [
|
|
30
|
+
return [
|
|
31
|
+
hardcodedNamespaceRule,
|
|
32
|
+
latestImageTagRule,
|
|
33
|
+
missingResourceLimitsRule,
|
|
34
|
+
argoAutomatedPruneRule,
|
|
35
|
+
argoAppSetSingleProjectRule,
|
|
36
|
+
];
|
|
29
37
|
},
|
|
30
38
|
|
|
31
39
|
postSynthChecks() {
|
|
@@ -566,10 +574,45 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
|
|
|
566
574
|
},
|
|
567
575
|
],
|
|
568
576
|
},
|
|
577
|
+
{
|
|
578
|
+
file: "chant-k8s-argo.md",
|
|
579
|
+
name: "chant-k8s-argo",
|
|
580
|
+
description: "Argo CD composites — ArgoAppFor, ArgoAppSetForRegions, AppProject scoping, cluster registration, and the Argo-vs-Temporal split",
|
|
581
|
+
triggers: [
|
|
582
|
+
{ type: "context", value: "argo" },
|
|
583
|
+
{ type: "context", value: "argo cd" },
|
|
584
|
+
{ type: "context", value: "argocd" },
|
|
585
|
+
{ type: "context", value: "gitops" },
|
|
586
|
+
{ type: "context", value: "application" },
|
|
587
|
+
{ type: "context", value: "applicationset" },
|
|
588
|
+
{ type: "context", value: "appproject" },
|
|
589
|
+
{ type: "context", value: "reconcile" },
|
|
590
|
+
],
|
|
591
|
+
parameters: [],
|
|
592
|
+
examples: [
|
|
593
|
+
{
|
|
594
|
+
title: "Application from a build target",
|
|
595
|
+
description: "Reconcile a Chant build target with Argo CD",
|
|
596
|
+
input: "Deploy my api target through Argo CD",
|
|
597
|
+
output: "import { ArgoAppFor } from \"@intentius/chant-lexicon-k8s\";\n\nexport const api = ArgoAppFor(\"api\", {\n repo: \"https://github.com/acme/infra\",\n path: \"dist/api\",\n destination: { server: \"https://kubernetes.default.svc\", namespace: \"api\" },\n});",
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
title: "Per-region ApplicationSet",
|
|
601
|
+
description: "Fan one app out across regional clusters",
|
|
602
|
+
input: "Deploy crdb to east, central, and west clusters via Argo",
|
|
603
|
+
output: "import { ArgoAppSetForRegions } from \"@intentius/chant-lexicon-k8s\";\n\nexport const crdb = ArgoAppSetForRegions(\n [\"east\", \"central\", \"west\"],\n (region) => ({ server: servers[region], namespace: `crdb-${region}`, path: `dist/${region}` }),\n { name: \"crdb\", repo: \"https://github.com/acme/infra\", project: \"crdb\" },\n);",
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
},
|
|
569
607
|
]),
|
|
570
608
|
|
|
571
609
|
async describeResources(options) {
|
|
572
610
|
const { describeResources } = await import("./describe-resources");
|
|
573
611
|
return describeResources(options);
|
|
574
612
|
},
|
|
613
|
+
|
|
614
|
+
async exportResources(options) {
|
|
615
|
+
const { exportResources } = await import("./export-resources");
|
|
616
|
+
return exportResources(options);
|
|
617
|
+
},
|
|
575
618
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { k8sSerializer } from "./serializer";
|
|
3
|
+
import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
|
|
4
|
+
|
|
5
|
+
function mockResource(entityType: string, props: Record<string, unknown>): any {
|
|
6
|
+
return { [DECLARABLE_MARKER]: true, lexicon: "k8s", entityType, kind: "resource", props };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe("k8sSerializer ownership stamping (#119)", () => {
|
|
10
|
+
test("stamps the ownership marker as labels when context.ownership is set", () => {
|
|
11
|
+
const entities = new Map<string, any>([
|
|
12
|
+
["web", mockResource("K8s::Apps::Deployment", { metadata: { name: "web" }, spec: { replicas: 1 } })],
|
|
13
|
+
]);
|
|
14
|
+
const yaml = k8sSerializer.serialize(entities, [], { ownership: { stack: "billing", env: "prod" } });
|
|
15
|
+
expect(yaml).toContain("app.kubernetes.io/managed-by: chant");
|
|
16
|
+
expect(yaml).toContain("chant.intentius.io/stack: billing");
|
|
17
|
+
expect(yaml).toContain("chant.intentius.io/env: prod");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("explicit resource labels still win over the stamped marker", () => {
|
|
21
|
+
const entities = new Map<string, any>([
|
|
22
|
+
[
|
|
23
|
+
"web",
|
|
24
|
+
mockResource("K8s::Apps::Deployment", {
|
|
25
|
+
metadata: { name: "web", labels: { "app.kubernetes.io/managed-by": "argocd" } },
|
|
26
|
+
spec: { replicas: 1 },
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
]);
|
|
30
|
+
const yaml = k8sSerializer.serialize(entities, [], { ownership: { stack: "billing" } });
|
|
31
|
+
expect(yaml).toContain("app.kubernetes.io/managed-by: argocd");
|
|
32
|
+
// stack identity still stamped (no explicit override)
|
|
33
|
+
expect(yaml).toContain("chant.intentius.io/stack: billing");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("no ownership context → no chant labels", () => {
|
|
37
|
+
const entities = new Map<string, any>([
|
|
38
|
+
["web", mockResource("K8s::Apps::Deployment", { metadata: { name: "web" }, spec: { replicas: 1 } })],
|
|
39
|
+
]);
|
|
40
|
+
const yaml = k8sSerializer.serialize(entities, []);
|
|
41
|
+
expect(yaml).not.toContain("app.kubernetes.io/managed-by: chant");
|
|
42
|
+
expect(yaml).not.toContain("chant.intentius.io/stack");
|
|
43
|
+
});
|
|
44
|
+
});
|
package/src/serializer.test.ts
CHANGED
|
@@ -128,6 +128,31 @@ describe("k8sSerializer", () => {
|
|
|
128
128
|
expect(result).not.toContain("spec:");
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
test("Argo Application serializes with argoproj.io/v1alpha1 GVK", () => {
|
|
132
|
+
const entities = new Map<string, any>();
|
|
133
|
+
entities.set(
|
|
134
|
+
"guestbook",
|
|
135
|
+
mockResource("K8s::Argo::Application", {
|
|
136
|
+
metadata: { name: "guestbook", namespace: "argocd" },
|
|
137
|
+
spec: {
|
|
138
|
+
project: "default",
|
|
139
|
+
source: {
|
|
140
|
+
repoURL: "https://github.com/argoproj/argocd-example-apps",
|
|
141
|
+
path: "guestbook",
|
|
142
|
+
targetRevision: "HEAD",
|
|
143
|
+
},
|
|
144
|
+
destination: { server: "https://kubernetes.default.svc", namespace: "guestbook" },
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const result = k8sSerializer.serialize(entities);
|
|
150
|
+
expect(result).toContain("apiVersion: argoproj.io/v1alpha1");
|
|
151
|
+
expect(result).toContain("kind: Application");
|
|
152
|
+
expect(result).toContain("name: guestbook");
|
|
153
|
+
expect(result).toContain("project: default");
|
|
154
|
+
});
|
|
155
|
+
|
|
131
156
|
test("Namespace is specless type", () => {
|
|
132
157
|
const entities = new Map<string, any>();
|
|
133
158
|
entities.set(
|
package/src/serializer.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import { createRequire } from "module";
|
|
9
9
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
10
10
|
import { isPropertyDeclarable } from "@intentius/chant/declarable";
|
|
11
|
-
import type { Serializer, SerializerResult } from "@intentius/chant/serializer";
|
|
11
|
+
import type { Serializer, SerializerResult, SerializeContext } from "@intentius/chant/serializer";
|
|
12
|
+
import { ownershipEntries } from "@intentius/chant/ownership";
|
|
12
13
|
import type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
13
14
|
import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
|
|
14
15
|
import { emitYAML } from "@intentius/chant/yaml";
|
|
@@ -153,15 +154,19 @@ export const k8sSerializer: Serializer = {
|
|
|
153
154
|
name: "k8s",
|
|
154
155
|
rulePrefix: "WK8",
|
|
155
156
|
|
|
156
|
-
serialize(entities: Map<string, Declarable>, _outputs?: LexiconOutput[]): string {
|
|
157
|
+
serialize(entities: Map<string, Declarable>, _outputs?: LexiconOutput[], context?: SerializeContext): string {
|
|
157
158
|
// Build reverse map: entity → name
|
|
158
159
|
const entityNames = new Map<Declarable, string>();
|
|
159
160
|
for (const [name, entity] of entities) {
|
|
160
161
|
entityNames.set(entity, name);
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
// Collect default labels and annotations
|
|
164
|
-
|
|
164
|
+
// Collect default labels and annotations. Ownership markers are stamped as
|
|
165
|
+
// labels, so they seed defaultLabelEntries and flow through the same merge
|
|
166
|
+
// (explicit resource labels still win).
|
|
167
|
+
let defaultLabelEntries: Record<string, unknown> = context?.ownership
|
|
168
|
+
? { ...ownershipEntries("label", context.ownership) }
|
|
169
|
+
: {};
|
|
165
170
|
let defaultAnnotationEntries: Record<string, unknown> = {};
|
|
166
171
|
|
|
167
172
|
for (const [, entity] of entities) {
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-k8s-argo
|
|
3
|
+
description: Argo CD composites for GitOps reconciliation — ArgoAppFor, ArgoAppSetForRegions, AppProject scoping, cluster registration, and the Argo-vs-Temporal split
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Argo CD Composites
|
|
8
|
+
|
|
9
|
+
Chant authors typed infrastructure into manifests. Argo CD continuously reconciles those manifests into a cluster. These composites are the opt-in bridge — the k8s lexicon itself stays runtime-agnostic and only emits YAML; nothing here is implied unless you reach for it.
|
|
10
|
+
|
|
11
|
+
## The three-layer model
|
|
12
|
+
|
|
13
|
+
| Layer | Owns | In Chant |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **Chant** | Authoring typed infra → manifests | the lexicons |
|
|
16
|
+
| **Argo CD** | Continuously reconciling declarative manifests (the apply layer) | `ArgoAppFor` / `ArgoAppSetForRegions` |
|
|
17
|
+
| **Temporal** | Procedural steps Argo can't express — ordering, signals, human gates, one-shot RPCs | the temporal lexicon + `waitForArgoSync` |
|
|
18
|
+
|
|
19
|
+
Rule of thumb: **if it's declarative and converges, let Argo reconcile it. If it's a procedure with ordering, gates, or out-of-band steps, orchestrate it in Temporal.** Prefer Argo CD over Argo Workflows — the procedural layer stays Temporal.
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
Argo CD must be installed in the target cluster before applying any Argo CRs:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
kubectl create namespace argocd
|
|
27
|
+
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.13.3/manifests/install.yaml
|
|
28
|
+
kubectl -n argocd wait deploy/argocd-server --for=condition=Available --timeout=180s
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## When to use which composite
|
|
32
|
+
|
|
33
|
+
| Composite | Use case |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `ArgoAppFor` | A single Chant build target reconciled by Argo |
|
|
36
|
+
| `ArgoAppSetForRegions` | The same app fanned out across regions/clusters from one declaration |
|
|
37
|
+
| `registerArgoCluster` | Teaching Argo about an external (non in-cluster) target |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## ArgoAppFor — one Application from a build target
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { ArgoAppFor } from "@intentius/chant-lexicon-k8s";
|
|
45
|
+
|
|
46
|
+
export const api = ArgoAppFor("api", {
|
|
47
|
+
repo: "https://github.com/acme/infra",
|
|
48
|
+
path: "dist/api",
|
|
49
|
+
destination: { server: "https://kubernetes.default.svc", namespace: "api" },
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
One call replaces ~30 lines of hand-written `Application` YAML. Defaults are production-friendly:
|
|
54
|
+
|
|
55
|
+
- **destination** — defaults to the in-cluster target (`https://kubernetes.default.svc`, namespace = target name) when omitted.
|
|
56
|
+
- **project** — defaults to `default`. Pass `project` to scope it to a declared `AppProject`.
|
|
57
|
+
- **syncPolicy** — defaults to **automated, non-pruning, self-healing** with `CreateNamespace=true`. Pass `syncPolicy: {}` for manual sync, or override fields explicitly.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
export const api = ArgoAppFor("api", {
|
|
61
|
+
repo: "https://github.com/acme/infra",
|
|
62
|
+
path: "dist/api",
|
|
63
|
+
project: "payments",
|
|
64
|
+
syncPolicy: { automated: { prune: false, selfHeal: true }, syncOptions: ["ServerSideApply=true"] },
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> **ARGO001** — on a *production* Application (name / namespace / destination namespace contains `prod`), automated `prune` must be `false` unless you opt in with the `argocd.chant.dev/allow-prune` annotation. Pruning deletes live resources that vanish from git; on prod that's a foot-gun.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ArgoAppSetForRegions — fan out across clusters
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { ArgoAppSetForRegions } from "@intentius/chant-lexicon-k8s";
|
|
76
|
+
|
|
77
|
+
const clusterServers: Record<string, string> = {
|
|
78
|
+
east: "https://east.example.com",
|
|
79
|
+
central: "https://central.example.com",
|
|
80
|
+
west: "https://west.example.com",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const crdb = ArgoAppSetForRegions(
|
|
84
|
+
["east", "central", "west"],
|
|
85
|
+
(region) => ({
|
|
86
|
+
server: clusterServers[region],
|
|
87
|
+
namespace: `crdb-${region}`,
|
|
88
|
+
path: `dist/${region}`,
|
|
89
|
+
}),
|
|
90
|
+
{ name: "crdb", repo: "https://github.com/acme/infra", project: "crdb" },
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Emits **one `ApplicationSet`** with a list generator — Argo expands it into one synced `Application` per region (`east-crdb`, `central-crdb`, `west-crdb`). The mapper resolves per-region values (`server`, `namespace`, `path`, `targetRevision`); the template interpolates them (`{{server}}`, `{{namespace}}`, `{{path}}`).
|
|
95
|
+
|
|
96
|
+
> **ARGO004** — the template scopes to a **single static** `AppProject`. `ArgoAppSetForRegions` always sets a static `project`; never template it (`project: "{{...}}"`) or the set sprays Applications across projects and defeats the RBAC boundary.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## AppProject scoping
|
|
101
|
+
|
|
102
|
+
An `AppProject` is the RBAC and source/destination guardrail for a group of Applications. Declare one and reference it by name:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { AppProject } from "@intentius/chant-lexicon-k8s";
|
|
106
|
+
|
|
107
|
+
export const payments = new AppProject({
|
|
108
|
+
metadata: { name: "payments", namespace: "argocd" },
|
|
109
|
+
spec: {
|
|
110
|
+
description: "Payments team applications",
|
|
111
|
+
sourceRepos: ["https://github.com/acme/infra"],
|
|
112
|
+
destinations: [{ server: "https://kubernetes.default.svc", namespace: "payments-*" }],
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
> **ARGO002** — every `Application.spec.project` must reference a declared `AppProject` (the built-in `default` is exempt). Declaring the project in the same build keeps the reference honest.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## registerArgoCluster — external clusters
|
|
122
|
+
|
|
123
|
+
The in-cluster target needs no registration. For any other cluster, emit the registration Secret:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { registerArgoCluster } from "@intentius/chant-lexicon-k8s";
|
|
127
|
+
|
|
128
|
+
export const east = registerArgoCluster({
|
|
129
|
+
name: "east",
|
|
130
|
+
server: "https://east.example.com",
|
|
131
|
+
config: { tlsClientConfig: { insecure: false }, bearerToken: process.env.EAST_TOKEN },
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Produces a `Secret` labelled `argocd.argoproj.io/secret-type: cluster`. After this, Applications can target the cluster by `destination.server: "https://east.example.com"` or `destination.name: "east"`.
|
|
136
|
+
|
|
137
|
+
> **ARGO003** — every `Application.spec.destination` must reference a registered cluster (a cluster Secret) or the in-cluster target. Register external clusters before pointing Applications at them.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## The Argo-vs-Temporal split
|
|
142
|
+
|
|
143
|
+
When a deploy has both declarative and procedural parts, let each layer own what it's good at. Example — the multi-region CockroachDB deploy:
|
|
144
|
+
|
|
145
|
+
| Step | Owner | Why |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| Apply shared + regional infra | **Argo** | Declarative, converges — Argo reconciles it |
|
|
148
|
+
| Install ESO / operators (Helm) | **Argo** | Declarative Helm source |
|
|
149
|
+
| Apply per-cluster K8s manifests | **Argo** (`ApplicationSet`) | One App per workload cluster |
|
|
150
|
+
| Wait for workloads Healthy | **Argo** (`Health=Healthy`) | Subsumed by Application health |
|
|
151
|
+
| Wait for DNS delegation | **Temporal** | Signal/update/auto-poll race — out of band |
|
|
152
|
+
| Generate + push TLS certs | **Temporal** | One-shot procedure, secrets not in git |
|
|
153
|
+
| `cockroach init`, configure regions | **Temporal** | Ordered one-shot RPCs |
|
|
154
|
+
|
|
155
|
+
From a Temporal workflow, gate procedural steps on Argo finishing a declarative apply with the `waitForArgoSync` activity (temporal lexicon, `argoSync` profile):
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// In a Temporal Op workflow:
|
|
159
|
+
await waitForArgoSync({ appName: "east-crdb", namespace: "argocd" });
|
|
160
|
+
// ...now run the procedural steps that depend on the workloads being Healthy.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`waitForArgoSync` is dependency-free — it polls the Application's status (`health=Healthy && sync=Synced`) and never imports the Argo CRD types.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Troubleshooting
|
|
168
|
+
|
|
169
|
+
| Symptom | Likely cause | Fix |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| Application stuck `OutOfSync` | Manual sync policy, no auto-sync | Set `syncPolicy.automated`, or sync via `argocd app sync <name>` |
|
|
172
|
+
| Application `Healthy` but resources missing | Wrong `destination.namespace` or `source.path` | Check ARGO005 (path) / ARGO003 (destination) |
|
|
173
|
+
| `ComparisonError: project not found` | `spec.project` references an undeclared `AppProject` | Declare the project (ARGO002) |
|
|
174
|
+
| `cluster ... not found` at sync | Destination cluster not registered | `registerArgoCluster` before targeting it (ARGO003) |
|
|
175
|
+
| Prod resources unexpectedly deleted | Automated `prune: true` on prod | Set `prune: false` (ARGO001) |
|
|
176
|
+
| `ApplicationSet` generates apps in wrong projects | Templated `spec.project` | Pin to a single static project (ARGO004) |
|