@calltelemetry/cli 1.3.0 → 1.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/dist/lib/k8s/appliance-info.d.ts +78 -0
- package/dist/lib/k8s/appliance-info.d.ts.map +1 -0
- package/dist/lib/k8s/appliance-info.js +226 -0
- package/dist/lib/k8s/appliance-info.js.map +1 -0
- package/dist/lib/k8s/appliance-job-logger.d.ts +63 -0
- package/dist/lib/k8s/appliance-job-logger.d.ts.map +1 -0
- package/dist/lib/k8s/appliance-job-logger.js +228 -0
- package/dist/lib/k8s/appliance-job-logger.js.map +1 -0
- package/dist/lib/k8s/appliance-orchestrator.d.ts +101 -0
- package/dist/lib/k8s/appliance-orchestrator.d.ts.map +1 -0
- package/dist/lib/k8s/appliance-orchestrator.js +496 -0
- package/dist/lib/k8s/appliance-orchestrator.js.map +1 -0
- package/dist/lib/k8s/index.d.ts +8 -0
- package/dist/lib/k8s/index.d.ts.map +1 -0
- package/dist/lib/k8s/index.js +8 -0
- package/dist/lib/k8s/index.js.map +1 -0
- package/dist/lib/prefs.d.ts +25 -0
- package/dist/lib/prefs.d.ts.map +1 -1
- package/dist/lib/prefs.js +0 -0
- package/dist/lib/prefs.js.map +1 -1
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type Preferences } from '../prefs.js';
|
|
2
|
+
import type { DeploymentMode } from '../deployment.js';
|
|
3
|
+
import type { HelmfileEnvironment } from '../helmfile.js';
|
|
4
|
+
export declare const APPLIANCE_INFO_SCHEMA_VERSION = 1;
|
|
5
|
+
export declare const DEFAULT_APPLIANCE_HOSTNAME = "calltelemetry";
|
|
6
|
+
export declare const APPLIANCE_DEPLOYMENT_MODES: readonly DeploymentMode[];
|
|
7
|
+
export declare const APPLIANCE_HELMFILE_ENVIRONMENTS: readonly HelmfileEnvironment[];
|
|
8
|
+
/** Non-secret image pins carried in the appliance `.env`. */
|
|
9
|
+
export interface ApplianceVersions {
|
|
10
|
+
cli?: string;
|
|
11
|
+
web?: string;
|
|
12
|
+
vue?: string;
|
|
13
|
+
jtapi?: string;
|
|
14
|
+
ctMedia?: string;
|
|
15
|
+
syslog?: string;
|
|
16
|
+
traceroute?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ApplianceInfo {
|
|
19
|
+
schemaVersion: typeof APPLIANCE_INFO_SCHEMA_VERSION;
|
|
20
|
+
/** Stable installation identifier derived from the install dir + machine id. */
|
|
21
|
+
installationId: string;
|
|
22
|
+
installDir: string;
|
|
23
|
+
hostname: string;
|
|
24
|
+
deploymentMode: DeploymentMode;
|
|
25
|
+
k8sNamespace: string;
|
|
26
|
+
k8sEnvironment: HelmfileEnvironment;
|
|
27
|
+
versions: ApplianceVersions;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ApplianceInfoValidation {
|
|
32
|
+
valid: boolean;
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare class ApplianceInfoValidationError extends Error {
|
|
36
|
+
readonly errors: string[];
|
|
37
|
+
constructor(errors: string[]);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Derive a stable, non-license installation identifier. This is not the
|
|
41
|
+
* licensing `CT-APP-*` identifier; it exists only so job receipts from the
|
|
42
|
+
* same install can be correlated.
|
|
43
|
+
*/
|
|
44
|
+
export declare function deriveInstallationId(installDir: string, machineId?: string): string;
|
|
45
|
+
/** Map `.env` pin keys onto the non-secret appliance version record. */
|
|
46
|
+
export declare function versionsFromEnv(env: Record<string, string>): ApplianceVersions;
|
|
47
|
+
export declare function validateApplianceInfo(info: unknown): ApplianceInfoValidation;
|
|
48
|
+
export declare function assertValidApplianceInfo(info: unknown): asserts info is ApplianceInfo;
|
|
49
|
+
/** Resolve the persisted inventory path under the appliance k8s payload. */
|
|
50
|
+
export declare function resolveApplianceInfoPath(installDir: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Persist a validated inventory snapshot. The file is mode 0600 because it
|
|
53
|
+
* carries host placement details, even though it carries no credentials.
|
|
54
|
+
*/
|
|
55
|
+
export declare function saveApplianceInfo(info: ApplianceInfo, path?: string): Promise<void>;
|
|
56
|
+
/** Load and validate an inventory snapshot. Missing files return null. */
|
|
57
|
+
export declare function loadApplianceInfo(path: string): ApplianceInfo | null;
|
|
58
|
+
export interface CollectApplianceInfoOptions {
|
|
59
|
+
installDir?: string;
|
|
60
|
+
hostname?: string;
|
|
61
|
+
machineId?: string;
|
|
62
|
+
env?: Record<string, string>;
|
|
63
|
+
prefs?: Preferences;
|
|
64
|
+
cliVersion?: string;
|
|
65
|
+
now?: Date;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a fresh snapshot from the appliance `.env`, paths, and preferences.
|
|
69
|
+
* The caller is responsible for persisting it with `saveApplianceInfo`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function collectApplianceInfo(options?: CollectApplianceInfoOptions): ApplianceInfo;
|
|
72
|
+
/**
|
|
73
|
+
* Refresh an existing snapshot in place. Identity (`installationId`,
|
|
74
|
+
* `installDir`, `createdAt`) is preserved; placement, versions, and
|
|
75
|
+
* `updatedAt` are replaced.
|
|
76
|
+
*/
|
|
77
|
+
export declare function refreshApplianceInfo(previous: ApplianceInfo, updates: Pick<ApplianceInfo, 'deploymentMode' | 'k8sNamespace' | 'k8sEnvironment' | 'versions' | 'hostname'>, now?: Date): ApplianceInfo;
|
|
78
|
+
//# sourceMappingURL=appliance-info.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliance-info.d.ts","sourceRoot":"","sources":["../../../src/lib/k8s/appliance-info.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appliance inventory for the Kubernetes deployment path.
|
|
3
|
+
*
|
|
4
|
+
* This is the durable, validated record of *what this appliance is* — the
|
|
5
|
+
* installation identity, deployment mode, namespace/environment, and the
|
|
6
|
+
* image pins read from the appliance `.env`. It is deliberately separate
|
|
7
|
+
* from `~/.ct/preferences.json`: preferences are operator overrides, while
|
|
8
|
+
* this file is a generated snapshot that can be attached to a job receipt.
|
|
9
|
+
*
|
|
10
|
+
* The snapshot never contains credentials. Only non-secret image pins and
|
|
11
|
+
* placement settings are persisted; `.env` remains the source of truth for
|
|
12
|
+
* everything else.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { mkdirSafe, writeFileSafe } from '../fs.js';
|
|
18
|
+
import { getPaths } from '../paths.js';
|
|
19
|
+
import { getPrefs } from '../prefs.js';
|
|
20
|
+
import { isValidHostname } from '../identity.js';
|
|
21
|
+
import { readEnvKeys } from '../env.js';
|
|
22
|
+
export const APPLIANCE_INFO_SCHEMA_VERSION = 1;
|
|
23
|
+
export const DEFAULT_APPLIANCE_HOSTNAME = 'calltelemetry';
|
|
24
|
+
export const APPLIANCE_DEPLOYMENT_MODES = [
|
|
25
|
+
'docker-compose',
|
|
26
|
+
'k3s',
|
|
27
|
+
];
|
|
28
|
+
export const APPLIANCE_HELMFILE_ENVIRONMENTS = [
|
|
29
|
+
'ct-dev',
|
|
30
|
+
'ct-prod',
|
|
31
|
+
'ct-test',
|
|
32
|
+
'ct-appliance',
|
|
33
|
+
];
|
|
34
|
+
export class ApplianceInfoValidationError extends Error {
|
|
35
|
+
errors;
|
|
36
|
+
constructor(errors) {
|
|
37
|
+
super(`Invalid appliance info: ${errors.join('; ')}`);
|
|
38
|
+
this.name = 'ApplianceInfoValidationError';
|
|
39
|
+
this.errors = errors;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const DNS_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
43
|
+
const INSTALLATION_ID = /^[a-f0-9]{16,64}$/;
|
|
44
|
+
const VERSION_VALUE = /^\S+$/;
|
|
45
|
+
const MAX_VERSION_LENGTH = 128;
|
|
46
|
+
function isIsoTimestamp(value) {
|
|
47
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
48
|
+
return false;
|
|
49
|
+
const parsed = Date.parse(value);
|
|
50
|
+
return Number.isFinite(parsed);
|
|
51
|
+
}
|
|
52
|
+
function isScalarVersion(value) {
|
|
53
|
+
return (typeof value === 'string' &&
|
|
54
|
+
value.length > 0 &&
|
|
55
|
+
value.length <= MAX_VERSION_LENGTH &&
|
|
56
|
+
VERSION_VALUE.test(value) &&
|
|
57
|
+
// eslint-disable-next-line no-control-regex
|
|
58
|
+
!/[\u0000-\u001f\u007f]/.test(value));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Derive a stable, non-license installation identifier. This is not the
|
|
62
|
+
* licensing `CT-APP-*` identifier; it exists only so job receipts from the
|
|
63
|
+
* same install can be correlated.
|
|
64
|
+
*/
|
|
65
|
+
export function deriveInstallationId(installDir, machineId = '') {
|
|
66
|
+
return createHash('sha256')
|
|
67
|
+
.update(`${installDir}\u0000${machineId}`)
|
|
68
|
+
.digest('hex')
|
|
69
|
+
.slice(0, 16);
|
|
70
|
+
}
|
|
71
|
+
/** Map `.env` pin keys onto the non-secret appliance version record. */
|
|
72
|
+
export function versionsFromEnv(env) {
|
|
73
|
+
const versions = {};
|
|
74
|
+
const mapping = [
|
|
75
|
+
['cli', 'CT_CLI_VERSION'],
|
|
76
|
+
['web', 'WEB_VERSION'],
|
|
77
|
+
['vue', 'VUE_VERSION'],
|
|
78
|
+
['jtapi', 'JTAPI_VERSION'],
|
|
79
|
+
['ctMedia', 'CT_MEDIA_VERSION'],
|
|
80
|
+
['syslog', 'CT_SYSLOG_INGEST_VERSION'],
|
|
81
|
+
['traceroute', 'TRACEROUTE_VERSION'],
|
|
82
|
+
];
|
|
83
|
+
for (const [field, key] of mapping) {
|
|
84
|
+
const value = env[key]?.trim();
|
|
85
|
+
if (value)
|
|
86
|
+
versions[field] = value;
|
|
87
|
+
}
|
|
88
|
+
return versions;
|
|
89
|
+
}
|
|
90
|
+
export function validateApplianceInfo(info) {
|
|
91
|
+
const errors = [];
|
|
92
|
+
if (typeof info !== 'object' || info === null || Array.isArray(info)) {
|
|
93
|
+
return { valid: false, errors: ['appliance info must be an object'] };
|
|
94
|
+
}
|
|
95
|
+
const value = info;
|
|
96
|
+
if (value.schemaVersion !== APPLIANCE_INFO_SCHEMA_VERSION) {
|
|
97
|
+
errors.push(`schemaVersion must be ${APPLIANCE_INFO_SCHEMA_VERSION}`);
|
|
98
|
+
}
|
|
99
|
+
if (typeof value.installationId !== 'string' || !INSTALLATION_ID.test(value.installationId)) {
|
|
100
|
+
errors.push('installationId must be a 16-64 character lowercase hex string');
|
|
101
|
+
}
|
|
102
|
+
if (typeof value.installDir !== 'string' || value.installDir.trim().length === 0) {
|
|
103
|
+
errors.push('installDir must be a non-empty string');
|
|
104
|
+
}
|
|
105
|
+
if (typeof value.hostname !== 'string' ||
|
|
106
|
+
!isValidHostname(value.hostname)) {
|
|
107
|
+
errors.push('hostname must be a valid RFC 1123 hostname');
|
|
108
|
+
}
|
|
109
|
+
if (typeof value.deploymentMode !== 'string' ||
|
|
110
|
+
!APPLIANCE_DEPLOYMENT_MODES.includes(value.deploymentMode)) {
|
|
111
|
+
errors.push(`deploymentMode must be one of ${APPLIANCE_DEPLOYMENT_MODES.join(', ')}`);
|
|
112
|
+
}
|
|
113
|
+
if (typeof value.k8sNamespace !== 'string' ||
|
|
114
|
+
!DNS_LABEL.test(value.k8sNamespace)) {
|
|
115
|
+
errors.push('k8sNamespace must be a valid DNS-1123 label');
|
|
116
|
+
}
|
|
117
|
+
if (typeof value.k8sEnvironment !== 'string' ||
|
|
118
|
+
!APPLIANCE_HELMFILE_ENVIRONMENTS.includes(value.k8sEnvironment)) {
|
|
119
|
+
errors.push(`k8sEnvironment must be one of ${APPLIANCE_HELMFILE_ENVIRONMENTS.join(', ')}`);
|
|
120
|
+
}
|
|
121
|
+
if (typeof value.versions !== 'object' || value.versions === null || Array.isArray(value.versions)) {
|
|
122
|
+
errors.push('versions must be an object');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
for (const [field, pin] of Object.entries(value.versions)) {
|
|
126
|
+
if (!['cli', 'web', 'vue', 'jtapi', 'ctMedia', 'syslog', 'traceroute'].includes(field)) {
|
|
127
|
+
errors.push(`versions.${field} is not a recognized component`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (pin !== undefined && !isScalarVersion(pin)) {
|
|
131
|
+
errors.push(`versions.${field} must be a non-empty single-token version string`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!isIsoTimestamp(value.createdAt)) {
|
|
136
|
+
errors.push('createdAt must be an ISO timestamp');
|
|
137
|
+
}
|
|
138
|
+
if (!isIsoTimestamp(value.updatedAt)) {
|
|
139
|
+
errors.push('updatedAt must be an ISO timestamp');
|
|
140
|
+
}
|
|
141
|
+
else if (isIsoTimestamp(value.createdAt) && Date.parse(value.updatedAt) < Date.parse(value.createdAt)) {
|
|
142
|
+
errors.push('updatedAt must not be earlier than createdAt');
|
|
143
|
+
}
|
|
144
|
+
return { valid: errors.length === 0, errors };
|
|
145
|
+
}
|
|
146
|
+
export function assertValidApplianceInfo(info) {
|
|
147
|
+
const result = validateApplianceInfo(info);
|
|
148
|
+
if (!result.valid)
|
|
149
|
+
throw new ApplianceInfoValidationError(result.errors);
|
|
150
|
+
}
|
|
151
|
+
/** Resolve the persisted inventory path under the appliance k8s payload. */
|
|
152
|
+
export function resolveApplianceInfoPath(installDir) {
|
|
153
|
+
return join(installDir, 'k8s', 'appliance-info.json');
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Persist a validated inventory snapshot. The file is mode 0600 because it
|
|
157
|
+
* carries host placement details, even though it carries no credentials.
|
|
158
|
+
*/
|
|
159
|
+
export async function saveApplianceInfo(info, path = resolveApplianceInfoPath(info.installDir)) {
|
|
160
|
+
assertValidApplianceInfo(info);
|
|
161
|
+
await mkdirSafe(dirname(path));
|
|
162
|
+
await writeFileSafe(path, JSON.stringify(info, null, 2) + '\n', { mode: 0o600 });
|
|
163
|
+
}
|
|
164
|
+
/** Load and validate an inventory snapshot. Missing files return null. */
|
|
165
|
+
export function loadApplianceInfo(path) {
|
|
166
|
+
if (!existsSync(path))
|
|
167
|
+
return null;
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
throw new ApplianceInfoValidationError([`could not parse ${path}: ${err.message}`]);
|
|
174
|
+
}
|
|
175
|
+
assertValidApplianceInfo(parsed);
|
|
176
|
+
return parsed;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Build a fresh snapshot from the appliance `.env`, paths, and preferences.
|
|
180
|
+
* The caller is responsible for persisting it with `saveApplianceInfo`.
|
|
181
|
+
*/
|
|
182
|
+
export function collectApplianceInfo(options = {}) {
|
|
183
|
+
const installDir = options.installDir ?? getPaths().installDir;
|
|
184
|
+
const env = options.env ?? readEnvKeys();
|
|
185
|
+
const prefs = options.prefs ?? getPrefs();
|
|
186
|
+
const now = options.now ?? new Date();
|
|
187
|
+
const timestamp = now.toISOString();
|
|
188
|
+
if (!prefs.deploymentMode || !APPLIANCE_DEPLOYMENT_MODES.includes(prefs.deploymentMode)) {
|
|
189
|
+
throw new ApplianceInfoValidationError(['preferences.deploymentMode is invalid']);
|
|
190
|
+
}
|
|
191
|
+
if (!prefs.k8sEnvironment || !APPLIANCE_HELMFILE_ENVIRONMENTS.includes(prefs.k8sEnvironment)) {
|
|
192
|
+
throw new ApplianceInfoValidationError(['preferences.k8sEnvironment is invalid']);
|
|
193
|
+
}
|
|
194
|
+
const versions = versionsFromEnv(env);
|
|
195
|
+
if (options.cliVersion)
|
|
196
|
+
versions.cli = options.cliVersion;
|
|
197
|
+
const info = {
|
|
198
|
+
schemaVersion: APPLIANCE_INFO_SCHEMA_VERSION,
|
|
199
|
+
installationId: deriveInstallationId(installDir, options.machineId),
|
|
200
|
+
installDir,
|
|
201
|
+
hostname: options.hostname ?? DEFAULT_APPLIANCE_HOSTNAME,
|
|
202
|
+
deploymentMode: prefs.deploymentMode,
|
|
203
|
+
k8sNamespace: prefs.k8sNamespace,
|
|
204
|
+
k8sEnvironment: prefs.k8sEnvironment,
|
|
205
|
+
versions,
|
|
206
|
+
createdAt: timestamp,
|
|
207
|
+
updatedAt: timestamp,
|
|
208
|
+
};
|
|
209
|
+
assertValidApplianceInfo(info);
|
|
210
|
+
return info;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Refresh an existing snapshot in place. Identity (`installationId`,
|
|
214
|
+
* `installDir`, `createdAt`) is preserved; placement, versions, and
|
|
215
|
+
* `updatedAt` are replaced.
|
|
216
|
+
*/
|
|
217
|
+
export function refreshApplianceInfo(previous, updates, now = new Date()) {
|
|
218
|
+
const next = {
|
|
219
|
+
...previous,
|
|
220
|
+
...updates,
|
|
221
|
+
updatedAt: now.toISOString(),
|
|
222
|
+
};
|
|
223
|
+
assertValidApplianceInfo(next);
|
|
224
|
+
return next;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=appliance-info.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliance-info.js","sourceRoot":"","sources":["../../../src/lib/k8s/appliance-info.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAoB,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAIxC,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,0BAA0B,GAAG,eAAe,CAAC;AAE1D,MAAM,CAAC,MAAM,0BAA0B,GAA8B;IACnE,gBAAgB;IAChB,KAAK;CACN,CAAC;AAEF,MAAM,CAAC,MAAM,+BAA+B,GAAmC;IAC7E,QAAQ;IACR,SAAS;IACT,SAAS;IACT,cAAc;CACf,CAAC;AAgCF,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IAC5C,MAAM,CAAW;IAE1B,YAAY,MAAgB;QAC1B,KAAK,CAAC,2BAA2B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,SAAS,GAAG,sCAAsC,CAAC;AACzD,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAC5C,MAAM,aAAa,GAAG,OAAO,CAAC;AAC9B,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,MAAM,GAAG,CAAC;QAChB,KAAK,CAAC,MAAM,IAAI,kBAAkB;QAClC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC;QACzB,4CAA4C;QAC5C,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CACrC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,SAAS,GAAG,EAAE;IACrE,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,GAAG,UAAU,SAAS,SAAS,EAAE,CAAC;SACzC,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,GAA2B;IACzD,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,OAAO,GAA6C;QACxD,CAAC,KAAK,EAAE,gBAAgB,CAAC;QACzB,CAAC,KAAK,EAAE,aAAa,CAAC;QACtB,CAAC,KAAK,EAAE,aAAa,CAAC;QACtB,CAAC,OAAO,EAAE,eAAe,CAAC;QAC1B,CAAC,SAAS,EAAE,kBAAkB,CAAC;QAC/B,CAAC,QAAQ,EAAE,0BAA0B,CAAC;QACtC,CAAC,YAAY,EAAE,oBAAoB,CAAC;KACrC,CAAC;IACF,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;QAC/B,IAAI,KAAK;YAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;IACrC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAa;IACjD,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,kCAAkC,CAAC,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,KAAK,GAAG,IAA8B,CAAC;IAE7C,IAAI,KAAK,CAAC,aAAa,KAAK,6BAA6B,EAAE,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,yBAAyB,6BAA6B,EAAE,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,cAAc,KAAK,QAAQ,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QAC5F,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjF,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACvD,CAAC;IACD,IACE,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,CAAC,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,EAChC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC5D,CAAC;IACD,IACE,OAAO,KAAK,CAAC,cAAc,KAAK,QAAQ;QACxC,CAAC,0BAA0B,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAgC,CAAC,EAC5E,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,iCAAiC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,IACE,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;QACtC,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EACnC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IAC7D,CAAC;IACD,IACE,OAAO,KAAK,CAAC,cAAc,KAAK,QAAQ;QACxC,CAAC,+BAA+B,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAqC,CAAC,EACtF,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,iCAAiC,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnG,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1D,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvF,MAAM,CAAC,IAAI,CAAC,YAAY,KAAK,gCAAgC,CAAC,CAAC;gBAC/D,SAAS;YACX,CAAC;YACD,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/C,MAAM,CAAC,IAAI,CAAC,YAAY,KAAK,kDAAkD,CAAC,CAAC;YACnF,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACpD,CAAC;SAAM,IAAI,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACxG,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,IAAa;IACpD,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,MAAM,IAAI,4BAA4B,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC3E,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IACzD,OAAO,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,qBAAqB,CAAC,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAmB,EACnB,IAAI,GAAG,wBAAwB,CAAC,IAAI,CAAC,UAAU,CAAC;IAEhD,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/B,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,MAAM,IAAI,4BAA4B,CAAC,CAAC,mBAAmB,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACtF,CAAC;IACD,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC;AAChB,CAAC;AAYD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAuC,EAAE;IAC5E,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,QAAQ,EAAE,CAAC,UAAU,CAAC;IAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,QAAQ,EAAE,CAAC;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACtC,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAEpC,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,CAAC,0BAA0B,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QACxF,MAAM,IAAI,4BAA4B,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,CAAC,+BAA+B,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7F,MAAM,IAAI,4BAA4B,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,OAAO,CAAC,UAAU;QAAE,QAAQ,CAAC,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC;IAE1D,MAAM,IAAI,GAAkB;QAC1B,aAAa,EAAE,6BAA6B;QAC5C,cAAc,EAAE,oBAAoB,CAAC,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC;QACnE,UAAU;QACV,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,0BAA0B;QACxD,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,QAAQ;QACR,SAAS,EAAE,SAAS;QACpB,SAAS,EAAE,SAAS;KACrB,CAAC;IACF,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAAuB,EACvB,OAA4G,EAC5G,GAAG,GAAG,IAAI,IAAI,EAAE;IAEhB,MAAM,IAAI,GAAkB;QAC1B,GAAG,QAAQ;QACX,GAAG,OAAO;QACV,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE;KAC7B,CAAC;IACF,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const JOB_LOG_SCHEMA_VERSION = 1;
|
|
2
|
+
export declare const DEFAULT_JOB_LOG_MAX_BYTES: number;
|
|
3
|
+
export type ApplianceJobLogLevel = 'info' | 'warn' | 'error';
|
|
4
|
+
export declare const APPLIANCE_JOB_LOG_LEVELS: readonly ApplianceJobLogLevel[];
|
|
5
|
+
export type ApplianceJobLogDetail = string | number | boolean;
|
|
6
|
+
export interface ApplianceJobLogEvent {
|
|
7
|
+
schemaVersion: typeof JOB_LOG_SCHEMA_VERSION;
|
|
8
|
+
jobId: string;
|
|
9
|
+
seq: number;
|
|
10
|
+
at: string;
|
|
11
|
+
level: ApplianceJobLogLevel;
|
|
12
|
+
phase: string;
|
|
13
|
+
message: string;
|
|
14
|
+
details?: Record<string, ApplianceJobLogDetail>;
|
|
15
|
+
}
|
|
16
|
+
export interface JobLogValidation {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
errors: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface JobLogReadResult {
|
|
21
|
+
events: ApplianceJobLogEvent[];
|
|
22
|
+
invalid: Array<{
|
|
23
|
+
line: number;
|
|
24
|
+
error: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export declare class JobLogValidationError extends Error {
|
|
28
|
+
readonly errors: string[];
|
|
29
|
+
constructor(errors: string[]);
|
|
30
|
+
}
|
|
31
|
+
/** Redact a single string value without changing non-secret text. */
|
|
32
|
+
export declare function redactJobLogValue(value: string): string;
|
|
33
|
+
/** Redact secret-looking keys and URL userinfo from a detail map. */
|
|
34
|
+
export declare function redactJobLogDetails(details: Record<string, unknown> | undefined): Record<string, ApplianceJobLogDetail> | undefined;
|
|
35
|
+
export declare function validateJobLogEvent(event: unknown): JobLogValidation;
|
|
36
|
+
export declare function assertValidJobLogEvent(event: unknown): asserts event is ApplianceJobLogEvent;
|
|
37
|
+
/** Build a fully populated, redacted event. */
|
|
38
|
+
export declare function createJobLogEvent(input: {
|
|
39
|
+
jobId: string;
|
|
40
|
+
seq: number;
|
|
41
|
+
phase: string;
|
|
42
|
+
message: string;
|
|
43
|
+
level?: ApplianceJobLogLevel;
|
|
44
|
+
details?: Record<string, unknown>;
|
|
45
|
+
now?: Date;
|
|
46
|
+
}): ApplianceJobLogEvent;
|
|
47
|
+
export declare function resolveJobLogPath(installDir: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Append one event as a single JSONL record. The file is created/kept mode
|
|
50
|
+
* 0600. A permission failure falls back to passwordless `sudo tee -a`, the
|
|
51
|
+
* same contract as `writeFileSafe`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function appendJobLogEvent(logPath: string, event: ApplianceJobLogEvent): Promise<void>;
|
|
54
|
+
/** Parse the log, reporting (but not throwing on) corrupt lines. */
|
|
55
|
+
export declare function readJobLog(logPath: string): JobLogReadResult;
|
|
56
|
+
/** Return the most recent valid events, preserving file order. */
|
|
57
|
+
export declare function tailJobLog(logPath: string, limit?: number): ApplianceJobLogEvent[];
|
|
58
|
+
/**
|
|
59
|
+
* Rotate an oversized log to `<path>.1`, keeping one previous generation.
|
|
60
|
+
* Returns true when a rotation happened.
|
|
61
|
+
*/
|
|
62
|
+
export declare function rotateJobLogIfNeeded(logPath: string, maxBytes?: number): boolean;
|
|
63
|
+
//# sourceMappingURL=appliance-job-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliance-job-logger.d.ts","sourceRoot":"","sources":["../../../src/lib/k8s/appliance-job-logger.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,yBAAyB,QAAkB,CAAC;AAEzD,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAC7D,eAAO,MAAM,wBAAwB,EAAE,SAAS,oBAAoB,EAA8B,CAAC;AAEnG,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,OAAO,sBAAsB,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,oBAAoB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjD;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,MAAM,EAAE,MAAM,EAAE;CAK7B;AA2BD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC3C,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,SAAS,CAenD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CA0DpE;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,oBAAoB,CAG5F;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GAAG,oBAAoB,CAcvB;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAsB5D;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,oBAAoB,EAAE,CAM9E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,SAA4B,GACnC,OAAO,CAKT"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable job log for appliance Kubernetes orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Each job appends one JSON object per line. The log is intentionally
|
|
5
|
+
* append-only and self-describing so a failed `ct k8s sync` can be audited
|
|
6
|
+
* after the process exits, and corrupt/truncated tails are skipped rather
|
|
7
|
+
* than making the whole log unreadable.
|
|
8
|
+
*
|
|
9
|
+
* Log records never contain credentials: secret-looking detail keys and
|
|
10
|
+
* URL userinfo are redacted before the record is validated and written.
|
|
11
|
+
*/
|
|
12
|
+
import { appendFileSync, chmodSync, existsSync, readFileSync, renameSync, statSync, } from 'node:fs';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import { execa } from 'execa';
|
|
15
|
+
import { mkdirSafe } from '../fs.js';
|
|
16
|
+
export const JOB_LOG_SCHEMA_VERSION = 1;
|
|
17
|
+
export const DEFAULT_JOB_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
18
|
+
export const APPLIANCE_JOB_LOG_LEVELS = ['info', 'warn', 'error'];
|
|
19
|
+
export class JobLogValidationError extends Error {
|
|
20
|
+
errors;
|
|
21
|
+
constructor(errors) {
|
|
22
|
+
super(`Invalid job log event: ${errors.join('; ')}`);
|
|
23
|
+
this.name = 'JobLogValidationError';
|
|
24
|
+
this.errors = errors;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const JOB_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
28
|
+
const PHASE = /^[a-z][a-z0-9._-]{0,63}$/;
|
|
29
|
+
const DETAIL_KEY = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
30
|
+
const SECRET_KEY = /(password|passwd|secret|token|credential|api[-_]?key|private[-_]?key|authorization)/i;
|
|
31
|
+
const URL_USERINFO = /(:\/\/)([^/@\s:]+):([^/@\s]+)@/g;
|
|
32
|
+
const MAX_MESSAGE_LENGTH = 4096;
|
|
33
|
+
const MAX_DETAIL_STRING_LENGTH = 1024;
|
|
34
|
+
const MAX_DETAIL_KEYS = 50;
|
|
35
|
+
const REDACTED = '[redacted]';
|
|
36
|
+
function isIsoTimestamp(value) {
|
|
37
|
+
return typeof value === 'string' && Number.isFinite(Date.parse(value));
|
|
38
|
+
}
|
|
39
|
+
function hasControlCharacter(value) {
|
|
40
|
+
// eslint-disable-next-line no-control-regex
|
|
41
|
+
return /[\u0000-\u001f\u007f]/.test(value);
|
|
42
|
+
}
|
|
43
|
+
function isScalarDetail(value) {
|
|
44
|
+
if (typeof value === 'number')
|
|
45
|
+
return Number.isFinite(value);
|
|
46
|
+
if (typeof value === 'boolean')
|
|
47
|
+
return true;
|
|
48
|
+
return typeof value === 'string';
|
|
49
|
+
}
|
|
50
|
+
/** Redact a single string value without changing non-secret text. */
|
|
51
|
+
export function redactJobLogValue(value) {
|
|
52
|
+
return value.replace(URL_USERINFO, `$1$2:${REDACTED}@`);
|
|
53
|
+
}
|
|
54
|
+
/** Redact secret-looking keys and URL userinfo from a detail map. */
|
|
55
|
+
export function redactJobLogDetails(details) {
|
|
56
|
+
if (!details)
|
|
57
|
+
return undefined;
|
|
58
|
+
const redacted = {};
|
|
59
|
+
for (const [key, raw] of Object.entries(details)) {
|
|
60
|
+
if (SECRET_KEY.test(key)) {
|
|
61
|
+
redacted[key] = REDACTED;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (typeof raw === 'string') {
|
|
65
|
+
redacted[key] = redactJobLogValue(raw);
|
|
66
|
+
}
|
|
67
|
+
else if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
68
|
+
redacted[key] = raw;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return redacted;
|
|
72
|
+
}
|
|
73
|
+
export function validateJobLogEvent(event) {
|
|
74
|
+
const errors = [];
|
|
75
|
+
if (typeof event !== 'object' || event === null || Array.isArray(event)) {
|
|
76
|
+
return { valid: false, errors: ['event must be an object'] };
|
|
77
|
+
}
|
|
78
|
+
const value = event;
|
|
79
|
+
if (value.schemaVersion !== JOB_LOG_SCHEMA_VERSION) {
|
|
80
|
+
errors.push(`schemaVersion must be ${JOB_LOG_SCHEMA_VERSION}`);
|
|
81
|
+
}
|
|
82
|
+
if (typeof value.jobId !== 'string' || !JOB_ID.test(value.jobId)) {
|
|
83
|
+
errors.push('jobId must be a 1-128 character identifier');
|
|
84
|
+
}
|
|
85
|
+
if (!Number.isInteger(value.seq) || (value.seq ?? -1) < 0) {
|
|
86
|
+
errors.push('seq must be a non-negative integer');
|
|
87
|
+
}
|
|
88
|
+
if (!isIsoTimestamp(value.at)) {
|
|
89
|
+
errors.push('at must be an ISO timestamp');
|
|
90
|
+
}
|
|
91
|
+
if (typeof value.level !== 'string' || !APPLIANCE_JOB_LOG_LEVELS.includes(value.level)) {
|
|
92
|
+
errors.push(`level must be one of ${APPLIANCE_JOB_LOG_LEVELS.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
if (typeof value.phase !== 'string' || !PHASE.test(value.phase)) {
|
|
95
|
+
errors.push('phase must be a lowercase phase identifier');
|
|
96
|
+
}
|
|
97
|
+
if (typeof value.message !== 'string' ||
|
|
98
|
+
value.message.length === 0 ||
|
|
99
|
+
value.message.length > MAX_MESSAGE_LENGTH ||
|
|
100
|
+
hasControlCharacter(value.message)) {
|
|
101
|
+
errors.push(`message must be a single line of 1-${MAX_MESSAGE_LENGTH} characters`);
|
|
102
|
+
}
|
|
103
|
+
if (value.details !== undefined) {
|
|
104
|
+
if (typeof value.details !== 'object' || value.details === null || Array.isArray(value.details)) {
|
|
105
|
+
errors.push('details must be an object');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const entries = Object.entries(value.details);
|
|
109
|
+
if (entries.length > MAX_DETAIL_KEYS)
|
|
110
|
+
errors.push(`details must have at most ${MAX_DETAIL_KEYS} keys`);
|
|
111
|
+
for (const [key, detail] of entries) {
|
|
112
|
+
if (!DETAIL_KEY.test(key) || ['__proto__', 'constructor', 'prototype'].includes(key)) {
|
|
113
|
+
errors.push(`details.${key} is not a valid key`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!isScalarDetail(detail)) {
|
|
117
|
+
errors.push(`details.${key} must be a string, number, or boolean`);
|
|
118
|
+
}
|
|
119
|
+
else if (typeof detail === 'string' &&
|
|
120
|
+
(detail.length > MAX_DETAIL_STRING_LENGTH || hasControlCharacter(detail))) {
|
|
121
|
+
errors.push(`details.${key} must be a single line of at most ${MAX_DETAIL_STRING_LENGTH} characters`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { valid: errors.length === 0, errors };
|
|
127
|
+
}
|
|
128
|
+
export function assertValidJobLogEvent(event) {
|
|
129
|
+
const result = validateJobLogEvent(event);
|
|
130
|
+
if (!result.valid)
|
|
131
|
+
throw new JobLogValidationError(result.errors);
|
|
132
|
+
}
|
|
133
|
+
/** Build a fully populated, redacted event. */
|
|
134
|
+
export function createJobLogEvent(input) {
|
|
135
|
+
const event = {
|
|
136
|
+
schemaVersion: JOB_LOG_SCHEMA_VERSION,
|
|
137
|
+
jobId: input.jobId,
|
|
138
|
+
seq: input.seq,
|
|
139
|
+
at: (input.now ?? new Date()).toISOString(),
|
|
140
|
+
level: input.level ?? 'info',
|
|
141
|
+
phase: input.phase,
|
|
142
|
+
message: redactJobLogValue(input.message),
|
|
143
|
+
};
|
|
144
|
+
const details = redactJobLogDetails(input.details);
|
|
145
|
+
if (details)
|
|
146
|
+
event.details = details;
|
|
147
|
+
assertValidJobLogEvent(event);
|
|
148
|
+
return event;
|
|
149
|
+
}
|
|
150
|
+
export function resolveJobLogPath(installDir) {
|
|
151
|
+
return join(installDir, 'k8s', 'jobs.jsonl');
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Append one event as a single JSONL record. The file is created/kept mode
|
|
155
|
+
* 0600. A permission failure falls back to passwordless `sudo tee -a`, the
|
|
156
|
+
* same contract as `writeFileSafe`.
|
|
157
|
+
*/
|
|
158
|
+
export async function appendJobLogEvent(logPath, event) {
|
|
159
|
+
assertValidJobLogEvent(event);
|
|
160
|
+
const line = JSON.stringify(event) + '\n';
|
|
161
|
+
await mkdirSafe(dirname(logPath));
|
|
162
|
+
try {
|
|
163
|
+
appendFileSync(logPath, line, { encoding: 'utf-8', mode: 0o600 });
|
|
164
|
+
try {
|
|
165
|
+
chmodSync(logPath, 0o600);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
/* chmod is best-effort on filesystems that do not support modes */
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
if (err?.code !== 'EACCES' && err?.code !== 'EPERM')
|
|
173
|
+
throw err;
|
|
174
|
+
await execa('sudo', ['-n', 'tee', '-a', logPath], {
|
|
175
|
+
input: line,
|
|
176
|
+
stdout: 'ignore',
|
|
177
|
+
stderr: 'pipe',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Parse the log, reporting (but not throwing on) corrupt lines. */
|
|
182
|
+
export function readJobLog(logPath) {
|
|
183
|
+
if (!existsSync(logPath))
|
|
184
|
+
return { events: [], invalid: [] };
|
|
185
|
+
const events = [];
|
|
186
|
+
const invalid = [];
|
|
187
|
+
const lines = readFileSync(logPath, 'utf-8').split('\n');
|
|
188
|
+
lines.forEach((line, index) => {
|
|
189
|
+
if (!line.trim())
|
|
190
|
+
return;
|
|
191
|
+
let parsed;
|
|
192
|
+
try {
|
|
193
|
+
parsed = JSON.parse(line);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
invalid.push({ line: index + 1, error: `invalid JSON: ${err.message}` });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const result = validateJobLogEvent(parsed);
|
|
200
|
+
if (!result.valid) {
|
|
201
|
+
invalid.push({ line: index + 1, error: result.errors.join('; ') });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
events.push(parsed);
|
|
205
|
+
});
|
|
206
|
+
return { events, invalid };
|
|
207
|
+
}
|
|
208
|
+
/** Return the most recent valid events, preserving file order. */
|
|
209
|
+
export function tailJobLog(logPath, limit = 50) {
|
|
210
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
211
|
+
throw new Error('limit must be a non-negative integer');
|
|
212
|
+
}
|
|
213
|
+
const { events } = readJobLog(logPath);
|
|
214
|
+
return limit === 0 ? [] : events.slice(-limit);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Rotate an oversized log to `<path>.1`, keeping one previous generation.
|
|
218
|
+
* Returns true when a rotation happened.
|
|
219
|
+
*/
|
|
220
|
+
export function rotateJobLogIfNeeded(logPath, maxBytes = DEFAULT_JOB_LOG_MAX_BYTES) {
|
|
221
|
+
if (!existsSync(logPath))
|
|
222
|
+
return false;
|
|
223
|
+
if (statSync(logPath).size <= maxBytes)
|
|
224
|
+
return false;
|
|
225
|
+
renameSync(logPath, `${logPath}.1`);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=appliance-job-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliance-job-logger.js","sourceRoot":"","sources":["../../../src/lib/k8s/appliance-job-logger.ts"],"names":[],"mappings":"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"}
|