@intentius/chant-lexicon-k8s 0.1.13 → 0.1.15
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 +13 -5
- 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/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 +13 -0
- 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 +123 -0
- 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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARGO002: Application.spec.project must reference a declared AppProject
|
|
3
|
+
*
|
|
4
|
+
* An Argo `Application` names an `AppProject` in `spec.project`; the project is
|
|
5
|
+
* the RBAC and source/destination guardrail. If the named project isn't
|
|
6
|
+
* declared in the build, Argo will reject the Application at sync time. The
|
|
7
|
+
* built-in `default` project always exists on an Argo install, so a reference
|
|
8
|
+
* to `default` is never flagged.
|
|
9
|
+
*/
|
|
10
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
|
+
import { allManifests, manifestsOfKind } from "./argo-helpers";
|
|
12
|
+
|
|
13
|
+
export const argo002: PostSynthCheck = {
|
|
14
|
+
id: "ARGO002",
|
|
15
|
+
description: "Application.spec.project must reference a declared AppProject (or the built-in default)",
|
|
16
|
+
|
|
17
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
18
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
19
|
+
const manifests = allManifests(ctx);
|
|
20
|
+
|
|
21
|
+
const declaredProjects = new Set(
|
|
22
|
+
manifestsOfKind(manifests, "AppProject")
|
|
23
|
+
.map((p) => p.metadata?.name)
|
|
24
|
+
.filter((n): n is string => typeof n === "string"),
|
|
25
|
+
);
|
|
26
|
+
// Argo ships a built-in default project.
|
|
27
|
+
declaredProjects.add("default");
|
|
28
|
+
|
|
29
|
+
for (const app of manifestsOfKind(manifests, "Application")) {
|
|
30
|
+
const project = app.spec?.project;
|
|
31
|
+
const name = app.metadata?.name ?? "Application";
|
|
32
|
+
// No project set → defaults to `default` at sync time; not this check's job.
|
|
33
|
+
if (typeof project !== "string" || project === "") continue;
|
|
34
|
+
if (declaredProjects.has(project)) continue;
|
|
35
|
+
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "ARGO002",
|
|
38
|
+
severity: "error",
|
|
39
|
+
message: `Application "${name}" references AppProject "${project}", which is not declared. Add an AppProject named "${project}" or reference an existing project.`,
|
|
40
|
+
entity: name,
|
|
41
|
+
lexicon: "k8s",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return diagnostics;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARGO003: Application.spec.destination must reference a registered cluster
|
|
3
|
+
*
|
|
4
|
+
* An Argo `Application` targets a cluster via `spec.destination.server` (an API
|
|
5
|
+
* server URL) or `spec.destination.name` (a registered cluster name). External
|
|
6
|
+
* clusters are registered with a Secret labelled
|
|
7
|
+
* `argocd.argoproj.io/secret-type: cluster`. The in-cluster target
|
|
8
|
+
* (`https://kubernetes.default.svc` / name `in-cluster`) is always available.
|
|
9
|
+
* A destination that names neither a registered cluster nor the in-cluster
|
|
10
|
+
* target won't resolve at sync time.
|
|
11
|
+
*/
|
|
12
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
13
|
+
import {
|
|
14
|
+
allManifests,
|
|
15
|
+
manifestsOfKind,
|
|
16
|
+
isClusterSecret,
|
|
17
|
+
secretField,
|
|
18
|
+
IN_CLUSTER_SERVER,
|
|
19
|
+
IN_CLUSTER_NAME,
|
|
20
|
+
} from "./argo-helpers";
|
|
21
|
+
|
|
22
|
+
export const argo003: PostSynthCheck = {
|
|
23
|
+
id: "ARGO003",
|
|
24
|
+
description: "Application.spec.destination must reference a registered cluster or the in-cluster target",
|
|
25
|
+
|
|
26
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
27
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
28
|
+
const manifests = allManifests(ctx);
|
|
29
|
+
|
|
30
|
+
const registeredServers = new Set<string>([IN_CLUSTER_SERVER]);
|
|
31
|
+
const registeredNames = new Set<string>([IN_CLUSTER_NAME]);
|
|
32
|
+
for (const secret of manifests.filter(isClusterSecret)) {
|
|
33
|
+
const server = secretField(secret, "server");
|
|
34
|
+
if (server) registeredServers.add(server);
|
|
35
|
+
const name = secretField(secret, "name") ?? secret.metadata?.name;
|
|
36
|
+
if (typeof name === "string") registeredNames.add(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const app of manifestsOfKind(manifests, "Application")) {
|
|
40
|
+
const name = app.metadata?.name ?? "Application";
|
|
41
|
+
const destination = app.spec?.destination as
|
|
42
|
+
| { server?: unknown; name?: unknown }
|
|
43
|
+
| undefined;
|
|
44
|
+
|
|
45
|
+
if (!destination || (destination.server === undefined && destination.name === undefined)) {
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "ARGO003",
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: `Application "${name}" has no spec.destination.server or spec.destination.name — Argo cannot resolve a target cluster.`,
|
|
50
|
+
entity: name,
|
|
51
|
+
lexicon: "k8s",
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof destination.server === "string" && !registeredServers.has(destination.server)) {
|
|
57
|
+
diagnostics.push({
|
|
58
|
+
checkId: "ARGO003",
|
|
59
|
+
severity: "error",
|
|
60
|
+
message: `Application "${name}" targets cluster server "${destination.server}", which is not registered. Register it with a cluster Secret (label argocd.argoproj.io/secret-type=cluster) or use the in-cluster target.`,
|
|
61
|
+
entity: name,
|
|
62
|
+
lexicon: "k8s",
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof destination.name === "string" && !registeredNames.has(destination.name)) {
|
|
68
|
+
diagnostics.push({
|
|
69
|
+
checkId: "ARGO003",
|
|
70
|
+
severity: "error",
|
|
71
|
+
message: `Application "${name}" targets cluster name "${destination.name}", which is not registered. Register it with a cluster Secret or use the in-cluster target.`,
|
|
72
|
+
entity: name,
|
|
73
|
+
lexicon: "k8s",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return diagnostics;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARGO005: Application source.path should point at an existing directory (warn)
|
|
3
|
+
*
|
|
4
|
+
* When an Argo `Application` syncs from a git source it names a `source.path`
|
|
5
|
+
* inside the repo. In the common monorepo layout — Argo watches the same repo
|
|
6
|
+
* Chant builds — that path is relative to the build root, so a typo'd or moved
|
|
7
|
+
* path surfaces as a sync error only after deploy. This check warns when the
|
|
8
|
+
* path doesn't resolve to a directory under the build root.
|
|
9
|
+
*
|
|
10
|
+
* It is a warning, not an error: Applications that sync from a *different*
|
|
11
|
+
* remote repo legitimately reference paths that don't exist locally. Helm chart
|
|
12
|
+
* sources (`source.chart`) and pathless sources are skipped.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, statSync } from "fs";
|
|
15
|
+
import { isAbsolute, resolve } from "path";
|
|
16
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
17
|
+
import { allManifests, manifestsOfKind } from "./argo-helpers";
|
|
18
|
+
|
|
19
|
+
function dirExists(path: string): boolean {
|
|
20
|
+
try {
|
|
21
|
+
const full = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
22
|
+
return existsSync(full) && statSync(full).isDirectory();
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const argo005: PostSynthCheck = {
|
|
29
|
+
id: "ARGO005",
|
|
30
|
+
description: "Application source.path should resolve to an existing directory under the build root",
|
|
31
|
+
|
|
32
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
33
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
34
|
+
|
|
35
|
+
for (const app of manifestsOfKind(allManifests(ctx), "Application")) {
|
|
36
|
+
const name = app.metadata?.name ?? "Application";
|
|
37
|
+
const source = app.spec?.source as
|
|
38
|
+
| { path?: unknown; chart?: unknown }
|
|
39
|
+
| undefined;
|
|
40
|
+
if (!source) continue;
|
|
41
|
+
// Helm chart sources have no filesystem path.
|
|
42
|
+
if (typeof source.chart === "string" && source.chart !== "") continue;
|
|
43
|
+
|
|
44
|
+
const path = source.path;
|
|
45
|
+
if (typeof path !== "string" || path === "") continue;
|
|
46
|
+
if (dirExists(path)) continue;
|
|
47
|
+
|
|
48
|
+
diagnostics.push({
|
|
49
|
+
checkId: "ARGO005",
|
|
50
|
+
severity: "warning",
|
|
51
|
+
message: `Application "${name}" source.path "${path}" does not resolve to a directory under the build root. Confirm the path (or ignore if it lives in a different repo).`,
|
|
52
|
+
entity: name,
|
|
53
|
+
lexicon: "k8s",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return diagnostics;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -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) |
|
package/dist/types/index.d.ts
CHANGED
|
@@ -76,6 +76,40 @@ export declare class APIVersions {
|
|
|
76
76
|
readonly uid: string;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export declare class Application {
|
|
80
|
+
constructor(props: {
|
|
81
|
+
metadata: Record<string, unknown>;
|
|
82
|
+
/** ApplicationSpec represents desired application state. Contains link to repository with application definition and additional parameters link definition revision. */
|
|
83
|
+
spec: Record<string, unknown>;
|
|
84
|
+
/** Operation contains information about a requested or running operation */
|
|
85
|
+
operation?: Record<string, unknown>;
|
|
86
|
+
});
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly namespace: string;
|
|
89
|
+
readonly uid: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export declare class ApplicationSet {
|
|
93
|
+
constructor(props: {
|
|
94
|
+
metadata: Record<string, unknown>;
|
|
95
|
+
spec: Record<string, unknown>;
|
|
96
|
+
});
|
|
97
|
+
readonly name: string;
|
|
98
|
+
readonly namespace: string;
|
|
99
|
+
readonly uid: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export declare class AppProject {
|
|
103
|
+
constructor(props: {
|
|
104
|
+
metadata: Record<string, unknown>;
|
|
105
|
+
/** AppProjectSpec is the specification of an AppProject */
|
|
106
|
+
spec: Record<string, unknown>;
|
|
107
|
+
});
|
|
108
|
+
readonly name: string;
|
|
109
|
+
readonly namespace: string;
|
|
110
|
+
readonly uid: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
79
113
|
export declare class BatchJob {
|
|
80
114
|
constructor(props: {
|
|
81
115
|
/** Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */
|
package/package.json
CHANGED
package/src/codegen/docs.ts
CHANGED
|
@@ -397,6 +397,18 @@ Post-synth checks run against the serialized YAML after build.
|
|
|
397
397
|
| WK8302 | Single replica Deployment |
|
|
398
398
|
| WK8303 | HA Deployment without PodDisruptionBudget |
|
|
399
399
|
|
|
400
|
+
### Argo CD
|
|
401
|
+
|
|
402
|
+
Quality checks for the [Argo CD composites](/chant/lexicons/k8s/argo-composites/). ARGO001/ARGO004 are declarative (source AST); ARGO002/003/005 are post-synth (cross-resource / filesystem).
|
|
403
|
+
|
|
404
|
+
| Rule | Description |
|
|
405
|
+
|------|-------------|
|
|
406
|
+
| ARGO001 | Production \`Application\` enables automated \`prune\` without the \`argocd.chant.dev/allow-prune\` override |
|
|
407
|
+
| ARGO002 | \`Application.spec.project\` references an undeclared \`AppProject\` |
|
|
408
|
+
| ARGO003 | \`Application.spec.destination\` references an unregistered cluster |
|
|
409
|
+
| ARGO004 | \`ApplicationSet\` template doesn't scope to a single static \`AppProject\` |
|
|
410
|
+
| ARGO005 | \`Application\` \`source.path\` doesn't resolve to a directory (warn) |
|
|
411
|
+
|
|
400
412
|
## Running lint
|
|
401
413
|
|
|
402
414
|
\`\`\`bash
|
|
@@ -1139,6 +1151,7 @@ The lexicon also provides MCP (Model Context Protocol) tools and resources:
|
|
|
1139
1151
|
],
|
|
1140
1152
|
basePath: "/chant/lexicons/k8s/",
|
|
1141
1153
|
sidebarExtra: [
|
|
1154
|
+
{ label: "Argo CD Composites", slug: "argo-composites" },
|
|
1142
1155
|
{
|
|
1143
1156
|
label: "Vendor Composites",
|
|
1144
1157
|
items: [
|
package/src/codegen/versions.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* a helper to check for newer Kubernetes releases.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { fetchWithRetry } from "@intentius/chant/codegen/fetch";
|
|
9
|
+
|
|
8
10
|
/** Pinned versions of external dependencies. */
|
|
9
11
|
export const PINNED_VERSIONS = {
|
|
10
12
|
k8sOpenAPI: "v1.32.0",
|
|
@@ -24,13 +26,12 @@ export function k8sSwaggerUrl(version?: string): string {
|
|
|
24
26
|
*/
|
|
25
27
|
export async function checkForUpdates(): Promise<string | null> {
|
|
26
28
|
try {
|
|
27
|
-
const res = await
|
|
29
|
+
const res = await fetchWithRetry(
|
|
28
30
|
"https://api.github.com/repos/kubernetes/kubernetes/releases/latest",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
31
|
+
undefined,
|
|
32
|
+
undefined,
|
|
33
|
+
{ headers: { Accept: "application/vnd.github+json" } },
|
|
32
34
|
);
|
|
33
|
-
if (!res.ok) return null;
|
|
34
35
|
|
|
35
36
|
const data = (await res.json()) as { tag_name?: string };
|
|
36
37
|
const latest = data.tag_name;
|
|
@@ -38,6 +39,8 @@ export async function checkForUpdates(): Promise<string | null> {
|
|
|
38
39
|
|
|
39
40
|
return latest !== PINNED_VERSIONS.k8sOpenAPI ? latest : null;
|
|
40
41
|
} catch {
|
|
42
|
+
// Transient failures are retried inside fetchWithRetry; permanent
|
|
43
|
+
// failures (or exhausted retries) land here — treat as up-to-date.
|
|
41
44
|
return null;
|
|
42
45
|
}
|
|
43
46
|
}
|