@ansible-flow/node 0.87.0 → 0.88.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 +1 -1
- package/dist/chunks/{chunk-LGUG2Q7Z.js → chunk-N5T2ZJ72.js} +11 -8
- package/dist/chunks/chunk-N5T2ZJ72.js.map +7 -0
- package/dist/repoFiles.d.ts +4 -0
- package/dist/repoFiles.js +42 -0
- package/dist/repoFiles.js.map +7 -0
- package/dist/run/facts.d.ts +2 -4
- package/dist/run/facts.js +6 -5
- package/dist/run/facts.js.map +2 -2
- package/dist/run/runFlow.d.ts +2 -5
- package/dist/run/runFlow.js +3 -3
- package/dist/run/segments.js +3 -3
- package/dist/scenario/runner.js +3 -3
- package/package.json +2 -2
- package/dist/chunks/chunk-LGUG2Q7Z.js.map +0 -7
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Ansible Flow Designer for Node hosts: layout, the probe client, runs, the AWX client, the module catalog and SSH.
|
|
4
4
|
|
|
5
|
-
Built from [Ansible Flow Designer](https://github.com/ChistokhinSV/ansible-flow-designer) 0.
|
|
5
|
+
Built from [Ansible Flow Designer](https://github.com/ChistokhinSV/ansible-flow-designer) 0.88.0
|
|
6
6
|
and released with it. Modules are imported by their path in the source tree, for example
|
|
7
7
|
`@ansible-flow/node/ai/agents`.
|
|
8
8
|
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
tailFile
|
|
3
|
+
} from "./chunk-SSZNBJZQ.js";
|
|
1
4
|
import {
|
|
2
5
|
followRunFile,
|
|
3
6
|
putRunFile,
|
|
4
7
|
removeRunDir,
|
|
5
8
|
syncWorkspace
|
|
6
9
|
} from "./chunk-RETJPSKL.js";
|
|
10
|
+
import {
|
|
11
|
+
startProcess
|
|
12
|
+
} from "./chunk-I6FIOP7G.js";
|
|
7
13
|
import {
|
|
8
14
|
keepRun,
|
|
9
15
|
runsDir
|
|
10
16
|
} from "./chunk-4FHPTGI4.js";
|
|
11
|
-
import {
|
|
12
|
-
tailFile
|
|
13
|
-
} from "./chunk-SSZNBJZQ.js";
|
|
14
17
|
import {
|
|
15
18
|
stopStarted
|
|
16
19
|
} from "./chunk-JDPN3LTT.js";
|
|
@@ -22,16 +25,16 @@ import {
|
|
|
22
25
|
dropVars,
|
|
23
26
|
pruneRuns
|
|
24
27
|
} from "./chunk-SUV3VHKR.js";
|
|
25
|
-
import {
|
|
26
|
-
startProcess
|
|
27
|
-
} from "./chunk-I6FIOP7G.js";
|
|
28
28
|
|
|
29
29
|
// src/node/run/runFlow.ts
|
|
30
30
|
import { existsSync } from "node:fs";
|
|
31
31
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
32
32
|
import { join } from "node:path";
|
|
33
33
|
import { nodeDesigns } from "@ansible-flow/core/flow/semantic";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
runProcess,
|
|
36
|
+
splitInputs
|
|
37
|
+
} from "@ansible-flow/core/run/command";
|
|
35
38
|
import { createSummarizer, summarizeRun } from "@ansible-flow/core/run/events";
|
|
36
39
|
import { redactLine, redactText, secretValues } from "@ansible-flow/core/run/redact";
|
|
37
40
|
var LIVE_MS = 500;
|
|
@@ -188,4 +191,4 @@ export {
|
|
|
188
191
|
STALL_MS,
|
|
189
192
|
runFlow
|
|
190
193
|
};
|
|
191
|
-
//# sourceMappingURL=chunk-
|
|
194
|
+
//# sourceMappingURL=chunk-N5T2ZJ72.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/node/run/runFlow.ts"],
|
|
4
|
+
"sourcesContent": ["// One run of a flow's generated code (AFD-271): what the extension's local run and a server's run\n// have in common. The caller has generated the code, resolved the environment, collected the\n// inputs and asked whatever it asks; from there this writes the run's folder, starts\n// ansible-playbook, follows its events into a view of the run, redacts and keeps the result.\n//\n// What differs between hosts is given as hooks: how the process is started (a VS Code terminal\n// task, or a plain child process), who is told that the run went quiet, what is cleaned up.\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { GenerateResult } from '../../core/engine/types';\nimport { nodeDesigns } from '../../core/flow/semantic';\nimport type { FlowDocument } from '../../core/flow/types';\nimport type { RunView } from '../../core/protocol';\nimport {\n type ProcessSpec,\n type RepositoryConfig,\n runProcess,\n splitInputs,\n} from '../../core/run/command';\nimport type { Environment } from '../../core/run/environment';\nimport { createSummarizer, type RunSummary, summarizeRun } from '../../core/run/events';\nimport { redactLine, redactText, secretValues } from '../../core/run/redact';\nimport { followRunFile, putRunFile, removeRunDir, syncWorkspace } from '../ssh/remote';\nimport { appendAudit } from './audit';\nimport { type Execution, startProcess } from './exec';\nimport { keepRun, runsDir } from './pastRuns';\nimport { stopStarted } from './stop';\nimport { dropVars, KEEP_RUNS, pruneRuns } from './storage';\nimport { tailFile } from './tail';\n\n/** How often a live run updates its view, at most (AFD-81). */\nexport const LIVE_MS = 500;\n/** No new events for this long: the run may wait for input. */\nexport const STALL_MS = 20_000;\n\nexport interface RunFlowRequest {\n /** The run's id (`stamp()`), which names its folder. */\n id: string;\n startedAt: string;\n mode: 'check' | 'real';\n doc: FlowDocument;\n /** The code that runs: its routes map the events back to nodes. */\n code: GenerateResult;\n env: Environment;\n /** The environment's name for people (\"afd-ee:2.19 (docker)\"). */\n environment: string;\n /** Repository root on this machine. */\n root: string;\n /** Folder with afd_probe/ and callback_plugins/afd_events.py. */\n probeDir: string;\n /** The playbook to run, relative to `root`, with forward slashes. */\n playbook: string;\n inventory?: string;\n /** Where runs are kept: `<storage>/runs/<flow id>/<run id>/`, and the audit log. */\n storage: string;\n /** How many runs of the flow to keep (default `KEEP_RUNS`). */\n keepRuns?: number;\n /** Flow input values. Secret ones never reach a file. */\n inputs: Record<string, unknown>;\n vaultPassword?: string;\n /** The repository's own callbacks, kept running beside the run's (`repositoryCallbacks`). */\n callbacks?: RepositoryConfig;\n /** AFD-244: the run is of a part of the flow; its code is in a folder of its own. */\n partial?: RunView['partial'];\n /** AFD-247: paths the SSH copy needs that Git does not list (a partial run's folder). */\n syncAlso?: string[];\n /** What the audit log says about the launch, besides the run's id. Never input values. */\n audit?: Record<string, unknown>;\n /** Stops the run when it aborts. */\n signal?: AbortSignal;\n}\n\nexport interface RunFlowHooks {\n /** The run's view: when it starts, as it goes (at most every `LIVE_MS`), and when it ends. */\n report(run: RunView): void;\n /**\n * Starts the process. Default: a child process whose output goes to `log`. `stopping` says the\n * run is being stopped, for a host whose process may end without an exit code then.\n */\n execute?(\n spec: ProcessSpec,\n env: Record<string, string>,\n stopping: () => boolean,\n ): Execution | Promise<Execution>;\n /** The default process's output, line by line. */\n log?(line: string): void;\n /** The run started; `stop` ends it. Returns what to call when the run has ended. */\n started?(stop: () => Promise<void>): (() => void) | undefined;\n /** Whether to watch for a run gone quiet (no event for `STALL_MS`). */\n watchQuiet?: boolean;\n /** The run went quiet. */\n quiet?(): void;\n /** The process ended; before its events are read. */\n ended?(): Promise<void>;\n /** How the run ended, before it is kept and reported. */\n finished?(end: {\n stopped: boolean;\n rc: number | undefined;\n events: boolean;\n summary: RunSummary;\n }): void;\n}\n\n/** Runs the flow's code and resolves with the finished run (also reported). Never rejects. */\nexport async function runFlow(request: RunFlowRequest, hooks: RunFlowHooks): Promise<RunView> {\n const { id, mode, doc, code, startedAt, vaultPassword, partial } = request;\n const fail = (error: string): RunView => {\n const run: RunView = { id, mode, state: 'error', startedAt, error };\n hooks.report(run);\n return run;\n };\n\n const runDir = join(runsDir(request.storage, doc.flow.id), id);\n await mkdir(runDir, { recursive: true });\n // Secret inputs travel in an environment variable only; vars.json goes when the run ends.\n const { plain, secret } = splitInputs(request.inputs);\n const secrets = Object.keys(secret).length > 0;\n await writeFile(join(runDir, 'vars.json'), JSON.stringify(plain, null, 2), 'utf8');\n await pruneRuns(request.storage, doc.flow.id, request.keepRuns ?? KEEP_RUNS).catch(\n () => undefined,\n );\n // AFD-247: an SSH host runs a copy of the repository: brought up to date now, with the code just\n // generated, and the inputs.\n const remote = request.env.kind === 'ssh' ? request.env : undefined;\n if (remote) {\n const failed =\n (await syncWorkspace(\n remote,\n { root: request.root, probe: request.probeDir },\n request.syncAlso ?? [],\n )) ?? (await putRunFile(remote, runDir, 'vars.json', JSON.stringify(plain, null, 2)));\n if (failed) return fail(failed);\n }\n const spec = runProcess(\n request.env,\n { root: request.root, probe: request.probeDir },\n {\n playbook: request.playbook,\n inventory: request.inventory,\n check: mode === 'check',\n runDir,\n vault: vaultPassword !== undefined,\n secrets,\n hasConfig: existsSync(join(request.root, 'ansible.cfg')),\n callbacks: request.callbacks,\n },\n `afd-run-${id.toLowerCase()}`,\n );\n\n if (request.audit)\n await appendAudit(request.storage, { event: 'launch', run: id, ...request.audit });\n const running: RunView = {\n id,\n mode,\n state: 'running',\n startedAt,\n environment: request.environment,\n source: 'local',\n flowVersion: doc.flow.version,\n design: nodeDesigns(doc),\n ...(partial ? { partial } : {}),\n };\n hooks.report(running);\n\n const env: Record<string, string> = { ...spec.env };\n // AFD-75: names WSL should take from Windows join the ones already set, if any.\n if (spec.env.WSLENV && process.env.WSLENV)\n env.WSLENV = `${process.env.WSLENV}:${spec.env.WSLENV}`;\n if (vaultPassword !== undefined) env.AFD_VAULT_PASSWORD = vaultPassword;\n if (secrets) env.AFD_SECRET_VARS = JSON.stringify(secret);\n\n const t0 = Date.now();\n // The events as they come: a partial summary at most every LIVE_MS, secrets out.\n // AFD-138: the vault password too, if a task prints it.\n const hidden = secretValues(vaultPassword ? { ...secret, '': vaultPassword } : secret);\n const live = createSummarizer(code.routes);\n const eventsFile = join(runDir, 'events.jsonl');\n // AFD-247: the events are written on the host; they are followed into the file here.\n const following = remote ? followRunFile(remote, runDir, 'events.jsonl', eventsFile) : undefined;\n let lastEvent = Date.now();\n let changed = false;\n let quiet = false;\n const tail = tailFile(eventsFile, (lines) => {\n for (const line of lines) live.push(redactLine(line, hidden));\n lastEvent = Date.now();\n changed = true;\n quiet = false;\n });\n const ticker = setInterval(() => {\n if (hooks.watchQuiet && !quiet && Date.now() - lastEvent > STALL_MS) {\n quiet = true;\n changed = true;\n hooks.quiet?.();\n }\n if (!changed) return;\n changed = false;\n hooks.report({\n ...running,\n summary: live.summary({ partial: true }),\n ...(quiet ? { quiet } : {}),\n });\n }, LIVE_MS);\n\n // Stopping ends the process; the run then ends as stopped, not as an error.\n let stopped = false;\n const execute = hooks.execute ?? ((s, e) => startProcess(s, e, hooks.log ?? (() => {})));\n let execution: Execution;\n try {\n execution = await execute(spec, env, () => stopped);\n } catch {\n execution = { exit: Promise.resolve(undefined), terminate: () => undefined };\n }\n const stop = async () => {\n stopped = true;\n // AFD-131: the container first, so the playbook stops changing hosts and writing events\n // before the redaction pass below reads them; the process then ends by itself.\n await stopStarted(spec);\n execution.terminate();\n };\n const onAbort = () => void stop();\n if (request.signal?.aborted) onAbort();\n else request.signal?.addEventListener('abort', onAbort, { once: true });\n const release = hooks.started?.(stop);\n const rc = await execution.exit;\n release?.();\n request.signal?.removeEventListener('abort', onAbort);\n clearInterval(ticker);\n await following?.stop();\n await tail.stop();\n if (remote) await removeRunDir(remote, runDir);\n\n await dropVars(runDir).catch(() => undefined);\n await hooks.ended?.();\n let text = '';\n try {\n text = await readFile(eventsFile, 'utf8');\n // The kept events hold no secret input value either (the output now comes with them).\n if (hidden.length > 0) {\n text = redactText(text, hidden);\n await writeFile(eventsFile, text, 'utf8');\n }\n } catch {}\n const summary = summarizeRun(text, code.routes);\n hooks.finished?.({ stopped, rc, events: !!text.trim(), summary });\n const done: RunView = {\n ...running,\n state: rc === undefined || stopped ? 'error' : 'done',\n finishedAt: new Date().toISOString(),\n rc,\n summary,\n error: stopped\n ? 'Stopped by the user'\n : rc === undefined\n ? 'The run could not be started'\n : text\n ? undefined\n : 'The run produced no events',\n ...(stopped ? { errorCode: 'cancelled' as const } : {}),\n };\n if (request.audit)\n await appendAudit(request.storage, {\n event: 'finish',\n run: id,\n rc,\n ms: Date.now() - t0,\n hosts: summary.hosts.length,\n failed: Object.keys(summary.outcome).length + summary.failures.length,\n });\n // AFD-245: kept with its events, so the run can be shown again later.\n await keepRun(runDir, done);\n hooks.report(done);\n return done;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FlowDocument } from '@ansible-flow/core/flow/types';
|
|
2
|
+
import type { Finding } from '@ansible-flow/core/flow/validate';
|
|
3
|
+
/** Warnings for steps whose repository file is missing; none without a repository root. */
|
|
4
|
+
export declare function repoFileFindings(doc: FlowDocument, root?: string): Finding[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/node/repoFiles.ts
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, normalize, relative } from "node:path";
|
|
4
|
+
import { LOCAL_FILES, localFileOf, REPO_ROOT_VAR } from "@ansible-flow/core/flow/localFiles";
|
|
5
|
+
import { modelOf } from "@ansible-flow/core/flow/model";
|
|
6
|
+
import { shortName } from "@ansible-flow/core/import/builtins";
|
|
7
|
+
var ROOTED = new RegExp(`^\\{\\{\\s*${REPO_ROOT_VAR}\\s*\\}\\}/(?<path>[^{}%]+)$`);
|
|
8
|
+
function repoPathOf(module, params) {
|
|
9
|
+
const plain = localFileOf(module, params);
|
|
10
|
+
if (plain) return plain;
|
|
11
|
+
const spec = LOCAL_FILES[shortName(module).replace(/^ansible\.windows\./, "")];
|
|
12
|
+
if (!spec || !params || typeof params !== "object") return void 0;
|
|
13
|
+
let value = params[spec.param];
|
|
14
|
+
if (spec.param === "cmd" && typeof value === "string") value = value.trim().split(/\s+/)[0];
|
|
15
|
+
const path = typeof value === "string" ? ROOTED.exec(value.trim())?.groups?.path : void 0;
|
|
16
|
+
return path ? { param: spec.param, path } : void 0;
|
|
17
|
+
}
|
|
18
|
+
function repoFileFindings(doc, root) {
|
|
19
|
+
if (!root) return [];
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const { node, path } of modelOf(doc).nodes.values()) {
|
|
22
|
+
const impl = node.impl;
|
|
23
|
+
if (impl?.kind !== "module" || !impl.ref) continue;
|
|
24
|
+
const file = repoPathOf(impl.ref, impl.params);
|
|
25
|
+
if (!file) continue;
|
|
26
|
+
const full = normalize(join(root, file.path));
|
|
27
|
+
if (relative(root, full).startsWith("..")) continue;
|
|
28
|
+
if (existsSync(full)) continue;
|
|
29
|
+
out.push({
|
|
30
|
+
code: "binding-file-missing",
|
|
31
|
+
severity: "warning",
|
|
32
|
+
message: `"${node.title}" reads ${file.path}, which is not in the repository: a relative path is from the repository root when the flow runs`,
|
|
33
|
+
path: [...path, "impl", "params", file.param],
|
|
34
|
+
node: node.id
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
repoFileFindings
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=repoFiles.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/node/repoFiles.ts"],
|
|
4
|
+
"sourcesContent": ["// AFD-291: a step that reads a repository file (a template's or copy's source, a script, a vars\n// file) whose file is not there. Generated plays read such a path from the repository root\n// (core/flow/localFiles.ts), so it is looked for there, written as the step has it or already\n// from `{{ afd_repo_root }}` (as import writes it).\nimport { existsSync } from 'node:fs';\nimport { join, normalize, relative } from 'node:path';\nimport { LOCAL_FILES, localFileOf, REPO_ROOT_VAR } from '../core/flow/localFiles';\nimport { modelOf } from '../core/flow/model';\nimport type { FlowDocument } from '../core/flow/types';\nimport type { Finding } from '../core/flow/validate';\nimport { shortName } from '../core/import/builtins';\n\nconst ROOTED = new RegExp(`^\\\\{\\\\{\\\\s*${REPO_ROOT_VAR}\\\\s*\\\\}\\\\}/(?<path>[^{}%]+)$`);\n\n/** The repository path a step's file param names, relative or from afd_repo_root; else none. */\nfunction repoPathOf(module: string, params: unknown): { param: string; path: string } | undefined {\n const plain = localFileOf(module, params);\n if (plain) return plain;\n const spec = LOCAL_FILES[shortName(module).replace(/^ansible\\.windows\\./, '')];\n if (!spec || !params || typeof params !== 'object') return undefined;\n let value = (params as Record<string, unknown>)[spec.param];\n if (spec.param === 'cmd' && typeof value === 'string') value = value.trim().split(/\\s+/)[0];\n const path = typeof value === 'string' ? ROOTED.exec(value.trim())?.groups?.path : undefined;\n return path ? { param: spec.param, path } : undefined;\n}\n\n/** Warnings for steps whose repository file is missing; none without a repository root. */\nexport function repoFileFindings(doc: FlowDocument, root?: string): Finding[] {\n if (!root) return [];\n const out: Finding[] = [];\n for (const { node, path } of modelOf(doc).nodes.values()) {\n const impl = node.impl;\n if (impl?.kind !== 'module' || !impl.ref) continue;\n const file = repoPathOf(impl.ref, impl.params);\n if (!file) continue;\n const full = normalize(join(root, file.path));\n // A path that climbs out of the repository is not checked here.\n if (relative(root, full).startsWith('..')) continue;\n if (existsSync(full)) continue;\n out.push({\n code: 'binding-file-missing',\n severity: 'warning',\n message: `\"${node.title}\" reads ${file.path}, which is not in the repository: a relative path is from the repository root when the flow runs`,\n path: [...path, 'impl', 'params', file.param],\n node: node.id,\n });\n }\n return out;\n}\n"],
|
|
5
|
+
"mappings": ";AAIA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,WAAW,gBAAgB;AAC1C,SAAS,aAAa,aAAa,qBAAqB;AACxD,SAAS,eAAe;AAGxB,SAAS,iBAAiB;AAE1B,IAAM,SAAS,IAAI,OAAO,cAAc,aAAa,8BAA8B;AAGnF,SAAS,WAAW,QAAgB,QAA8D;AAChG,QAAM,QAAQ,YAAY,QAAQ,MAAM;AACxC,MAAI,MAAO,QAAO;AAClB,QAAM,OAAO,YAAY,UAAU,MAAM,EAAE,QAAQ,uBAAuB,EAAE,CAAC;AAC7E,MAAI,CAAC,QAAQ,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAC3D,MAAI,QAAS,OAAmC,KAAK,KAAK;AAC1D,MAAI,KAAK,UAAU,SAAS,OAAO,UAAU,SAAU,SAAQ,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC;AAC1F,QAAM,OAAO,OAAO,UAAU,WAAW,OAAO,KAAK,MAAM,KAAK,CAAC,GAAG,QAAQ,OAAO;AACnF,SAAO,OAAO,EAAE,OAAO,KAAK,OAAO,KAAK,IAAI;AAC9C;AAGO,SAAS,iBAAiB,KAAmB,MAA0B;AAC5E,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,QAAM,MAAiB,CAAC;AACxB,aAAW,EAAE,MAAM,KAAK,KAAK,QAAQ,GAAG,EAAE,MAAM,OAAO,GAAG;AACxD,UAAM,OAAO,KAAK;AAClB,QAAI,MAAM,SAAS,YAAY,CAAC,KAAK,IAAK;AAC1C,UAAM,OAAO,WAAW,KAAK,KAAK,KAAK,MAAM;AAC7C,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,UAAU,KAAK,MAAM,KAAK,IAAI,CAAC;AAE5C,QAAI,SAAS,MAAM,IAAI,EAAE,WAAW,IAAI,EAAG;AAC3C,QAAI,WAAW,IAAI,EAAG;AACtB,QAAI,KAAK;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS,IAAI,KAAK,KAAK,WAAW,KAAK,IAAI;AAAA,MAC3C,MAAM,CAAC,GAAG,MAAM,QAAQ,UAAU,KAAK,KAAK;AAAA,MAC5C,MAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/run/facts.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FlowDocument } from '@ansible-flow/core/flow/types';
|
|
2
|
+
import type { RepositoryConfig } from '@ansible-flow/core/run/command';
|
|
2
3
|
import { type LaunchFacts } from '@ansible-flow/core/run/launch';
|
|
3
4
|
/** The probe of the Ansible environment a run uses, and the name people know it by. */
|
|
4
5
|
export interface RunProbe {
|
|
@@ -33,7 +34,4 @@ export declare function inventoryGroups(probe: RunProbe, inventory: string | und
|
|
|
33
34
|
* AFD-141: the callbacks the repository's ansible.cfg enables, so they keep running next to the
|
|
34
35
|
* run's own; undefined when the environment does not answer (the run's callback alone, then).
|
|
35
36
|
*/
|
|
36
|
-
export declare function repositoryCallbacks(probe: RunProbe): Promise<
|
|
37
|
-
enabled: string[];
|
|
38
|
-
paths: string[];
|
|
39
|
-
} | undefined>;
|
|
37
|
+
export declare function repositoryCallbacks(probe: RunProbe): Promise<RepositoryConfig | undefined>;
|
package/dist/run/facts.js
CHANGED
|
@@ -82,11 +82,12 @@ async function inventoryGroups(probe, inventory) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
async function repositoryCallbacks(probe) {
|
|
85
|
-
return probe.client.call(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
return probe.client.call("run_config", {}, TIMEOUTS.probeInventory).then((c) => ({
|
|
86
|
+
enabled: c.callbacks_enabled ?? [],
|
|
87
|
+
paths: c.callback_plugins ?? [],
|
|
88
|
+
// AFD-291: an older probe says nothing of these: the run leaves the paths alone then.
|
|
89
|
+
...c.plugin_paths ? { plugins: c.plugin_paths, varsEnabled: c.vars_enabled ?? [] } : {}
|
|
90
|
+
})).catch(() => void 0);
|
|
90
91
|
}
|
|
91
92
|
export {
|
|
92
93
|
inventoryGroups,
|
package/dist/run/facts.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/node/run/facts.ts"],
|
|
4
|
-
"sourcesContent": ["// What a run would touch, asked of the inventory through the probe (the real-run confirmation and\n// the launch dialog, AFD-82). AFD-271: without VS Code, for every host that runs flows.\nimport { resolvePattern, targetUses } from '../../core/engine/targets';\nimport { buildModel } from '../../core/flow/model';\nimport type { FlowDocument } from '../../core/flow/types';\nimport { type LaunchFacts, type TargetUse, targetsOf } from '../../core/run/launch';\nimport { groupHosts, type InventoryTree } from '../../core/view/inventoryTree';\nimport { TIMEOUTS } from '../timeouts';\n\n/** The probe of the Ansible environment a run uses, and the name people know it by. */\nexport interface RunProbe {\n client: {\n call<T>(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;\n };\n label: string;\n}\n\ninterface InventoryFacts {\n sources: string[];\n count: number;\n hosts: string[];\n unknown: string[];\n prod_groups: string[];\n prod_hosts: string[];\n vault: boolean;\n /** Per group, how many of the matched hosts it has. */\n groups?: Record<string, number>;\n}\n\nasync function targetFacts(\n doc: FlowDocument,\n inputs: Record<string, unknown>,\n count: (pattern: string) => Promise<InventoryFacts>,\n): Promise<TargetUse[]> {\n return Promise.all(\n targetUses(buildModel(doc)).map(async (u): Promise<TargetUse> => {\n const pattern = resolvePattern(u.target, inputs);\n const use: TargetUse = { pattern, declared: u.target, nodes: u.nodes, prod: [] };\n if (!pattern) return use;\n try {\n const f = await count(pattern);\n use.count = f.count;\n use.prod = [...f.prod_groups, ...f.prod_hosts];\n } catch {\n // Counted at run time only; the confirmation says so.\n }\n return use;\n }),\n );\n}\n\n/**\n * What a run with these inputs would touch: the hosts the targets match in the inventory, the\n * inventory_targets of the flow's steps, what looks like production.\n */\nexport async function launchFacts(\n doc: FlowDocument,\n inputs: Record<string, unknown>,\n probe: RunProbe,\n inventory: string | undefined,\n): Promise<{ ok: true; facts: LaunchFacts } | { ok: false; error: string }> {\n const targets = targetsOf(doc, inputs);\n if (targets.length === 0)\n return { ok: false, error: 'No target hosts: the targets input is empty' };\n let found: InventoryFacts;\n try {\n found = await probe.client.call<InventoryFacts>(\n 'inventory',\n { inventory, targets },\n TIMEOUTS.probeInventory,\n );\n } catch (e) {\n return {\n ok: false,\n error: `The Ansible environment (${probe.label}) did not answer: ${(e as Error).message}`,\n };\n }\n if (found.count === 0)\n return { ok: false, error: `None of the targets (${targets.join(', ')}) is in the inventory` };\n const uses = await targetFacts(doc, inputs, (pattern) =>\n probe.client.call<InventoryFacts>(\n 'inventory',\n { inventory, targets: [pattern] },\n TIMEOUTS.probeInventory,\n ),\n );\n const prod = [\n ...new Set([...found.prod_groups, ...found.prod_hosts, ...uses.flatMap((u) => u.prod)]),\n ];\n return {\n ok: true,\n facts: {\n sources: found.sources,\n count: found.count,\n hosts: found.hosts,\n unknown: found.unknown,\n uses,\n prod,\n vault: found.vault,\n environment: probe.label,\n },\n };\n}\n\n/**\n * The inventory's groups, for the dialog's host list suggestions (AFD-82), with each group's hosts\n * (AFD-95, from the probe's inventory_tree, one inventory parse as before); none when it fails.\n */\nexport async function inventoryGroups(\n probe: RunProbe,\n inventory: string | undefined,\n): Promise<{ groups: string[]; hosts: Record<string, { count: number; hosts: string[] }> }> {\n try {\n const tree = await probe.client.call<InventoryTree>(\n 'inventory_tree',\n { inventory },\n TIMEOUTS.probeInventory,\n );\n const hosts = groupHosts(tree);\n return { groups: Object.keys(hosts).sort((a, b) => a.localeCompare(b)), hosts };\n } catch {\n return { groups: [], hosts: {} };\n }\n}\n\n/**\n * AFD-141: the callbacks the repository's ansible.cfg enables, so they keep running next to the\n * run's own; undefined when the environment does not answer (the run's callback alone, then).\n */\nexport async function repositoryCallbacks(
|
|
5
|
-
"mappings": ";;;;;AAEA,SAAS,gBAAgB,kBAAkB;AAC3C,SAAS,kBAAkB;
|
|
4
|
+
"sourcesContent": ["// What a run would touch, asked of the inventory through the probe (the real-run confirmation and\n// the launch dialog, AFD-82). AFD-271: without VS Code, for every host that runs flows.\nimport { resolvePattern, targetUses } from '../../core/engine/targets';\nimport { buildModel } from '../../core/flow/model';\nimport type { FlowDocument } from '../../core/flow/types';\nimport type { RepositoryConfig } from '../../core/run/command';\nimport { type LaunchFacts, type TargetUse, targetsOf } from '../../core/run/launch';\nimport { groupHosts, type InventoryTree } from '../../core/view/inventoryTree';\nimport { TIMEOUTS } from '../timeouts';\n\n/** The probe of the Ansible environment a run uses, and the name people know it by. */\nexport interface RunProbe {\n client: {\n call<T>(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;\n };\n label: string;\n}\n\ninterface InventoryFacts {\n sources: string[];\n count: number;\n hosts: string[];\n unknown: string[];\n prod_groups: string[];\n prod_hosts: string[];\n vault: boolean;\n /** Per group, how many of the matched hosts it has. */\n groups?: Record<string, number>;\n}\n\nasync function targetFacts(\n doc: FlowDocument,\n inputs: Record<string, unknown>,\n count: (pattern: string) => Promise<InventoryFacts>,\n): Promise<TargetUse[]> {\n return Promise.all(\n targetUses(buildModel(doc)).map(async (u): Promise<TargetUse> => {\n const pattern = resolvePattern(u.target, inputs);\n const use: TargetUse = { pattern, declared: u.target, nodes: u.nodes, prod: [] };\n if (!pattern) return use;\n try {\n const f = await count(pattern);\n use.count = f.count;\n use.prod = [...f.prod_groups, ...f.prod_hosts];\n } catch {\n // Counted at run time only; the confirmation says so.\n }\n return use;\n }),\n );\n}\n\n/**\n * What a run with these inputs would touch: the hosts the targets match in the inventory, the\n * inventory_targets of the flow's steps, what looks like production.\n */\nexport async function launchFacts(\n doc: FlowDocument,\n inputs: Record<string, unknown>,\n probe: RunProbe,\n inventory: string | undefined,\n): Promise<{ ok: true; facts: LaunchFacts } | { ok: false; error: string }> {\n const targets = targetsOf(doc, inputs);\n if (targets.length === 0)\n return { ok: false, error: 'No target hosts: the targets input is empty' };\n let found: InventoryFacts;\n try {\n found = await probe.client.call<InventoryFacts>(\n 'inventory',\n { inventory, targets },\n TIMEOUTS.probeInventory,\n );\n } catch (e) {\n return {\n ok: false,\n error: `The Ansible environment (${probe.label}) did not answer: ${(e as Error).message}`,\n };\n }\n if (found.count === 0)\n return { ok: false, error: `None of the targets (${targets.join(', ')}) is in the inventory` };\n const uses = await targetFacts(doc, inputs, (pattern) =>\n probe.client.call<InventoryFacts>(\n 'inventory',\n { inventory, targets: [pattern] },\n TIMEOUTS.probeInventory,\n ),\n );\n const prod = [\n ...new Set([...found.prod_groups, ...found.prod_hosts, ...uses.flatMap((u) => u.prod)]),\n ];\n return {\n ok: true,\n facts: {\n sources: found.sources,\n count: found.count,\n hosts: found.hosts,\n unknown: found.unknown,\n uses,\n prod,\n vault: found.vault,\n environment: probe.label,\n },\n };\n}\n\n/**\n * The inventory's groups, for the dialog's host list suggestions (AFD-82), with each group's hosts\n * (AFD-95, from the probe's inventory_tree, one inventory parse as before); none when it fails.\n */\nexport async function inventoryGroups(\n probe: RunProbe,\n inventory: string | undefined,\n): Promise<{ groups: string[]; hosts: Record<string, { count: number; hosts: string[] }> }> {\n try {\n const tree = await probe.client.call<InventoryTree>(\n 'inventory_tree',\n { inventory },\n TIMEOUTS.probeInventory,\n );\n const hosts = groupHosts(tree);\n return { groups: Object.keys(hosts).sort((a, b) => a.localeCompare(b)), hosts };\n } catch {\n return { groups: [], hosts: {} };\n }\n}\n\n/**\n * AFD-141: the callbacks the repository's ansible.cfg enables, so they keep running next to the\n * run's own; undefined when the environment does not answer (the run's callback alone, then).\n */\nexport async function repositoryCallbacks(probe: RunProbe): Promise<RepositoryConfig | undefined> {\n return probe.client\n .call<{\n callbacks_enabled: string[];\n callback_plugins: string[];\n plugin_paths?: Record<string, string[]>;\n vars_enabled?: string[];\n }>('run_config', {}, TIMEOUTS.probeInventory)\n .then((c) => ({\n enabled: c.callbacks_enabled ?? [],\n paths: c.callback_plugins ?? [],\n // AFD-291: an older probe says nothing of these: the run leaves the paths alone then.\n ...(c.plugin_paths ? { plugins: c.plugin_paths, varsEnabled: c.vars_enabled ?? [] } : {}),\n }))\n .catch(() => undefined);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/run/runFlow.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GenerateResult } from '@ansible-flow/core/engine/types';
|
|
2
2
|
import type { FlowDocument } from '@ansible-flow/core/flow/types';
|
|
3
3
|
import type { RunView } from '@ansible-flow/core/protocol';
|
|
4
|
-
import { type ProcessSpec } from '@ansible-flow/core/run/command';
|
|
4
|
+
import { type ProcessSpec, type RepositoryConfig } from '@ansible-flow/core/run/command';
|
|
5
5
|
import type { Environment } from '@ansible-flow/core/run/environment';
|
|
6
6
|
import { type RunSummary } from '@ansible-flow/core/run/events';
|
|
7
7
|
import { type Execution } from './exec';
|
|
@@ -35,10 +35,7 @@ export interface RunFlowRequest {
|
|
|
35
35
|
inputs: Record<string, unknown>;
|
|
36
36
|
vaultPassword?: string;
|
|
37
37
|
/** The repository's own callbacks, kept running beside the run's (`repositoryCallbacks`). */
|
|
38
|
-
callbacks?:
|
|
39
|
-
enabled: string[];
|
|
40
|
-
paths: string[];
|
|
41
|
-
};
|
|
38
|
+
callbacks?: RepositoryConfig;
|
|
42
39
|
/** AFD-244: the run is of a part of the flow; its code is in a folder of its own. */
|
|
43
40
|
partial?: RunView['partial'];
|
|
44
41
|
/** AFD-247: paths the SSH copy needs that Git does not list (a partial run's folder). */
|
package/dist/run/runFlow.js
CHANGED
|
@@ -2,16 +2,16 @@ import {
|
|
|
2
2
|
LIVE_MS,
|
|
3
3
|
STALL_MS,
|
|
4
4
|
runFlow
|
|
5
|
-
} from "../chunks/chunk-
|
|
5
|
+
} from "../chunks/chunk-N5T2ZJ72.js";
|
|
6
|
+
import "../chunks/chunk-SSZNBJZQ.js";
|
|
6
7
|
import "../chunks/chunk-RETJPSKL.js";
|
|
7
8
|
import "../chunks/chunk-THNSGAKN.js";
|
|
8
9
|
import "../chunks/chunk-FG53MLUX.js";
|
|
10
|
+
import "../chunks/chunk-I6FIOP7G.js";
|
|
9
11
|
import "../chunks/chunk-4FHPTGI4.js";
|
|
10
|
-
import "../chunks/chunk-SSZNBJZQ.js";
|
|
11
12
|
import "../chunks/chunk-JDPN3LTT.js";
|
|
12
13
|
import "../chunks/chunk-NTZRNEZM.js";
|
|
13
14
|
import "../chunks/chunk-SUV3VHKR.js";
|
|
14
|
-
import "../chunks/chunk-I6FIOP7G.js";
|
|
15
15
|
export {
|
|
16
16
|
LIVE_MS,
|
|
17
17
|
STALL_MS,
|
package/dist/run/segments.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runFlow
|
|
3
|
-
} from "../chunks/chunk-
|
|
3
|
+
} from "../chunks/chunk-N5T2ZJ72.js";
|
|
4
|
+
import "../chunks/chunk-SSZNBJZQ.js";
|
|
4
5
|
import "../chunks/chunk-RETJPSKL.js";
|
|
5
6
|
import "../chunks/chunk-THNSGAKN.js";
|
|
6
7
|
import "../chunks/chunk-FG53MLUX.js";
|
|
8
|
+
import "../chunks/chunk-I6FIOP7G.js";
|
|
7
9
|
import {
|
|
8
10
|
runsDir
|
|
9
11
|
} from "../chunks/chunk-4FHPTGI4.js";
|
|
10
|
-
import "../chunks/chunk-SSZNBJZQ.js";
|
|
11
12
|
import "../chunks/chunk-JDPN3LTT.js";
|
|
12
13
|
import "../chunks/chunk-NTZRNEZM.js";
|
|
13
14
|
import "../chunks/chunk-SUV3VHKR.js";
|
|
14
|
-
import "../chunks/chunk-I6FIOP7G.js";
|
|
15
15
|
import {
|
|
16
16
|
withoutBookkeeping
|
|
17
17
|
} from "../chunks/chunk-A2NMQGQE.js";
|
package/dist/scenario/runner.js
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
} from "../chunks/chunk-RETJPSKL.js";
|
|
7
7
|
import "../chunks/chunk-THNSGAKN.js";
|
|
8
8
|
import "../chunks/chunk-FG53MLUX.js";
|
|
9
|
-
import {
|
|
10
|
-
stopStarted
|
|
11
|
-
} from "../chunks/chunk-JDPN3LTT.js";
|
|
12
9
|
import {
|
|
13
10
|
startProcess
|
|
14
11
|
} from "../chunks/chunk-I6FIOP7G.js";
|
|
12
|
+
import {
|
|
13
|
+
stopStarted
|
|
14
|
+
} from "../chunks/chunk-JDPN3LTT.js";
|
|
15
15
|
|
|
16
16
|
// src/node/scenario/runner.ts
|
|
17
17
|
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ansible-flow/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.0",
|
|
4
4
|
"description": "Ansible Flow Designer for Node hosts: layout, the probe client, runs, the AWX client, the module catalog and SSH.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/ChistokhinSV/ansible-flow-designer/blob/main/docs/packages.md",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"node": ">=22.13"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ansible-flow/core": "0.
|
|
29
|
+
"@ansible-flow/core": "0.88.0",
|
|
30
30
|
"elkjs": "0.12.0",
|
|
31
31
|
"jsonc-parser": "3.3.1",
|
|
32
32
|
"yaml": "2.9.1"
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../../src/node/run/runFlow.ts"],
|
|
4
|
-
"sourcesContent": ["// One run of a flow's generated code (AFD-271): what the extension's local run and a server's run\n// have in common. The caller has generated the code, resolved the environment, collected the\n// inputs and asked whatever it asks; from there this writes the run's folder, starts\n// ansible-playbook, follows its events into a view of the run, redacts and keeps the result.\n//\n// What differs between hosts is given as hooks: how the process is started (a VS Code terminal\n// task, or a plain child process), who is told that the run went quiet, what is cleaned up.\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { GenerateResult } from '../../core/engine/types';\nimport { nodeDesigns } from '../../core/flow/semantic';\nimport type { FlowDocument } from '../../core/flow/types';\nimport type { RunView } from '../../core/protocol';\nimport { type ProcessSpec, runProcess, splitInputs } from '../../core/run/command';\nimport type { Environment } from '../../core/run/environment';\nimport { createSummarizer, type RunSummary, summarizeRun } from '../../core/run/events';\nimport { redactLine, redactText, secretValues } from '../../core/run/redact';\nimport { followRunFile, putRunFile, removeRunDir, syncWorkspace } from '../ssh/remote';\nimport { appendAudit } from './audit';\nimport { type Execution, startProcess } from './exec';\nimport { keepRun, runsDir } from './pastRuns';\nimport { stopStarted } from './stop';\nimport { dropVars, KEEP_RUNS, pruneRuns } from './storage';\nimport { tailFile } from './tail';\n\n/** How often a live run updates its view, at most (AFD-81). */\nexport const LIVE_MS = 500;\n/** No new events for this long: the run may wait for input. */\nexport const STALL_MS = 20_000;\n\nexport interface RunFlowRequest {\n /** The run's id (`stamp()`), which names its folder. */\n id: string;\n startedAt: string;\n mode: 'check' | 'real';\n doc: FlowDocument;\n /** The code that runs: its routes map the events back to nodes. */\n code: GenerateResult;\n env: Environment;\n /** The environment's name for people (\"afd-ee:2.19 (docker)\"). */\n environment: string;\n /** Repository root on this machine. */\n root: string;\n /** Folder with afd_probe/ and callback_plugins/afd_events.py. */\n probeDir: string;\n /** The playbook to run, relative to `root`, with forward slashes. */\n playbook: string;\n inventory?: string;\n /** Where runs are kept: `<storage>/runs/<flow id>/<run id>/`, and the audit log. */\n storage: string;\n /** How many runs of the flow to keep (default `KEEP_RUNS`). */\n keepRuns?: number;\n /** Flow input values. Secret ones never reach a file. */\n inputs: Record<string, unknown>;\n vaultPassword?: string;\n /** The repository's own callbacks, kept running beside the run's (`repositoryCallbacks`). */\n callbacks?: { enabled: string[]; paths: string[] };\n /** AFD-244: the run is of a part of the flow; its code is in a folder of its own. */\n partial?: RunView['partial'];\n /** AFD-247: paths the SSH copy needs that Git does not list (a partial run's folder). */\n syncAlso?: string[];\n /** What the audit log says about the launch, besides the run's id. Never input values. */\n audit?: Record<string, unknown>;\n /** Stops the run when it aborts. */\n signal?: AbortSignal;\n}\n\nexport interface RunFlowHooks {\n /** The run's view: when it starts, as it goes (at most every `LIVE_MS`), and when it ends. */\n report(run: RunView): void;\n /**\n * Starts the process. Default: a child process whose output goes to `log`. `stopping` says the\n * run is being stopped, for a host whose process may end without an exit code then.\n */\n execute?(\n spec: ProcessSpec,\n env: Record<string, string>,\n stopping: () => boolean,\n ): Execution | Promise<Execution>;\n /** The default process's output, line by line. */\n log?(line: string): void;\n /** The run started; `stop` ends it. Returns what to call when the run has ended. */\n started?(stop: () => Promise<void>): (() => void) | undefined;\n /** Whether to watch for a run gone quiet (no event for `STALL_MS`). */\n watchQuiet?: boolean;\n /** The run went quiet. */\n quiet?(): void;\n /** The process ended; before its events are read. */\n ended?(): Promise<void>;\n /** How the run ended, before it is kept and reported. */\n finished?(end: {\n stopped: boolean;\n rc: number | undefined;\n events: boolean;\n summary: RunSummary;\n }): void;\n}\n\n/** Runs the flow's code and resolves with the finished run (also reported). Never rejects. */\nexport async function runFlow(request: RunFlowRequest, hooks: RunFlowHooks): Promise<RunView> {\n const { id, mode, doc, code, startedAt, vaultPassword, partial } = request;\n const fail = (error: string): RunView => {\n const run: RunView = { id, mode, state: 'error', startedAt, error };\n hooks.report(run);\n return run;\n };\n\n const runDir = join(runsDir(request.storage, doc.flow.id), id);\n await mkdir(runDir, { recursive: true });\n // Secret inputs travel in an environment variable only; vars.json goes when the run ends.\n const { plain, secret } = splitInputs(request.inputs);\n const secrets = Object.keys(secret).length > 0;\n await writeFile(join(runDir, 'vars.json'), JSON.stringify(plain, null, 2), 'utf8');\n await pruneRuns(request.storage, doc.flow.id, request.keepRuns ?? KEEP_RUNS).catch(\n () => undefined,\n );\n // AFD-247: an SSH host runs a copy of the repository: brought up to date now, with the code just\n // generated, and the inputs.\n const remote = request.env.kind === 'ssh' ? request.env : undefined;\n if (remote) {\n const failed =\n (await syncWorkspace(\n remote,\n { root: request.root, probe: request.probeDir },\n request.syncAlso ?? [],\n )) ?? (await putRunFile(remote, runDir, 'vars.json', JSON.stringify(plain, null, 2)));\n if (failed) return fail(failed);\n }\n const spec = runProcess(\n request.env,\n { root: request.root, probe: request.probeDir },\n {\n playbook: request.playbook,\n inventory: request.inventory,\n check: mode === 'check',\n runDir,\n vault: vaultPassword !== undefined,\n secrets,\n hasConfig: existsSync(join(request.root, 'ansible.cfg')),\n callbacks: request.callbacks,\n },\n `afd-run-${id.toLowerCase()}`,\n );\n\n if (request.audit)\n await appendAudit(request.storage, { event: 'launch', run: id, ...request.audit });\n const running: RunView = {\n id,\n mode,\n state: 'running',\n startedAt,\n environment: request.environment,\n source: 'local',\n flowVersion: doc.flow.version,\n design: nodeDesigns(doc),\n ...(partial ? { partial } : {}),\n };\n hooks.report(running);\n\n const env: Record<string, string> = { ...spec.env };\n // AFD-75: names WSL should take from Windows join the ones already set, if any.\n if (spec.env.WSLENV && process.env.WSLENV)\n env.WSLENV = `${process.env.WSLENV}:${spec.env.WSLENV}`;\n if (vaultPassword !== undefined) env.AFD_VAULT_PASSWORD = vaultPassword;\n if (secrets) env.AFD_SECRET_VARS = JSON.stringify(secret);\n\n const t0 = Date.now();\n // The events as they come: a partial summary at most every LIVE_MS, secrets out.\n // AFD-138: the vault password too, if a task prints it.\n const hidden = secretValues(vaultPassword ? { ...secret, '': vaultPassword } : secret);\n const live = createSummarizer(code.routes);\n const eventsFile = join(runDir, 'events.jsonl');\n // AFD-247: the events are written on the host; they are followed into the file here.\n const following = remote ? followRunFile(remote, runDir, 'events.jsonl', eventsFile) : undefined;\n let lastEvent = Date.now();\n let changed = false;\n let quiet = false;\n const tail = tailFile(eventsFile, (lines) => {\n for (const line of lines) live.push(redactLine(line, hidden));\n lastEvent = Date.now();\n changed = true;\n quiet = false;\n });\n const ticker = setInterval(() => {\n if (hooks.watchQuiet && !quiet && Date.now() - lastEvent > STALL_MS) {\n quiet = true;\n changed = true;\n hooks.quiet?.();\n }\n if (!changed) return;\n changed = false;\n hooks.report({\n ...running,\n summary: live.summary({ partial: true }),\n ...(quiet ? { quiet } : {}),\n });\n }, LIVE_MS);\n\n // Stopping ends the process; the run then ends as stopped, not as an error.\n let stopped = false;\n const execute = hooks.execute ?? ((s, e) => startProcess(s, e, hooks.log ?? (() => {})));\n let execution: Execution;\n try {\n execution = await execute(spec, env, () => stopped);\n } catch {\n execution = { exit: Promise.resolve(undefined), terminate: () => undefined };\n }\n const stop = async () => {\n stopped = true;\n // AFD-131: the container first, so the playbook stops changing hosts and writing events\n // before the redaction pass below reads them; the process then ends by itself.\n await stopStarted(spec);\n execution.terminate();\n };\n const onAbort = () => void stop();\n if (request.signal?.aborted) onAbort();\n else request.signal?.addEventListener('abort', onAbort, { once: true });\n const release = hooks.started?.(stop);\n const rc = await execution.exit;\n release?.();\n request.signal?.removeEventListener('abort', onAbort);\n clearInterval(ticker);\n await following?.stop();\n await tail.stop();\n if (remote) await removeRunDir(remote, runDir);\n\n await dropVars(runDir).catch(() => undefined);\n await hooks.ended?.();\n let text = '';\n try {\n text = await readFile(eventsFile, 'utf8');\n // The kept events hold no secret input value either (the output now comes with them).\n if (hidden.length > 0) {\n text = redactText(text, hidden);\n await writeFile(eventsFile, text, 'utf8');\n }\n } catch {}\n const summary = summarizeRun(text, code.routes);\n hooks.finished?.({ stopped, rc, events: !!text.trim(), summary });\n const done: RunView = {\n ...running,\n state: rc === undefined || stopped ? 'error' : 'done',\n finishedAt: new Date().toISOString(),\n rc,\n summary,\n error: stopped\n ? 'Stopped by the user'\n : rc === undefined\n ? 'The run could not be started'\n : text\n ? undefined\n : 'The run produced no events',\n ...(stopped ? { errorCode: 'cancelled' as const } : {}),\n };\n if (request.audit)\n await appendAudit(request.storage, {\n event: 'finish',\n run: id,\n rc,\n ms: Date.now() - t0,\n hosts: summary.hosts.length,\n failed: Object.keys(summary.outcome).length + summary.failures.length,\n });\n // AFD-245: kept with its events, so the run can be shown again later.\n await keepRun(runDir, done);\n hooks.report(done);\n return done;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|