hammerkit 1.27.0-next.2 → 1.27.0-next.20
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/build.schema.json +9 -0
- package/dist/cache/backends/registry-cache-backend.js +2 -2
- package/dist/cache/explain.js +17 -0
- package/dist/cli.js +1 -0
- package/dist/docker/item-network.js +13 -5
- package/dist/executer/deadline.js +17 -3
- package/dist/executer/docker-service.js +30 -8
- package/dist/executer/event-cache.js +20 -2
- package/dist/executer/execute-work-task.js +5 -3
- package/dist/executer/execution-steps.js +5 -2
- package/dist/executer/scheduler/enqueue-next.js +7 -0
- package/dist/executer/scheduler/state-key.js +42 -5
- package/dist/harness/connect.js +20 -6
- package/dist/harness/mcp-command.js +22 -6
- package/dist/harness/mcp.js +63 -114
- package/dist/harness/opencode.js +158 -50
- package/dist/harness/pi.js +29 -0
- package/dist/harness/sandbox-client.js +207 -65
- package/dist/harness/service-tools.js +141 -0
- package/dist/harness/task-tools.js +221 -0
- package/dist/kubernetes/apply.js +27 -16
- package/dist/kubernetes/claim-layout.js +71 -0
- package/dist/kubernetes/ensure-http-route.js +1 -1
- package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +5 -9
- package/dist/kubernetes/ensure-kubernetes-persistent-volume-claim-exists.js +1 -1
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +6 -9
- package/dist/kubernetes/ensure-persistent-data.js +30 -20
- package/dist/kubernetes/get-service-ip.js +2 -2
- package/dist/kubernetes/job-secrets.js +33 -19
- package/dist/kubernetes/kept-claims.js +22 -10
- package/dist/kubernetes/network-policy.js +85 -27
- package/dist/kubernetes/remove-persistent-data.js +3 -1
- package/dist/kubernetes/resources.js +25 -3
- package/dist/kubernetes/restore-kubernetes-data.js +9 -3
- package/dist/kubernetes/sandbox-runner.js +626 -0
- package/dist/kubernetes/secrets.js +7 -11
- package/dist/kubernetes/volumes.js +6 -3
- package/dist/network/egress-proxy.js +45 -6
- package/dist/optimizer/calculate-checksum.js +3 -2
- package/dist/optimizer/check-key.js +94 -0
- package/dist/optimizer/check-keys.js +64 -0
- package/dist/optimizer/get-work-cache-stats.js +17 -3
- package/dist/optimizer/work-task-cache-description.js +51 -1
- package/dist/planner/utils/append-work-dependencies.js +10 -5
- package/dist/planner/utils/append-work-service.js +1 -0
- package/dist/planner/utils/append-work-task.js +15 -2
- package/dist/planner/utils/get-work-runtime.js +6 -1
- package/dist/planner/utils/normalize-path.js +19 -1
- package/dist/planner/utils/parse-work-mount.js +7 -1
- package/dist/planner/utils/parse-work-port.js +4 -1
- package/dist/planner/utils/task-arguments.js +3 -0
- package/dist/planner/validate.js +22 -0
- package/dist/planner/work-network.js +3 -1
- package/dist/planner/work-runtime-aggregate.js +32 -0
- package/dist/planner/work-runtime-docker.js +2 -1
- package/dist/planner/work-runtime-kubernetes.js +97 -48
- package/dist/planner/work-runtime-local.js +2 -1
- package/dist/planner/work-task.js +6 -1
- package/dist/program.js +14 -4
- package/dist/remote/annotations.js +13 -1
- package/dist/remote/device-login.js +7 -2
- package/dist/remote/host-config.js +64 -3
- package/dist/remote/remote-commands.js +41 -9
- package/dist/remote/remote-work-commands.js +246 -1
- package/dist/remote/server-client.js +15 -2
- package/dist/remote/token-source.js +64 -26
- package/dist/run-program.js +9 -0
- package/dist/schema/build-file-base-task-schema.js +5 -1
- package/dist/schema/build-file-container-service-schema.js +2 -0
- package/dist/schema/reference-parser.js +9 -1
- package/dist/schema/work-scope-parser.js +6 -0
- package/dist/secrets/github-app-token-command.js +3 -1
- package/dist/server/accounts-api.js +3 -1
- package/dist/server/approvals-api.js +1 -1
- package/dist/server/checks-api.js +83 -0
- package/dist/server/checks.js +116 -0
- package/dist/server/definitions-api.js +4 -2
- package/dist/server/endpoints.js +612 -0
- package/dist/server/executor.js +56 -5
- package/dist/server/kubernetes-backend.js +303 -64
- package/dist/server/kubernetes-sandbox-storage.js +48 -3
- package/dist/server/oauth.js +12 -1
- package/dist/server/plan.js +126 -62
- package/dist/server/policy.js +32 -2
- package/dist/server/repo-access.js +5 -0
- package/dist/server/run-plan.js +50 -19
- package/dist/server/run-request.js +64 -3
- package/dist/server/run-worker.js +146 -10
- package/dist/server/runs.js +347 -27
- package/dist/server/sandbox-pod.js +28 -0
- package/dist/server/sandbox-storage.js +23 -0
- package/dist/server/sandboxes-api.js +10 -4
- package/dist/server/sandboxes.js +8 -0
- package/dist/server/server-command.js +39 -20
- package/dist/server/server-config.js +213 -14
- package/dist/server/server.js +268 -15
- package/dist/server/service-runs.js +82 -0
- package/dist/utils/abort-on-signals.js +46 -6
- package/dist/utils/tar.js +36 -0
- package/package.json +1 -1
package/dist/run-program.js
CHANGED
|
@@ -7,6 +7,8 @@ exports.runProgram = void 0;
|
|
|
7
7
|
const program_1 = require("./program");
|
|
8
8
|
const parse_error_1 = require("./schema/parse-error");
|
|
9
9
|
const colors_1 = __importDefault(require("colors"));
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const log_1 = require("./log");
|
|
10
12
|
function runProgram(env, argv, exitOverride) {
|
|
11
13
|
return (0, program_1.getProgram)(env, argv)
|
|
12
14
|
.then(({ program, args }) => {
|
|
@@ -18,8 +20,15 @@ function runProgram(env, argv, exitOverride) {
|
|
|
18
20
|
for (const error of err.zod.errors) {
|
|
19
21
|
env.stderr.write(` ${colors_1.default.underline(colors_1.default.blue(`error`))} at ${colors_1.default.gray(error.path.join('.'))} ${error.message} ${colors_1.default.gray(error.code)}\n`);
|
|
20
22
|
}
|
|
23
|
+
// printed, but still a failure: CI must not pass on a build file that doesn't parse
|
|
24
|
+
throw err;
|
|
21
25
|
}
|
|
22
26
|
else {
|
|
27
|
+
// what commander reports (program.error, a wrong option) is printed already; anything
|
|
28
|
+
// else, such as a build file that is no YAML, would otherwise end the process silently
|
|
29
|
+
if (!(err instanceof commander_1.CommanderError)) {
|
|
30
|
+
env.stderr.write(`${colors_1.default.red('error')}: ${(0, log_1.getErrorMessage)(err)}\n`);
|
|
31
|
+
}
|
|
23
32
|
throw err;
|
|
24
33
|
}
|
|
25
34
|
});
|
|
@@ -40,8 +40,12 @@ exports.buildFileBaseTaskSchema = (0, zod_1.object)({
|
|
|
40
40
|
account: (0, zod_1.string)().optional(),
|
|
41
41
|
// CPU and memory limits of the container; a local task is not limited
|
|
42
42
|
resources: resources_schema_1.resourcesSchema.optional(),
|
|
43
|
-
// what the container reaches besides its needs; a local task can
|
|
43
|
+
// what the container reaches besides its needs; a local task can be limited only where
|
|
44
|
+
// it runs in a container, on a server's cluster
|
|
44
45
|
network: network_schema_1.networkSchema.optional(),
|
|
46
|
+
// whether its last command takes the arguments after `--`: false refuses them, true
|
|
47
|
+
// says it is meant to, which a server with `runs.args: declared` requires
|
|
48
|
+
args: (0, zod_1.boolean)().optional(),
|
|
45
49
|
// Name of the server (see `hammerkit remote`) the task has to run on; defaults
|
|
46
50
|
// to the build file's `runner`. Never a local fallback when it is unknown.
|
|
47
51
|
runner: runner_schema_1.runnerSchema.optional(),
|
|
@@ -45,6 +45,8 @@ exports.buildFileContainerServiceSchema = (0, zod_1.object)({
|
|
|
45
45
|
volumeSize: size_schema_1.sizeSchema.describe('size of its claim on Kubernetes, e.g. 10Gi').optional(),
|
|
46
46
|
// what the service reaches besides its needs; tasks needing it always reach it
|
|
47
47
|
network: network_schema_1.networkSchema.optional(),
|
|
48
|
+
// whether its `cmd` takes the arguments after `--` of `up`: false refuses them
|
|
49
|
+
args: (0, zod_1.boolean)().optional(),
|
|
48
50
|
})
|
|
49
51
|
.strict()
|
|
50
52
|
.describe('container service');
|
|
@@ -129,6 +129,7 @@ function applySchemaExtension(reference, task) {
|
|
|
129
129
|
task.schema.timeout = task.schema.timeout ?? extend.schema.timeout;
|
|
130
130
|
task.schema.runner = task.schema.runner ?? extend.schema.runner;
|
|
131
131
|
task.schema.network = task.schema.network ?? extend.schema.network;
|
|
132
|
+
task.schema.args = task.schema.args ?? extend.schema.args;
|
|
132
133
|
if (!reset.has('cmds'))
|
|
133
134
|
task.schema.cmds = extendArray(task.schema.cmds, extend.schema.cmds);
|
|
134
135
|
if (!reset.has('src'))
|
|
@@ -209,7 +210,9 @@ function resolveNeeds(reference, taskOrService, scope, schema) {
|
|
|
209
210
|
for (const needName of schema.needs || []) {
|
|
210
211
|
if (typeof needName === 'string') {
|
|
211
212
|
taskOrService.needs.push({
|
|
212
|
-
|
|
213
|
+
// the host name it is reached by: a service of an include, `services:postgres`, as
|
|
214
|
+
// `services-postgres`, since Docker and DNS take no colon
|
|
215
|
+
relativeName: needName.replace(/:/g, '-'),
|
|
213
216
|
service: findService(reference, scope, needName),
|
|
214
217
|
});
|
|
215
218
|
}
|
|
@@ -220,6 +223,11 @@ function resolveNeeds(reference, taskOrService, scope, schema) {
|
|
|
220
223
|
});
|
|
221
224
|
}
|
|
222
225
|
}
|
|
226
|
+
const names = taskOrService.needs.map((need) => need.relativeName);
|
|
227
|
+
const twice = names.find((name, index) => names.indexOf(name) !== index);
|
|
228
|
+
if (twice) {
|
|
229
|
+
throw new Error(`${taskOrService.relativeName} needs two services as ${twice}, give one another name with name:`);
|
|
230
|
+
}
|
|
223
231
|
}
|
|
224
232
|
function findTask(ctx, scope, name, type) {
|
|
225
233
|
const ref = (0, split_name_1.splitName)(name);
|
|
@@ -117,12 +117,18 @@ function matchesTaskWorkScope(task, scope) {
|
|
|
117
117
|
if ((0, work_scope_1.isContextTaskFilter)(scope)) {
|
|
118
118
|
return task.name === scope.taskName;
|
|
119
119
|
}
|
|
120
|
+
if (scope.serviceNames) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
120
123
|
return (0, label_values_1.appliesToLabels)(task.data.labels, scope);
|
|
121
124
|
}
|
|
122
125
|
function matchesServiceWorkScope(service, scope) {
|
|
123
126
|
if ((0, work_scope_1.isContextTaskFilter)(scope)) {
|
|
124
127
|
return false;
|
|
125
128
|
}
|
|
129
|
+
if (scope.serviceNames) {
|
|
130
|
+
return scope.serviceNames.includes(service.name);
|
|
131
|
+
}
|
|
126
132
|
return (0, label_values_1.appliesToLabels)(service.labels, scope);
|
|
127
133
|
}
|
|
128
134
|
//# sourceMappingURL=work-scope-parser.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerGithubAppTokenCommand = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
4
5
|
const log_1 = require("../log");
|
|
5
6
|
const github_app_token_1 = require("./github-app-token");
|
|
6
7
|
// `hammerkit github-app-token <owner/repo>`: a secret provider command for the token a
|
|
@@ -18,7 +19,8 @@ function registerGithubAppTokenCommand(program, environment) {
|
|
|
18
19
|
}
|
|
19
20
|
catch (e) {
|
|
20
21
|
environment.stderr.write(`github-app-token: ${(0, log_1.getErrorMessage)(e)}\n`);
|
|
21
|
-
|
|
22
|
+
// said already: the CLI does not print it again
|
|
23
|
+
throw new commander_1.CommanderError(1, 'hammerkit.githubAppToken', (0, log_1.getErrorMessage)(e));
|
|
22
24
|
}
|
|
23
25
|
});
|
|
24
26
|
}
|
|
@@ -6,7 +6,7 @@ const policy_1 = require("./policy");
|
|
|
6
6
|
/**
|
|
7
7
|
* Who the caller is and what they may do:
|
|
8
8
|
*
|
|
9
|
-
* GET /v1/me identity, accounts with their level, repositories, the builds level
|
|
9
|
+
* GET /v1/me identity, accounts with their level, repositories, the builds level the sandbox and endpoint grants
|
|
10
10
|
* GET /v1/accounts the accounts the caller has a level on, with what the operator says about them
|
|
11
11
|
*/
|
|
12
12
|
async function handleAccounts(config, log, principal, req, res, url) {
|
|
@@ -31,7 +31,9 @@ async function handleAccounts(config, log, principal, req, res, url) {
|
|
|
31
31
|
accounts,
|
|
32
32
|
repos: grant.repos,
|
|
33
33
|
builds: grant.builds,
|
|
34
|
+
checks: grant.checks || grant.builds !== null ? 'read' : null,
|
|
34
35
|
sandboxes: grant.sandboxes,
|
|
36
|
+
endpoints: grant.endpoints,
|
|
35
37
|
});
|
|
36
38
|
return true;
|
|
37
39
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleChecks = exports.MAX_LOOKUP_KEYS = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const http_1 = require("./http");
|
|
6
|
+
const policy_1 = require("./policy");
|
|
7
|
+
const checks_1 = require("./checks");
|
|
8
|
+
const repo_1 = require("./repo");
|
|
9
|
+
// the tasks one lookup may ask about
|
|
10
|
+
exports.MAX_LOOKUP_KEYS = 100;
|
|
11
|
+
const lookupSchema = zod_1.z
|
|
12
|
+
.object({
|
|
13
|
+
repo: zod_1.z.string().transform((value, ctx) => {
|
|
14
|
+
const repo = (0, repo_1.normalizeRepo)(value);
|
|
15
|
+
if (!repo) {
|
|
16
|
+
ctx.addIssue({ code: zod_1.z.ZodIssueCode.custom, message: 'expected a repository like github.com/corp/payments' });
|
|
17
|
+
return zod_1.z.NEVER;
|
|
18
|
+
}
|
|
19
|
+
return repo;
|
|
20
|
+
}),
|
|
21
|
+
keys: zod_1.z
|
|
22
|
+
.array(zod_1.z
|
|
23
|
+
.object({
|
|
24
|
+
task: zod_1.z.string().min(1).max(200),
|
|
25
|
+
key: zod_1.z.string().regex(checks_1.CHECK_KEY_PATTERN, 'expected a check key, 64 hex digits'),
|
|
26
|
+
})
|
|
27
|
+
.strict())
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(exports.MAX_LOOKUP_KEYS),
|
|
30
|
+
})
|
|
31
|
+
.strict();
|
|
32
|
+
/**
|
|
33
|
+
* What runs passed, for CI that verifies instead of running (`hammerkit remote verify`):
|
|
34
|
+
*
|
|
35
|
+
* POST /v1/checks/lookup { repo, keys: [{ task, key }] } the newest passed run per key
|
|
36
|
+
*
|
|
37
|
+
* The caller computed the keys from its own checkout (check-key.ts); a key nothing passed
|
|
38
|
+
* for, or whose version of the build file was yanked since, is answered with none.
|
|
39
|
+
*/
|
|
40
|
+
async function handleChecks(config, checks, definitions, log, principal, req, res, url) {
|
|
41
|
+
if (url.pathname !== '/v1/checks/lookup') {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (req.method !== 'POST') {
|
|
45
|
+
(0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow: 'POST' });
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const parsed = lookupSchema.safeParse(await (0, http_1.readJson)(req));
|
|
49
|
+
if (!parsed.success) {
|
|
50
|
+
const issue = parsed.error.issues[0];
|
|
51
|
+
(0, http_1.send)(res, 400, { error: `${issue.path.join('.') || 'request'}: ${issue.message}` });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const { repo, keys } = parsed.data;
|
|
55
|
+
const grant = (0, policy_1.getGrant)(config.entitlements, principal);
|
|
56
|
+
if (!(0, policy_1.mayReadChecks)(grant, repo)) {
|
|
57
|
+
log(`refuse checks iss=${principal.iss} sub=${principal.sub} repo=${repo}`);
|
|
58
|
+
(0, http_1.send)(res, 403, { error: `you may not read the checks of ${repo}` });
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const answers = await Promise.all(keys.map(async ({ task, key }) => {
|
|
62
|
+
const record = await checks.find(repo, key);
|
|
63
|
+
const yanked = record ? definitions.find(record.definition)?.yanked : undefined;
|
|
64
|
+
if (!record || yanked) {
|
|
65
|
+
return {
|
|
66
|
+
task,
|
|
67
|
+
key,
|
|
68
|
+
passed: null,
|
|
69
|
+
...(yanked ? { reason: `it passed with ${record?.definition}, which was yanked since` } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const { repo: _repo, key: _key, accounts: _accounts, ...view } = record;
|
|
73
|
+
void _repo;
|
|
74
|
+
void _key;
|
|
75
|
+
void _accounts;
|
|
76
|
+
return { task, key, passed: view };
|
|
77
|
+
}));
|
|
78
|
+
log(`checks iss=${principal.iss} sub=${principal.sub} repo=${repo} passed=${answers.filter((a) => a.passed).length}/${answers.length}`);
|
|
79
|
+
(0, http_1.send)(res, 200, { checks: answers });
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
exports.handleChecks = handleChecks;
|
|
83
|
+
//# sourceMappingURL=checks-api.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CheckStore = exports.getCheckRecords = exports.isCheckRun = exports.CHECK_KEY_PATTERN = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const promises_1 = require("fs/promises");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
exports.CHECK_KEY_PATTERN = /^[0-9a-f]{64}$/;
|
|
8
|
+
// The runs whose results are checks: those of a commit the server checked out, and those
|
|
9
|
+
// on a copy of the files of a sandbox, which hold nothing a commit of them would not. A run
|
|
10
|
+
// with arguments ran something other than the task, and is no check of it.
|
|
11
|
+
function isCheckRun(run) {
|
|
12
|
+
// a run of services keeps them up, it passes nothing
|
|
13
|
+
if ((run.options.args ?? []).length > 0 || run.up) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return run.source.type === 'ref' || (run.source.type === 'sandbox' && run.source.check === true);
|
|
17
|
+
}
|
|
18
|
+
exports.isCheckRun = isCheckRun;
|
|
19
|
+
// What a run that ended passed, with the keys its executor reported before the tasks ran.
|
|
20
|
+
function getCheckRecords(run, keys) {
|
|
21
|
+
if (!isCheckRun(run) || run.source.type === 'upload') {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const source = run.source.type === 'ref'
|
|
25
|
+
? { type: 'ref', ref: run.source.ref, commit: run.source.commit }
|
|
26
|
+
: { type: 'sandbox', sandbox: run.source.id, commit: run.source.commit, tree: run.source.tree };
|
|
27
|
+
const records = [];
|
|
28
|
+
for (const [task, result] of Object.entries(run.results ?? {})) {
|
|
29
|
+
const key = keys[task];
|
|
30
|
+
if (!key || !exports.CHECK_KEY_PATTERN.test(key) || (result.status !== 'succeeded' && result.status !== 'cached')) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
records.push({
|
|
34
|
+
repo: run.source.repo,
|
|
35
|
+
key,
|
|
36
|
+
task,
|
|
37
|
+
status: result.status,
|
|
38
|
+
run: run.id,
|
|
39
|
+
by: { iss: run.owner.iss, sub: run.owner.sub },
|
|
40
|
+
source,
|
|
41
|
+
definition: run.definition,
|
|
42
|
+
accounts: [...run.accounts],
|
|
43
|
+
finishedAt: result.finishedAt ?? run.finishedAt ?? new Date().toISOString(),
|
|
44
|
+
...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return records;
|
|
48
|
+
}
|
|
49
|
+
exports.getCheckRecords = getCheckRecords;
|
|
50
|
+
/**
|
|
51
|
+
* One file per repository and key, the newest run's: `<directory>/<repo hash>/<key>.json`,
|
|
52
|
+
* written to a temporary file and renamed. A record older than `keep` is not found and is
|
|
53
|
+
* removed by `sweep`.
|
|
54
|
+
*/
|
|
55
|
+
class CheckStore {
|
|
56
|
+
constructor(directory, options) {
|
|
57
|
+
this.directory = directory;
|
|
58
|
+
this.options = options;
|
|
59
|
+
this.now = options.now ?? Date.now;
|
|
60
|
+
}
|
|
61
|
+
async save(records) {
|
|
62
|
+
for (const record of records) {
|
|
63
|
+
const folder = (0, path_1.join)(this.directory, repoFolder(record.repo));
|
|
64
|
+
await (0, promises_1.mkdir)(folder, { recursive: true, mode: 0o700 });
|
|
65
|
+
const file = (0, path_1.join)(folder, `${record.key}.json`);
|
|
66
|
+
const temporary = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
67
|
+
await (0, promises_1.writeFile)(temporary, JSON.stringify(record), { mode: 0o600 });
|
|
68
|
+
await (0, promises_1.rename)(temporary, file);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async find(repo, key) {
|
|
72
|
+
if (!exports.CHECK_KEY_PATTERN.test(key)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
let record;
|
|
76
|
+
try {
|
|
77
|
+
record = JSON.parse(await (0, promises_1.readFile)((0, path_1.join)(this.directory, repoFolder(repo), `${key}.json`), 'utf8'));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
if (record.repo !== repo || record.key !== key || this.expired(record)) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
return record;
|
|
86
|
+
}
|
|
87
|
+
// Removes the records that are older than `keep`.
|
|
88
|
+
async sweep() {
|
|
89
|
+
let folders;
|
|
90
|
+
try {
|
|
91
|
+
folders = await (0, promises_1.readdir)(this.directory);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
for (const folder of folders) {
|
|
97
|
+
const path = (0, path_1.join)(this.directory, folder);
|
|
98
|
+
for (const name of await (0, promises_1.readdir)(path).catch(() => [])) {
|
|
99
|
+
const file = (0, path_1.join)(path, name);
|
|
100
|
+
const stats = await (0, promises_1.stat)(file).catch(() => undefined);
|
|
101
|
+
if (stats && this.now() - stats.mtimeMs > this.options.keep) {
|
|
102
|
+
await (0, promises_1.rm)(file, { force: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
expired(record) {
|
|
108
|
+
const at = Date.parse(record.finishedAt);
|
|
109
|
+
return !Number.isFinite(at) || this.now() - at > this.options.keep;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.CheckStore = CheckStore;
|
|
113
|
+
function repoFolder(repo) {
|
|
114
|
+
return (0, crypto_1.createHash)('sha256').update(repo).digest('hex').slice(0, 32);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -5,6 +5,7 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const bundle_1 = require("../remote/bundle");
|
|
6
6
|
const definitions_1 = require("./definitions");
|
|
7
7
|
const http_1 = require("./http");
|
|
8
|
+
const run_plan_1 = require("./run-plan");
|
|
8
9
|
const oauth_1 = require("./oauth");
|
|
9
10
|
const policy_1 = require("./policy");
|
|
10
11
|
const proposals_1 = require("./proposals");
|
|
@@ -19,7 +20,8 @@ const MAX_PUBLISH_BYTES = bundle_1.MAX_BUNDLE_BYTES * 2;
|
|
|
19
20
|
* `annotations`, which are not part of the hash
|
|
20
21
|
* GET /v1/definitions the versions, newest first; `?<key>=<value>`
|
|
21
22
|
* keeps the ones with those annotations
|
|
22
|
-
* GET /v1/definitions/<hash> one version
|
|
23
|
+
* GET /v1/definitions/<hash> one version, with `arguments`: per task null when
|
|
24
|
+
* a run of it may pass arguments, else why not
|
|
23
25
|
* GET /v1/definitions/<hash>/files its files
|
|
24
26
|
* POST /v1/definitions/<hash>/yank pull it back (body: optional {reason})
|
|
25
27
|
* POST /v1/definitions/<hash>/unyank let it go on
|
|
@@ -72,7 +74,7 @@ async function handleDefinitions(config, store, proposals, log, principal, req,
|
|
|
72
74
|
bundle ? (0, http_1.send)(res, 200, bundle) : (0, http_1.send)(res, 404, { error: 'not found' });
|
|
73
75
|
}
|
|
74
76
|
else {
|
|
75
|
-
(0, http_1.send)(res, 200, found);
|
|
77
|
+
(0, http_1.send)(res, 200, found.plan ? { ...found, arguments: (0, run_plan_1.describeArguments)(found.plan, config.runs) } : found);
|
|
76
78
|
}
|
|
77
79
|
return true;
|
|
78
80
|
}
|