@intentius/chant-lexicon-k8s 0.124.31 → 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,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CockroachDbRegionStack composite — all K8s resources for one CockroachDB region.
|
|
3
|
+
*
|
|
4
|
+
* Collapses the 8-file per-region K8s pattern into a single composite call:
|
|
5
|
+
* namespace, storage, CockroachDB StatefulSet, External Secrets, GCE Ingress,
|
|
6
|
+
* ExternalDNS, Cloud Armor BackendConfig, TLS cert, and optional Prometheus monitoring.
|
|
7
|
+
*
|
|
8
|
+
* @gke Requires: External Secrets Operator (ESO), ExternalDNS (via GkeExternalDnsAgent).
|
|
9
|
+
*/
|
|
10
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
11
|
+
import { createResource } from "@intentius/chant/runtime";
|
|
12
|
+
import { NetworkPolicy, Deployment, Service, ConfigMap, } from "../generated/index.js";
|
|
13
|
+
import { NamespaceEnv } from "./namespace-env.js";
|
|
14
|
+
import { GcePdStorageClass } from "./gce-pd-storage-class.js";
|
|
15
|
+
import { CockroachDbCluster } from "./cockroachdb-cluster.js";
|
|
16
|
+
import { GceIngress } from "./gce-ingress.js";
|
|
17
|
+
import { GkeExternalDnsAgent } from "./gke-external-dns-agent.js";
|
|
18
|
+
// ── External CRDs (not in main generated index) ──────────────────────────────────
|
|
19
|
+
const ClusterSecretStore = createResource("K8s::ExternalSecrets::ClusterSecretStore", "k8s", {});
|
|
20
|
+
const ExternalSecret = createResource("K8s::ExternalSecrets::ExternalSecret", "k8s", {});
|
|
21
|
+
const BackendConfig = createResource("K8s::GKE::BackendConfig", "k8s", {});
|
|
22
|
+
const ManagedCertificate = createResource("K8s::NetworkingGKE::ManagedCertificate", "k8s", {});
|
|
23
|
+
const FrontendConfig = createResource("K8s::NetworkingGKEBeta::FrontendConfig", "k8s", {});
|
|
24
|
+
/**
|
|
25
|
+
* Create a CockroachDbRegionStack composite — returns all K8s resources for one
|
|
26
|
+
* CockroachDB region in a single call.
|
|
27
|
+
*
|
|
28
|
+
* Replaces: namespace.ts, storage.ts, cockroachdb.ts, external-secrets.ts,
|
|
29
|
+
* ingress.ts, tls.ts, backend-config.ts, monitoring.ts (8 files → 1 call).
|
|
30
|
+
*
|
|
31
|
+
* @gke
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { CockroachDbRegionStack } from "@intentius/chant-lexicon-k8s";
|
|
35
|
+
*
|
|
36
|
+
* export const east = CockroachDbRegionStack({
|
|
37
|
+
* region: "east",
|
|
38
|
+
* namespace: "crdb-east",
|
|
39
|
+
* domain: `east.${CRDB_DOMAIN}`,
|
|
40
|
+
* internalDomain: `east.${INTERNAL_DOMAIN}`,
|
|
41
|
+
* publicRootDomain: CRDB_DOMAIN,
|
|
42
|
+
* projectId: GCP_PROJECT_ID,
|
|
43
|
+
* clusterName: "gke-crdb-east",
|
|
44
|
+
* clusterRegion: "us-east4",
|
|
45
|
+
* crdbGsaEmail: `gke-crdb-east-crdb@${GCP_PROJECT_ID}.iam.gserviceaccount.com`,
|
|
46
|
+
* externalDnsGsaEmail: `gke-crdb-east-dns@${GCP_PROJECT_ID}.iam.gserviceaccount.com`,
|
|
47
|
+
* cockroachdb: {
|
|
48
|
+
* locality: "cloud=gcp,region=us-east4",
|
|
49
|
+
* joinAddresses: [...],
|
|
50
|
+
* advertiseHostDomain: `east.${INTERNAL_DOMAIN}`,
|
|
51
|
+
* skipInit: false,
|
|
52
|
+
* mountClientCerts: true,
|
|
53
|
+
* },
|
|
54
|
+
* tls: {
|
|
55
|
+
* gcpSecretNames: {
|
|
56
|
+
* ca: "crdb-ca-crt", nodeCrt: "crdb-node-crt", nodeKey: "crdb-node-key",
|
|
57
|
+
* clientRootCrt: "crdb-client-root-crt", clientRootKey: "crdb-client-root-key",
|
|
58
|
+
* },
|
|
59
|
+
* },
|
|
60
|
+
* allowCidrs: ALL_CIDRS,
|
|
61
|
+
* cloudArmor: { policyName: "crdb-ui-waf" },
|
|
62
|
+
* monitoring: true,
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export const CockroachDbRegionStack = Composite((props) => {
|
|
67
|
+
const { region, namespace: ns, domain, internalDomain, publicRootDomain, projectId, clusterName, clusterRegion, crdbGsaEmail, externalDnsGsaEmail, cockroachdb: crdbProps, tls, quota, allowCidrs = [], cloudArmor, monitoring = false, labels: extraLabels = {}, } = props;
|
|
68
|
+
const crdbName = crdbProps.name ?? "cockroachdb";
|
|
69
|
+
const commonLabels = {
|
|
70
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
71
|
+
"app.kubernetes.io/part-of": `cockroachdb-multi-region`,
|
|
72
|
+
"app.kubernetes.io/instance": region,
|
|
73
|
+
...extraLabels,
|
|
74
|
+
};
|
|
75
|
+
// ── Namespace + Quotas ─────────────────────────────────────────────────────────
|
|
76
|
+
const nsResult = NamespaceEnv({
|
|
77
|
+
name: ns,
|
|
78
|
+
...(quota?.cpu && { cpuQuota: quota.cpu }),
|
|
79
|
+
...(quota?.memory && { memoryQuota: quota.memory }),
|
|
80
|
+
...(quota?.maxPods !== undefined && { maxPods: quota.maxPods }),
|
|
81
|
+
defaultCpuRequest: "250m",
|
|
82
|
+
defaultMemoryRequest: "512Mi",
|
|
83
|
+
defaultCpuLimit: "1",
|
|
84
|
+
defaultMemoryLimit: "4Gi",
|
|
85
|
+
defaultDenyIngress: true,
|
|
86
|
+
labels: {
|
|
87
|
+
"pod-security.kubernetes.io/enforce": "baseline",
|
|
88
|
+
"pod-security.kubernetes.io/enforce-version": "latest",
|
|
89
|
+
"pod-security.kubernetes.io/warn": "restricted",
|
|
90
|
+
"pod-security.kubernetes.io/audit": "restricted",
|
|
91
|
+
...commonLabels,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
// Allow CockroachDB inter-node (26257) + HTTP UI (8080) from all region CIDRs.
|
|
95
|
+
const crdbNetworkPolicy = new NetworkPolicy(mergeDefaults({
|
|
96
|
+
metadata: {
|
|
97
|
+
name: "allow-cockroachdb",
|
|
98
|
+
namespace: ns,
|
|
99
|
+
labels: commonLabels,
|
|
100
|
+
},
|
|
101
|
+
spec: {
|
|
102
|
+
podSelector: { matchLabels: { "app.kubernetes.io/name": crdbName } },
|
|
103
|
+
policyTypes: ["Ingress"],
|
|
104
|
+
ingress: allowCidrs.length > 0
|
|
105
|
+
? [{
|
|
106
|
+
from: allowCidrs.map((cidr) => ({ ipBlock: { cidr } })),
|
|
107
|
+
ports: [
|
|
108
|
+
{ protocol: "TCP", port: 26257 },
|
|
109
|
+
{ protocol: "TCP", port: 8080 },
|
|
110
|
+
],
|
|
111
|
+
}]
|
|
112
|
+
: [],
|
|
113
|
+
},
|
|
114
|
+
}, {}));
|
|
115
|
+
// ── Storage ────────────────────────────────────────────────────────────────────
|
|
116
|
+
const { storageClass } = GcePdStorageClass({
|
|
117
|
+
name: "pd-ssd",
|
|
118
|
+
type: "pd-ssd",
|
|
119
|
+
labels: commonLabels,
|
|
120
|
+
});
|
|
121
|
+
// ── CockroachDB Cluster ────────────────────────────────────────────────────────
|
|
122
|
+
const crdb = CockroachDbCluster({
|
|
123
|
+
name: crdbName,
|
|
124
|
+
namespace: ns,
|
|
125
|
+
replicas: crdbProps.replicas ?? 3,
|
|
126
|
+
image: crdbProps.image,
|
|
127
|
+
storageSize: crdbProps.storageSize,
|
|
128
|
+
storageClassName: "pd-ssd",
|
|
129
|
+
cpuLimit: crdbProps.cpuLimit,
|
|
130
|
+
memoryLimit: crdbProps.memoryLimit,
|
|
131
|
+
locality: crdbProps.locality,
|
|
132
|
+
joinAddresses: crdbProps.joinAddresses,
|
|
133
|
+
secure: true,
|
|
134
|
+
skipCertGen: true,
|
|
135
|
+
skipInit: crdbProps.skipInit,
|
|
136
|
+
mountClientCerts: crdbProps.mountClientCerts,
|
|
137
|
+
advertiseHostDomain: crdbProps.advertiseHostDomain,
|
|
138
|
+
extraCertNodeAddresses: crdbProps.extraCertNodeAddresses,
|
|
139
|
+
labels: commonLabels,
|
|
140
|
+
defaults: {
|
|
141
|
+
serviceAccount: {
|
|
142
|
+
metadata: {
|
|
143
|
+
annotations: {
|
|
144
|
+
"iam.gke.io/gcp-service-account": crdbGsaEmail,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
publicService: {
|
|
149
|
+
metadata: {
|
|
150
|
+
annotations: {
|
|
151
|
+
"cloud.google.com/backend-config": `{"default":"${crdbName}-ui-backend"}`,
|
|
152
|
+
"cloud.google.com/app-protocols": `{"http":"HTTPS"}`,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
headlessService: {
|
|
157
|
+
metadata: {
|
|
158
|
+
annotations: {
|
|
159
|
+
"external-dns.alpha.kubernetes.io/hostname": internalDomain,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// ── External Secrets (TLS certs from GCP Secret Manager) ──────────────────────
|
|
166
|
+
const secretStoreName = "gcp-secret-manager";
|
|
167
|
+
const gcpSecretStore = new ClusterSecretStore({
|
|
168
|
+
metadata: { name: secretStoreName },
|
|
169
|
+
spec: {
|
|
170
|
+
provider: {
|
|
171
|
+
gcpsm: {
|
|
172
|
+
projectID: projectId,
|
|
173
|
+
auth: {
|
|
174
|
+
workloadIdentity: {
|
|
175
|
+
clusterLocation: clusterRegion,
|
|
176
|
+
clusterName,
|
|
177
|
+
serviceAccountRef: { name: "external-secrets-sa", namespace: "kube-system" },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
const nodeCertsSecret = new ExternalSecret({
|
|
185
|
+
metadata: { name: `${crdbName}-node-certs-eso`, namespace: ns },
|
|
186
|
+
spec: {
|
|
187
|
+
refreshInterval: "1h",
|
|
188
|
+
secretStoreRef: { name: secretStoreName, kind: "ClusterSecretStore" },
|
|
189
|
+
target: { name: `${crdbName}-node-certs`, creationPolicy: "Owner" },
|
|
190
|
+
data: [
|
|
191
|
+
{ secretKey: "ca.crt", remoteRef: { key: tls.gcpSecretNames.ca } },
|
|
192
|
+
{ secretKey: "node.crt", remoteRef: { key: tls.gcpSecretNames.nodeCrt } },
|
|
193
|
+
{ secretKey: "node.key", remoteRef: { key: tls.gcpSecretNames.nodeKey } },
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const clientCertsSecret = new ExternalSecret({
|
|
198
|
+
metadata: { name: `${crdbName}-client-certs-eso`, namespace: ns },
|
|
199
|
+
spec: {
|
|
200
|
+
refreshInterval: "1h",
|
|
201
|
+
secretStoreRef: { name: secretStoreName, kind: "ClusterSecretStore" },
|
|
202
|
+
target: { name: `${crdbName}-client-certs`, creationPolicy: "Owner" },
|
|
203
|
+
data: [
|
|
204
|
+
{ secretKey: "ca.crt", remoteRef: { key: tls.gcpSecretNames.ca } },
|
|
205
|
+
{ secretKey: "client.root.crt", remoteRef: { key: tls.gcpSecretNames.clientRootCrt } },
|
|
206
|
+
{ secretKey: "client.root.key", remoteRef: { key: tls.gcpSecretNames.clientRootKey } },
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
// ── TLS: Managed Certificate + FrontendConfig ─────────────────────────────────
|
|
211
|
+
const certName = `${crdbName}-ui-cert`;
|
|
212
|
+
const frontendName = `${crdbName}-ui-frontend`;
|
|
213
|
+
const backendName = `${crdbName}-ui-backend`;
|
|
214
|
+
const crdbManagedCert = new ManagedCertificate({
|
|
215
|
+
metadata: {
|
|
216
|
+
name: certName,
|
|
217
|
+
namespace: ns,
|
|
218
|
+
labels: commonLabels,
|
|
219
|
+
},
|
|
220
|
+
spec: { domains: [domain] },
|
|
221
|
+
});
|
|
222
|
+
const crdbFrontendConfig = new FrontendConfig({
|
|
223
|
+
metadata: {
|
|
224
|
+
name: frontendName,
|
|
225
|
+
namespace: ns,
|
|
226
|
+
labels: commonLabels,
|
|
227
|
+
},
|
|
228
|
+
spec: {
|
|
229
|
+
redirectToHttps: {
|
|
230
|
+
enabled: true,
|
|
231
|
+
responseCodeName: "MOVED_PERMANENTLY_DEFAULT",
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
// ── GCE Ingress ────────────────────────────────────────────────────────────────
|
|
236
|
+
const { ingress: gceIngress } = GceIngress({
|
|
237
|
+
name: `${crdbName}-ui`,
|
|
238
|
+
hosts: [{
|
|
239
|
+
hostname: domain,
|
|
240
|
+
paths: [{ path: "/", serviceName: `${crdbName}-public`, servicePort: 8080 }],
|
|
241
|
+
}],
|
|
242
|
+
namespace: ns,
|
|
243
|
+
managedCertificate: certName,
|
|
244
|
+
frontendConfig: frontendName,
|
|
245
|
+
labels: commonLabels,
|
|
246
|
+
});
|
|
247
|
+
// ── ExternalDNS ────────────────────────────────────────────────────────────────
|
|
248
|
+
const dnsAgent = GkeExternalDnsAgent({
|
|
249
|
+
gcpServiceAccountEmail: externalDnsGsaEmail,
|
|
250
|
+
gcpProjectId: projectId,
|
|
251
|
+
domainFilters: [publicRootDomain, internalDomain.split(".").slice(1).join(".")],
|
|
252
|
+
txtOwnerId: clusterName,
|
|
253
|
+
source: ["service", "ingress"],
|
|
254
|
+
labels: commonLabels,
|
|
255
|
+
});
|
|
256
|
+
// ── Result object ─────────────────────────────────────────────────────────────
|
|
257
|
+
const result = {
|
|
258
|
+
// Namespace
|
|
259
|
+
...nsResult,
|
|
260
|
+
crdbNetworkPolicy,
|
|
261
|
+
// Storage
|
|
262
|
+
storageClass,
|
|
263
|
+
// CockroachDB
|
|
264
|
+
cockroachdbServiceAccount: crdb.serviceAccount,
|
|
265
|
+
cockroachdbRole: crdb.role,
|
|
266
|
+
cockroachdbRoleBinding: crdb.roleBinding,
|
|
267
|
+
cockroachdbClusterRole: crdb.clusterRole,
|
|
268
|
+
cockroachdbClusterRoleBinding: crdb.clusterRoleBinding,
|
|
269
|
+
cockroachdbPublicService: crdb.publicService,
|
|
270
|
+
cockroachdbHeadlessService: crdb.headlessService,
|
|
271
|
+
cockroachdbPdb: crdb.pdb,
|
|
272
|
+
cockroachdbStatefulSet: crdb.statefulSet,
|
|
273
|
+
...(crdb.initJob && { cockroachdbInitJob: crdb.initJob }),
|
|
274
|
+
// External Secrets
|
|
275
|
+
gcpSecretStore,
|
|
276
|
+
nodeCertsSecret,
|
|
277
|
+
clientCertsSecret,
|
|
278
|
+
// TLS
|
|
279
|
+
crdbManagedCert,
|
|
280
|
+
crdbFrontendConfig,
|
|
281
|
+
// Ingress + ExternalDNS
|
|
282
|
+
gceIngress,
|
|
283
|
+
dnsDeployment: dnsAgent.deployment,
|
|
284
|
+
dnsServiceAccount: dnsAgent.serviceAccount,
|
|
285
|
+
dnsClusterRole: dnsAgent.clusterRole,
|
|
286
|
+
dnsClusterRoleBinding: dnsAgent.clusterRoleBinding,
|
|
287
|
+
};
|
|
288
|
+
// Optional: Cloud Armor BackendConfig
|
|
289
|
+
if (cloudArmor) {
|
|
290
|
+
result.crdbBackendConfig = new BackendConfig({
|
|
291
|
+
metadata: {
|
|
292
|
+
name: backendName,
|
|
293
|
+
namespace: ns,
|
|
294
|
+
labels: commonLabels,
|
|
295
|
+
},
|
|
296
|
+
spec: {
|
|
297
|
+
securityPolicy: { name: cloudArmor.policyName },
|
|
298
|
+
healthCheck: { type: "HTTPS", requestPath: "/health", port: 8080 },
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
// Optional: Prometheus monitoring
|
|
303
|
+
if (monitoring) {
|
|
304
|
+
result.prometheusConfig = new ConfigMap({
|
|
305
|
+
metadata: { name: "prometheus-config", namespace: ns },
|
|
306
|
+
data: {
|
|
307
|
+
"prometheus.yml": [
|
|
308
|
+
`global:`,
|
|
309
|
+
` scrape_interval: 15s`,
|
|
310
|
+
`scrape_configs:`,
|
|
311
|
+
` - job_name: "cockroachdb"`,
|
|
312
|
+
` kubernetes_sd_configs:`,
|
|
313
|
+
` - role: pod`,
|
|
314
|
+
` namespaces:`,
|
|
315
|
+
` names: ["${ns}"]`,
|
|
316
|
+
` relabel_configs:`,
|
|
317
|
+
` - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]`,
|
|
318
|
+
` regex: "cockroachdb"`,
|
|
319
|
+
` action: keep`,
|
|
320
|
+
` - source_labels: [__meta_kubernetes_pod_ip]`,
|
|
321
|
+
` target_label: __address__`,
|
|
322
|
+
` replacement: "$1:8080"`,
|
|
323
|
+
` - source_labels: [__meta_kubernetes_pod_name]`,
|
|
324
|
+
` target_label: instance`,
|
|
325
|
+
` metrics_path: "/_status/vars"`,
|
|
326
|
+
].join("\n"),
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
result.prometheusDeployment = new Deployment({
|
|
330
|
+
metadata: {
|
|
331
|
+
name: "prometheus",
|
|
332
|
+
namespace: ns,
|
|
333
|
+
labels: { "app.kubernetes.io/name": "prometheus", ...commonLabels },
|
|
334
|
+
},
|
|
335
|
+
spec: {
|
|
336
|
+
replicas: 1,
|
|
337
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "prometheus" } },
|
|
338
|
+
template: {
|
|
339
|
+
metadata: { labels: { "app.kubernetes.io/name": "prometheus" } },
|
|
340
|
+
spec: {
|
|
341
|
+
containers: [{
|
|
342
|
+
name: "prometheus",
|
|
343
|
+
image: "prom/prometheus:v2.51.0",
|
|
344
|
+
args: [
|
|
345
|
+
"--config.file=/etc/prometheus/prometheus.yml",
|
|
346
|
+
"--storage.tsdb.retention.time=15d",
|
|
347
|
+
],
|
|
348
|
+
ports: [{ name: "http", containerPort: 9090 }],
|
|
349
|
+
resources: {
|
|
350
|
+
requests: { cpu: "500m", memory: "1Gi" },
|
|
351
|
+
limits: { cpu: "1", memory: "2Gi" },
|
|
352
|
+
},
|
|
353
|
+
volumeMounts: [{ name: "config", mountPath: "/etc/prometheus" }],
|
|
354
|
+
}],
|
|
355
|
+
volumes: [{ name: "config", configMap: { name: "prometheus-config" } }],
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
result.prometheusService = new Service({
|
|
361
|
+
metadata: { name: "prometheus", namespace: ns },
|
|
362
|
+
spec: {
|
|
363
|
+
selector: { "app.kubernetes.io/name": "prometheus" },
|
|
364
|
+
ports: [{ name: "http", port: 9090, targetPort: "http" }],
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}, "CockroachDbRegionStack");
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfigConnectorContext composite — bootstrap Config Connector per-namespace context.
|
|
3
|
+
*
|
|
4
|
+
* @gke Creates a ConfigConnectorContext resource that configures
|
|
5
|
+
* Config Connector to manage GCP resources in a specific namespace.
|
|
6
|
+
*/
|
|
7
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
8
|
+
import { Deployment } from "../generated/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create a ConfigConnectorContext composite — returns prop objects for
|
|
11
|
+
* a ConfigConnectorContext resource.
|
|
12
|
+
*
|
|
13
|
+
* @gke
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { ConfigConnectorContext } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
*
|
|
18
|
+
* const { context } = ConfigConnectorContext({
|
|
19
|
+
* googleServiceAccountEmail: "cnrm@my-project.iam.gserviceaccount.com",
|
|
20
|
+
* namespace: "config-connector",
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const ConfigConnectorContext = Composite((props) => {
|
|
25
|
+
const { name = "configconnectorcontext.core.cnrm.cloud.google.com", googleServiceAccountEmail, namespace = "default", stateIntoSpec = "Absent", defaults: defs, } = props;
|
|
26
|
+
// ConfigConnectorContext is a CRD — use Deployment constructor as a generic Declarable wrapper
|
|
27
|
+
const context = new Deployment(mergeDefaults({
|
|
28
|
+
apiVersion: "core.cnrm.cloud.google.com/v1beta1",
|
|
29
|
+
kind: "ConfigConnectorContext",
|
|
30
|
+
metadata: {
|
|
31
|
+
name,
|
|
32
|
+
namespace,
|
|
33
|
+
},
|
|
34
|
+
spec: {
|
|
35
|
+
googleServiceAccount: googleServiceAccountEmail,
|
|
36
|
+
stateIntoSpec,
|
|
37
|
+
},
|
|
38
|
+
}, defs?.context));
|
|
39
|
+
return { context };
|
|
40
|
+
}, "ConfigConnectorContext");
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfiguredApp composite — Deployment + Service + optional ConfigMap.
|
|
3
|
+
*
|
|
4
|
+
* Wires ConfigMap→Volume→VolumeMount and Secret→Volume→VolumeMount
|
|
5
|
+
* triangles, plus envFrom injection. Covers cdk8s-plus Volume.fromConfigMap(),
|
|
6
|
+
* Volume.fromSecret(), and container.mount() patterns.
|
|
7
|
+
*/
|
|
8
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
9
|
+
import { Deployment, Service, ConfigMap } from "../generated/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Create a ConfiguredApp composite — returns prop objects for
|
|
12
|
+
* a Deployment, Service, and optional ConfigMap.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { ConfiguredApp } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
*
|
|
18
|
+
* const { deployment, service, configMap } = ConfiguredApp({
|
|
19
|
+
* name: "api",
|
|
20
|
+
* image: "api:1.0",
|
|
21
|
+
* port: 8080,
|
|
22
|
+
* configData: { "app.conf": "key=value" },
|
|
23
|
+
* configMountPath: "/etc/api",
|
|
24
|
+
* secretName: "api-creds",
|
|
25
|
+
* secretMountPath: "/secrets",
|
|
26
|
+
* envFrom: { secretRef: "api-env-secret" },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const ConfiguredApp = Composite((props) => {
|
|
31
|
+
const { name, image, port = 80, replicas = 2, configData, configMountPath, secretName, secretMountPath, envFrom, initContainers, labels: extraLabels = {}, cpuLimit = "500m", memoryLimit = "256Mi", cpuRequest = "100m", memoryRequest = "128Mi", namespace, env, securityContext, defaults: defs, } = props;
|
|
32
|
+
const configMapName = `${name}-config`;
|
|
33
|
+
const commonLabels = {
|
|
34
|
+
"app.kubernetes.io/name": name,
|
|
35
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
36
|
+
...extraLabels,
|
|
37
|
+
};
|
|
38
|
+
// Build volumes and volumeMounts
|
|
39
|
+
const volumes = [];
|
|
40
|
+
const volumeMounts = [];
|
|
41
|
+
if (configData && configMountPath) {
|
|
42
|
+
volumes.push({
|
|
43
|
+
name: "config",
|
|
44
|
+
configMap: { name: configMapName },
|
|
45
|
+
});
|
|
46
|
+
volumeMounts.push({
|
|
47
|
+
name: "config",
|
|
48
|
+
mountPath: configMountPath,
|
|
49
|
+
readOnly: true,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (secretName && secretMountPath) {
|
|
53
|
+
volumes.push({
|
|
54
|
+
name: "secret",
|
|
55
|
+
secret: { secretName },
|
|
56
|
+
});
|
|
57
|
+
volumeMounts.push({
|
|
58
|
+
name: "secret",
|
|
59
|
+
mountPath: secretMountPath,
|
|
60
|
+
readOnly: true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Build envFrom array
|
|
64
|
+
const envFromList = [];
|
|
65
|
+
if (envFrom?.configMapRef) {
|
|
66
|
+
envFromList.push({ configMapRef: { name: envFrom.configMapRef } });
|
|
67
|
+
}
|
|
68
|
+
if (envFrom?.secretRef) {
|
|
69
|
+
envFromList.push({ secretRef: { name: envFrom.secretRef } });
|
|
70
|
+
}
|
|
71
|
+
const container = {
|
|
72
|
+
name,
|
|
73
|
+
image,
|
|
74
|
+
ports: [{ containerPort: port, name: "http" }],
|
|
75
|
+
resources: {
|
|
76
|
+
limits: { cpu: cpuLimit, memory: memoryLimit },
|
|
77
|
+
requests: { cpu: cpuRequest, memory: memoryRequest },
|
|
78
|
+
},
|
|
79
|
+
...(env && { env }),
|
|
80
|
+
...(envFromList.length > 0 && { envFrom: envFromList }),
|
|
81
|
+
...(volumeMounts.length > 0 && { volumeMounts }),
|
|
82
|
+
...(securityContext && { securityContext }),
|
|
83
|
+
};
|
|
84
|
+
const podSpec = {
|
|
85
|
+
containers: [container],
|
|
86
|
+
...(volumes.length > 0 && { volumes }),
|
|
87
|
+
...(initContainers && {
|
|
88
|
+
initContainers: initContainers.map((ic) => ({
|
|
89
|
+
name: ic.name,
|
|
90
|
+
image: ic.image,
|
|
91
|
+
...(ic.command && { command: ic.command }),
|
|
92
|
+
...(ic.args && { args: ic.args }),
|
|
93
|
+
})),
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
const deployment = new Deployment(mergeDefaults({
|
|
97
|
+
metadata: {
|
|
98
|
+
name,
|
|
99
|
+
...(namespace && { namespace }),
|
|
100
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "server" },
|
|
101
|
+
},
|
|
102
|
+
spec: {
|
|
103
|
+
replicas,
|
|
104
|
+
selector: { matchLabels: { "app.kubernetes.io/name": name } },
|
|
105
|
+
template: {
|
|
106
|
+
metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
|
|
107
|
+
spec: podSpec,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}, defs?.deployment));
|
|
111
|
+
const service = new Service(mergeDefaults({
|
|
112
|
+
metadata: {
|
|
113
|
+
name,
|
|
114
|
+
...(namespace && { namespace }),
|
|
115
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "server" },
|
|
116
|
+
},
|
|
117
|
+
spec: {
|
|
118
|
+
selector: { "app.kubernetes.io/name": name },
|
|
119
|
+
ports: [{ port: 80, targetPort: port, protocol: "TCP", name: "http" }],
|
|
120
|
+
type: "ClusterIP",
|
|
121
|
+
},
|
|
122
|
+
}, defs?.service));
|
|
123
|
+
const result = { deployment, service };
|
|
124
|
+
if (configData) {
|
|
125
|
+
result.configMap = new ConfigMap(mergeDefaults({
|
|
126
|
+
metadata: {
|
|
127
|
+
name: configMapName,
|
|
128
|
+
...(namespace && { namespace }),
|
|
129
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "config" },
|
|
130
|
+
},
|
|
131
|
+
data: configData,
|
|
132
|
+
}, defs?.configMap));
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}, "ConfiguredApp");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CronJob schedule validation (issue #2071 item 4).
|
|
3
|
+
*
|
|
4
|
+
* `OperatorStack` and `CronWorkload` both hand their `schedule` field
|
|
5
|
+
* straight to a k8s `CronJob`, which parses only five fields (minute hour
|
|
6
|
+
* day-of-month month day-of-week). Core's own cron parser
|
|
7
|
+
* (`isValidCronExpression`, `packages/core/src/op/cron.ts`, #2120) is
|
|
8
|
+
* deliberately more permissive: it also accepts the 6-field Quartz form
|
|
9
|
+
* (`second minute hour dom month dow`) that a `ConvergeOp`'s own `schedule`
|
|
10
|
+
* may legitimately carry, since the final word on cron syntax belongs to
|
|
11
|
+
* whichever scheduler runs the string. A k8s CronJob is that scheduler here,
|
|
12
|
+
* and it is stricter than core's parser, so a 6-field `ConvergeOp` schedule
|
|
13
|
+
* reused as-is on a CronJob fails at `kubectl apply`, not at build time.
|
|
14
|
+
*
|
|
15
|
+
* This module gives both composites one place to catch that at construction,
|
|
16
|
+
* reusing core's parser rather than a second copy of it.
|
|
17
|
+
*/
|
|
18
|
+
import { isValidCronExpression } from "@intentius/chant/op";
|
|
19
|
+
/**
|
|
20
|
+
* Validate `schedule` for a k8s CronJob, throwing before a bad value ever
|
|
21
|
+
* reaches `kubectl apply`. `context` names the composite (and host, for
|
|
22
|
+
* `OperatorStack`) so the thrown message reads like the rest of that
|
|
23
|
+
* composite's build-time refusals, e.g. `OperatorStack "chant-operator",
|
|
24
|
+
* host "fountain-observe"` or `CronWorkload "backup"`.
|
|
25
|
+
*/
|
|
26
|
+
export function validateCronJobSchedule(context, schedule) {
|
|
27
|
+
if (!schedule || schedule.trim().length === 0) {
|
|
28
|
+
throw new Error(`${context}: schedule is required. A CronJob with no schedule never ticks.`);
|
|
29
|
+
}
|
|
30
|
+
const fields = schedule.trim().split(/\s+/);
|
|
31
|
+
if (!isValidCronExpression(schedule)) {
|
|
32
|
+
throw new Error(`${context}: schedule "${schedule}" is not valid cron syntax. A Kubernetes CronJob takes five fields: minute hour day-of-month month day-of-week.`);
|
|
33
|
+
}
|
|
34
|
+
if (fields.length === 6) {
|
|
35
|
+
throw new Error(`${context}: schedule "${schedule}" is 6-field cron. A Kubernetes CronJob takes five fields (minute hour day-of-month month day-of-week) and has no seconds column. ` +
|
|
36
|
+
`A ConvergeOp schedule string in that form must be converted before it reaches a CronJob: drop the leading seconds field and keep the remaining five.`);
|
|
37
|
+
}
|
|
38
|
+
}
|