@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.
Files changed (270) hide show
  1. package/dist/actions/apps.js +18 -0
  2. package/dist/actions/batch.js +6 -0
  3. package/dist/actions/core.js +48 -0
  4. package/dist/actions/index.js +13 -0
  5. package/dist/actions/networking.js +11 -0
  6. package/dist/actions/rbac.js +8 -0
  7. package/dist/api/classify.js +123 -0
  8. package/dist/api/connect.js +62 -0
  9. package/dist/api/fake-cluster.js +175 -0
  10. package/dist/api/operation-surface.d.ts.map +1 -1
  11. package/dist/api/operation-surface.js +109 -0
  12. package/dist/api/owner-chain.js +100 -0
  13. package/dist/api/sweep-types.js +35 -0
  14. package/dist/behaviour-kinds.js +82 -0
  15. package/dist/codegen/docs-cli.js +2 -0
  16. package/dist/codegen/docs.js +146 -0
  17. package/dist/codegen/generate-cli.js +30 -0
  18. package/dist/codegen/generate-lexicon.js +74 -0
  19. package/dist/codegen/generate-list-map-keys.js +88 -0
  20. package/dist/codegen/generate-operations.js +53 -0
  21. package/dist/codegen/generate-typescript.js +124 -0
  22. package/dist/codegen/generate.js +173 -0
  23. package/dist/codegen/naming.js +174 -0
  24. package/dist/codegen/package.js +36 -0
  25. package/dist/codegen/patches.js +88 -0
  26. package/dist/codegen/typecheck.js +4 -0
  27. package/dist/codegen/versions.js +41 -0
  28. package/dist/components/argo-app.js +51 -0
  29. package/dist/components/builders.js +22 -0
  30. package/dist/components/capability-plugin.js +31 -0
  31. package/dist/components/flux-reconcile.js +61 -0
  32. package/dist/components/index.d.ts +1 -1
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +18 -0
  35. package/dist/components/kubectl-apply.d.ts +83 -1
  36. package/dist/components/kubectl-apply.d.ts.map +1 -1
  37. package/dist/components/kubectl-apply.js +274 -0
  38. package/dist/components/kustomize-apply.js +25 -0
  39. package/dist/composites/adot-collector.js +184 -0
  40. package/dist/composites/agic-ingress.js +85 -0
  41. package/dist/composites/aks-external-dns-agent.js +146 -0
  42. package/dist/composites/alb-ingress.js +86 -0
  43. package/dist/composites/argo-app.js +199 -0
  44. package/dist/composites/autoscaled-service.js +174 -0
  45. package/dist/composites/azure-disk-storage-class.js +47 -0
  46. package/dist/composites/azure-file-storage-class.js +48 -0
  47. package/dist/composites/azure-monitor-collector.js +176 -0
  48. package/dist/composites/batch-job.js +139 -0
  49. package/dist/composites/catalog.js +4110 -0
  50. package/dist/composites/cockroachdb-cluster.js +371 -0
  51. package/dist/composites/cockroachdb-region-stack.js +369 -0
  52. package/dist/composites/config-connector-context.js +40 -0
  53. package/dist/composites/configured-app.js +135 -0
  54. package/dist/composites/cron-schedule.js +38 -0
  55. package/dist/composites/cron-workload.js +109 -0
  56. package/dist/composites/ebs-storage-class.js +54 -0
  57. package/dist/composites/efs-storage-class.js +46 -0
  58. package/dist/composites/external-dns-agent.js +122 -0
  59. package/dist/composites/filestore-storage-class.js +49 -0
  60. package/dist/composites/fluent-bit-agent.js +157 -0
  61. package/dist/composites/flux-app.js +156 -0
  62. package/dist/composites/gce-ingress.js +83 -0
  63. package/dist/composites/gce-pd-storage-class.js +49 -0
  64. package/dist/composites/gke-external-dns-agent.js +129 -0
  65. package/dist/composites/gke-fluent-bit-agent.js +165 -0
  66. package/dist/composites/gke-gateway.js +91 -0
  67. package/dist/composites/gke-otel-collector.js +94 -0
  68. package/dist/composites/index.js +51 -0
  69. package/dist/composites/inference-service.js +76 -0
  70. package/dist/composites/irsa-service-account.js +77 -0
  71. package/dist/composites/member-defaults.js +14 -0
  72. package/dist/composites/metrics-server.js +181 -0
  73. package/dist/composites/model.js +92 -0
  74. package/dist/composites/monitored-service.js +152 -0
  75. package/dist/composites/namespace-env.js +114 -0
  76. package/dist/composites/network-isolated-app.js +124 -0
  77. package/dist/composites/node-agent.js +149 -0
  78. package/dist/composites/operator-stack.js +268 -0
  79. package/dist/composites/otel-collector-agent.js +142 -0
  80. package/dist/composites/otel-collector-gateway.js +216 -0
  81. package/dist/composites/otel-collector-node.js +176 -0
  82. package/dist/composites/otel-collector-rbac.js +115 -0
  83. package/dist/composites/otel-collector-shape.js +128 -0
  84. package/dist/composites/otel-collector.js +115 -0
  85. package/dist/composites/otel-operator-collector.js +123 -0
  86. package/dist/composites/ray-cluster.js +378 -0
  87. package/dist/composites/ray-job.js +136 -0
  88. package/dist/composites/ray-service.js +178 -0
  89. package/dist/composites/secure-ingress.js +100 -0
  90. package/dist/composites/security-context.js +1 -0
  91. package/dist/composites/sidecar-app.js +105 -0
  92. package/dist/composites/stateful-app.js +111 -0
  93. package/dist/composites/vllm-serving-runtime.js +114 -0
  94. package/dist/composites/web-app.js +157 -0
  95. package/dist/composites/worker-pool.js +167 -0
  96. package/dist/composites/workload-identity-sa.js +81 -0
  97. package/dist/composites/workload-identity-service-account.js +77 -0
  98. package/dist/config-schema.js +72 -0
  99. package/dist/config.js +59 -0
  100. package/dist/coverage.js +27 -0
  101. package/dist/crd/crd-sources.js +615 -0
  102. package/dist/crd/loader.js +181 -0
  103. package/dist/crd/oci-chart.js +144 -0
  104. package/dist/crd/parser.js +382 -0
  105. package/dist/crd/project-codegen.js +273 -0
  106. package/dist/crd/types.js +7 -0
  107. package/dist/deep-observe-hooks.js +306 -0
  108. package/dist/deep-observe.js +234 -0
  109. package/dist/default-labels.js +87 -0
  110. package/dist/describe-resources.js +699 -0
  111. package/dist/describe-stack-status.js +99 -0
  112. package/dist/detect.js +11 -0
  113. package/dist/effect-receipt-row.js +205 -0
  114. package/dist/export-resources.js +39 -0
  115. package/dist/generated/index.js +1092 -0
  116. package/dist/generated/runtime.js +4 -0
  117. package/dist/group-namespace.js +96 -0
  118. package/dist/import/embedded.js +197 -0
  119. package/dist/import/generator.js +288 -0
  120. package/dist/import/live-export.js +85 -0
  121. package/dist/import/parser.js +204 -0
  122. package/dist/import/testdata/embedded/fixtures.js +60 -0
  123. package/dist/include/entity.js +78 -0
  124. package/dist/include/resolve.js +170 -0
  125. package/dist/index.js +59 -0
  126. package/dist/integrity.json +3 -3
  127. package/dist/kube/apply.js +90 -0
  128. package/dist/kube/connect.js +46 -0
  129. package/dist/kube/delete.js +138 -0
  130. package/dist/kube/describe.js +169 -0
  131. package/dist/kube/events.js +132 -0
  132. package/dist/kube/flags.js +91 -0
  133. package/dist/kube/get.js +159 -0
  134. package/dist/kube/group.js +123 -0
  135. package/dist/kube/logs.js +82 -0
  136. package/dist/kube/project.js +94 -0
  137. package/dist/kube/render.js +227 -0
  138. package/dist/kube/source.js +122 -0
  139. package/dist/kube/target.js +63 -0
  140. package/dist/kube/testing.js +28 -0
  141. package/dist/kube/top.js +99 -0
  142. package/dist/kube/verdict.js +72 -0
  143. package/dist/kube/wait.js +129 -0
  144. package/dist/kustomize/render.js +73 -0
  145. package/dist/kustomize/rendered-entity.js +67 -0
  146. package/dist/kustomize/root.js +68 -0
  147. package/dist/lint/audit-catalog.js +61 -0
  148. package/dist/lint/audit-lineage.js +118 -0
  149. package/dist/lint/post-synth/argo-helpers.js +36 -0
  150. package/dist/lint/post-synth/argo002.js +31 -0
  151. package/dist/lint/post-synth/argo003.js +53 -0
  152. package/dist/lint/post-synth/argo005.js +54 -0
  153. package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
  154. package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
  155. package/dist/lint/post-synth/flux002.js +56 -0
  156. package/dist/lint/post-synth/flux003.js +50 -0
  157. package/dist/lint/post-synth/index.js +104 -0
  158. package/dist/lint/post-synth/k8s-helpers.js +186 -0
  159. package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
  160. package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
  161. package/dist/lint/post-synth/sops-helpers.js +43 -0
  162. package/dist/lint/post-synth/wk8005.js +44 -0
  163. package/dist/lint/post-synth/wk8006.js +56 -0
  164. package/dist/lint/post-synth/wk8041.js +60 -0
  165. package/dist/lint/post-synth/wk8042.js +36 -0
  166. package/dist/lint/post-synth/wk8101.js +52 -0
  167. package/dist/lint/post-synth/wk8102.js +30 -0
  168. package/dist/lint/post-synth/wk8103.js +33 -0
  169. package/dist/lint/post-synth/wk8104.js +58 -0
  170. package/dist/lint/post-synth/wk8105.js +33 -0
  171. package/dist/lint/post-synth/wk8201.js +44 -0
  172. package/dist/lint/post-synth/wk8202.js +34 -0
  173. package/dist/lint/post-synth/wk8203.js +34 -0
  174. package/dist/lint/post-synth/wk8204.js +71 -0
  175. package/dist/lint/post-synth/wk8205.js +38 -0
  176. package/dist/lint/post-synth/wk8207.js +33 -0
  177. package/dist/lint/post-synth/wk8208.js +33 -0
  178. package/dist/lint/post-synth/wk8209.js +33 -0
  179. package/dist/lint/post-synth/wk8301.js +49 -0
  180. package/dist/lint/post-synth/wk8302.js +34 -0
  181. package/dist/lint/post-synth/wk8303.js +68 -0
  182. package/dist/lint/post-synth/wk8304.js +58 -0
  183. package/dist/lint/post-synth/wk8305.js +96 -0
  184. package/dist/lint/post-synth/wk8306.js +38 -0
  185. package/dist/lint/post-synth/wk8401.js +86 -0
  186. package/dist/lint/post-synth/wk8402.js +31 -0
  187. package/dist/lint/post-synth/wk8403.js +49 -0
  188. package/dist/lint/post-synth/wk8404.js +36 -0
  189. package/dist/lint/post-synth/wk8405.js +65 -0
  190. package/dist/lint/post-synth/wk8406.js +50 -0
  191. package/dist/lint/post-synth/wk8407.js +58 -0
  192. package/dist/lint/post-synth/wk8501.js +36 -0
  193. package/dist/lint/post-synth/wk8502.js +35 -0
  194. package/dist/lint/post-synth/wk8503.js +164 -0
  195. package/dist/lint/post-synth/wk8504.js +20 -0
  196. package/dist/lint/post-synth/wk8505.js +31 -0
  197. package/dist/lint/post-synth/wk8601.js +40 -0
  198. package/dist/lint/post-synth/wk8602.js +120 -0
  199. package/dist/lint/post-synth/wk8603.js +63 -0
  200. package/dist/lint/post-synth/wk8604.js +31 -0
  201. package/dist/lint/post-synth/wk8605.js +124 -0
  202. package/dist/lint/post-synth/wk8606.js +22 -0
  203. package/dist/lint/post-synth/wk8701.js +31 -0
  204. package/dist/lint/post-synth/wk8702.js +37 -0
  205. package/dist/lint/post-synth/wk8703.js +31 -0
  206. package/dist/lint/post-synth/wk8704.js +135 -0
  207. package/dist/lint/post-synth/wk8705.js +38 -0
  208. package/dist/lint/rules/argo-appset-single-project.js +56 -0
  209. package/dist/lint/rules/argo-ast.js +94 -0
  210. package/dist/lint/rules/argo-automated-prune.js +59 -0
  211. package/dist/lint/rules/flux-source-ref-pin.js +44 -0
  212. package/dist/lint/rules/hardcoded-namespace.js +57 -0
  213. package/dist/lint/rules/latest-image-tag.js +103 -0
  214. package/dist/lint/rules/missing-resource-limits.js +85 -0
  215. package/dist/lsp/completions.d.ts.map +1 -1
  216. package/dist/lsp/completions.js +18 -0
  217. package/dist/lsp/hover.d.ts.map +1 -1
  218. package/dist/lsp/hover.js +57 -0
  219. package/dist/manifest-entity.js +71 -0
  220. package/dist/manifest.json +1 -1
  221. package/dist/micro-time.js +49 -0
  222. package/dist/op/activities/apply-diff.d.ts +31 -0
  223. package/dist/op/activities/apply-diff.d.ts.map +1 -0
  224. package/dist/op/activities/apply-diff.js +79 -0
  225. package/dist/op/activities/argo.js +98 -0
  226. package/dist/op/activities/ensure-secret.js +38 -0
  227. package/dist/op/activities/index.js +51 -0
  228. package/dist/op/activities/kubectl.d.ts +64 -0
  229. package/dist/op/activities/kubectl.d.ts.map +1 -1
  230. package/dist/op/activities/kubectl.js +517 -0
  231. package/dist/op/activities/wait-for-ready.js +229 -0
  232. package/dist/op/builders.js +68 -0
  233. package/dist/package-cli.js +21 -0
  234. package/dist/patch/json-patch.js +187 -0
  235. package/dist/patch/rendered-patch.js +94 -0
  236. package/dist/plugin.js +741 -0
  237. package/dist/project-kinds.js +56 -0
  238. package/dist/receipt-store.js +216 -0
  239. package/dist/rules/crd-schema-helpers.ts +2 -1
  240. package/dist/secret-labels.js +33 -0
  241. package/dist/secret-store.js +116 -0
  242. package/dist/serializer.d.ts.map +1 -1
  243. package/dist/serializer.js +578 -0
  244. package/dist/sops/encrypted-secret-file.js +194 -0
  245. package/dist/sops/entity.js +32 -0
  246. package/dist/spec/fetch.js +58 -0
  247. package/dist/spec/parse.js +622 -0
  248. package/dist/subscribe-changes.js +168 -0
  249. package/dist/teardown.js +197 -0
  250. package/dist/telemetry-release.js +86 -0
  251. package/dist/validate-cli.js +17 -0
  252. package/dist/validate.js +47 -0
  253. package/dist/variables.js +34 -0
  254. package/dist/whoami.js +109 -0
  255. package/package.json +12 -12
  256. package/src/api/operation-surface.ts +2 -1
  257. package/src/codegen/package.ts +2 -2
  258. package/src/components/index.ts +3 -0
  259. package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
  260. package/src/components/kubectl-apply.test.ts +262 -0
  261. package/src/components/kubectl-apply.ts +341 -12
  262. package/src/deep-observe-hooks.ts +2 -2
  263. package/src/import/roundtrip-build.test.ts +46 -0
  264. package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
  265. package/src/lsp/completions.ts +2 -1
  266. package/src/lsp/hover.ts +2 -1
  267. package/src/op/activities/apply-diff.ts +87 -0
  268. package/src/op/activities/kubectl-plan.test.ts +224 -0
  269. package/src/op/activities/kubectl.ts +204 -16
  270. package/src/serializer.ts +2 -1
