@checkstack/healthcheck-ping-backend 0.3.20 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,94 @@
1
1
  # @checkstack/healthcheck-ping-backend
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 43e4484: Extend `{{ … }}` environment templating across every built-in health-check type
8
+ and add editor UX for it, so one check config can cover N environments (mirrors
9
+ the existing HTTP `url` pattern).
10
+
11
+ Templatable connection/target fields now marked `x-templatable`:
12
+
13
+ - TLS: `host`, `servername`; TCP: `host`; Ping: `host`; gRPC: `host`, `service`.
14
+ - MySQL / Postgres: `host`, `database`, `user`, `query`.
15
+ - SSH: `host`, `username`, `command`; Redis: `host`, `args`; RCON: `host`,
16
+ `command`.
17
+ - DNS: `hostname`, `nameserver`; Jenkins: `url` (`baseUrl`), `jobName`;
18
+ Container: `endpoint`, `container`.
19
+ - SNMP: `host` (strategy), `oid` (collector).
20
+ - Script (shell): `cwd` (working directory).
21
+
22
+ This closes the last gaps so the coverage is now truly every built-in
23
+ health-check type. The Script collectors' `script` bodies are deliberately NOT
24
+ templatable: rendering `{{ … }}` into shell/TypeScript source would splice env
25
+ values into executed code. Per-environment data reaches those scripts safely via
26
+ the reserved `CHECKSTACK_ENV_*` shell vars (shell collector) and
27
+ `globalThis.context.environment` (inline collector) instead.
28
+
29
+ Because templating strips `{{ }}` and renders an undefined variable to an empty
30
+ string, every REQUIRED templatable field now has a post-render config-error
31
+ guard so an empty/invalid render is treated as a transport failure instead of a
32
+ silent "healthy" empty probe. Strategy connection fields (host, database, user,
33
+ endpoint, container, Jenkins base URL, SNMP host) throw from `createClient`;
34
+ collector target fields (query, command, hostname, jobName, SNMP oid) return a
35
+ `CollectorResult` with an `error`. Jenkins `baseUrl` moves its `.url()` validation to post-render.
36
+ Secret fields (passwords/tokens/keys) are never templatable; optional fields
37
+ (SNI `servername`, gRPC `service`, DNS `nameserver`, Redis `args`, Script `cwd`)
38
+ are templatable but not non-empty-guarded, since an empty render is a legitimate
39
+ "unset". SSRF/egress guards continue to run on the rendered host (rendering
40
+ happens before `createClient`).
41
+
42
+ Editor UX (`@checkstack/ui` + `@checkstack/healthcheck-frontend`):
43
+
44
+ - The environment "Preview as" picker + live preview line now also apply to the
45
+ strategy (connection) form, not just collector forms, so host/port templates
46
+ preview too.
47
+ - A single-line templatable field shows a small "Templating" badge next to its
48
+ label and, when a completion provider is supplied, renders a
49
+ `TemplateValueInput` with `{{ … }}` autocomplete. The health-check editor
50
+ seeds the provider with the fixed `environment.* / check.* / system.*`
51
+ namespace (`createReferenceCompletionProvider`, new `@checkstack/ui` export),
52
+ and `DynamicForm` gains a `templatableFieldsOnly` prop so only `x-templatable`
53
+ fields become template inputs (automation keeps templating every string field).
54
+
55
+ BREAKING CHANGE: none. Existing non-templatable configs and stored values are
56
+ unaffected; only fields explicitly marked `x-templatable` change behavior.
57
+
58
+ The `@checkstack/ai-backend` bump reflects the regenerated docs index for the
59
+ updated health-check collector and config-schema templating documentation.
60
+
61
+ Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
62
+
63
+ ### Patch Changes
64
+
65
+ - Updated dependencies [43e4484]
66
+ - Updated dependencies [43e4484]
67
+ - Updated dependencies [43e4484]
68
+ - Updated dependencies [43e4484]
69
+ - Updated dependencies [43e4484]
70
+ - Updated dependencies [43e4484]
71
+ - Updated dependencies [43e4484]
72
+ - @checkstack/healthcheck-common@1.16.0
73
+ - @checkstack/backend-api@0.31.1
74
+
75
+ ## 0.3.21
76
+
77
+ ### Patch Changes
78
+
79
+ - Updated dependencies [8aae4e2]
80
+ - Updated dependencies [f93ee7a]
81
+ - Updated dependencies [f93ee7a]
82
+ - Updated dependencies [f93ee7a]
83
+ - Updated dependencies [d0eddc9]
84
+ - Updated dependencies [d0eddc9]
85
+ - Updated dependencies [8aae4e2]
86
+ - Updated dependencies [d0eddc9]
87
+ - Updated dependencies [f93ee7a]
88
+ - @checkstack/healthcheck-common@1.15.0
89
+ - @checkstack/common@0.22.0
90
+ - @checkstack/backend-api@0.31.0
91
+
3
92
  ## 0.3.20
4
93
 
5
94
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-ping-backend",
3
- "version": "0.3.20",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "checkstack": {
@@ -14,15 +14,15 @@
14
14
  "pack": "bunx @checkstack/scripts plugin-pack"
15
15
  },
16
16
  "dependencies": {
17
- "@checkstack/backend-api": "0.30.0",
18
- "@checkstack/common": "0.21.0",
19
- "@checkstack/healthcheck-common": "1.14.0"
17
+ "@checkstack/backend-api": "0.31.1",
18
+ "@checkstack/common": "0.22.0",
19
+ "@checkstack/healthcheck-common": "1.16.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/bun": "^1.0.0",
23
23
  "typescript": "^5.0.0",
24
24
  "@checkstack/tsconfig": "0.0.7",
25
- "@checkstack/scripts": "0.7.2"
25
+ "@checkstack/scripts": "0.7.3"
26
26
  },
