@intentius/chant-lexicon-otel 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/dist/catalog.d.ts +18 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +8 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +19 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +7 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/collector.d.ts +29 -0
- package/dist/collector.d.ts.map +1 -0
- package/dist/components/common.d.ts +144 -0
- package/dist/components/common.d.ts.map +1 -0
- package/dist/components/exporters.d.ts +86 -0
- package/dist/components/exporters.d.ts.map +1 -0
- package/dist/components/extensions.d.ts +32 -0
- package/dist/components/extensions.d.ts.map +1 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/processors.d.ts +118 -0
- package/dist/components/processors.d.ts.map +1 -0
- package/dist/components/receivers.d.ts +118 -0
- package/dist/components/receivers.d.ts.map +1 -0
- package/dist/define.d.ts +136 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/integrity.json +25 -0
- package/dist/lint/audit-catalog.d.ts +13 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/post-synth/otel-helpers.d.ts +22 -0
- package/dist/lint/post-synth/otel-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/otel101.d.ts +8 -0
- package/dist/lint/post-synth/otel101.d.ts.map +1 -0
- package/dist/lint/post-synth/otel102.d.ts +8 -0
- package/dist/lint/post-synth/otel102.d.ts.map +1 -0
- package/dist/lint/post-synth/otel103.d.ts +8 -0
- package/dist/lint/post-synth/otel103.d.ts.map +1 -0
- package/dist/lint/post-synth/otel104.d.ts +8 -0
- package/dist/lint/post-synth/otel104.d.ts.map +1 -0
- package/dist/lint/post-synth/otel105.d.ts +8 -0
- package/dist/lint/post-synth/otel105.d.ts.map +1 -0
- package/dist/lint/post-synth/otel106.d.ts +8 -0
- package/dist/lint/post-synth/otel106.d.ts.map +1 -0
- package/dist/lint/post-synth/otel107.d.ts +8 -0
- package/dist/lint/post-synth/otel107.d.ts.map +1 -0
- package/dist/lint/post-synth/otel108.d.ts +8 -0
- package/dist/lint/post-synth/otel108.d.ts.map +1 -0
- package/dist/lint/post-synth/otel109.d.ts +8 -0
- package/dist/lint/post-synth/otel109.d.ts.map +1 -0
- package/dist/lint/rules/component-id-syntax.d.ts +14 -0
- package/dist/lint/rules/component-id-syntax.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +6 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/literal-credential.d.ts +13 -0
- package/dist/lint/rules/literal-credential.d.ts.map +1 -0
- package/dist/lint/rules/otel-ast.d.ts +14 -0
- package/dist/lint/rules/otel-ast.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +4 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/meta.json +102 -0
- package/dist/model.d.ts +53 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/okf/index.md +40 -0
- package/dist/okf/rules/OTEL001.md +15 -0
- package/dist/okf/rules/OTEL002.md +11 -0
- package/dist/okf/rules/OTEL101.md +11 -0
- package/dist/okf/rules/OTEL102.md +11 -0
- package/dist/okf/rules/OTEL103.md +11 -0
- package/dist/okf/rules/OTEL104.md +11 -0
- package/dist/okf/rules/OTEL105.md +11 -0
- package/dist/okf/rules/OTEL106.md +11 -0
- package/dist/okf/rules/OTEL107.md +11 -0
- package/dist/okf/rules/OTEL108.md +11 -0
- package/dist/okf/rules/OTEL109.md +11 -0
- package/dist/okf/types/AttributesProcessor.md +9 -0
- package/dist/okf/types/BatchProcessor.md +9 -0
- package/dist/okf/types/DebugExporter.md +9 -0
- package/dist/okf/types/FileLogReceiver.md +9 -0
- package/dist/okf/types/GoogleCloudExporter.md +9 -0
- package/dist/okf/types/HealthCheckExtension.md +9 -0
- package/dist/okf/types/HostMetricsReceiver.md +9 -0
- package/dist/okf/types/K8sAttributesProcessor.md +9 -0
- package/dist/okf/types/MemoryLimiterProcessor.md +9 -0
- package/dist/okf/types/OtlpExporter.md +9 -0
- package/dist/okf/types/OtlpHttpExporter.md +9 -0
- package/dist/okf/types/OtlpReceiver.md +9 -0
- package/dist/okf/types/Pipeline.md +13 -0
- package/dist/okf/types/PprofExtension.md +9 -0
- package/dist/okf/types/PrometheusExporter.md +9 -0
- package/dist/okf/types/PrometheusReceiver.md +9 -0
- package/dist/okf/types/ResourceDetectionProcessor.md +9 -0
- package/dist/okf/types/ResourceProcessor.md +9 -0
- package/dist/okf/types/Service.md +9 -0
- package/dist/okf/types/ZPagesExtension.md +9 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/pipeline.d.ts +66 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/platform.d.ts +57 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/rules/component-id-syntax.ts +68 -0
- package/dist/rules/literal-credential.ts +62 -0
- package/dist/rules/otel-ast.ts +30 -0
- package/dist/rules/otel-helpers.ts +70 -0
- package/dist/rules/otel101.ts +17 -0
- package/dist/rules/otel102.ts +17 -0
- package/dist/rules/otel103.ts +17 -0
- package/dist/rules/otel104.ts +17 -0
- package/dist/rules/otel105.ts +17 -0
- package/dist/rules/otel106.ts +17 -0
- package/dist/rules/otel107.ts +17 -0
- package/dist/rules/otel108.ts +17 -0
- package/dist/rules/otel109.ts +17 -0
- package/dist/serializer.d.ts +16 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skill-defs.d.ts +3 -0
- package/dist/skill-defs.d.ts.map +1 -0
- package/dist/skills/chant-otel-custom-components.md +51 -0
- package/dist/skills/chant-otel-platforms.md +34 -0
- package/dist/skills/chant-otel.md +60 -0
- package/dist/topology.d.ts +56 -0
- package/dist/topology.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-config.d.ts +30 -0
- package/dist/validate-config.d.ts.map +1 -0
- package/dist/validate.d.ts +11 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/yaml.d.ts +27 -0
- package/dist/yaml.d.ts.map +1 -0
- package/package.json +74 -0
- package/src/catalog.ts +56 -0
- package/src/codegen/docs-cli.ts +4 -0
- package/src/codegen/docs.ts +90 -0
- package/src/codegen/generate-cli.ts +8 -0
- package/src/codegen/generate.ts +50 -0
- package/src/codegen/package.ts +41 -0
- package/src/collector.ts +147 -0
- package/src/components/common.ts +151 -0
- package/src/components/exporters.ts +141 -0
- package/src/components/extensions.ts +51 -0
- package/src/components/index.ts +5 -0
- package/src/components/processors.ts +208 -0
- package/src/components/receivers.ts +177 -0
- package/src/define.test.ts +131 -0
- package/src/define.ts +249 -0
- package/src/detect.ts +11 -0
- package/src/generated/lexicon-otel.json +102 -0
- package/src/index.ts +73 -0
- package/src/lint/audit-catalog.ts +102 -0
- package/src/lint/post-synth/index.ts +23 -0
- package/src/lint/post-synth/otel-helpers.ts +70 -0
- package/src/lint/post-synth/otel101.ts +17 -0
- package/src/lint/post-synth/otel102.ts +17 -0
- package/src/lint/post-synth/otel103.ts +17 -0
- package/src/lint/post-synth/otel104.ts +17 -0
- package/src/lint/post-synth/otel105.ts +17 -0
- package/src/lint/post-synth/otel106.ts +17 -0
- package/src/lint/post-synth/otel107.ts +17 -0
- package/src/lint/post-synth/otel108.ts +17 -0
- package/src/lint/post-synth/otel109.ts +17 -0
- package/src/lint/post-synth/post-synth.test.ts +184 -0
- package/src/lint/rules/component-id-syntax.ts +68 -0
- package/src/lint/rules/index.ts +9 -0
- package/src/lint/rules/literal-credential.ts +62 -0
- package/src/lint/rules/otel-ast.ts +30 -0
- package/src/lint/rules/rules.test.ts +70 -0
- package/src/lsp/completions.test.ts +23 -0
- package/src/lsp/completions.ts +15 -0
- package/src/lsp/hover.test.ts +20 -0
- package/src/lsp/hover.ts +30 -0
- package/src/model.ts +88 -0
- package/src/package-cli.ts +17 -0
- package/src/pipeline.ts +89 -0
- package/src/platform.test.ts +79 -0
- package/src/platform.ts +143 -0
- package/src/plugin.test.ts +55 -0
- package/src/plugin.ts +111 -0
- package/src/serializer.test.ts +187 -0
- package/src/serializer.ts +31 -0
- package/src/skill-defs.ts +36 -0
- package/src/skills/chant-otel-custom-components.md +51 -0
- package/src/skills/chant-otel-platforms.md +34 -0
- package/src/skills/chant-otel.md +60 -0
- package/src/topology.test.ts +80 -0
- package/src/topology.ts +122 -0
- package/src/validate-cli.ts +7 -0
- package/src/validate-config.ts +232 -0
- package/src/validate.ts +82 -0
- package/src/yaml.test.ts +56 -0
- package/src/yaml.ts +145 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
+
import { isComponentId } from "../../model";
|
|
4
|
+
import { calleeName, COMPONENT_CLASS, literalText, position, propertyName } from "./otel-ast";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OTEL001: a component id or instance name the collector can't parse.
|
|
8
|
+
*
|
|
9
|
+
* A pipeline may name a component by id string (`"otlp/backend"`), and every
|
|
10
|
+
* component takes an instance `name`. Both end up in the collector's
|
|
11
|
+
* `type[/name]` syntax, where a space, an empty name or a stray `/` is a
|
|
12
|
+
* config error at collector start. This catches the literal cases in source,
|
|
13
|
+
* before a build: a string in a `Pipeline`'s receivers, processors or
|
|
14
|
+
* exporters that isn't `type` or `type/name`, and a component `name` that is
|
|
15
|
+
* empty, contains whitespace or starts with `/`.
|
|
16
|
+
*/
|
|
17
|
+
export const componentIdSyntaxRule: LintRule = {
|
|
18
|
+
id: "OTEL001",
|
|
19
|
+
severity: "error",
|
|
20
|
+
category: "correctness",
|
|
21
|
+
description: "Collector component id or instance name is not valid type[/name] syntax",
|
|
22
|
+
|
|
23
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
24
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
25
|
+
const source = context.sourceFile;
|
|
26
|
+
|
|
27
|
+
const flag = (node: ts.Node, message: string) => {
|
|
28
|
+
diagnostics.push({ ruleId: "OTEL001", severity: "error", message, file: context.filePath, ...position(source, node) });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const visit = (node: ts.Node) => {
|
|
32
|
+
if (ts.isNewExpression(node) || ts.isCallExpression(node)) {
|
|
33
|
+
const name = calleeName(node);
|
|
34
|
+
const arg = node.arguments?.[0];
|
|
35
|
+
if (name && arg && ts.isObjectLiteralExpression(arg)) {
|
|
36
|
+
if (name === "Pipeline") {
|
|
37
|
+
for (const prop of arg.properties) {
|
|
38
|
+
const key = propertyName(prop);
|
|
39
|
+
if (key !== "receivers" && key !== "processors" && key !== "exporters") continue;
|
|
40
|
+
const init = (prop as ts.PropertyAssignment).initializer;
|
|
41
|
+
if (!ts.isArrayLiteralExpression(init)) continue;
|
|
42
|
+
for (const el of init.elements) {
|
|
43
|
+
const text = literalText(el as ts.Expression);
|
|
44
|
+
if (text !== undefined && !isComponentId(text)) {
|
|
45
|
+
flag(el, `"${text}" in ${key} is not a collector component id; write type or type/name, e.g. "otlp" or "otlp/backend"`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (name === "Pipeline" || COMPONENT_CLASS.test(name)) {
|
|
51
|
+
for (const prop of arg.properties) {
|
|
52
|
+
if (propertyName(prop) !== "name") continue;
|
|
53
|
+
const text = literalText((prop as ts.PropertyAssignment).initializer);
|
|
54
|
+
if (text === undefined) continue;
|
|
55
|
+
if (text === "" || /\s/.test(text) || text.startsWith("/")) {
|
|
56
|
+
flag(prop, `instance name "${text}" makes an invalid id; a name is non-empty, has no whitespace and does not start with "/"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
ts.forEachChild(node, visit);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
visit(source);
|
|
66
|
+
return diagnostics;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LintRule } from "@intentius/chant/lint/rule";
|
|
2
|
+
import { componentIdSyntaxRule } from "./component-id-syntax";
|
|
3
|
+
import { literalCredentialRule } from "./literal-credential";
|
|
4
|
+
|
|
5
|
+
export { componentIdSyntaxRule } from "./component-id-syntax";
|
|
6
|
+
export { literalCredentialRule } from "./literal-credential";
|
|
7
|
+
|
|
8
|
+
/** All lint rules provided by this lexicon (imported by plugin.ts's lintRules()). */
|
|
9
|
+
export const rules: LintRule[] = [componentIdSyntaxRule, literalCredentialRule];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
+
import { calleeName, COMPONENT_CLASS, literalText, position, propertyName } from "./otel-ast";
|
|
4
|
+
|
|
5
|
+
/** Keys whose value is a credential. `*_file` keys name a path, which is fine. */
|
|
6
|
+
const SECRET_KEY = /(authorization|api[-_]?key|password|passwd|secret|token|key_pem|x-honeycomb-team)/i;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* OTEL002: a credential written as a literal in a component's config.
|
|
10
|
+
*
|
|
11
|
+
* Exporters authenticate with headers (`authorization`, `api-key`, vendor
|
|
12
|
+
* headers) or keys (`api_key`, `password`, `key_pem`). A literal value there
|
|
13
|
+
* is a secret in the repository and in the emitted YAML. The collector reads
|
|
14
|
+
* `${env:NAME}` and `${file:/path}` at start-up, so the declaration never
|
|
15
|
+
* needs the value itself. Any string containing `${` is treated as such a
|
|
16
|
+
* reference, and keys ending in `_file` name a path and are left alone.
|
|
17
|
+
*/
|
|
18
|
+
export const literalCredentialRule: LintRule = {
|
|
19
|
+
id: "OTEL002",
|
|
20
|
+
severity: "error",
|
|
21
|
+
category: "security",
|
|
22
|
+
description: "Collector credential declared as a literal instead of ${env:...}",
|
|
23
|
+
|
|
24
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
25
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
26
|
+
const source = context.sourceFile;
|
|
27
|
+
|
|
28
|
+
const scan = (obj: ts.ObjectLiteralExpression) => {
|
|
29
|
+
for (const prop of obj.properties) {
|
|
30
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
31
|
+
const key = propertyName(prop);
|
|
32
|
+
const init = prop.initializer;
|
|
33
|
+
if (ts.isObjectLiteralExpression(init)) {
|
|
34
|
+
scan(init);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!key || !SECRET_KEY.test(key) || /_file$/i.test(key)) continue;
|
|
38
|
+
const text = literalText(init);
|
|
39
|
+
if (text === undefined || text === "" || text.includes("${")) continue;
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
ruleId: "OTEL002",
|
|
42
|
+
severity: "error",
|
|
43
|
+
message: `\`${key}\` is a literal credential; write "\${env:NAME}" (or "\${file:/path}") and let the collector read it at start-up`,
|
|
44
|
+
file: context.filePath,
|
|
45
|
+
...position(source, init),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const visit = (node: ts.Node) => {
|
|
51
|
+
if (ts.isNewExpression(node) || ts.isCallExpression(node)) {
|
|
52
|
+
const name = calleeName(node);
|
|
53
|
+
const arg = node.arguments?.[0];
|
|
54
|
+
if (name && COMPONENT_CLASS.test(name) && arg && ts.isObjectLiteralExpression(arg)) scan(arg);
|
|
55
|
+
}
|
|
56
|
+
ts.forEachChild(node, visit);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
visit(source);
|
|
60
|
+
return diagnostics;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/** The constructor name of a `new X(...)` or `X(...)` expression, if it is a plain or dotted name. */
|
|
4
|
+
export function calleeName(node: ts.CallExpression | ts.NewExpression): string | undefined {
|
|
5
|
+
const callee = node.expression;
|
|
6
|
+
if (ts.isIdentifier(callee)) return callee.text;
|
|
7
|
+
if (ts.isPropertyAccessExpression(callee)) return callee.name.text;
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Built-in and custom collector component classes follow this naming, e.g. `OtlpExporter`. */
|
|
12
|
+
export const COMPONENT_CLASS = /(Receiver|Processor|Exporter|Extension)$/;
|
|
13
|
+
|
|
14
|
+
/** A property's key as text, for identifier and string-literal keys. */
|
|
15
|
+
export function propertyName(prop: ts.ObjectLiteralElementLike): string | undefined {
|
|
16
|
+
if (!ts.isPropertyAssignment(prop)) return undefined;
|
|
17
|
+
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) return prop.name.text;
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The text of a string literal or substitution-free template, else undefined. */
|
|
22
|
+
export function literalText(node: ts.Expression): string | undefined {
|
|
23
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function position(source: ts.SourceFile, node: ts.Node): { line: number; column: number } {
|
|
28
|
+
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
29
|
+
return { line: line + 1, column: character + 1 };
|
|
30
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import type { LintContext } from "@intentius/chant/lint/rule";
|
|
4
|
+
import { componentIdSyntaxRule } from "./component-id-syntax";
|
|
5
|
+
import { literalCredentialRule } from "./literal-credential";
|
|
6
|
+
|
|
7
|
+
function ctx(code: string): LintContext {
|
|
8
|
+
const sourceFile = ts.createSourceFile("collector.ts", code, ts.ScriptTarget.Latest, true);
|
|
9
|
+
return { sourceFile, entities: [], filePath: "collector.ts" };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("OTEL001 component id syntax", () => {
|
|
13
|
+
test("flags a pipeline string that is not type[/name]", () => {
|
|
14
|
+
const diags = componentIdSyntaxRule.check(
|
|
15
|
+
ctx(`export const t = new Pipeline({ signal: "traces", receivers: ["otlp/"], exporters: ["otlp backend", "debug"] });`),
|
|
16
|
+
);
|
|
17
|
+
expect(diags.map((d) => d.ruleId)).toEqual(["OTEL001", "OTEL001"]);
|
|
18
|
+
expect(diags[0].message).toContain('"otlp/"');
|
|
19
|
+
expect(diags[1].message).toContain('"otlp backend"');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("flags an instance name with whitespace, an empty name or a leading slash", () => {
|
|
23
|
+
const diags = componentIdSyntaxRule.check(
|
|
24
|
+
ctx(`
|
|
25
|
+
new OtlpExporter({ name: "my backend", endpoint: "x:4317" });
|
|
26
|
+
new DebugExporter({ name: "" });
|
|
27
|
+
new BatchProcessor({ name: "/fast" });
|
|
28
|
+
`),
|
|
29
|
+
);
|
|
30
|
+
expect(diags).toHaveLength(3);
|
|
31
|
+
expect(diags[0].line).toBe(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("accepts ids, names, entity references and non-literal values", () => {
|
|
35
|
+
const diags = componentIdSyntaxRule.check(
|
|
36
|
+
ctx(`
|
|
37
|
+
const n = "dynamic";
|
|
38
|
+
new OtlpExporter({ name: "backend", endpoint: "x:4317" });
|
|
39
|
+
new OtlpExporter({ name: n, endpoint: "x:4317" });
|
|
40
|
+
new Pipeline({ signal: "traces", receivers: [otlp, "otlp/edge"], processors: ["batch"], exporters: ["otlp/backend"] });
|
|
41
|
+
new Deployment({ name: "not otel at all" });
|
|
42
|
+
`),
|
|
43
|
+
);
|
|
44
|
+
expect(diags).toEqual([]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("OTEL002 literal credential", () => {
|
|
49
|
+
test("flags literal credentials in headers and nested keys", () => {
|
|
50
|
+
const diags = literalCredentialRule.check(
|
|
51
|
+
ctx(`
|
|
52
|
+
new OtlpHttpExporter({ endpoint: "https://api.example", headers: { authorization: "Bearer abc123", "x-api-key": "k" } });
|
|
53
|
+
new DatadogExporter({ api: { key: "x", api_key: "literal" } });
|
|
54
|
+
`),
|
|
55
|
+
);
|
|
56
|
+
expect(diags.map((d) => d.ruleId)).toEqual(["OTEL002", "OTEL002", "OTEL002"]);
|
|
57
|
+
expect(diags[0].message).toContain("authorization");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("accepts env and file references, *_file paths, and non-component constructors", () => {
|
|
61
|
+
const diags = literalCredentialRule.check(
|
|
62
|
+
ctx(`
|
|
63
|
+
new OtlpExporter({ endpoint: "x:4317", headers: { authorization: "Bearer \${env:TOKEN}" } });
|
|
64
|
+
new OtlpExporter({ endpoint: "x:4317", tls: { key_file: "/etc/tls/key.pem", cert_file: "/etc/tls/cert.pem" } });
|
|
65
|
+
new Secret({ password: "not ours to judge" });
|
|
66
|
+
`),
|
|
67
|
+
);
|
|
68
|
+
expect(diags).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { completions } from "./completions";
|
|
3
|
+
|
|
4
|
+
describe("otel LSP completions", () => {
|
|
5
|
+
it("offers the built-in classes after new", () => {
|
|
6
|
+
const content = 'import { OtlpReceiver } from "@intentius/chant-lexicon-otel";\nconst r = new Otlp';
|
|
7
|
+
const items = completions({
|
|
8
|
+
uri: "file:///collector.ts",
|
|
9
|
+
content,
|
|
10
|
+
position: { line: 1, character: content.split("\n")[1].length },
|
|
11
|
+
wordAtCursor: "Otlp",
|
|
12
|
+
linePrefix: "const r = new Otlp",
|
|
13
|
+
});
|
|
14
|
+
const labels = items.map((i) => i.label);
|
|
15
|
+
expect(labels).toContain("OtlpReceiver");
|
|
16
|
+
expect(labels).toContain("OtlpExporter");
|
|
17
|
+
expect(labels).toContain("OtlpHttpExporter");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns an array for an empty context", () => {
|
|
21
|
+
expect(Array.isArray(completions({} as never))).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
|
|
2
|
+
import { LexiconIndex, lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
|
|
3
|
+
import { lexiconRegistry } from "../catalog";
|
|
4
|
+
|
|
5
|
+
let cachedIndex: LexiconIndex | null = null;
|
|
6
|
+
|
|
7
|
+
function getIndex(): LexiconIndex {
|
|
8
|
+
cachedIndex ??= new LexiconIndex(lexiconRegistry());
|
|
9
|
+
return cachedIndex;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Completions for the otel entity classes: the built-in components, Pipeline and Service. */
|
|
13
|
+
export function completions(ctx: CompletionContext): CompletionItem[] {
|
|
14
|
+
return lexiconCompletions(ctx, getIndex(), "OpenTelemetry Collector entity");
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { hover } from "./hover";
|
|
3
|
+
|
|
4
|
+
describe("otel LSP hover", () => {
|
|
5
|
+
it("describes a built-in component and its id", () => {
|
|
6
|
+
const info = hover({
|
|
7
|
+
uri: "file:///collector.ts",
|
|
8
|
+
content: "new BatchProcessor({})",
|
|
9
|
+
position: { line: 0, character: 6 },
|
|
10
|
+
word: "BatchProcessor",
|
|
11
|
+
lineText: "new BatchProcessor({})",
|
|
12
|
+
});
|
|
13
|
+
expect(info?.contents).toContain("OTel::Processor::batch");
|
|
14
|
+
expect(info?.contents).toContain("`batch/<name>`");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns undefined for an unknown word", () => {
|
|
18
|
+
expect(hover({ uri: "file:///x.ts", content: "", position: { line: 0, character: 0 }, word: "Nope", lineText: "" })).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
});
|
package/src/lsp/hover.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
|
|
2
|
+
import { LexiconIndex, lexiconHover, type LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
|
|
3
|
+
import { BUILTIN_CATALOG, lexiconRegistry } from "../catalog";
|
|
4
|
+
import { COLLECTOR_PIN } from "../define";
|
|
5
|
+
|
|
6
|
+
let cachedIndex: LexiconIndex | null = null;
|
|
7
|
+
|
|
8
|
+
function getIndex(): LexiconIndex {
|
|
9
|
+
cachedIndex ??= new LexiconIndex(lexiconRegistry());
|
|
10
|
+
return cachedIndex;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Hover for otel entity classes: what the component does and its collector id. */
|
|
14
|
+
export function hover(ctx: HoverContext): HoverInfo | undefined {
|
|
15
|
+
return lexiconHover(ctx, getIndex(), resourceHover);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resourceHover(className: string, entry: LexiconEntry): HoverInfo | undefined {
|
|
19
|
+
const cat = BUILTIN_CATALOG.find((c) => c.className === className);
|
|
20
|
+
const lines = [`**${className}**`, "", `otel type: \`${entry.resourceType}\``];
|
|
21
|
+
if (cat?.description) lines.push("", cat.description);
|
|
22
|
+
if (cat?.type) {
|
|
23
|
+
lines.push(
|
|
24
|
+
"",
|
|
25
|
+
`Collector id \`${cat.type}\`, or \`${cat.type}/<name>\` with \`name\` set. ` +
|
|
26
|
+
`Config typed against ${COLLECTOR_PIN.source} ${COLLECTOR_PIN.version}.`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return { contents: lines.join("\n") };
|
|
30
|
+
}
|
package/src/model.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plain-data model of an OpenTelemetry Collector config.
|
|
3
|
+
*
|
|
4
|
+
* Everything else in this lexicon reads or writes this shape: the serializer
|
|
5
|
+
* builds it from declared entities, the YAML emitter prints it, the checks
|
|
6
|
+
* validate it, and `collectorTopology()` reads it back for other tools. It is
|
|
7
|
+
* exactly the collector's own top-level layout, so a parsed collector YAML
|
|
8
|
+
* file is already a `CollectorConfig` and needs no conversion.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The three signals a collector pipeline can carry. */
|
|
12
|
+
export const SIGNALS = ["traces", "metrics", "logs"] as const;
|
|
13
|
+
export type Signal = (typeof SIGNALS)[number];
|
|
14
|
+
|
|
15
|
+
/** The component kinds this lexicon types. Connectors are not modeled yet. */
|
|
16
|
+
export const COMPONENT_KINDS = ["receiver", "processor", "exporter", "extension"] as const;
|
|
17
|
+
export type ComponentKind = (typeof COMPONENT_KINDS)[number];
|
|
18
|
+
|
|
19
|
+
/** The top-level config section each component kind lives under. */
|
|
20
|
+
export const SECTION_OF: Record<ComponentKind, "receivers" | "processors" | "exporters" | "extensions"> = {
|
|
21
|
+
receiver: "receivers",
|
|
22
|
+
processor: "processors",
|
|
23
|
+
exporter: "exporters",
|
|
24
|
+
extension: "extensions",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** A component id, `type` or `type/name`, exactly as the collector spells it. */
|
|
28
|
+
export type ComponentId = string;
|
|
29
|
+
|
|
30
|
+
/** One pipeline under `service.pipelines`. */
|
|
31
|
+
export interface PipelineConfig {
|
|
32
|
+
receivers?: ComponentId[];
|
|
33
|
+
processors?: ComponentId[];
|
|
34
|
+
exporters?: ComponentId[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ServiceConfig {
|
|
38
|
+
extensions?: ComponentId[];
|
|
39
|
+
telemetry?: Record<string, unknown>;
|
|
40
|
+
pipelines?: Record<string, PipelineConfig>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A whole collector config file. Every section is optional so a partial file still parses. */
|
|
44
|
+
export interface CollectorConfig {
|
|
45
|
+
receivers?: Record<ComponentId, Record<string, unknown> | null>;
|
|
46
|
+
processors?: Record<ComponentId, Record<string, unknown> | null>;
|
|
47
|
+
exporters?: Record<ComponentId, Record<string, unknown> | null>;
|
|
48
|
+
extensions?: Record<ComponentId, Record<string, unknown> | null>;
|
|
49
|
+
connectors?: Record<ComponentId, Record<string, unknown> | null>;
|
|
50
|
+
service?: ServiceConfig;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The collector's id grammar: a type, then optionally `/` and a non-empty name. */
|
|
54
|
+
const ID_PATTERN = /^([A-Za-z0-9_]+)(?:\/([^\s/][^\s]*))?$/;
|
|
55
|
+
|
|
56
|
+
/** Build a component id from a type and an optional instance name. */
|
|
57
|
+
export function componentId(type: string, name?: string): ComponentId {
|
|
58
|
+
return name === undefined || name === "" ? type : `${type}/${name}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Split a component id into its type and name, or `undefined` when it isn't valid id syntax. */
|
|
62
|
+
export function parseComponentId(id: string): { type: string; name?: string } | undefined {
|
|
63
|
+
const m = ID_PATTERN.exec(id);
|
|
64
|
+
if (!m) return undefined;
|
|
65
|
+
return m[2] === undefined ? { type: m[1] } : { type: m[1], name: m[2] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** True when `id` is a valid `type[/name]` component id. */
|
|
69
|
+
export function isComponentId(id: string): boolean {
|
|
70
|
+
return ID_PATTERN.test(id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The signal a pipeline id names, e.g. `traces` for `traces/backend`. */
|
|
74
|
+
export function pipelineSignal(pipelineId: string): string {
|
|
75
|
+
const slash = pipelineId.indexOf("/");
|
|
76
|
+
return slash === -1 ? pipelineId : pipelineId.slice(0, slash);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True when a parsed document has the shape of a collector config. */
|
|
80
|
+
export function looksLikeCollectorConfig(value: unknown): value is CollectorConfig {
|
|
81
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
82
|
+
const v = value as Record<string, unknown>;
|
|
83
|
+
const service = v.service;
|
|
84
|
+
if (typeof service !== "object" || service === null) return false;
|
|
85
|
+
const pipelines = (service as Record<string, unknown>).pipelines;
|
|
86
|
+
if (typeof pipelines !== "object" || pipelines === null) return false;
|
|
87
|
+
return "receivers" in v || "exporters" in v;
|
|
88
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Thin entry point for `npm run bundle` in lexicon-otel.
|
|
4
|
+
*/
|
|
5
|
+
import { join, dirname } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { writeBundleSpec } from "@intentius/chant/codegen/package";
|
|
8
|
+
import { generate, writeGeneratedFiles } from "./codegen/generate";
|
|
9
|
+
import { packageLexicon } from "./codegen/package";
|
|
10
|
+
|
|
11
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
12
|
+
|
|
13
|
+
writeGeneratedFiles(await generate({ verbose: true }));
|
|
14
|
+
const { spec, stats } = await packageLexicon({ verbose: true });
|
|
15
|
+
writeBundleSpec(spec, join(pkgDir, "dist"));
|
|
16
|
+
|
|
17
|
+
console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
|
package/src/pipeline.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Pipeline` and `Service`: the `service:` half of a collector config.
|
|
3
|
+
*
|
|
4
|
+
* A pipeline names its components either by the declared entity, which
|
|
5
|
+
* keeps the reference checked by TypeScript, or by id string (`otlp/backend`)
|
|
6
|
+
* for a component declared somewhere chant can't see. OTEL101 catches a
|
|
7
|
+
* string that names nothing declared.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createResource } from "@intentius/chant/runtime";
|
|
11
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
12
|
+
import type { OTelComponent } from "./define";
|
|
13
|
+
import { componentId, type ComponentKind, type Signal } from "./model";
|
|
14
|
+
|
|
15
|
+
/** A reference to a component of kind `K`: the declared entity, or its id. */
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
export type ComponentRef<K extends ComponentKind> = OTelComponent<K, string, any> | string;
|
|
18
|
+
|
|
19
|
+
export interface PipelineProps {
|
|
20
|
+
signal: Signal;
|
|
21
|
+
/** The instance name. The pipeline id becomes `signal/name`. */
|
|
22
|
+
name?: string;
|
|
23
|
+
receivers: ComponentRef<"receiver">[];
|
|
24
|
+
processors?: ComponentRef<"processor">[];
|
|
25
|
+
exporters: ComponentRef<"exporter">[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PipelineEntity extends Declarable {
|
|
29
|
+
readonly props: PipelineProps;
|
|
30
|
+
/** The pipeline id under `service.pipelines`, `signal` or `signal/name`. */
|
|
31
|
+
readonly pipelineId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const PIPELINE_TYPE = "OTel::Pipeline";
|
|
35
|
+
export const SERVICE_TYPE = "OTel::Service";
|
|
36
|
+
|
|
37
|
+
const PipelineBase = createResource(PIPELINE_TYPE, "otel", {}) as unknown as (this: object, props: Record<string, unknown>) => void;
|
|
38
|
+
|
|
39
|
+
/** One entry under `service.pipelines`. */
|
|
40
|
+
export const Pipeline = function (this: object, props: PipelineProps) {
|
|
41
|
+
PipelineBase.call(this, props as unknown as Record<string, unknown>);
|
|
42
|
+
Object.defineProperty(this, "pipelineId", { value: componentId(props.signal, props.name), enumerable: false });
|
|
43
|
+
} as unknown as new (props: PipelineProps) => PipelineEntity;
|
|
44
|
+
Object.defineProperty(Pipeline, "name", { value: "Pipeline" });
|
|
45
|
+
|
|
46
|
+
export interface ServiceTelemetry {
|
|
47
|
+
logs?: {
|
|
48
|
+
level?: "debug" | "info" | "warn" | "error";
|
|
49
|
+
encoding?: "console" | "json";
|
|
50
|
+
development?: boolean;
|
|
51
|
+
output_paths?: string[];
|
|
52
|
+
error_output_paths?: string[];
|
|
53
|
+
initial_fields?: Record<string, string>;
|
|
54
|
+
sampling?: Record<string, unknown>;
|
|
55
|
+
};
|
|
56
|
+
metrics?: {
|
|
57
|
+
level?: "none" | "basic" | "normal" | "detailed";
|
|
58
|
+
readers?: Array<Record<string, unknown>>;
|
|
59
|
+
};
|
|
60
|
+
traces?: {
|
|
61
|
+
processors?: Array<Record<string, unknown>>;
|
|
62
|
+
propagators?: string[];
|
|
63
|
+
};
|
|
64
|
+
resource?: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ServiceProps {
|
|
68
|
+
/**
|
|
69
|
+
* Extensions to enable, in start order. Leave it out and every declared
|
|
70
|
+
* extension is enabled in declaration order.
|
|
71
|
+
*/
|
|
72
|
+
extensions?: ComponentRef<"extension">[];
|
|
73
|
+
telemetry?: ServiceTelemetry;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ServiceEntity extends Declarable {
|
|
77
|
+
readonly props: ServiceProps;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** `service.extensions` and `service.telemetry`. Optional; declare at most one. */
|
|
81
|
+
export const Service = createResource(SERVICE_TYPE, "otel", {}) as unknown as new (props: ServiceProps) => ServiceEntity;
|
|
82
|
+
|
|
83
|
+
export function isPipelineEntity(value: unknown): value is PipelineEntity {
|
|
84
|
+
return typeof value === "object" && value !== null && (value as Declarable).entityType === PIPELINE_TYPE;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isServiceEntity(value: unknown): value is ServiceEntity {
|
|
88
|
+
return typeof value === "object" && value !== null && (value as Declarable).entityType === SERVICE_TYPE;
|
|
89
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { collectorEndpoints, otlpCollector, COLLECTOR_IMAGE, COLLECTOR_CONFIG_PATH } from "./platform";
|
|
3
|
+
import { buildCollectorConfig } from "./collector";
|
|
4
|
+
import { validateCollectorConfig } from "./validate-config";
|
|
5
|
+
import type { CollectorConfig } from "./model";
|
|
6
|
+
|
|
7
|
+
describe("otlpCollector", () => {
|
|
8
|
+
test("the default is OTLP in, memory_limiter then batch, debug out, health_check, every signal", () => {
|
|
9
|
+
const { config } = buildCollectorConfig(otlpCollector());
|
|
10
|
+
expect(validateCollectorConfig(config)).toEqual([]);
|
|
11
|
+
expect(Object.keys(config.receivers ?? {})).toEqual(["otlp"]);
|
|
12
|
+
expect(Object.keys(config.exporters ?? {})).toEqual(["debug"]);
|
|
13
|
+
expect(config.service?.extensions).toEqual(["health_check"]);
|
|
14
|
+
expect(Object.keys(config.service?.pipelines ?? {}).sort()).toEqual(["logs", "metrics", "traces"]);
|
|
15
|
+
expect(config.service?.pipelines?.traces?.processors).toEqual(["memory_limiter", "batch"]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("signals and healthCheck narrow it", () => {
|
|
19
|
+
const { config } = buildCollectorConfig(otlpCollector({ signals: ["traces"], healthCheck: false }));
|
|
20
|
+
expect(Object.keys(config.service?.pipelines ?? {})).toEqual(["traces"]);
|
|
21
|
+
expect(config.extensions).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("collectorEndpoints", () => {
|
|
26
|
+
test("the default config listens on otlp-grpc and otlp-http, with a health check on 13133", () => {
|
|
27
|
+
const { config } = buildCollectorConfig(otlpCollector());
|
|
28
|
+
expect(collectorEndpoints(config)).toEqual({
|
|
29
|
+
ports: [
|
|
30
|
+
{ name: "otlp-grpc", port: 4317 },
|
|
31
|
+
{ name: "otlp-http", port: 4318 },
|
|
32
|
+
],
|
|
33
|
+
healthCheck: { port: 13133, path: "/" },
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("a named receiver's ports carry its name, and a repeated port is listed once", () => {
|
|
38
|
+
const config: CollectorConfig = {
|
|
39
|
+
receivers: {
|
|
40
|
+
"otlp/edge": { protocols: { grpc: { endpoint: "0.0.0.0:14317" } } },
|
|
41
|
+
"otlp/again": { protocols: { grpc: { endpoint: "0.0.0.0:14317" } } },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
expect(collectorEndpoints(config).ports).toEqual([{ name: "otlp-edge-grpc", port: 14317 }]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("port names are cut to 15 characters", () => {
|
|
48
|
+
const config: CollectorConfig = {
|
|
49
|
+
receivers: { "otlp/very_long_name": { protocols: { http: { endpoint: "0.0.0.0:4318" } } } },
|
|
50
|
+
};
|
|
51
|
+
const [port] = collectorEndpoints(config).ports;
|
|
52
|
+
expect(port.name.length).toBeLessThanOrEqual(15);
|
|
53
|
+
expect(port.name).toMatch(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a health_check on localhost, or one the service does not enable, is not a health check", () => {
|
|
57
|
+
const local: CollectorConfig = {
|
|
58
|
+
extensions: { health_check: { endpoint: "localhost:13133" } },
|
|
59
|
+
service: { extensions: ["health_check"] },
|
|
60
|
+
};
|
|
61
|
+
expect(collectorEndpoints(local).healthCheck).toBeUndefined();
|
|
62
|
+
|
|
63
|
+
const disabled: CollectorConfig = { extensions: { health_check: { endpoint: "0.0.0.0:13133" } } };
|
|
64
|
+
expect(collectorEndpoints(disabled).healthCheck).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("a health_check path is carried through", () => {
|
|
68
|
+
const config: CollectorConfig = {
|
|
69
|
+
extensions: { "health_check/ready": { endpoint: "0.0.0.0:8080", path: "/ready" } },
|
|
70
|
+
service: { extensions: ["health_check/ready"] },
|
|
71
|
+
};
|
|
72
|
+
expect(collectorEndpoints(config).healthCheck).toEqual({ port: 8080, path: "/ready" });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("the image is the contrib collector at a pinned version, and the config lives under /etc/otel", () => {
|
|
77
|
+
expect(COLLECTOR_IMAGE).toMatch(/^otel\/opentelemetry-collector-contrib:\d+\.\d+\.\d+$/);
|
|
78
|
+
expect(COLLECTOR_CONFIG_PATH).toBe("/etc/otel/config.yaml");
|
|
79
|
+
});
|