@@ -0,0 +1,176 @@
1
+ /**
2
+ * What a collector pod needs from its node, worked out from the collector
3
+ * config it runs, the way `agentClusterRules` works out its RBAC (chant
4
+ * #3103). `OtelCollector` and `GkeOtelCollector` add the result to their
5
+ * DaemonSet, and WK8605 reports a workload that runs such a config without it.
6
+ *
7
+ * Only components a pipeline runs count, since the collector starts no
8
+ * other. A config that reads nothing from the node gets nothing, so its
9
+ * DaemonSet is unchanged. Each requirement cites the component's README at
10
+ * collector-contrib v0.130.0, the version the otel lexicon pins:
11
+ *
12
+ * - The node's name, from the downward API (`spec.nodeName`), in each
13
+ * variable a `k8sattributes` processor names in `filter.node_from_env_var`
14
+ * (processor/k8sattributesprocessor README, "Deployment scenarios": "Use
15
+ * the downward API to inject the node name as an environment variable"),
16
+ * and in each `${env:VAR}` a `kubeletstats` receiver's `node` uses, or its
17
+ * `endpoint` uses when the variable's name ends in `NODE_NAME`
18
+ * (receiver/kubeletstatsreceiver README, "Service Account Authentication
19
+ * Example"). An `endpoint` variable ending in `NODE_IP` or `HOST_IP` gets
20
+ * `status.hostIP` instead. Any other variable is the caller's to set.
21
+ * - The host root, read-only at a `hostmetrics` receiver's `root_path`
22
+ * (receiver/hostmetricsreceiver README, "Collecting host metrics from
23
+ * inside a container (Linux only)": bind mount the host filesystem, then
24
+ * configure `root_path`). Mounted with `HostToContainer` propagation so
25
+ * filesystems the node mounts later show up, as the upstream Helm chart's
26
+ * hostMetrics preset does.
27
+ * - The directories a `filelog` receiver's `include` patterns read, read-only
28
+ * at the same path (receiver/filelogreceiver README, "Configuration":
29
+ * `include` is a list of file glob patterns the receiver reads). The mount
30
+ * is the fixed directory part of each pattern, so `/var/log/pods/*\/*\/*.log`
31
+ * mounts `/var/log/pods`. `/var/log/containers` holds symlinks into
32
+ * `/var/log/pods`, so reading it mounts both. A pattern with no fixed
33
+ * directory below `/`, or one that would cover the config directory, is
34
+ * left to the caller.
35
+ *
36
+ * `readsLogs` is set when a `filelog` receiver reads a host directory: the
37
+ * kubelet's container logs there are owned by root, which the composites
38
+ * deal with (see `logAccess` on `OtelCollector`).
39
+ */
40
+ import { canonicalTypeOf } from "@intentius/chant-lexicon-otel/model";
41
+ function isRecord(v) {
42
+ return typeof v === "object" && v !== null && !Array.isArray(v);
43
+ }
44
+ /** Component ids of one kind that some pipeline runs. */
45
+ function runningIds(config, kind) {
46
+ const ids = new Set();
47
+ const pipelines = isRecord(config.service) && isRecord(config.service.pipelines) ? config.service.pipelines : {};
48
+ for (const p of Object.values(pipelines)) {
49
+ const list = isRecord(p) ? p[kind] : undefined;
50
+ if (Array.isArray(list))
51
+ for (const id of list)
52
+ if (typeof id === "string")
53
+ ids.add(id);
54
+ }
55
+ return ids;
56
+ }
57
+ /** [id, settings] of each running component of `type`, under its old or new name (`kubelet_stats`). */
58
+ function running(config, kind, type) {
59
+ const ids = runningIds(config, kind);
60
+ const section = isRecord(config[kind]) ? config[kind] : {};
61
+ const componentKind = kind === "receivers" ? "receiver" : "processor";
62
+ return Object.entries(section)
63
+ .filter(([id]) => ids.has(id) && canonicalTypeOf(componentKind, id) === type)
64
+ .map(([id, cfg]) => [id, isRecord(cfg) ? cfg : {}]);
65
+ }
66
+ /** The variables a string reads as `${env:NAME}`. */
67
+ function envRefs(value) {
68
+ if (typeof value !== "string")
69
+ return [];
70
+ return [...value.matchAll(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g)].map((m) => m[1]);
71
+ }
72
+ /** The directory part of a glob before its first wildcard: `/var/log/pods/*\/x.log` is `/var/log/pods`. */
73
+ export function fixedDirectory(pattern) {
74
+ if (!pattern.startsWith("/"))
75
+ return undefined;
76
+ const parts = pattern.split("/");
77
+ const fixed = [];
78
+ for (const part of parts) {
79
+ if (/[*?[{]/.test(part))
80
+ break;
81
+ fixed.push(part);
82
+ }
83
+ // No wildcard at all: a file, so its directory.
84
+ if (fixed.length === parts.length)
85
+ fixed.pop();
86
+ const dir = fixed.join("/").replace(/\/+$/, "");
87
+ return dir === "" ? undefined : dir;
88
+ }
89
+ function within(path, dir) {
90
+ return path === dir || path.startsWith(`${dir}/`);
91
+ }
92
+ function volumeName(prefix, path) {
93
+ const slug = path.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
94
+ return `${prefix}-${slug}`.slice(0, 63).replace(/-+$/, "");
95
+ }
96
+ /**
97
+ * What a collector config needs from the node. `configDir` is where the
98
+ * collector's own config is mounted; a log directory covering it is skipped.
99
+ */
100
+ export function collectorNodeAccess(config, configDir = "/etc/otel") {
101
+ const env = new Map();
102
+ const setEnv = (name, fieldPath) => {
103
+ if (!env.has(name))
104
+ env.set(name, fieldPath);
105
+ };
106
+ for (const [, p] of running(config, "processors", "k8sattributes")) {
107
+ const name = isRecord(p.filter) ? p.filter.node_from_env_var : undefined;
108
+ if (typeof name === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
109
+ setEnv(name, "spec.nodeName");
110
+ }
111
+ for (const [, r] of running(config, "receivers", "kubeletstats")) {
112
+ for (const name of envRefs(r.node))
113
+ setEnv(name, "spec.nodeName");
114
+ for (const name of envRefs(r.endpoint)) {
115
+ if (/(NODE|HOST)_IP$/.test(name))
116
+ setEnv(name, "status.hostIP");
117
+ else if (/NODE_NAME$/.test(name))
118
+ setEnv(name, "spec.nodeName");
119
+ }
120
+ }
121
+ const mounts = [];
122
+ const rootPaths = new Set();
123
+ for (const [id, r] of running(config, "receivers", "hostmetrics")) {
124
+ const root = typeof r.root_path === "string" ? r.root_path.replace(/\/+$/, "") : "";
125
+ if (!root.startsWith("/") || rootPaths.has(root))
126
+ continue;
127
+ rootPaths.add(root);
128
+ mounts.push({
129
+ name: rootPaths.size === 1 ? "hostfs" : volumeName("hostfs", root),
130
+ hostPath: "/",
131
+ mountPath: root,
132
+ component: id,
133
+ partial: true,
134
+ mountPropagation: "HostToContainer",
135
+ });
136
+ }
137
+ const logDirs = [];
138
+ for (const [id, r] of running(config, "receivers", "filelog")) {
139
+ const include = Array.isArray(r.include) ? r.include : [];
140
+ for (const pattern of include) {
141
+ if (typeof pattern !== "string")
142
+ continue;
143
+ const dir = fixedDirectory(pattern);
144
+ if (!dir || within(configDir, dir))
145
+ continue;
146
+ logDirs.push({ dir, component: id });
147
+ if (within(dir, "/var/log/containers"))
148
+ logDirs.push({ dir: "/var/log/pods", component: id });
149
+ }
150
+ }
151
+ // One mount per directory, the outermost of nested ones.
152
+ const kept = logDirs.filter(({ dir }, i) => !logDirs.some((o, j) => (o.dir !== dir && within(dir, o.dir)) || (o.dir === dir && j < i)));
153
+ for (const { dir, component } of kept) {
154
+ if ([...rootPaths].some((root) => within(dir, root)))
155
+ continue;
156
+ mounts.push({ name: volumeName("host", dir), hostPath: dir, mountPath: dir, component });
157
+ }
158
+ return {
159
+ env: [...env].map(([name, fieldPath]) => ({ name, valueFrom: { fieldRef: { fieldPath } } })),
160
+ mounts,
161
+ readsLogs: kept.length > 0,
162
+ };
163
+ }
164
+ /** The pod volumes for a set of mounts. */
165
+ export function nodeVolumes(mounts) {
166
+ return mounts.map((m) => ({ name: m.name, hostPath: { path: m.hostPath } }));
167
+ }
168
+ /** The container volume mounts for a set of mounts, all read-only. */
169
+ export function nodeVolumeMounts(mounts) {
170
+ return mounts.map((m) => ({
171
+ name: m.name,
172
+ mountPath: m.mountPath,
173
+ readOnly: true,
174
+ ...(m.mountPropagation ? { mountPropagation: m.mountPropagation } : {}),
175
+ }));
176
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The RBAC the collector composites grant, worked out from the collector
3
+ * config they deploy.
4
+ *
5
+ * `agentClusterRules` is the ClusterRole `OtelCollector` and
6
+ * `GkeOtelCollector` share: what the `k8sattributes` processor needs, plus
7
+ * what the config's Kubernetes receivers need. Each rule cites the
8
+ * component's RBAC requirements at collector-contrib v0.130.0, the version the
9
+ * otel lexicon pins.
10
+ *
11
+ * `namespacedRoles` builds one Role and RoleBinding per namespace, for access
12
+ * a collector needs in namespaces other than its own: Endpoints for a
13
+ * `loadbalancing` exporter's `k8s` resolver, Leases for a
14
+ * `k8s_leader_elector` extension.
15
+ */
16
+ import { mergeDefaults } from "@intentius/chant";
17
+ import { canonicalTypeOf } from "@intentius/chant-lexicon-otel/model";
18
+ import { Role, RoleBinding } from "../generated/index.js";
19
+ const READ = ["get", "list", "watch"];
20
+ /** The configs of the components of `kind` whose type is `type`, under its old or new name (`k8s_attributes`). */
21
+ function componentsOfType(kind, section, type) {
22
+ return Object.entries(section ?? {})
23
+ .filter(([id]) => canonicalTypeOf(kind, id) === type)
24
+ .map(([, cfg]) => (cfg && typeof cfg === "object" ? cfg : {}));
25
+ }
26
+ /**
27
+ * The agent ClusterRole's rules for a collector config.
28
+ *
29
+ * Always, for `k8sattributes` (processor/k8sattributesprocessor README,
30
+ * "Cluster-scoped RBAC"): `get`, `list` and `watch` on pods, namespaces and
31
+ * nodes, and on `apps` replicasets for `k8s.deployment.name`, which is
32
+ * extracted by default. The processor lists ReplicaSets through `apps/v1`
33
+ * only, so the README's `extensions` group is left out. When a
34
+ * `k8sattributes` processor extracts labels or annotations `from: deployment`,
35
+ * `apps` deployments are added.
36
+ *
37
+ * For each `kubeletstats` receiver (receiver/kubeletstatsreceiver README,
38
+ * "Role-based access control"): `get` on `nodes/stats`; `get` on
39
+ * `nodes/proxy` when it sets `extra_metadata_labels` or enables a
40
+ * `*_request_utilization` or `*_limit_utilization` metric; and, when it sets
41
+ * `k8s_api_config`, `get` on persistentvolumeclaims and persistentvolumes,
42
+ * which it reads for volume metadata (scraper.go).
43
+ *
44
+ * Other components that read the Kubernetes API, such as `k8s_cluster`
45
+ * (which belongs on a gateway, see WK8603), add their rules through
46
+ * `defaults.clusterRole.rules`, which are appended.
47
+ */
48
+ export function agentClusterRules(config) {
49
+ const apps = ["replicasets"];
50
+ const k8sattributes = componentsOfType("processor", config.processors, "k8sattributes");
51
+ const fromDeployment = k8sattributes.some((p) => {
52
+ const extract = p.extract;
53
+ return [...(extract?.labels ?? []), ...(extract?.annotations ?? [])].some((f) => f?.from === "deployment");
54
+ });
55
+ if (fromDeployment)
56
+ apps.push("deployments");
57
+ const rules = [
58
+ { apiGroups: [""], resources: ["pods", "namespaces", "nodes"], verbs: READ },
59
+ { apiGroups: ["apps"], resources: apps, verbs: READ },
60
+ ];
61
+ const kubeletstats = componentsOfType("receiver", config.receivers, "kubeletstats");
62
+ if (kubeletstats.length) {
63
+ const nodeSubresources = ["nodes/stats"];
64
+ const needsProxy = kubeletstats.some((r) => {
65
+ const labels = r.extra_metadata_labels;
66
+ if (Array.isArray(labels) && labels.length)
67
+ return true;
68
+ const metrics = (r.metrics ?? {});
69
+ return Object.entries(metrics).some(([metric, toggle]) => /_(request|limit)_utilization$/.test(metric) && toggle?.enabled === true);
70
+ });
71
+ if (needsProxy)
72
+ nodeSubresources.push("nodes/proxy");
73
+ rules.push({ apiGroups: [""], resources: nodeSubresources, verbs: ["get"] });
74
+ if (kubeletstats.some((r) => r.k8s_api_config)) {
75
+ rules.push({ apiGroups: [""], resources: ["persistentvolumeclaims", "persistentvolumes"], verbs: ["get"] });
76
+ }
77
+ }
78
+ return rules;
79
+ }
80
+ function pascal(namespace) {
81
+ return namespace
82
+ .split(/[^a-zA-Z0-9]+/)
83
+ .filter(Boolean)
84
+ .map((part) => part[0].toUpperCase() + part.slice(1))
85
+ .join("");
86
+ }
87
+ /**
88
+ * One Role and RoleBinding per namespace, as composite members. The first
89
+ * namespace's are `<key>Role` and `<key>RoleBinding`, so a single-namespace
90
+ * config keeps the member names it had before; each further namespace's are
91
+ * `<key>RoleIn<Namespace>` and `<key>RoleBindingIn<Namespace>`, with the
92
+ * namespace in PascalCase (`team-a` gives `endpointsRoleInTeamA`).
93
+ */
94
+ export function namespacedRoles(opts) {
95
+ const out = {};
96
+ const seen = new Map();
97
+ opts.namespaces.forEach((namespace, i) => {
98
+ const suffix = i === 0 ? "" : `In${pascal(namespace)}`;
99
+ const clash = seen.get(suffix);
100
+ if (clash !== undefined) {
101
+ throw new Error(`${opts.name}: namespaces ${clash} and ${namespace} give the same member name ${opts.key}Role${suffix}`);
102
+ }
103
+ seen.set(suffix, namespace);
104
+ out[`${opts.key}Role${suffix}`] = new Role(mergeDefaults({
105
+ metadata: { name: opts.name, namespace, labels: opts.labels },
106
+ rules: opts.rules,
107
+ }, opts.roleDefaults));
108
+ out[`${opts.key}RoleBinding${suffix}`] = new RoleBinding(mergeDefaults({
109
+ metadata: { name: opts.name, namespace, labels: opts.labels },
110
+ roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: opts.name },
111
+ subjects: [{ kind: "ServiceAccount", name: opts.serviceAccount.name, namespace: opts.serviceAccount.namespace }],
112
+ }, opts.roleBindingDefaults));
113
+ });
114
+ return out;
115
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * What `OtelCollector` and `OtelCollectorGateway` share beyond the container:
3
+ * reading ports and probes back from a declared config, and the annotations
4
+ * that record, in the built manifests, how each collector config is deployed.
5
+ *
6
+ * A post-synth check sees only the rendered YAML. The collector config is a
7
+ * string inside a ConfigMap, and whether it runs once per node or as N
8
+ * replicas behind a Service lives on another document. The annotations below
9
+ * join the two, so a check can take a ConfigMap, find its workload, and know
10
+ * the collector's role, its replica count, and (for an agent) which gateway
11
+ * it exports to and whether through `loadbalancing`.
12
+ */
13
+ import { buildCollectorConfig, canonicalTypeOf, collectorEndpoints, collectorYaml, COLLECTOR_CONFIG_PATH, } from "@intentius/chant-lexicon-otel";
14
+ /**
15
+ * Annotation keys the collector composites write on their ConfigMap and
16
+ * workload. `GkeOtelCollector` writes none of them; its output is fixed.
17
+ *
18
+ * | Key | On | Value |
19
+ * |---|---|---|
20
+ * | `role` | ConfigMap, workload | `agent` (a DaemonSet, one collector per node) or `gateway` (a Deployment) |
21
+ * | `workload` | ConfigMap | the workload that mounts it, `DaemonSet/<name>` or `Deployment/<name>`, in the ConfigMap's namespace |
22
+ * | `config` | workload | the name of the ConfigMap holding its `config.yaml` |
23
+ * | `header` | `OtelOperatorCollector`'s `OpenTelemetryCollector` | the config's `# chant:` header lines (custom-component pins, semconv use), one per line, which an object-valued `spec.config` has no comments to hold |
24
+ * | `gateways` | agent ConfigMap and DaemonSet | comma-separated `<namespace>/<gateway name>=<routing>`, where routing is `loadbalancing` or `service` |
25
+ *
26
+ * The replica count is the workload's own `spec.replicas`.
27
+ */
28
+ export const OTEL_COLLECTOR_ANNOTATIONS = {
29
+ role: "otel.chant.dev/role",
30
+ workload: "otel.chant.dev/workload",
31
+ config: "otel.chant.dev/config",
32
+ gateways: "otel.chant.dev/gateways",
33
+ header: "otel.chant.dev/header",
34
+ };
35
+ const gatewayTargets = new WeakMap();
36
+ /** Record which gateway an exporter entity was built for. */
37
+ export function markGatewayExporter(exporter, target) {
38
+ gatewayTargets.set(exporter, target);
39
+ }
40
+ /** The gateways a built config's exporters were built for, in declaration order. */
41
+ export function gatewayTargetsOf(built) {
42
+ const out = [];
43
+ for (const c of built.components) {
44
+ const t = gatewayTargets.get(c);
45
+ if (t && !out.some((o) => o.name === t.name && o.namespace === t.namespace && o.routing === t.routing))
46
+ out.push(t);
47
+ }
48
+ return out;
49
+ }
50
+ /** The `gateways` annotation value for a list of targets. */
51
+ export function gatewaysAnnotation(targets) {
52
+ return targets.map((t) => `${t.namespace}/${t.name}=${t.routing}`).join(",");
53
+ }
54
+ export function collectorRuntime(entities) {
55
+ const built = buildCollectorConfig(entities);
56
+ const { ports, healthCheck } = collectorEndpoints(built.config);
57
+ const configDir = COLLECTOR_CONFIG_PATH.slice(0, COLLECTOR_CONFIG_PATH.lastIndexOf("/"));
58
+ // The probe names the port. A health_check on a receiver's port reuses that
59
+ // port's name, since a container port is declared once.
60
+ const healthPortName = healthCheck ? (ports.find((p) => p.port === healthCheck.port && !p.protocol)?.name ?? "health") : undefined;
61
+ const probe = healthCheck ? { httpGet: { path: healthCheck.path, port: healthPortName } } : undefined;
62
+ const containerPorts = [
63
+ ...ports.map((p) => ({ containerPort: p.port, name: p.name, ...(p.protocol ? { protocol: p.protocol } : {}) })),
64
+ ...(healthCheck && !ports.some((p) => p.port === healthCheck.port && !p.protocol)
65
+ ? [{ containerPort: healthCheck.port, name: "health" }]
66
+ : []),
67
+ ];
68
+ return {
69
+ built,
70
+ configYaml: collectorYaml(entities),
71
+ configDir,
72
+ containerPorts,
73
+ servicePorts: ports.map((p) => ({ name: p.name, port: p.port, ...(p.protocol ? { protocol: p.protocol } : {}) })),
74
+ containerExtra: {
75
+ imagePullPolicy: "IfNotPresent",
76
+ securityContext: {
77
+ runAsNonRoot: true,
78
+ runAsUser: 10001,
79
+ readOnlyRootFilesystem: true,
80
+ allowPrivilegeEscalation: false,
81
+ capabilities: { drop: ["ALL"] },
82
+ },
83
+ ...(probe ? { livenessProbe: probe, readinessProbe: probe } : {}),
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * The namespaces whose Endpoints a config's `loadbalancing` exporters watch
89
+ * through the `k8s` resolver. A service given as `name` resolves in the
90
+ * collector's own namespace, `name.namespace` in that one.
91
+ */
92
+ export function k8sResolverNamespaces(built, ownNamespace) {
93
+ const out = [];
94
+ for (const [id, cfg] of Object.entries(built.config.exporters ?? {})) {
95
+ if (canonicalTypeOf("exporter", id) !== "loadbalancing")
96
+ continue;
97
+ const service = cfg?.resolver?.k8s?.service;
98
+ if (!service)
99
+ continue;
100
+ const dot = service.indexOf(".");
101
+ const ns = dot === -1 ? ownNamespace : service.slice(dot + 1);
102
+ if (!out.includes(ns))
103
+ out.push(ns);
104
+ }
105
+ return out;
106
+ }
107
+ /**
108
+ * The namespaces of the Leases a config's `k8s_leader_elector` extensions
109
+ * take, for those `service.extensions` enables (the collector starts no
110
+ * other). An elector without `lease_namespace` fails the otel lexicon's
111
+ * OTEL107 and is skipped here.
112
+ */
113
+ export function leaderElectorLeaseNamespaces(built) {
114
+ const out = [];
115
+ const enabled = built.config.service?.extensions ?? [];
116
+ for (const [id, cfg] of Object.entries(built.config.extensions ?? {})) {
117
+ if (id !== "k8s_leader_elector" && !id.startsWith("k8s_leader_elector/"))
118
+ continue;
119
+ if (!enabled.includes(id))
120
+ continue;
121
+ const ns = cfg?.lease_namespace;
122
+ if (typeof ns !== "string" || ns === "")
123
+ continue;
124
+ if (!out.includes(ns))
125
+ out.push(ns);
126
+ }
127
+ return out;
128
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * OtelCollector composite: an OpenTelemetry Collector agent on any
3
+ * Kubernetes cluster.
4
+ *
5
+ * A DaemonSet runs the collector on every node, with its config mounted from
6
+ * a ConfigMap, and a Service with `internalTrafficPolicy: Local` sends each
7
+ * pod's OTLP traffic to the collector on its own node. The config is declared
8
+ * with the otel lexicon (`@intentius/chant-lexicon-otel`) and rendered with
9
+ * `collectorYaml`. The container ports, the Service ports and the probes are
10
+ * read back from that config, so they follow whatever config is passed in.
11
+ *
12
+ * `GkeOtelCollector` builds the same DaemonSet, RBAC and ConfigMap for GKE.
13
+ * `OtelCollectorGateway` is the central tier agents export to; give this
14
+ * composite an exporter from `gatewayExporter()` to point it there.
15
+ */
16
+ import { Composite, mergeDefaults } from "@intentius/chant";
17
+ import { otlpCollector, COLLECTOR_IMAGE, } from "@intentius/chant-lexicon-otel";
18
+ import { Service } from "../generated/index.js";
19
+ import { collectorAgentResources } from "./otel-collector-agent.js";
20
+ import { collectorNodeAccess } from "./otel-collector-node.js";
21
+ import { agentClusterRules, namespacedRoles } from "./otel-collector-rbac.js";
22
+ import { collectorRuntime, gatewaysAnnotation, gatewayTargetsOf, k8sResolverNamespaces, OTEL_COLLECTOR_ANNOTATIONS, } from "./otel-collector-shape.js";
23
+ /**
24
+ * Create an OtelCollector composite. Returns a DaemonSet, Service,
25
+ * ServiceAccount, ClusterRole, ClusterRoleBinding and ConfigMap.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { OtelCollector } from "@intentius/chant-lexicon-k8s";
30
+ * import { OtlpExporter } from "@intentius/chant-lexicon-otel";
31
+ *
32
+ * export const collector = OtelCollector({
33
+ * exporters: [new OtlpExporter({ endpoint: "tempo.observability.svc:4317", tls: { insecure: true } })],
34
+ * });
35
+ * ```
36
+ */
37
+ export const OtelCollector = Composite((props) => {
38
+ const { name = "otel-collector", namespace = "observability", image = COLLECTOR_IMAGE, labels: extraLabels = {}, logAccess = "group", cpuRequest = "100m", memoryRequest = "256Mi", cpuLimit = "500m", memoryLimit = "512Mi", defaults: defs, } = props;
39
+ const commonLabels = {
40
+ "app.kubernetes.io/name": name,
41
+ "app.kubernetes.io/managed-by": "chant",
42
+ ...extraLabels,
43
+ };
44
+ const entities = props.config ? [...props.config] : otlpCollector({ exporters: props.exporters, signals: props.signals });
45
+ const runtime = collectorRuntime(entities);
46
+ const configMapName = `${name}-config`;
47
+ // How this agent is deployed, and which gateways it sends to, recorded on
48
+ // the ConfigMap and the DaemonSet for post-synth checks (see
49
+ // OTEL_COLLECTOR_ANNOTATIONS).
50
+ const gateways = gatewayTargetsOf(runtime.built);
51
+ const shape = gateways.length ? { [OTEL_COLLECTOR_ANNOTATIONS.gateways]: gatewaysAnnotation(gateways) } : {};
52
+ const resources = collectorAgentResources({
53
+ name,
54
+ namespace,
55
+ image,
56
+ commonLabels,
57
+ extraLabels,
58
+ configYaml: runtime.configYaml,
59
+ configDir: runtime.configDir,
60
+ ports: runtime.containerPorts,
61
+ clusterRules: agentClusterRules(runtime.built.config),
62
+ // The node name, host mounts and log access the config's node-reading
63
+ // components need (#3103); nothing for a config that reads no node.
64
+ nodeAccess: collectorNodeAccess(runtime.built.config, runtime.configDir),
65
+ logAccess,
66
+ cpuRequest,
67
+ memoryRequest,
68
+ cpuLimit,
69
+ memoryLimit,
70
+ containerExtra: runtime.containerExtra,
71
+ workloadAnnotations: {
72
+ [OTEL_COLLECTOR_ANNOTATIONS.role]: "agent",
73
+ [OTEL_COLLECTOR_ANNOTATIONS.config]: configMapName,
74
+ ...shape,
75
+ },
76
+ configMapAnnotations: {
77
+ [OTEL_COLLECTOR_ANNOTATIONS.role]: "agent",
78
+ [OTEL_COLLECTOR_ANNOTATIONS.workload]: `DaemonSet/${name}`,
79
+ ...shape,
80
+ },
81
+ defaults: defs,
82
+ });
83
+ const service = new Service(mergeDefaults({
84
+ metadata: {
85
+ name,
86
+ namespace,
87
+ labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
88
+ },
89
+ spec: {
90
+ selector: { "app.kubernetes.io/name": name },
91
+ internalTrafficPolicy: "Local",
92
+ ports: runtime.servicePorts.map((p) => ({ name: p.name, port: p.port, targetPort: p.name, protocol: p.protocol ?? "TCP" })),
93
+ },
94
+ }, defs?.service));
95
+ // The loadbalancing exporter's k8s resolver runs in this agent and watches
96
+ // the gateway's headless Service, so the agent's ServiceAccount gets read
97
+ // access to Endpoints (what the resolver watches at the pinned collector
98
+ // version) and EndpointSlices (what later versions watch), with one Role
99
+ // and RoleBinding in each namespace the resolvers point at.
100
+ const saName = resources.serviceAccount.props.metadata.name;
101
+ const endpointsRbac = namespacedRoles({
102
+ key: "endpoints",
103
+ name: `${name}-endpoints`,
104
+ namespaces: k8sResolverNamespaces(runtime.built, namespace),
105
+ rules: [
106
+ { apiGroups: [""], resources: ["endpoints"], verbs: ["get", "list", "watch"] },
107
+ { apiGroups: ["discovery.k8s.io"], resources: ["endpointslices"], verbs: ["get", "list", "watch"] },
108
+ ],
109
+ labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
110
+ serviceAccount: { name: saName, namespace },
111
+ roleDefaults: defs?.endpointsRole,
112
+ roleBindingDefaults: defs?.endpointsRoleBinding,
113
+ });
114
+ return { ...resources, service, ...endpointsRbac };
115
+ }, "OtelCollector");
@@ -0,0 +1,123 @@
1
+ /**
2
+ * OtelOperatorCollector composite: an OpenTelemetry Collector run by the
3
+ * OpenTelemetry Operator, as an `OpenTelemetryCollector` custom resource
4
+ * (opentelemetry.io/v1beta1).
5
+ *
6
+ * `OtelCollector` builds the DaemonSet, ConfigMap and Service itself. Here the
7
+ * operator builds them from one custom resource, and the otel lexicon's
8
+ * config goes straight into `spec.config`, which v1beta1 declares as an
9
+ * object (`exporters`, `receivers` and `service` required). The ports, the
10
+ * node access (downward API variables, host mounts, log group) and the RBAC
11
+ * come from the same readers `OtelCollector` uses, so the two follow a config
12
+ * the same way.
13
+ *
14
+ * An object has no comment lines, so the `# chant:` header that carries
15
+ * custom-component pins and semconv use (see `buildCollectorConfig`) is kept
16
+ * in the `otel.chant.dev/header` annotation, one line per entry. `chant
17
+ * import` puts it back on top of the config when it reads the CR.
18
+ *
19
+ * The operator names the workload `<name>-collector` and, with
20
+ * `spec.serviceAccount` set as it is here, runs it as the ServiceAccount this
21
+ * composite makes. The ClusterRole is bound to that ServiceAccount.
22
+ */
23
+ import { Composite, mergeDefaults } from "@intentius/chant";
24
+ import { otlpCollector, COLLECTOR_IMAGE, } from "@intentius/chant-lexicon-otel";
25
+ import { ServiceAccount, ClusterRole, ClusterRoleBinding, OpenTelemetryCollector } from "../generated/index.js";
26
+ import { collectorNodeAccess, nodeVolumeMounts, nodeVolumes } from "./otel-collector-node.js";
27
+ import { agentClusterRules, namespacedRoles } from "./otel-collector-rbac.js";
28
+ import { collectorRuntime, gatewaysAnnotation, gatewayTargetsOf, k8sResolverNamespaces, OTEL_COLLECTOR_ANNOTATIONS, } from "./otel-collector-shape.js";
29
+ /**
30
+ * Create an OtelOperatorCollector composite. Returns an OpenTelemetryCollector
31
+ * custom resource, a ServiceAccount, a ClusterRole and a ClusterRoleBinding.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { OtelOperatorCollector } from "@intentius/chant-lexicon-k8s";
36
+ * import { OtlpExporter } from "@intentius/chant-lexicon-otel";
37
+ *
38
+ * export const collector = OtelOperatorCollector({
39
+ * mode: "deployment",
40
+ * replicas: 2,
41
+ * exporters: [new OtlpExporter({ endpoint: "tempo.observability.svc:4317", tls: { insecure: true } })],
42
+ * });
43
+ * ```
44
+ */
45
+ export const OtelOperatorCollector = Composite((props) => {
46
+ const { name = "otel-collector", namespace = "observability", mode = "daemonset", image = COLLECTOR_IMAGE, labels: extraLabels = {}, logAccess = "group", cpuRequest = "100m", memoryRequest = "256Mi", cpuLimit = "500m", memoryLimit = "512Mi", defaults: defs, } = props;
47
+ if (mode === "daemonset" && props.replicas !== undefined) {
48
+ throw new Error(`${name}: a daemonset runs one collector per node, so replicas does not apply`);
49
+ }
50
+ const commonLabels = {
51
+ "app.kubernetes.io/name": name,
52
+ "app.kubernetes.io/managed-by": "chant",
53
+ ...extraLabels,
54
+ };
55
+ const entities = props.config ? [...props.config] : otlpCollector({ exporters: props.exporters, signals: props.signals });
56
+ const runtime = collectorRuntime(entities);
57
+ const gateways = gatewayTargetsOf(runtime.built);
58
+ const saName = `${name}-sa`;
59
+ const access = collectorNodeAccess(runtime.built.config, runtime.configDir);
60
+ const asRoot = access.readsLogs && logAccess === "root";
61
+ const container = runtime.containerExtra.securityContext;
62
+ const annotations = {
63
+ [OTEL_COLLECTOR_ANNOTATIONS.role]: mode === "daemonset" ? "agent" : "gateway",
64
+ ...(gateways.length ? { [OTEL_COLLECTOR_ANNOTATIONS.gateways]: gatewaysAnnotation(gateways) } : {}),
65
+ ...(runtime.built.header.length ? { [OTEL_COLLECTOR_ANNOTATIONS.header]: runtime.built.header.join("\n") } : {}),
66
+ };
67
+ const collector = new OpenTelemetryCollector(mergeDefaults({
68
+ metadata: {
69
+ name,
70
+ namespace,
71
+ labels: { ...commonLabels, "app.kubernetes.io/component": mode === "daemonset" ? "agent" : "gateway" },
72
+ annotations,
73
+ },
74
+ spec: {
75
+ mode,
76
+ ...(props.replicas !== undefined ? { replicas: props.replicas } : {}),
77
+ image,
78
+ serviceAccount: saName,
79
+ config: runtime.built.config,
80
+ ports: runtime.servicePorts.map((p) => ({ name: p.name, port: p.port, protocol: p.protocol ?? "TCP" })),
81
+ resources: {
82
+ requests: { cpu: cpuRequest, memory: memoryRequest },
83
+ limits: { cpu: cpuLimit, memory: memoryLimit },
84
+ },
85
+ securityContext: asRoot ? { ...container, runAsNonRoot: false, runAsUser: 0 } : container,
86
+ ...(access.readsLogs && !asRoot ? { podSecurityContext: { supplementalGroups: [0] } } : {}),
87
+ ...(access.env.length ? { env: access.env } : {}),
88
+ ...(access.mounts.length ? { volumes: nodeVolumes(access.mounts), volumeMounts: nodeVolumeMounts(access.mounts) } : {}),
89
+ ...(mode === "daemonset" ? { tolerations: [{ operator: "Exists" }] } : {}),
90
+ },
91
+ }, defs?.collector));
92
+ const serviceAccount = new ServiceAccount(mergeDefaults({
93
+ metadata: {
94
+ name: saName,
95
+ namespace,
96
+ labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
97
+ },
98
+ }, defs?.serviceAccount));
99
+ const clusterRoleName = `${name}-role`;
100
+ const clusterRole = new ClusterRole(mergeDefaults({
101
+ metadata: { name: clusterRoleName, labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" } },
102
+ rules: agentClusterRules(runtime.built.config),
103
+ }, defs?.clusterRole));
104
+ const clusterRoleBinding = new ClusterRoleBinding(mergeDefaults({
105
+ metadata: { name: `${name}-binding`, labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" } },
106
+ roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "ClusterRole", name: clusterRoleName },
107
+ subjects: [{ kind: "ServiceAccount", name: saName, namespace }],
108
+ }, defs?.clusterRoleBinding));
109
+ const endpointsRbac = namespacedRoles({
110
+ key: "endpoints",
111
+ name: `${name}-endpoints`,
112
+ namespaces: k8sResolverNamespaces(runtime.built, namespace),
113
+ rules: [
114
+ { apiGroups: [""], resources: ["endpoints"], verbs: ["get", "list", "watch"] },
115
+ { apiGroups: ["discovery.k8s.io"], resources: ["endpointslices"], verbs: ["get", "list", "watch"] },
116
+ ],
117
+ labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
118
+ serviceAccount: { name: saName, namespace },
119
+ roleDefaults: defs?.endpointsRole,
120
+ roleBindingDefaults: defs?.endpointsRoleBinding,
121
+ });
122
+ return { collector, serviceAccount, clusterRole, clusterRoleBinding, ...endpointsRbac };
123
+ }, "OtelOperatorCollector");