27
27
  "description": "Checkstack healthcheck-ping-backend plugin",
28
28
  "author": {
@@ -64,6 +64,57 @@ describe("PingCollector", () => {
64
64
  expect(result.error).toBe("Host unreachable");
65
65
  });
66
66
 
67
+ it("pings a concrete (already-rendered) host", async () => {
68
+ // The executor renders `{{ environment.host }}` upstream, so execute always
69
+ // sees a concrete value; verify it is forwarded to the transport client.
70
+ const collector = new PingCollector();
71
+ const client = createMockClient();
72
+
73
+ const result = await collector.execute({
74
+ config: { host: "rendered.example.com", count: 3, timeout: 5000 },
75
+ client,
76
+ pluginId: "test",
77
+ });
78
+
79
+ expect(client.exec).toHaveBeenCalledWith({
80
+ host: "rendered.example.com",
81
+ count: 3,
82
+ timeout: 5000,
83
+ });
84
+ expect(result.error).toBeUndefined();
85
+ });
86
+
87
+ it("returns a transport error when the rendered host is empty", async () => {
88
+ // An env-less run renders `{{ environment.host }}` to "". A required target
89
+ // that renders empty is a config error, not a silent healthy probe.
90
+ const collector = new PingCollector();
91
+ const client = createMockClient();
92
+
93
+ const result = await collector.execute({
94
+ config: { host: "", count: 3, timeout: 5000 },
95
+ client,
96
+ pluginId: "test",
97
+ });
98
+
99
+ expect(result.error).toContain("Rendered host is empty");
100
+ expect(result.result.packetLoss).toBe(100);
101
+ expect(client.exec).not.toHaveBeenCalled();
102
+ });
103
+
104
+ it("returns a transport error when the rendered host is whitespace-only", async () => {
105
+ const collector = new PingCollector();
106
+ const client = createMockClient();
107
+
108
+ const result = await collector.execute({
109
+ config: { host: " ", count: 3, timeout: 5000 },
110
+ client,
111
+ pluginId: "test",
112
+ });
113
+
114
+ expect(result.error).toContain("Rendered host is empty");
115
+ expect(client.exec).not.toHaveBeenCalled();
116
+ });
117
+
67
118
  it("should pass correct parameters to client", async () => {
68
119
  const collector = new PingCollector();
69
120
  const client = createMockClient();
@@ -7,6 +7,7 @@ import {
7
7
  mergeAverage,
8
8
  VersionedAggregated,
9
9
  aggregatedAverage,
10
+ configString,
10
11
  type InferAggregatedResult,
11
12
  } from "@checkstack/backend-api";
12
13
  import {
@@ -21,7 +22,15 @@ import type { PingTransportClient } from "./transport-client";
21
22
  // ============================================================================
22
23
 
23
24
  const pingConfigSchema = z.object({
24
- host: z.string().min(1).describe("Hostname or IP address to ping"),
25
+ // Templatable: supports `{{ environment.host }}` so one config covers N
26
+ // environments. `.min(1)` still guards the STORED value (a `{{ }}` template is
27
+ // non-empty); the CONCRETE rendered host is re-checked POST-RENDER in
28
+ // `execute` because an empty render must not run as a successful probe.
29
+ host: configString({ "x-templatable": true })
30
+ .min(1)
31
+ .describe(
32
+ "Hostname or IP address to ping. Supports templating, e.g. {{ environment.host }}",
33
+ ),
25
34
  count: z
26
35
  .number()
27
36
  .int()
@@ -38,6 +47,13 @@ const pingConfigSchema = z.object({
38
47
 
39
48
  export type PingConfig = z.infer<typeof pingConfigSchema>;
40
49
 
50
+ /**
51
+ * Post-render validator for the rendered `host`. An empty render (e.g. an
52
+ * env-less run resolving `{{ environment.host }}` to "") is a config error that
53
+ * prevents the probe - transport-failure semantics - not a healthy empty ping.
54
+ */
55
+ const renderedHostSchema = z.string().trim().min(1);
56
+
41
57
  // ============================================================================
42
58
  // RESULT SCHEMAS
43
59
  // ============================================================================
@@ -180,8 +196,27 @@ export class PingCollector implements CollectorStrategy<
180
196
  client: PingTransportClient;
181
197
  pluginId: string;
182
198
  }): Promise<CollectorResult<PingResult>> {
199
+ // Post-render guard: `host` is a templatable string, so the concrete value
200
+ // is re-validated here after the executor rendered `{{ environment.* }}`.
201
+ // An empty render is a config error - fail as a transport failure rather
202
+ // than spawning a ping against an empty host.
203
+ const host = renderedHostSchema.safeParse(config.host);
204
+ if (!host.success) {
205
+ return {
206
+ result: {
207
+ packetsSent: 0,
208
+ packetsReceived: 0,
209
+ // No packet could reach an empty host: report total loss. The run is
210
+ // already short-circuited to unhealthy by the `error` below.
211
+ packetLoss: 100,
212
+ },
213
+ error: `Rendered host is empty: ${JSON.stringify(config.host)}. ` +
214
+ `Check the {{ environment.* }} templating for this environment.`,
215
+ };
216
+ }
217
+
183
218
  const response = await client.exec({
184
- host: config.host,
219
+ host: host.data,
185
220
  count: config.count,
186
221
  timeout: config.timeout,
187
222
  });