hammerkit 1.27.0-next.13 → 1.27.0-next.15
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/docker/item-network.js +13 -5
- package/dist/executer/docker-service.js +30 -8
- package/dist/harness/connect.js +19 -5
- package/dist/harness/mcp-command.js +22 -6
- package/dist/harness/mcp.js +62 -114
- package/dist/harness/opencode.js +151 -50
- package/dist/harness/pi.js +14 -0
- package/dist/harness/sandbox-client.js +81 -2
- package/dist/harness/service-tools.js +141 -0
- package/dist/harness/task-tools.js +210 -0
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +2 -0
- package/dist/planner/utils/parse-work-port.js +4 -1
- package/dist/remote/remote-work-commands.js +98 -12
- package/dist/schema/work-scope-parser.js +6 -0
- package/dist/server/accounts-api.js +2 -1
- package/dist/server/approvals-api.js +1 -1
- package/dist/server/definitions-api.js +4 -2
- package/dist/server/endpoints.js +612 -0
- package/dist/server/executor.js +14 -1
- package/dist/server/kubernetes-backend.js +200 -33
- package/dist/server/plan.js +1 -0
- package/dist/server/policy.js +24 -2
- package/dist/server/repo-access.js +5 -0
- package/dist/server/run-plan.js +38 -4
- package/dist/server/run-request.js +44 -2
- package/dist/server/run-worker.js +130 -9
- package/dist/server/runs.js +317 -24
- package/dist/server/server-config.js +160 -9
- package/dist/server/server.js +210 -14
- package/dist/server/service-runs.js +82 -0
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskToolArgs = exports.taskToolSchema = exports.createTaskTools = exports.describeTasks = exports.describeRun = exports.readLogHint = exports.readTaskListing = exports.LIST_TASKS_MS = void 0;
|
|
4
|
+
// the most tasks a description lists, and of a task's description or command it shows
|
|
5
|
+
const MAX_LISTED = 40;
|
|
6
|
+
const MAX_DESCRIPTION_CHARS = 200;
|
|
7
|
+
const MAX_COMMAND_CHARS = 100;
|
|
8
|
+
// how long a client waits for the tasks of the build file when it starts, it goes on without them
|
|
9
|
+
exports.LIST_TASKS_MS = 15000;
|
|
10
|
+
// The tasks of the published version, or why they could not be read within `ms`.
|
|
11
|
+
async function readTaskListing(list, ms = exports.LIST_TASKS_MS) {
|
|
12
|
+
let timer;
|
|
13
|
+
const timeout = new Promise((_, reject) => {
|
|
14
|
+
timer = setTimeout(() => reject(new Error(`no answer within ${ms / 1000} s`)), ms);
|
|
15
|
+
timer.unref?.();
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
return await Promise.race([list(), timeout]);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.readTaskListing = readTaskListing;
|
|
28
|
+
// the hint at the end of a cut output or a result, for the model
|
|
29
|
+
function readLogHint(run, task) {
|
|
30
|
+
return `read more of the log with run_logs { "run": "${run}", "task": "${task}" }`;
|
|
31
|
+
}
|
|
32
|
+
exports.readLogHint = readLogHint;
|
|
33
|
+
// A run of a task in a few lines: how it went, why it ran, its last lines.
|
|
34
|
+
function describeRun(task, ran) {
|
|
35
|
+
const result = ran.result;
|
|
36
|
+
const lines = [`run ${ran.run.id}: ${ran.run.state}`];
|
|
37
|
+
if (result) {
|
|
38
|
+
lines.push(`task ${task}: ${result.status}` +
|
|
39
|
+
(result.exitCode !== undefined ? `, exit code ${result.exitCode}` : '') +
|
|
40
|
+
(result.durationMs !== undefined ? `, ${result.durationMs} ms` : '') +
|
|
41
|
+
(result.cache?.hit ? ', from the cache' : ''));
|
|
42
|
+
if (result.error) {
|
|
43
|
+
lines.push(`error: ${result.error}`);
|
|
44
|
+
}
|
|
45
|
+
if (result.cache?.causes?.length) {
|
|
46
|
+
lines.push(`ran because: ${result.cache.causes.join('; ')}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (ran.run.error) {
|
|
50
|
+
lines.push(`error: ${ran.run.error}`);
|
|
51
|
+
}
|
|
52
|
+
if (result && result.lastLog.length > 0) {
|
|
53
|
+
lines.push('last lines:', ...result.lastLog);
|
|
54
|
+
}
|
|
55
|
+
lines.push(readLogHint(ran.run.id, task));
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
exports.describeRun = describeRun;
|
|
59
|
+
/**
|
|
60
|
+
* The tasks for the model, one line each: what it is for (or its command, when the build
|
|
61
|
+
* file says nothing) and whether it takes arguments. `shell` is left out, the shell tool runs it.
|
|
62
|
+
*/
|
|
63
|
+
function describeTasks(listing, shell) {
|
|
64
|
+
if (listing === undefined) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(listing)) {
|
|
68
|
+
return `The tasks of the build file could not be listed (${listing.error}); name one as the build file does.`;
|
|
69
|
+
}
|
|
70
|
+
const tasks = listing.filter((task) => !shell || task.name !== shell);
|
|
71
|
+
if (tasks.length === 0) {
|
|
72
|
+
return 'The published build file has no tasks to run.';
|
|
73
|
+
}
|
|
74
|
+
const lines = tasks.slice(0, MAX_LISTED).map((task) => {
|
|
75
|
+
const parts = [`- ${task.name}`];
|
|
76
|
+
if (task.description) {
|
|
77
|
+
parts.push(`: ${cut(oneLine(task.description), MAX_DESCRIPTION_CHARS)}`);
|
|
78
|
+
}
|
|
79
|
+
else if (task.command) {
|
|
80
|
+
parts.push(`: runs \`${cut(oneLine(task.command), MAX_COMMAND_CHARS)}\``);
|
|
81
|
+
}
|
|
82
|
+
if (task.args === null) {
|
|
83
|
+
parts.push(' (takes args)');
|
|
84
|
+
}
|
|
85
|
+
else if (task.args !== undefined || task.command === null) {
|
|
86
|
+
parts.push(' (takes no args)');
|
|
87
|
+
}
|
|
88
|
+
return parts.join('');
|
|
89
|
+
});
|
|
90
|
+
if (tasks.length > MAX_LISTED) {
|
|
91
|
+
lines.push(`- and ${tasks.length - MAX_LISTED} more`);
|
|
92
|
+
}
|
|
93
|
+
return ['The tasks of the published build file:', ...lines].join('\n');
|
|
94
|
+
}
|
|
95
|
+
exports.describeTasks = describeTasks;
|
|
96
|
+
function oneLine(text) {
|
|
97
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
98
|
+
}
|
|
99
|
+
function cut(text, max) {
|
|
100
|
+
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* `run_task` and `run_logs`, with the tasks the description of `run_task` lists. Without a
|
|
104
|
+
* listing it names none, as the MCP tools before a version could be read.
|
|
105
|
+
*/
|
|
106
|
+
function createTaskTools(listing, shell) {
|
|
107
|
+
const tasks = describeTasks(listing, shell);
|
|
108
|
+
return [
|
|
109
|
+
{
|
|
110
|
+
name: 'run_task',
|
|
111
|
+
title: 'Run a task',
|
|
112
|
+
description: 'Run a task of the published build file on the sandbox, such as test or build, and answer how it went: ' +
|
|
113
|
+
'its status, exit code, why it was not taken from the cache, and its last lines. args are appended to ' +
|
|
114
|
+
'its last command, for example a test file. It runs in the container, with the network and the ' +
|
|
115
|
+
'options the build file gives the task.' +
|
|
116
|
+
(tasks ? `\n\n${tasks}` : ''),
|
|
117
|
+
parameters: {
|
|
118
|
+
task: { type: 'string', description: 'The name of the task' },
|
|
119
|
+
args: { type: 'string[]', description: 'Arguments for its last command' },
|
|
120
|
+
},
|
|
121
|
+
required: ['task'],
|
|
122
|
+
async call(client, args, signal) {
|
|
123
|
+
const ran = await client.runTask(args.task, { args: args.args?.length ? args.args : undefined, signal });
|
|
124
|
+
return describeRun(args.task, ran);
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'run_logs',
|
|
129
|
+
title: 'Read the log of a run',
|
|
130
|
+
description: 'Read the log of a run as numbered lines: one task, the last lines (tail), a range (from, limit) or ' +
|
|
131
|
+
'the lines that contain a text (grep, a fixed string) with lines of context around them.',
|
|
132
|
+
parameters: {
|
|
133
|
+
run: { type: 'string', description: 'The id of the run' },
|
|
134
|
+
task: { type: 'string', description: 'Only the lines of this task' },
|
|
135
|
+
tail: {
|
|
136
|
+
type: 'integer',
|
|
137
|
+
description: 'The last lines, or with grep the last matches',
|
|
138
|
+
minimum: 1,
|
|
139
|
+
maximum: 1000,
|
|
140
|
+
},
|
|
141
|
+
from: { type: 'integer', description: 'The first line to read', minimum: 1 },
|
|
142
|
+
limit: { type: 'integer', description: 'How many lines to read', minimum: 1, maximum: 1000 },
|
|
143
|
+
grep: { type: 'string', description: 'Only lines that contain this text' },
|
|
144
|
+
ignore_case: { type: 'boolean', description: 'Ignore case in grep' },
|
|
145
|
+
context: { type: 'integer', description: 'Lines before and after each match', minimum: 0, maximum: 20 },
|
|
146
|
+
},
|
|
147
|
+
required: ['run'],
|
|
148
|
+
async call(client, args) {
|
|
149
|
+
const page = await client.logs(args.run, {
|
|
150
|
+
task: args.task || undefined,
|
|
151
|
+
tail: args.tail || undefined,
|
|
152
|
+
from: args.from || undefined,
|
|
153
|
+
limit: args.limit || undefined,
|
|
154
|
+
grep: args.grep || undefined,
|
|
155
|
+
i: args.ignore_case || undefined,
|
|
156
|
+
context: args.context || undefined,
|
|
157
|
+
});
|
|
158
|
+
const lines = page.lines.map((line) => `${line.line}${line.match === false ? '-' : ':'} ${args.task ? '' : `[${line.task}] `}${line.text}`);
|
|
159
|
+
lines.push('', `(${page.total} lines${args.grep ? ` searched, ${page.lines.filter((line) => line.match !== false).length} match here` : ''}${page.next ? `, read on with from ${page.next}` : ''}${page.truncated ? ', the server cut the log off at its limit' : ''})`);
|
|
160
|
+
return lines.join('\n');
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
exports.createTaskTools = createTaskTools;
|
|
166
|
+
// The parameters as a JSON schema, for MCP and opencode 2.
|
|
167
|
+
function taskToolSchema(tool) {
|
|
168
|
+
return {
|
|
169
|
+
type: 'object',
|
|
170
|
+
properties: Object.fromEntries(Object.entries(tool.parameters).map(([name, parameter]) => {
|
|
171
|
+
if (parameter.type === 'string[]') {
|
|
172
|
+
return [name, { type: 'array', items: { type: 'string' }, description: parameter.description }];
|
|
173
|
+
}
|
|
174
|
+
if (parameter.type === 'integer') {
|
|
175
|
+
const { maximum } = parameter;
|
|
176
|
+
return [
|
|
177
|
+
name,
|
|
178
|
+
{
|
|
179
|
+
type: 'integer',
|
|
180
|
+
description: parameter.description,
|
|
181
|
+
minimum: parameter.minimum,
|
|
182
|
+
...(maximum !== undefined ? { maximum } : {}),
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
return [name, { type: parameter.type, description: parameter.description }];
|
|
187
|
+
})),
|
|
188
|
+
required: tool.required,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
exports.taskToolSchema = taskToolSchema;
|
|
192
|
+
// The parameters with the zod of opencode 1.
|
|
193
|
+
function taskToolArgs(tool, z) {
|
|
194
|
+
return Object.fromEntries(Object.entries(tool.parameters).map(([name, parameter]) => {
|
|
195
|
+
let type;
|
|
196
|
+
if (parameter.type === 'string[]') {
|
|
197
|
+
type = z.array(z.string());
|
|
198
|
+
}
|
|
199
|
+
else if (parameter.type === 'integer') {
|
|
200
|
+
type = z.number().int().min(parameter.minimum);
|
|
201
|
+
type = parameter.maximum !== undefined ? type.max(parameter.maximum) : type;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
type = parameter.type === 'boolean' ? z.boolean() : z.string();
|
|
205
|
+
}
|
|
206
|
+
return [name, (tool.required.includes(name) ? type : type.optional()).describe(parameter.description)];
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
exports.taskToolArgs = taskToolArgs;
|
|
210
|
+
//# sourceMappingURL=task-tools.js.map
|
|
@@ -28,7 +28,9 @@ async function ensureKubernetesServiceExists(instance, env, service) {
|
|
|
28
28
|
...(headless
|
|
29
29
|
? {}
|
|
30
30
|
: {
|
|
31
|
+
// a Service of more than one port needs a name for each
|
|
31
32
|
ports: service.data.ports.map((port) => ({
|
|
33
|
+
name: `tcp-${port.hostPort ?? port.containerPort}`,
|
|
32
34
|
port: port.hostPort ?? port.containerPort,
|
|
33
35
|
targetPort: port.containerPort,
|
|
34
36
|
})),
|
|
@@ -8,7 +8,10 @@ function parseWorkPort(port) {
|
|
|
8
8
|
return { containerPort: port, hostPort: port };
|
|
9
9
|
}
|
|
10
10
|
else if (parts.length === 2) {
|
|
11
|
-
|
|
11
|
+
// `0:80` is not published, as an empty host port: 0 (a free port) is for the endpoints
|
|
12
|
+
// of a server only
|
|
13
|
+
const hostPort = parsePort(parts[0], true);
|
|
14
|
+
return { containerPort: parsePort(parts[1], false), hostPort: hostPort === 0 ? null : hostPort };
|
|
12
15
|
}
|
|
13
16
|
else {
|
|
14
17
|
throw new Error(`invalid port ${port}`);
|
|
@@ -13,6 +13,8 @@ const annotations_1 = require("./annotations");
|
|
|
13
13
|
const server_client_1 = require("./server-client");
|
|
14
14
|
const bundle_text_1 = require("./bundle-text");
|
|
15
15
|
const sandbox_client_1 = require("../harness/sandbox-client");
|
|
16
|
+
const units_1 = require("../utils/units");
|
|
17
|
+
const service_tools_1 = require("../harness/service-tools");
|
|
16
18
|
const path_1 = require("path");
|
|
17
19
|
const RUN_ID = /^run_[A-Za-z0-9_-]{16}$/;
|
|
18
20
|
// what the publisher said about where a version comes from, one line
|
|
@@ -97,6 +99,21 @@ function describePlan(plan) {
|
|
|
97
99
|
exports.describePlan = describePlan;
|
|
98
100
|
function registerWorkCommands(remote, context) {
|
|
99
101
|
const { environment, buildFile, getServer, guarded, passthrough, fail } = context;
|
|
102
|
+
// the ref a run starts from: what the options name, else the checkout here
|
|
103
|
+
const refSource = async (options) => {
|
|
104
|
+
const checkout = options.repo && options.ref ? {} : await (0, annotations_1.readCheckout)((0, path_1.dirname)(buildFile));
|
|
105
|
+
const repo = options.repo ?? checkout.repo;
|
|
106
|
+
const ref = options.ref ?? checkout.branch;
|
|
107
|
+
// the commit checked out here is the code meant, when the ref is the branch it is on
|
|
108
|
+
const commit = options.commit ?? (options.ref ? undefined : checkout.commit);
|
|
109
|
+
if (!repo) {
|
|
110
|
+
throw new Error('name the repository with --repo, the checkout here has no origin it can be told by');
|
|
111
|
+
}
|
|
112
|
+
if (!ref) {
|
|
113
|
+
throw new Error('name the branch or tag with --ref, the checkout here is on no branch');
|
|
114
|
+
}
|
|
115
|
+
return { type: 'ref', repo, ref, ...(commit ? { commit } : {}) };
|
|
116
|
+
};
|
|
100
117
|
const connect = async (name) => {
|
|
101
118
|
const chosen = await getServer(name);
|
|
102
119
|
// a CI job or an agent brings its own token, a person signs in
|
|
@@ -147,17 +164,8 @@ function registerWorkCommands(remote, context) {
|
|
|
147
164
|
if (!bundle_1.HASH_PATTERN.test(definition)) {
|
|
148
165
|
throw new Error(`${(0, server_client_1.printable)(definition)} is not the hash of a published version (sha256:...)`);
|
|
149
166
|
}
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const ref = options.ref ?? checkout.branch;
|
|
153
|
-
// the commit checked out here is the code meant, when the ref is the branch it is on
|
|
154
|
-
const commit = options.commit ?? (options.ref ? undefined : checkout.commit);
|
|
155
|
-
if (!repo) {
|
|
156
|
-
throw new Error('name the repository with --repo, the checkout here has no origin it can be told by');
|
|
157
|
-
}
|
|
158
|
-
if (!ref) {
|
|
159
|
-
throw new Error('name the branch or tag with --ref, the checkout here is on no branch');
|
|
160
|
-
}
|
|
167
|
+
const source = await refSource(options);
|
|
168
|
+
const ref = source.ref;
|
|
161
169
|
const { name: serverName, server } = await getServer(options.server);
|
|
162
170
|
const connection = { url: server.url, ...(0, token_source_1.getTokenSource)(environment, serverName, server) };
|
|
163
171
|
const runOptions = {};
|
|
@@ -174,7 +182,7 @@ function registerWorkCommands(remote, context) {
|
|
|
174
182
|
submitted = await (0, sandbox_client_1.submitRun)(connection, {
|
|
175
183
|
definition,
|
|
176
184
|
tasks,
|
|
177
|
-
source
|
|
185
|
+
source,
|
|
178
186
|
options: runOptions,
|
|
179
187
|
});
|
|
180
188
|
}
|
|
@@ -211,6 +219,84 @@ function registerWorkCommands(remote, context) {
|
|
|
211
219
|
const exitCode = failed?.exitCode && failed.exitCode > 0 ? failed.exitCode : 1;
|
|
212
220
|
fail(`${(0, server_client_1.printable)(run.id)} ${run.state}${run.error ? `: ${(0, server_client_1.printable)(run.error)}` : ''}`, exitCode);
|
|
213
221
|
}));
|
|
222
|
+
remote
|
|
223
|
+
.command('up <services...>')
|
|
224
|
+
.description('start services of a published build file on a server from a git ref and keep them up until their ttl ends, ' +
|
|
225
|
+
'with their endpoints at addresses of their own behind the sign-in of the server')
|
|
226
|
+
.addOption(new commander_1.Option('--server <name>', 'the server to run on; the default of remote use when left out'))
|
|
227
|
+
.addOption(new commander_1.Option('--endpoint <service:port>', 'publish this port of a service, such as web:8080; may be given more than once').argParser(collectOption))
|
|
228
|
+
.addOption(new commander_1.Option('--ttl <duration>', 'how long they stay once ready, such as 2h, then they are removed; the server default when left out'))
|
|
229
|
+
.addOption(new commander_1.Option('--sleep-ttl <duration>', 'how long they stay up without a request, such as 30m, then they sleep until the next request wakes them; ' +
|
|
230
|
+
'the server default when left out'))
|
|
231
|
+
.addOption(new commander_1.Option('--name <name>', 'a later run of yours with the same name replaces this one and keeps its addresses; the branch when left out'))
|
|
232
|
+
.addOption(new commander_1.Option('--build <hash>', 'the published version to run (sha256:...), or HAMMERKIT_BUILD; the hash of the build file here when left out'))
|
|
233
|
+
.addOption(new commander_1.Option('--repo <host/path>', 'the repository; origin of the checkout here when left out'))
|
|
234
|
+
.addOption(new commander_1.Option('--ref <ref>', 'the branch or tag to start; the branch checked out here when left out'))
|
|
235
|
+
.addOption(new commander_1.Option('--commit <sha>', 'the commit the ref has to be at; the commit checked out here when --ref is left out'))
|
|
236
|
+
.addOption(new commander_1.Option('--env <name>', 'the environment of the build file, as --env of run'))
|
|
237
|
+
.action(guarded('Remote up was not successful', async (services, options) => {
|
|
238
|
+
const named = options.build ?? environment.processEnvs.HAMMERKIT_BUILD;
|
|
239
|
+
const definition = named ?? (0, bundle_1.hashBundle)(await (0, collect_bundle_1.collectBundle)(buildFile, environment));
|
|
240
|
+
if (!bundle_1.HASH_PATTERN.test(definition)) {
|
|
241
|
+
throw new Error(`${(0, server_client_1.printable)(definition)} is not the hash of a published version (sha256:...)`);
|
|
242
|
+
}
|
|
243
|
+
const endpoints = (options.endpoint ?? []).map(service_tools_1.parseEndpoint);
|
|
244
|
+
const source = await refSource(options);
|
|
245
|
+
const { name: serverName, server } = await getServer(options.server);
|
|
246
|
+
const connection = { url: server.url, ...(0, token_source_1.getTokenSource)(environment, serverName, server) };
|
|
247
|
+
const submitted = await (0, sandbox_client_1.submitRun)(connection, {
|
|
248
|
+
definition,
|
|
249
|
+
services,
|
|
250
|
+
endpoints,
|
|
251
|
+
...(options.ttl ? { ttl: options.ttl } : {}),
|
|
252
|
+
...(options.sleepTtl ? { sleepTtl: options.sleepTtl } : {}),
|
|
253
|
+
...(options.name ? { name: options.name } : {}),
|
|
254
|
+
source,
|
|
255
|
+
options: options.env ? { env: options.env } : {},
|
|
256
|
+
});
|
|
257
|
+
environment.stdout.write(`${(0, server_client_1.printable)(submitted.id)} on ${serverName}: ${services.map(server_client_1.printable).join(', ')} from ${(0, server_client_1.printable)(source.ref)}${submitted.source.type === 'ref' ? ` at ${(0, server_client_1.printable)(submitted.source.commit)}` : ''}\n`);
|
|
258
|
+
const followed = await (0, sandbox_client_1.followRun)(connection, submitted, {
|
|
259
|
+
signal: environment.abortCtrl.signal,
|
|
260
|
+
untilUp: true,
|
|
261
|
+
onLine: (line) => environment.stdout.write(`[${(0, server_client_1.printable)(line.task)}] ${printableLine(line.text)}\n`),
|
|
262
|
+
onWaiting: (run) => {
|
|
263
|
+
for (const approval of run.approvals ?? []) {
|
|
264
|
+
environment.stdout.write(`waiting until somebody who uses ${(0, server_client_1.printable)(approval.account)} approves it, before ${(0, server_client_1.printable)(approval.expiresAt)}: hammerkit remote approve ${(0, server_client_1.printable)(run.id)}\n`);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
const run = followed.run;
|
|
269
|
+
if (run.state !== 'up') {
|
|
270
|
+
fail(`${(0, server_client_1.printable)(run.id)} ${run.state}${run.error ? `: ${(0, server_client_1.printable)(run.error)}` : ''}`, 1);
|
|
271
|
+
}
|
|
272
|
+
environment.stdout.write(`${(0, server_client_1.printable)(run.id)} is up until ${(0, server_client_1.printable)(run.expiresAt ?? '-')}${run.up?.sleepTtl ? `, asleep after ${(0, units_1.formatDuration)(run.up.sleepTtl)} without a request` : ''}\n`);
|
|
273
|
+
for (const endpoint of run.up?.endpoints ?? []) {
|
|
274
|
+
environment.stdout.write(` ${(0, server_client_1.printable)(endpoint.service)}:${endpoint.port} ${(0, server_client_1.printable)(endpoint.url)}\n`);
|
|
275
|
+
}
|
|
276
|
+
environment.stdout.write(`extend it with: hammerkit remote extend ${(0, server_client_1.printable)(run.id)} 8h, stop it with: hammerkit remote stop ${(0, server_client_1.printable)(run.id)}\n`);
|
|
277
|
+
}));
|
|
278
|
+
remote
|
|
279
|
+
.command('extend <run> <ttl> [server]')
|
|
280
|
+
.description('keep a service run that is up for this long from now, such as 8h, at most the maxTtl of the server')
|
|
281
|
+
.action(guarded('Remote extend was not successful', async (id, ttl, name) => {
|
|
282
|
+
if (!RUN_ID.test(id)) {
|
|
283
|
+
throw new Error(`${(0, server_client_1.printable)(id)} is not a run id (run_...)`);
|
|
284
|
+
}
|
|
285
|
+
const { name: serverName, server, token } = await connect(name);
|
|
286
|
+
const run = (await (0, server_client_1.callServer)(server, token, 'POST', `/v1/runs/${id}/extend`, { ttl })).body;
|
|
287
|
+
environment.stdout.write(`${(0, server_client_1.printable)(run.id)} on ${serverName} stays up until ${(0, server_client_1.printable)(run.expiresAt ?? '-')}\n`);
|
|
288
|
+
}));
|
|
289
|
+
remote
|
|
290
|
+
.command('stop <run> [server]')
|
|
291
|
+
.description('stop a run: a service run takes its services and endpoints down')
|
|
292
|
+
.action(guarded('Remote stop was not successful', async (id, name) => {
|
|
293
|
+
if (!RUN_ID.test(id)) {
|
|
294
|
+
throw new Error(`${(0, server_client_1.printable)(id)} is not a run id (run_...)`);
|
|
295
|
+
}
|
|
296
|
+
const { name: serverName, server, token } = await connect(name);
|
|
297
|
+
const run = (await (0, server_client_1.callServer)(server, token, 'POST', `/v1/runs/${id}/cancel`)).body;
|
|
298
|
+
environment.stdout.write(`${(0, server_client_1.printable)(run.id)} on ${serverName}: ${run.state === 'up' || run.state === 'running' ? 'stopping' : (0, server_client_1.printable)(run.state)}\n`);
|
|
299
|
+
}));
|
|
214
300
|
remote
|
|
215
301
|
.command('versions [server]')
|
|
216
302
|
.description('list the published versions of build files on a server, newest first, with where they come from')
|
|
@@ -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
|
|
@@ -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) {
|
|
@@ -32,6 +32,7 @@ async function handleAccounts(config, log, principal, req, res, url) {
|
|
|
32
32
|
repos: grant.repos,
|
|
33
33
|
builds: grant.builds,
|
|
34
34
|
sandboxes: grant.sandboxes,
|
|
35
|
+
endpoints: grant.endpoints,
|
|
35
36
|
});
|
|
36
37
|
return true;
|
|
37
38
|
}
|
|
@@ -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
|
}
|