@intentius/chant-lexicon-k8s 0.124.30 → 0.124.32
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/dist/actions/apps.js +18 -0
- package/dist/actions/batch.js +6 -0
- package/dist/actions/core.js +48 -0
- package/dist/actions/index.js +13 -0
- package/dist/actions/networking.js +11 -0
- package/dist/actions/rbac.js +8 -0
- package/dist/api/classify.js +123 -0
- package/dist/api/connect.js +62 -0
- package/dist/api/fake-cluster.js +175 -0
- package/dist/api/operation-surface.d.ts.map +1 -1
- package/dist/api/operation-surface.js +109 -0
- package/dist/api/owner-chain.js +100 -0
- package/dist/api/sweep-types.js +35 -0
- package/dist/behaviour-kinds.js +82 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +146 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +74 -0
- package/dist/codegen/generate-list-map-keys.js +88 -0
- package/dist/codegen/generate-operations.js +53 -0
- package/dist/codegen/generate-typescript.js +124 -0
- package/dist/codegen/generate.js +173 -0
- package/dist/codegen/naming.js +174 -0
- package/dist/codegen/package.js +36 -0
- package/dist/codegen/patches.js +88 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/codegen/versions.js +41 -0
- package/dist/components/argo-app.js +51 -0
- package/dist/components/builders.js +22 -0
- package/dist/components/capability-plugin.js +31 -0
- package/dist/components/flux-reconcile.js +61 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +18 -0
- package/dist/components/kubectl-apply.d.ts +83 -1
- package/dist/components/kubectl-apply.d.ts.map +1 -1
- package/dist/components/kubectl-apply.js +274 -0
- package/dist/components/kustomize-apply.js +25 -0
- package/dist/composites/adot-collector.js +184 -0
- package/dist/composites/agic-ingress.js +85 -0
- package/dist/composites/aks-external-dns-agent.js +146 -0
- package/dist/composites/alb-ingress.js +86 -0
- package/dist/composites/argo-app.js +199 -0
- package/dist/composites/autoscaled-service.js +174 -0
- package/dist/composites/azure-disk-storage-class.js +47 -0
- package/dist/composites/azure-file-storage-class.js +48 -0
- package/dist/composites/azure-monitor-collector.js +176 -0
- package/dist/composites/batch-job.js +139 -0
- package/dist/composites/catalog.js +4110 -0
- package/dist/composites/cockroachdb-cluster.js +371 -0
- package/dist/composites/cockroachdb-region-stack.js +369 -0
- package/dist/composites/config-connector-context.js +40 -0
- package/dist/composites/configured-app.js +135 -0
- package/dist/composites/cron-schedule.js +38 -0
- package/dist/composites/cron-workload.js +109 -0
- package/dist/composites/ebs-storage-class.js +54 -0
- package/dist/composites/efs-storage-class.js +46 -0
- package/dist/composites/external-dns-agent.js +122 -0
- package/dist/composites/filestore-storage-class.js +49 -0
- package/dist/composites/fluent-bit-agent.js +157 -0
- package/dist/composites/flux-app.js +156 -0
- package/dist/composites/gce-ingress.js +83 -0
- package/dist/composites/gce-pd-storage-class.js +49 -0
- package/dist/composites/gke-external-dns-agent.js +129 -0
- package/dist/composites/gke-fluent-bit-agent.js +165 -0
- package/dist/composites/gke-gateway.js +91 -0
- package/dist/composites/gke-otel-collector.js +94 -0
- package/dist/composites/index.js +51 -0
- package/dist/composites/inference-service.js +76 -0
- package/dist/composites/irsa-service-account.js +77 -0
- package/dist/composites/member-defaults.js +14 -0
- package/dist/composites/metrics-server.js +181 -0
- package/dist/composites/model.js +92 -0
- package/dist/composites/monitored-service.js +152 -0
- package/dist/composites/namespace-env.js +114 -0
- package/dist/composites/network-isolated-app.js +124 -0
- package/dist/composites/node-agent.js +149 -0
- package/dist/composites/operator-stack.js +268 -0
- package/dist/composites/otel-collector-agent.js +142 -0
- package/dist/composites/otel-collector-gateway.js +216 -0
- package/dist/composites/otel-collector-node.js +176 -0
- package/dist/composites/otel-collector-rbac.js +115 -0
- package/dist/composites/otel-collector-shape.js +128 -0
- package/dist/composites/otel-collector.js +115 -0
- package/dist/composites/otel-operator-collector.js +123 -0
- package/dist/composites/ray-cluster.js +378 -0
- package/dist/composites/ray-job.js +136 -0
- package/dist/composites/ray-service.js +178 -0
- package/dist/composites/secure-ingress.js +100 -0
- package/dist/composites/security-context.js +1 -0
- package/dist/composites/sidecar-app.js +105 -0
- package/dist/composites/stateful-app.js +111 -0
- package/dist/composites/vllm-serving-runtime.js +114 -0
- package/dist/composites/web-app.js +157 -0
- package/dist/composites/worker-pool.js +167 -0
- package/dist/composites/workload-identity-sa.js +81 -0
- package/dist/composites/workload-identity-service-account.js +77 -0
- package/dist/config-schema.js +72 -0
- package/dist/config.js +59 -0
- package/dist/coverage.js +27 -0
- package/dist/crd/crd-sources.js +615 -0
- package/dist/crd/loader.js +181 -0
- package/dist/crd/oci-chart.js +144 -0
- package/dist/crd/parser.js +382 -0
- package/dist/crd/project-codegen.js +273 -0
- package/dist/crd/types.js +7 -0
- package/dist/deep-observe-hooks.js +306 -0
- package/dist/deep-observe.js +234 -0
- package/dist/default-labels.js +87 -0
- package/dist/describe-resources.js +699 -0
- package/dist/describe-stack-status.js +99 -0
- package/dist/detect.js +11 -0
- package/dist/effect-receipt-row.js +205 -0
- package/dist/export-resources.js +39 -0
- package/dist/generated/index.js +1092 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/group-namespace.js +96 -0
- package/dist/import/embedded.js +197 -0
- package/dist/import/generator.js +288 -0
- package/dist/import/live-export.js +85 -0
- package/dist/import/parser.js +204 -0
- package/dist/import/testdata/embedded/fixtures.js +60 -0
- package/dist/include/entity.js +78 -0
- package/dist/include/resolve.js +170 -0
- package/dist/index.js +59 -0
- package/dist/integrity.json +3 -3
- package/dist/kube/apply.js +90 -0
- package/dist/kube/connect.js +46 -0
- package/dist/kube/delete.js +138 -0
- package/dist/kube/describe.js +169 -0
- package/dist/kube/events.js +132 -0
- package/dist/kube/flags.js +91 -0
- package/dist/kube/get.js +159 -0
- package/dist/kube/group.js +123 -0
- package/dist/kube/logs.js +82 -0
- package/dist/kube/project.js +94 -0
- package/dist/kube/render.js +227 -0
- package/dist/kube/source.js +122 -0
- package/dist/kube/target.js +63 -0
- package/dist/kube/testing.js +28 -0
- package/dist/kube/top.js +99 -0
- package/dist/kube/verdict.js +72 -0
- package/dist/kube/wait.js +129 -0
- package/dist/kustomize/render.js +73 -0
- package/dist/kustomize/rendered-entity.js +67 -0
- package/dist/kustomize/root.js +68 -0
- package/dist/lint/audit-catalog.js +61 -0
- package/dist/lint/audit-lineage.js +118 -0
- package/dist/lint/post-synth/argo-helpers.js +36 -0
- package/dist/lint/post-synth/argo002.js +31 -0
- package/dist/lint/post-synth/argo003.js +53 -0
- package/dist/lint/post-synth/argo005.js +54 -0
- package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
- package/dist/lint/post-synth/flux002.js +56 -0
- package/dist/lint/post-synth/flux003.js +50 -0
- package/dist/lint/post-synth/index.js +104 -0
- package/dist/lint/post-synth/k8s-helpers.js +186 -0
- package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
- package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
- package/dist/lint/post-synth/sops-helpers.js +43 -0
- package/dist/lint/post-synth/wk8005.js +44 -0
- package/dist/lint/post-synth/wk8006.js +56 -0
- package/dist/lint/post-synth/wk8041.js +60 -0
- package/dist/lint/post-synth/wk8042.js +36 -0
- package/dist/lint/post-synth/wk8101.js +52 -0
- package/dist/lint/post-synth/wk8102.js +30 -0
- package/dist/lint/post-synth/wk8103.js +33 -0
- package/dist/lint/post-synth/wk8104.js +58 -0
- package/dist/lint/post-synth/wk8105.js +33 -0
- package/dist/lint/post-synth/wk8201.js +44 -0
- package/dist/lint/post-synth/wk8202.js +34 -0
- package/dist/lint/post-synth/wk8203.js +34 -0
- package/dist/lint/post-synth/wk8204.js +71 -0
- package/dist/lint/post-synth/wk8205.js +38 -0
- package/dist/lint/post-synth/wk8207.js +33 -0
- package/dist/lint/post-synth/wk8208.js +33 -0
- package/dist/lint/post-synth/wk8209.js +33 -0
- package/dist/lint/post-synth/wk8301.js +49 -0
- package/dist/lint/post-synth/wk8302.js +34 -0
- package/dist/lint/post-synth/wk8303.js +68 -0
- package/dist/lint/post-synth/wk8304.js +58 -0
- package/dist/lint/post-synth/wk8305.js +96 -0
- package/dist/lint/post-synth/wk8306.js +38 -0
- package/dist/lint/post-synth/wk8401.js +86 -0
- package/dist/lint/post-synth/wk8402.js +31 -0
- package/dist/lint/post-synth/wk8403.js +49 -0
- package/dist/lint/post-synth/wk8404.js +36 -0
- package/dist/lint/post-synth/wk8405.js +65 -0
- package/dist/lint/post-synth/wk8406.js +50 -0
- package/dist/lint/post-synth/wk8407.js +58 -0
- package/dist/lint/post-synth/wk8501.js +36 -0
- package/dist/lint/post-synth/wk8502.js +35 -0
- package/dist/lint/post-synth/wk8503.js +164 -0
- package/dist/lint/post-synth/wk8504.js +20 -0
- package/dist/lint/post-synth/wk8505.js +31 -0
- package/dist/lint/post-synth/wk8601.js +40 -0
- package/dist/lint/post-synth/wk8602.js +120 -0
- package/dist/lint/post-synth/wk8603.js +63 -0
- package/dist/lint/post-synth/wk8604.js +31 -0
- package/dist/lint/post-synth/wk8605.js +124 -0
- package/dist/lint/post-synth/wk8606.js +22 -0
- package/dist/lint/post-synth/wk8701.js +31 -0
- package/dist/lint/post-synth/wk8702.js +37 -0
- package/dist/lint/post-synth/wk8703.js +31 -0
- package/dist/lint/post-synth/wk8704.js +135 -0
- package/dist/lint/post-synth/wk8705.js +38 -0
- package/dist/lint/rules/argo-appset-single-project.js +56 -0
- package/dist/lint/rules/argo-ast.js +94 -0
- package/dist/lint/rules/argo-automated-prune.js +59 -0
- package/dist/lint/rules/flux-source-ref-pin.js +44 -0
- package/dist/lint/rules/hardcoded-namespace.js +57 -0
- package/dist/lint/rules/latest-image-tag.js +103 -0
- package/dist/lint/rules/missing-resource-limits.js +85 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +57 -0
- package/dist/manifest-entity.js +71 -0
- package/dist/manifest.json +1 -1
- package/dist/micro-time.js +49 -0
- package/dist/op/activities/apply-diff.d.ts +31 -0
- package/dist/op/activities/apply-diff.d.ts.map +1 -0
- package/dist/op/activities/apply-diff.js +79 -0
- package/dist/op/activities/argo.js +98 -0
- package/dist/op/activities/ensure-secret.js +38 -0
- package/dist/op/activities/index.js +51 -0
- package/dist/op/activities/kubectl.d.ts +64 -0
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/kubectl.js +517 -0
- package/dist/op/activities/wait-for-ready.js +229 -0
- package/dist/op/builders.js +68 -0
- package/dist/package-cli.js +21 -0
- package/dist/patch/json-patch.js +187 -0
- package/dist/patch/rendered-patch.js +94 -0
- package/dist/plugin.js +741 -0
- package/dist/project-kinds.js +56 -0
- package/dist/receipt-store.js +216 -0
- package/dist/rules/crd-schema-helpers.ts +2 -1
- package/dist/secret-labels.js +33 -0
- package/dist/secret-store.js +116 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +578 -0
- package/dist/sops/encrypted-secret-file.js +194 -0
- package/dist/sops/entity.js +32 -0
- package/dist/spec/fetch.js +58 -0
- package/dist/spec/parse.js +622 -0
- package/dist/subscribe-changes.js +168 -0
- package/dist/teardown.js +197 -0
- package/dist/telemetry-release.js +86 -0
- package/dist/validate-cli.js +17 -0
- package/dist/validate.js +47 -0
- package/dist/variables.js +34 -0
- package/dist/whoami.js +109 -0
- package/package.json +12 -12
- package/src/api/operation-surface.ts +2 -1
- package/src/codegen/package.ts +2 -2
- package/src/components/index.ts +3 -0
- package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
- package/src/components/kubectl-apply.test.ts +262 -0
- package/src/components/kubectl-apply.ts +341 -12
- package/src/deep-observe-hooks.ts +2 -2
- package/src/import/roundtrip-build.test.ts +46 -0
- package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/op/activities/apply-diff.ts +87 -0
- package/src/op/activities/kubectl-plan.test.ts +224 -0
- package/src/op/activities/kubectl.ts +204 -16
- package/src/serializer.ts +2 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flux CD composites — turn Chant build targets into Flux sources and
|
|
3
|
+
* `Kustomization`s.
|
|
4
|
+
*
|
|
5
|
+
* The k8s lexicon stays runtime-agnostic: it only emits manifests. These
|
|
6
|
+
* composites are the opt-in bridge to Flux's reconciliation layer, the
|
|
7
|
+
* `ArgoAppFor` analogue for the source-controller/kustomize-controller pair.
|
|
8
|
+
* Authoring a `GitRepository` + `Kustomization` by hand is ~40 lines of YAML
|
|
9
|
+
* per app; `FluxAppFor` collapses the pair to one call with defaults taken
|
|
10
|
+
* from real Flux estates.
|
|
11
|
+
*
|
|
12
|
+
* - FluxGitSource(name, opts) → a single K8s::Flux::GitRepository
|
|
13
|
+
* - FluxAppFor(target, opts) → a single K8s::Flux::Kustomization
|
|
14
|
+
*
|
|
15
|
+
* The two are split deliberately: the common estate shape is one source
|
|
16
|
+
* shared by many Kustomizations. A `GitRepository` per app is the mistake
|
|
17
|
+
* this split makes hard — declare the source once, hand its result to every
|
|
18
|
+
* `FluxAppFor` that reconciles a path out of it.
|
|
19
|
+
*/
|
|
20
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
21
|
+
import { GitRepository as GitRepositoryResource, Kustomization as KustomizationResource, } from "../generated/index.js";
|
|
22
|
+
// ── Shared types ─────────────────────────────────────────────────────────────
|
|
23
|
+
/** The namespace the Flux controllers run in and watch by default. */
|
|
24
|
+
const FLUX_NAMESPACE = "flux-system";
|
|
25
|
+
/** knr-ops's name for the bootstrap-injected age identity Secret, and the
|
|
26
|
+
* name `flux bootstrap` documentation uses. */
|
|
27
|
+
const SOPS_AGE_SECRET_NAME = "sops-age";
|
|
28
|
+
const FluxGitRepository = Composite((props) => {
|
|
29
|
+
const { name, url, branch = "main", tag, interval = "5m", secretRef, fluxNamespace = FLUX_NAMESPACE, labels = {}, defaults, } = props;
|
|
30
|
+
const gitRepository = new GitRepositoryResource(mergeDefaults({
|
|
31
|
+
metadata: {
|
|
32
|
+
name,
|
|
33
|
+
namespace: fluxNamespace,
|
|
34
|
+
labels: {
|
|
35
|
+
"app.kubernetes.io/name": name,
|
|
36
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
37
|
+
...labels,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
spec: {
|
|
41
|
+
interval,
|
|
42
|
+
url,
|
|
43
|
+
// An unset spec.ref falls back to `master` — always pin (FLUX001).
|
|
44
|
+
ref: tag !== undefined ? { tag } : { branch },
|
|
45
|
+
...(secretRef !== undefined && { secretRef: { name: secretRef } }),
|
|
46
|
+
},
|
|
47
|
+
}, defaults?.gitRepository));
|
|
48
|
+
return { gitRepository };
|
|
49
|
+
}, "FluxGitRepository");
|
|
50
|
+
/**
|
|
51
|
+
* Declare a Flux `GitRepository` source in one call.
|
|
52
|
+
*
|
|
53
|
+
* Declare it once and share it: every `FluxAppFor` that reconciles a path out
|
|
54
|
+
* of the same repo should take this result as its `source`.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { FluxGitSource } from "@intentius/chant-lexicon-k8s";
|
|
59
|
+
*
|
|
60
|
+
* export const source = FluxGitSource("home-chant", {
|
|
61
|
+
* url: "https://github.com/jhgaylor/home-chant",
|
|
62
|
+
* branch: "main",
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function FluxGitSource(name, options) {
|
|
67
|
+
return FluxGitRepository({ name, ...options });
|
|
68
|
+
}
|
|
69
|
+
/** Resolve the `source` option to a rendered sourceRef. */
|
|
70
|
+
function resolveSourceRef(target, source) {
|
|
71
|
+
if (typeof source === "string") {
|
|
72
|
+
return { kind: "GitRepository", name: source };
|
|
73
|
+
}
|
|
74
|
+
if ("gitRepository" in source && source.gitRepository !== undefined) {
|
|
75
|
+
const metadata = source.gitRepository
|
|
76
|
+
.props?.metadata;
|
|
77
|
+
const name = metadata?.name;
|
|
78
|
+
if (typeof name !== "string" || name === "") {
|
|
79
|
+
throw new Error(`FluxAppFor("${target}"): the supplied FluxGitSource result has no metadata.name to reference.`);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
kind: "GitRepository",
|
|
83
|
+
name,
|
|
84
|
+
...(typeof metadata?.namespace === "string" && { namespace: metadata.namespace }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const ref = source;
|
|
88
|
+
return {
|
|
89
|
+
kind: ref.kind ?? "GitRepository",
|
|
90
|
+
name: ref.name,
|
|
91
|
+
...(ref.namespace !== undefined && { namespace: ref.namespace }),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const FluxKustomization = Composite((props) => {
|
|
95
|
+
const { target, source, path, targetNamespace, interval = "10m", prune = true, wait = true, dependsOn = [], timeout, suspend, serviceAccountName, decryption, fluxNamespace = FLUX_NAMESPACE, labels = {}, defaults, } = props;
|
|
96
|
+
const sourceRef = resolveSourceRef(target, source);
|
|
97
|
+
const decryptionSpec = decryption === undefined
|
|
98
|
+
? undefined
|
|
99
|
+
: {
|
|
100
|
+
provider: "sops",
|
|
101
|
+
secretRef: {
|
|
102
|
+
name: typeof decryption === "string" ? SOPS_AGE_SECRET_NAME : decryption.secretRef ?? SOPS_AGE_SECRET_NAME,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
// The Kustomization's namespace also scopes the sourceRef: an in-spec
|
|
106
|
+
// sourceRef without a namespace resolves in the Kustomization's own.
|
|
107
|
+
const kustomization = new KustomizationResource(mergeDefaults({
|
|
108
|
+
metadata: {
|
|
109
|
+
name: target,
|
|
110
|
+
namespace: fluxNamespace,
|
|
111
|
+
labels: {
|
|
112
|
+
"app.kubernetes.io/name": target,
|
|
113
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
114
|
+
...labels,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
spec: {
|
|
118
|
+
interval,
|
|
119
|
+
path,
|
|
120
|
+
prune,
|
|
121
|
+
wait,
|
|
122
|
+
sourceRef,
|
|
123
|
+
...(targetNamespace !== undefined && { targetNamespace }),
|
|
124
|
+
...(dependsOn.length > 0 && { dependsOn: dependsOn.map((name) => ({ name })) }),
|
|
125
|
+
...(timeout !== undefined && { timeout }),
|
|
126
|
+
...(suspend !== undefined && { suspend }),
|
|
127
|
+
...(serviceAccountName !== undefined && { serviceAccountName }),
|
|
128
|
+
...(decryptionSpec !== undefined && { decryption: decryptionSpec }),
|
|
129
|
+
},
|
|
130
|
+
}, defaults?.kustomization));
|
|
131
|
+
return { kustomization };
|
|
132
|
+
}, "FluxKustomization");
|
|
133
|
+
/**
|
|
134
|
+
* Reconcile a Chant build target with Flux in one call — the `Kustomization`
|
|
135
|
+
* half of the GitRepository + Kustomization pair. The source is passed in,
|
|
136
|
+
* not created here, so many apps share one repo declaration.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* import { FluxGitSource, FluxAppFor } from "@intentius/chant-lexicon-k8s";
|
|
141
|
+
*
|
|
142
|
+
* export const source = FluxGitSource("home-chant", {
|
|
143
|
+
* url: "https://github.com/jhgaylor/home-chant",
|
|
144
|
+
* });
|
|
145
|
+
*
|
|
146
|
+
* export const hello = FluxAppFor("hello-chant", {
|
|
147
|
+
* source,
|
|
148
|
+
* path: "./apps/hello-chant/k8s",
|
|
149
|
+
* targetNamespace: "default",
|
|
150
|
+
* dependsOn: ["cert-manager", "traefik"],
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export function FluxAppFor(target, options) {
|
|
155
|
+
return FluxKustomization({ target, ...options });
|
|
156
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GceIngress composite — Ingress with GCE Ingress Controller annotations.
|
|
3
|
+
*
|
|
4
|
+
* @gke Full `kubernetes.io/ingress.*` and `networking.gke.io/*` annotation set
|
|
5
|
+
* including static IP, managed certificates, and FrontendConfig.
|
|
6
|
+
*/
|
|
7
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
8
|
+
import { Ingress } from "../generated/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create a GceIngress composite — returns prop objects for
|
|
11
|
+
* an Ingress with GCE Ingress Controller annotations.
|
|
12
|
+
*
|
|
13
|
+
* @gke
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { GceIngress } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
*
|
|
18
|
+
* const { ingress } = GceIngress({
|
|
19
|
+
* name: "api-ingress",
|
|
20
|
+
* hosts: [
|
|
21
|
+
* {
|
|
22
|
+
* hostname: "api.example.com",
|
|
23
|
+
* paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
24
|
+
* },
|
|
25
|
+
* ],
|
|
26
|
+
* staticIpName: "microservice-ip",
|
|
27
|
+
* managedCertificate: "api-cert",
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const GceIngress = Composite((props) => {
|
|
32
|
+
const { name, hosts, staticIpName, managedCertificate, frontendConfig, healthCheckPath, sslRedirect, annotations: extraAnnotations = {}, labels: extraLabels = {}, namespace, defaults: defs, } = props;
|
|
33
|
+
const commonLabels = {
|
|
34
|
+
"app.kubernetes.io/name": name,
|
|
35
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
36
|
+
...extraLabels,
|
|
37
|
+
};
|
|
38
|
+
// Build GCE annotations
|
|
39
|
+
const annotations = {
|
|
40
|
+
"kubernetes.io/ingress.class": "gce",
|
|
41
|
+
...extraAnnotations,
|
|
42
|
+
};
|
|
43
|
+
if (staticIpName) {
|
|
44
|
+
annotations["kubernetes.io/ingress.global-static-ip-name"] = staticIpName;
|
|
45
|
+
}
|
|
46
|
+
if (managedCertificate) {
|
|
47
|
+
annotations["networking.gke.io/managed-certificates"] = managedCertificate;
|
|
48
|
+
}
|
|
49
|
+
if (frontendConfig) {
|
|
50
|
+
annotations["networking.gke.io/v1beta1.FrontendConfig"] = frontendConfig;
|
|
51
|
+
}
|
|
52
|
+
if (sslRedirect ?? !!managedCertificate) {
|
|
53
|
+
annotations["networking.gke.io/v1beta1.FrontendConfig"] =
|
|
54
|
+
annotations["networking.gke.io/v1beta1.FrontendConfig"] ?? `${name}-frontend-config`;
|
|
55
|
+
}
|
|
56
|
+
if (healthCheckPath) {
|
|
57
|
+
annotations["cloud.google.com/neg"] = '{"ingress": true}';
|
|
58
|
+
}
|
|
59
|
+
const ingressRules = hosts.map((host) => ({
|
|
60
|
+
host: host.hostname,
|
|
61
|
+
http: {
|
|
62
|
+
paths: host.paths.map((p) => ({
|
|
63
|
+
path: p.path,
|
|
64
|
+
pathType: p.pathType ?? "Prefix",
|
|
65
|
+
backend: {
|
|
66
|
+
service: { name: p.serviceName, port: { number: p.servicePort } },
|
|
67
|
+
},
|
|
68
|
+
})),
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
const ingress = new Ingress(mergeDefaults({
|
|
72
|
+
metadata: {
|
|
73
|
+
name,
|
|
74
|
+
...(namespace && { namespace }),
|
|
75
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "ingress" },
|
|
76
|
+
annotations,
|
|
77
|
+
},
|
|
78
|
+
spec: {
|
|
79
|
+
rules: ingressRules,
|
|
80
|
+
},
|
|
81
|
+
}, defs?.ingress));
|
|
82
|
+
return { ingress };
|
|
83
|
+
}, "GceIngress");
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GcePdStorageClass composite — StorageClass for GCE Persistent Disk CSI driver.
|
|
3
|
+
*
|
|
4
|
+
* @gke Creates a StorageClass with the `pd.csi.storage.gke.io` provisioner.
|
|
5
|
+
*/
|
|
6
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
7
|
+
import { StorageClass } from "../generated/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Create a GcePdStorageClass composite — returns prop objects for
|
|
10
|
+
* a StorageClass with the GCE Persistent Disk CSI provisioner.
|
|
11
|
+
*
|
|
12
|
+
* @gke
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { GcePdStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
16
|
+
*
|
|
17
|
+
* const { storageClass } = GcePdStorageClass({
|
|
18
|
+
* name: "pd-ssd",
|
|
19
|
+
* type: "pd-ssd",
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const GcePdStorageClass = Composite((props) => {
|
|
24
|
+
const { name, type = "pd-balanced", replicationType = "none", fsType = "ext4", reclaimPolicy = "Delete", volumeBindingMode = "WaitForFirstConsumer", allowVolumeExpansion = true, labels: extraLabels = {}, defaults: defs, } = props;
|
|
25
|
+
const commonLabels = {
|
|
26
|
+
"app.kubernetes.io/name": name,
|
|
27
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
28
|
+
...extraLabels,
|
|
29
|
+
};
|
|
30
|
+
const parameters = {
|
|
31
|
+
type,
|
|
32
|
+
"csi.storage.k8s.io/fstype": fsType,
|
|
33
|
+
};
|
|
34
|
+
if (replicationType !== "none") {
|
|
35
|
+
parameters["replication-type"] = replicationType;
|
|
36
|
+
}
|
|
37
|
+
const storageClass = new StorageClass(mergeDefaults({
|
|
38
|
+
metadata: {
|
|
39
|
+
name,
|
|
40
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "storage" },
|
|
41
|
+
},
|
|
42
|
+
provisioner: "pd.csi.storage.gke.io",
|
|
43
|
+
parameters,
|
|
44
|
+
reclaimPolicy,
|
|
45
|
+
volumeBindingMode,
|
|
46
|
+
allowVolumeExpansion,
|
|
47
|
+
}, defs?.storageClass));
|
|
48
|
+
return { storageClass };
|
|
49
|
+
}, "GcePdStorageClass");
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GkeExternalDnsAgent composite — Deployment + ServiceAccount + ClusterRole + ClusterRoleBinding.
|
|
3
|
+
*
|
|
4
|
+
* @gke Like ExternalDnsAgent but uses --provider=google and GKE Workload Identity
|
|
5
|
+
* instead of IRSA for Cloud DNS management.
|
|
6
|
+
*/
|
|
7
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
8
|
+
import { Deployment, ServiceAccount, ClusterRole, ClusterRoleBinding } from "../generated/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create a GkeExternalDnsAgent composite — returns prop objects for
|
|
11
|
+
* a Deployment, ServiceAccount (with Workload Identity), ClusterRole, and ClusterRoleBinding.
|
|
12
|
+
*
|
|
13
|
+
* @gke
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { GkeExternalDnsAgent } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
*
|
|
18
|
+
* const { deployment, serviceAccount, clusterRole, clusterRoleBinding } = GkeExternalDnsAgent({
|
|
19
|
+
* gcpServiceAccountEmail: "external-dns@my-project.iam.gserviceaccount.com",
|
|
20
|
+
* gcpProjectId: "my-project",
|
|
21
|
+
* domainFilters: ["example.com"],
|
|
22
|
+
* txtOwnerId: "my-cluster",
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const GkeExternalDnsAgent = Composite((props) => {
|
|
27
|
+
const { gcpServiceAccountEmail, gcpProjectId, domainFilters, txtOwnerId, source = "ingress", name = "external-dns", image = "registry.k8s.io/external-dns/external-dns:v0.14.0", namespace = "kube-system", labels: extraLabels = {}, defaults: defs, } = props;
|
|
28
|
+
const saName = `${name}-sa`;
|
|
29
|
+
const clusterRoleName = `${name}-role`;
|
|
30
|
+
const bindingName = `${name}-binding`;
|
|
31
|
+
const commonLabels = {
|
|
32
|
+
"app.kubernetes.io/name": name,
|
|
33
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
34
|
+
...extraLabels,
|
|
35
|
+
};
|
|
36
|
+
const sources = Array.isArray(source) ? source : [source];
|
|
37
|
+
const args = [
|
|
38
|
+
...sources.map((s) => `--source=${s}`),
|
|
39
|
+
"--provider=google",
|
|
40
|
+
`--google-project=${gcpProjectId}`,
|
|
41
|
+
"--policy=upsert-only",
|
|
42
|
+
"--registry=txt",
|
|
43
|
+
];
|
|
44
|
+
for (const domain of domainFilters) {
|
|
45
|
+
args.push(`--domain-filter=${domain}`);
|
|
46
|
+
}
|
|
47
|
+
if (txtOwnerId) {
|
|
48
|
+
args.push(`--txt-owner-id=${txtOwnerId}`);
|
|
49
|
+
}
|
|
50
|
+
const deployment = new Deployment(mergeDefaults({
|
|
51
|
+
metadata: {
|
|
52
|
+
name,
|
|
53
|
+
namespace,
|
|
54
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "dns" },
|
|
55
|
+
},
|
|
56
|
+
spec: {
|
|
57
|
+
replicas: 1,
|
|
58
|
+
selector: { matchLabels: { "app.kubernetes.io/name": name } },
|
|
59
|
+
template: {
|
|
60
|
+
metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
|
|
61
|
+
spec: {
|
|
62
|
+
serviceAccountName: saName,
|
|
63
|
+
containers: [
|
|
64
|
+
{
|
|
65
|
+
name,
|
|
66
|
+
image,
|
|
67
|
+
args,
|
|
68
|
+
resources: {
|
|
69
|
+
requests: { cpu: "50m", memory: "64Mi" },
|
|
70
|
+
limits: { cpu: "100m", memory: "128Mi" },
|
|
71
|
+
},
|
|
72
|
+
securityContext: {
|
|
73
|
+
runAsNonRoot: true,
|
|
74
|
+
runAsUser: 65534,
|
|
75
|
+
readOnlyRootFilesystem: true,
|
|
76
|
+
allowPrivilegeEscalation: false,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}, defs?.deployment));
|
|
84
|
+
const serviceAccount = new ServiceAccount(mergeDefaults({
|
|
85
|
+
metadata: {
|
|
86
|
+
name: saName,
|
|
87
|
+
namespace,
|
|
88
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "dns" },
|
|
89
|
+
annotations: {
|
|
90
|
+
"iam.gke.io/gcp-service-account": gcpServiceAccountEmail,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
}, defs?.serviceAccount));
|
|
94
|
+
const clusterRole = new ClusterRole(mergeDefaults({
|
|
95
|
+
metadata: {
|
|
96
|
+
name: clusterRoleName,
|
|
97
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
98
|
+
},
|
|
99
|
+
rules: [
|
|
100
|
+
{ apiGroups: [""], resources: ["services", "endpoints", "pods"], verbs: ["get", "watch", "list"] },
|
|
101
|
+
{ apiGroups: ["extensions", "networking.k8s.io"], resources: ["ingresses"], verbs: ["get", "watch", "list"] },
|
|
102
|
+
{ apiGroups: [""], resources: ["nodes"], verbs: ["list", "watch"] },
|
|
103
|
+
],
|
|
104
|
+
}, defs?.clusterRole));
|
|
105
|
+
const clusterRoleBinding = new ClusterRoleBinding(mergeDefaults({
|
|
106
|
+
metadata: {
|
|
107
|
+
name: bindingName,
|
|
108
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
109
|
+
},
|
|
110
|
+
roleRef: {
|
|
111
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
112
|
+
kind: "ClusterRole",
|
|
113
|
+
name: clusterRoleName,
|
|
114
|
+
},
|
|
115
|
+
subjects: [
|
|
116
|
+
{
|
|
117
|
+
kind: "ServiceAccount",
|
|
118
|
+
name: saName,
|
|
119
|
+
namespace,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
}, defs?.clusterRoleBinding));
|
|
123
|
+
return {
|
|
124
|
+
deployment,
|
|
125
|
+
serviceAccount,
|
|
126
|
+
clusterRole,
|
|
127
|
+
clusterRoleBinding,
|
|
128
|
+
};
|
|
129
|
+
}, "GkeExternalDnsAgent");
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GkeFluentBitAgent composite — DaemonSet + RBAC + ConfigMap for Fluent Bit on GKE.
|
|
3
|
+
*
|
|
4
|
+
* @gke Like FluentBitAgent but targets Cloud Logging via the stackdriver
|
|
5
|
+
* output plugin and uses GKE Workload Identity instead of IRSA.
|
|
6
|
+
*/
|
|
7
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
8
|
+
import { DaemonSet, ServiceAccount, ClusterRole, ClusterRoleBinding, ConfigMap } from "../generated/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create a GkeFluentBitAgent composite — returns prop objects for
|
|
11
|
+
* a DaemonSet, ServiceAccount, ClusterRole, ClusterRoleBinding, and ConfigMap.
|
|
12
|
+
*
|
|
13
|
+
* @gke
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { GkeFluentBitAgent } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
*
|
|
18
|
+
* const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding, configMap } = GkeFluentBitAgent({
|
|
19
|
+
* clusterName: "my-cluster",
|
|
20
|
+
* projectId: "my-project",
|
|
21
|
+
* gcpServiceAccountEmail: "fluent-bit@my-project.iam.gserviceaccount.com",
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export const GkeFluentBitAgent = Composite((props) => {
|
|
26
|
+
const { clusterName, projectId, gcpServiceAccountEmail, name = "fluent-bit", image = "fluent/fluent-bit:latest", namespace = "gke-logging", labels: extraLabels = {}, cpuRequest = "50m", memoryRequest = "64Mi", cpuLimit = "200m", memoryLimit = "128Mi", defaults: defs, } = props;
|
|
27
|
+
const saName = `${name}-sa`;
|
|
28
|
+
const clusterRoleName = `${name}-role`;
|
|
29
|
+
const bindingName = `${name}-binding`;
|
|
30
|
+
const configMapName = `${name}-config`;
|
|
31
|
+
const commonLabels = {
|
|
32
|
+
"app.kubernetes.io/name": name,
|
|
33
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
34
|
+
...extraLabels,
|
|
35
|
+
};
|
|
36
|
+
const fluentBitConfig = `[SERVICE]
|
|
37
|
+
Flush 5
|
|
38
|
+
Log_Level info
|
|
39
|
+
Daemon off
|
|
40
|
+
Parsers_File parsers.conf
|
|
41
|
+
|
|
42
|
+
[INPUT]
|
|
43
|
+
Name tail
|
|
44
|
+
Tag kube.*
|
|
45
|
+
Path /var/log/containers/*.log
|
|
46
|
+
Parser docker
|
|
47
|
+
DB /var/fluent-bit/state/flb_container.db
|
|
48
|
+
Mem_Buf_Limit 5MB
|
|
49
|
+
Skip_Long_Lines On
|
|
50
|
+
Refresh_Interval 10
|
|
51
|
+
|
|
52
|
+
[FILTER]
|
|
53
|
+
Name kubernetes
|
|
54
|
+
Match kube.*
|
|
55
|
+
Kube_URL https://kubernetes.default.svc:443
|
|
56
|
+
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
|
57
|
+
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
|
|
58
|
+
Merge_Log On
|
|
59
|
+
K8S-Logging.Parser On
|
|
60
|
+
K8S-Logging.Exclude Off
|
|
61
|
+
|
|
62
|
+
[OUTPUT]
|
|
63
|
+
Name stackdriver
|
|
64
|
+
Match *
|
|
65
|
+
google_service_credentials /var/run/secrets/kubernetes.io/serviceaccount/token
|
|
66
|
+
resource k8s_container
|
|
67
|
+
k8s_cluster_name ${clusterName}
|
|
68
|
+
k8s_cluster_location ${projectId}
|
|
69
|
+
`;
|
|
70
|
+
const container = {
|
|
71
|
+
name,
|
|
72
|
+
image,
|
|
73
|
+
resources: {
|
|
74
|
+
requests: { cpu: cpuRequest, memory: memoryRequest },
|
|
75
|
+
limits: { cpu: cpuLimit, memory: memoryLimit },
|
|
76
|
+
},
|
|
77
|
+
volumeMounts: [
|
|
78
|
+
{ name: "varlog", mountPath: "/var/log", readOnly: true },
|
|
79
|
+
{ name: "config", mountPath: `/etc/${name}`, readOnly: true },
|
|
80
|
+
{ name: "state", mountPath: "/var/fluent-bit/state" },
|
|
81
|
+
],
|
|
82
|
+
securityContext: {
|
|
83
|
+
runAsUser: 0,
|
|
84
|
+
readOnlyRootFilesystem: true,
|
|
85
|
+
allowPrivilegeEscalation: false,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const daemonSet = new DaemonSet(mergeDefaults({
|
|
89
|
+
metadata: {
|
|
90
|
+
name,
|
|
91
|
+
namespace,
|
|
92
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
|
|
93
|
+
},
|
|
94
|
+
spec: {
|
|
95
|
+
selector: { matchLabels: { "app.kubernetes.io/name": name } },
|
|
96
|
+
template: {
|
|
97
|
+
metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
|
|
98
|
+
spec: {
|
|
99
|
+
serviceAccountName: saName,
|
|
100
|
+
containers: [container],
|
|
101
|
+
volumes: [
|
|
102
|
+
{ name: "varlog", hostPath: { path: "/var/log" } },
|
|
103
|
+
{ name: "config", configMap: { name: configMapName } },
|
|
104
|
+
{ name: "state", hostPath: { path: `/var/fluent-bit/state/${name}` } },
|
|
105
|
+
],
|
|
106
|
+
tolerations: [{ operator: "Exists" }],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}, defs?.daemonSet));
|
|
111
|
+
const serviceAccount = new ServiceAccount(mergeDefaults({
|
|
112
|
+
metadata: {
|
|
113
|
+
name: saName,
|
|
114
|
+
namespace,
|
|
115
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
|
|
116
|
+
...(gcpServiceAccountEmail
|
|
117
|
+
? { annotations: { "iam.gke.io/gcp-service-account": gcpServiceAccountEmail } }
|
|
118
|
+
: {}),
|
|
119
|
+
},
|
|
120
|
+
}, defs?.serviceAccount));
|
|
121
|
+
const clusterRole = new ClusterRole(mergeDefaults({
|
|
122
|
+
metadata: {
|
|
123
|
+
name: clusterRoleName,
|
|
124
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
125
|
+
},
|
|
126
|
+
rules: [
|
|
127
|
+
{ apiGroups: [""], resources: ["namespaces", "pods"], verbs: ["get", "list", "watch"] },
|
|
128
|
+
],
|
|
129
|
+
}, defs?.clusterRole));
|
|
130
|
+
const clusterRoleBinding = new ClusterRoleBinding(mergeDefaults({
|
|
131
|
+
metadata: {
|
|
132
|
+
name: bindingName,
|
|
133
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
134
|
+
},
|
|
135
|
+
roleRef: {
|
|
136
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
137
|
+
kind: "ClusterRole",
|
|
138
|
+
name: clusterRoleName,
|
|
139
|
+
},
|
|
140
|
+
subjects: [
|
|
141
|
+
{
|
|
142
|
+
kind: "ServiceAccount",
|
|
143
|
+
name: saName,
|
|
144
|
+
namespace,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
}, defs?.clusterRoleBinding));
|
|
148
|
+
const configMap = new ConfigMap(mergeDefaults({
|
|
149
|
+
metadata: {
|
|
150
|
+
name: configMapName,
|
|
151
|
+
namespace,
|
|
152
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "config" },
|
|
153
|
+
},
|
|
154
|
+
data: {
|
|
155
|
+
"fluent-bit.conf": fluentBitConfig,
|
|
156
|
+
},
|
|
157
|
+
}, defs?.configMap));
|
|
158
|
+
return {
|
|
159
|
+
daemonSet,
|
|
160
|
+
serviceAccount,
|
|
161
|
+
clusterRole,
|
|
162
|
+
clusterRoleBinding,
|
|
163
|
+
configMap,
|
|
164
|
+
};
|
|
165
|
+
}, "GkeFluentBitAgent");
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GkeGateway composite — Gateway + HTTPRoute for GKE Gateway API.
|
|
3
|
+
*
|
|
4
|
+
* @gke Creates a Gateway with a GKE-specific `gatewayClassName` and
|
|
5
|
+
* HTTPRoute resources for traffic routing.
|
|
6
|
+
*/
|
|
7
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
8
|
+
import { Deployment } from "../generated/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create a GkeGateway composite — returns prop objects for
|
|
11
|
+
* a Gateway and HTTPRoute with GKE-specific gateway class.
|
|
12
|
+
*
|
|
13
|
+
* @gke
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { GkeGateway } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
*
|
|
18
|
+
* const { gateway, httpRoute } = GkeGateway({
|
|
19
|
+
* name: "api-gateway",
|
|
20
|
+
* hosts: [
|
|
21
|
+
* {
|
|
22
|
+
* hostname: "api.example.com",
|
|
23
|
+
* paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
24
|
+
* },
|
|
25
|
+
* ],
|
|
26
|
+
* certificateName: "api-cert",
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const GkeGateway = Composite((props) => {
|
|
31
|
+
const { name, gatewayClassName = "gke-l7-global-external-managed", hosts, certificateName, labels: extraLabels = {}, namespace, defaults: defs, } = props;
|
|
32
|
+
const routeName = `${name}-route`;
|
|
33
|
+
const commonLabels = {
|
|
34
|
+
"app.kubernetes.io/name": name,
|
|
35
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
36
|
+
...extraLabels,
|
|
37
|
+
};
|
|
38
|
+
// Build Gateway listeners
|
|
39
|
+
const listeners = [];
|
|
40
|
+
if (certificateName) {
|
|
41
|
+
listeners.push({
|
|
42
|
+
name: "https",
|
|
43
|
+
protocol: "HTTPS",
|
|
44
|
+
port: 443,
|
|
45
|
+
tls: {
|
|
46
|
+
mode: "Terminate",
|
|
47
|
+
certificateRefs: [{ kind: "ManagedCertificate", name: certificateName }],
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
listeners.push({
|
|
53
|
+
name: "http",
|
|
54
|
+
protocol: "HTTP",
|
|
55
|
+
port: 80,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Gateway and HTTPRoute are CRDs — use Deployment constructor as a generic Declarable wrapper
|
|
59
|
+
const gateway = new Deployment(mergeDefaults({
|
|
60
|
+
metadata: {
|
|
61
|
+
name,
|
|
62
|
+
...(namespace && { namespace }),
|
|
63
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "gateway" },
|
|
64
|
+
},
|
|
65
|
+
spec: {
|
|
66
|
+
gatewayClassName,
|
|
67
|
+
listeners,
|
|
68
|
+
},
|
|
69
|
+
}, defs?.gateway));
|
|
70
|
+
// Build HTTPRoute rules
|
|
71
|
+
const hostnames = hosts.map((h) => h.hostname);
|
|
72
|
+
const rules = hosts.flatMap((host) => host.paths.map((p) => ({
|
|
73
|
+
matches: [{ path: { type: "PathPrefix", value: p.path } }],
|
|
74
|
+
backendRefs: [
|
|
75
|
+
{ name: p.serviceName, port: p.servicePort },
|
|
76
|
+
],
|
|
77
|
+
})));
|
|
78
|
+
const httpRoute = new Deployment(mergeDefaults({
|
|
79
|
+
metadata: {
|
|
80
|
+
name: routeName,
|
|
81
|
+
...(namespace && { namespace }),
|
|
82
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "route" },
|
|
83
|
+
},
|
|
84
|
+
spec: {
|
|
85
|
+
parentRefs: [{ name }],
|
|
86
|
+
hostnames,
|
|
87
|
+
rules,
|
|
88
|
+
},
|
|
89
|
+
}, defs?.httpRoute));
|
|
90
|
+
return { gateway, httpRoute };
|
|
91
|
+
}, "GkeGateway");
|