hammerkit 1.27.0-next.9 → 2.0.0-next.2
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 +176 -6
- package/dist/cache/backends/local-cache-backend.js +6 -0
- package/dist/cache/backends/registry/registry-client.js +1 -0
- package/dist/cache/cache-sync.js +1 -1
- package/dist/cache/explain.js +17 -0
- package/dist/cli.js +2 -0
- package/dist/dashboard/assets/index-CjnKm5-3.css +2 -0
- package/dist/dashboard/assets/index-zPXa4klZ.js +13 -0
- package/dist/dashboard/favicon.svg +1 -0
- package/dist/dashboard/index.html +15 -0
- package/dist/docker/item-network.js +13 -5
- package/dist/executer/archive-task-entry.js +4 -2
- package/dist/executer/deadline.js +17 -3
- package/dist/executer/deferred-restore.js +36 -0
- package/dist/executer/docker-service.js +30 -8
- package/dist/executer/docker-task.js +21 -1
- package/dist/executer/execute-work-service.js +2 -0
- package/dist/executer/execute-work-task.js +11 -4
- package/dist/executer/execution-steps.js +25 -4
- package/dist/executer/reset-work-tree.js +4 -0
- package/dist/executer/scheduler/enqueue-next.js +57 -6
- package/dist/executer/scheduler/state-key.js +42 -5
- package/dist/executer/watch-loop.js +6 -3
- 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 +194 -23
- package/dist/harness/service-tools.js +141 -0
- package/dist/harness/task-tools.js +221 -0
- package/dist/kubernetes/apply.js +49 -1
- package/dist/kubernetes/await-running-state.js +172 -13
- package/dist/kubernetes/cluster-state.js +245 -0
- package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +133 -9
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +2 -0
- package/dist/kubernetes/ensure-persistent-data.js +211 -6
- package/dist/kubernetes/follow-pod-logs.js +182 -0
- package/dist/kubernetes/job-placement.js +36 -12
- package/dist/kubernetes/kubernetes-instance.js +1 -0
- package/dist/kubernetes/network-policy.js +2 -1
- package/dist/kubernetes/remove-stale-items.js +46 -0
- package/dist/kubernetes/restore-kubernetes-data.js +9 -5
- package/dist/kubernetes/sandbox-runner.js +628 -0
- package/dist/kubernetes/service-rollouts.js +74 -0
- package/dist/kubernetes/store-kubernetes-data.js +5 -2
- package/dist/kubernetes/volumes.js +94 -9
- package/dist/main.js +2 -0
- 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 +49 -1
- package/dist/planner/service-definition.js +9 -4
- package/dist/planner/utils/append-work-dependencies.js +5 -3
- package/dist/planner/utils/append-work-service.js +18 -1
- package/dist/planner/utils/append-work-task.js +13 -3
- 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/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-kubernetes.js +136 -13
- package/dist/planner/work-task.js +6 -1
- package/dist/program.js +27 -9
- package/dist/remote/device-login.js +7 -2
- package/dist/remote/host-config.js +69 -3
- package/dist/remote/remote-commands.js +33 -7
- package/dist/remote/remote-status.js +112 -0
- package/dist/remote/remote-work-commands.js +562 -45
- 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 +3 -3
- package/dist/schema/build-file-container-service-schema.js +10 -0
- package/dist/schema/build-file-container-task-schema.js +16 -1
- package/dist/schema/build-file-include-schema.js +18 -4
- package/dist/schema/build-file-kubernetes-service-schema.js +2 -0
- package/dist/schema/build-file-task-schema.js +5 -1
- package/dist/schema/capabilities-schema.js +56 -0
- package/dist/schema/reference-parser.js +119 -11
- package/dist/schema/removed-properties.js +20 -0
- package/dist/schema/schema-parser.js +94 -41
- package/dist/schema/work-scope-parser.js +33 -10
- package/dist/secrets/command-secret-provider.js +3 -1
- package/dist/secrets/github-app-token-command.js +3 -1
- package/dist/secrets/secret-catalog.js +43 -19
- package/dist/server/accounts-api.js +4 -1
- package/dist/server/approvals-api.js +1 -1
- package/dist/server/capacity-format.js +36 -0
- package/dist/server/capacity.js +300 -0
- package/dist/server/checks-api.js +83 -0
- package/dist/server/checks.js +116 -0
- package/dist/server/dashboard.js +196 -0
- package/dist/server/definitions-api.js +66 -3
- package/dist/server/endpoints.js +794 -0
- package/dist/server/executor.js +164 -9
- package/dist/server/kubernetes-backend.js +415 -48
- package/dist/server/kubernetes-sandbox-storage.js +46 -2
- package/dist/server/overview.js +271 -0
- package/dist/server/plan.js +170 -121
- package/dist/server/policy.js +34 -2
- package/dist/server/proposals-api.js +17 -8
- package/dist/server/repo-access.js +5 -0
- package/dist/server/run-logs.js +4 -0
- package/dist/server/run-plan.js +38 -4
- package/dist/server/run-request.js +58 -3
- package/dist/server/run-worker.js +262 -13
- package/dist/server/runs.js +962 -36
- package/dist/server/sandbox-pod.js +28 -0
- package/dist/server/sandbox-storage.js +23 -0
- package/dist/server/sandboxes.js +8 -0
- package/dist/server/server-config.js +246 -10
- package/dist/server/server.js +429 -32
- package/dist/server/service-runs.js +82 -0
- package/dist/server/timings.js +132 -0
- package/dist/server/wait-reason.js +21 -0
- package/dist/utils/tar.js +28 -1
- package/dist/utils/terminal-prompt.js +53 -0
- package/package.json +8 -2
|
@@ -6,6 +6,7 @@ const read_build_file_1 = require("../parser/read-build-file");
|
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const default_build_file_1 = require("../parser/default-build-file");
|
|
8
8
|
const parse_error_1 = require("./parse-error");
|
|
9
|
+
const removed_properties_1 = require("./removed-properties");
|
|
9
10
|
const build_file_include_schema_1 = require("./build-file-include-schema");
|
|
10
11
|
const resolve_git_source_1 = require("../includes/resolve-git-source");
|
|
11
12
|
async function createParseContext(fileName, environment, options) {
|
|
@@ -13,60 +14,80 @@ async function createParseContext(fileName, environment, options) {
|
|
|
13
14
|
files: {},
|
|
14
15
|
refreshed: options?.refresh ? new Map() : undefined,
|
|
15
16
|
directory: options?.directory,
|
|
17
|
+
checkGitSource: options?.checkGitSource,
|
|
16
18
|
};
|
|
17
19
|
const scope = await appendBuildFile(workDirectory(ctx, (0, path_1.dirname)(fileName)), ctx, environment, fileName, []);
|
|
18
20
|
return { ctx, scope };
|
|
19
21
|
}
|
|
20
22
|
exports.createParseContext = createParseContext;
|
|
21
|
-
async function appendBuildFile(cwd, ctx, environment, fileName, namePrefix, remote, inputs
|
|
23
|
+
async function appendBuildFile(cwd, ctx, environment, fileName, namePrefix, remote, inputs,
|
|
24
|
+
// on a server: the file is included with `publish: false`, or below such an include
|
|
25
|
+
leftOut = false) {
|
|
22
26
|
const relativeName = namePrefix.join(':');
|
|
23
|
-
// the same file included with other inputs is another set of tasks
|
|
24
|
-
|
|
27
|
+
// the same file included with other inputs is another set of tasks, and on a server a
|
|
28
|
+
// file left out is another one than the same file published (its git sources differ)
|
|
29
|
+
const key = `${cwd}:${fileName}${inputs ? `:${JSON.stringify(inputs)}` : ''}${leftOut ? ':left-out' : ''}`;
|
|
25
30
|
if (ctx.files[key]) {
|
|
26
31
|
return ctx.files[key];
|
|
27
32
|
}
|
|
28
33
|
const input = await (0, read_build_file_1.read)(fileName, environment);
|
|
34
|
+
(0, removed_properties_1.assertNoRemovedProperties)(input, fileName);
|
|
29
35
|
const result = await build_file_schema_1.buildFileSchema.safeParseAsync(input);
|
|
30
|
-
if (result.success) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (scope.references[referenceName]) {
|
|
48
|
-
throw new Error(referenceName + ' already exists');
|
|
49
|
-
}
|
|
50
|
-
scope.references[referenceName] = { type: 'reference', scope: referenceScope };
|
|
36
|
+
if (!result.success) {
|
|
37
|
+
throw new parse_error_1.ParseError(result.error, fileName);
|
|
38
|
+
}
|
|
39
|
+
const scope = {
|
|
40
|
+
fileName,
|
|
41
|
+
cwd,
|
|
42
|
+
schema: inputs ? applyInputs(result.data, inputs, namePrefix, fileName) : result.data,
|
|
43
|
+
namePrefix: relativeName,
|
|
44
|
+
remote,
|
|
45
|
+
inputs,
|
|
46
|
+
references: {},
|
|
47
|
+
};
|
|
48
|
+
ctx.files[key] = scope;
|
|
49
|
+
for (const block of ['references', 'includes']) {
|
|
50
|
+
for (const [name, include] of Object.entries(scope.schema[block] ?? {})) {
|
|
51
|
+
if (scope.references[name]) {
|
|
52
|
+
throw new Error(name + ' already exists');
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
scope.references[includeName] = { type: 'include', scope: includeScope };
|
|
54
|
+
const publish = (0, build_file_include_schema_1.isPublished)(include);
|
|
55
|
+
const type = block === 'references' ? 'reference' : 'include';
|
|
56
|
+
// a server does not fetch what it leaves out: it doesn't run, and isn't checked
|
|
57
|
+
const leaveOut = !!environment.hosted && (leftOut || !publish);
|
|
58
|
+
if ((0, build_file_include_schema_1.isGitSource)(include) && leaveOut) {
|
|
59
|
+
scope.references[name] = { type, publish, scope: notFetched(cwd, [...namePrefix, name], include) };
|
|
60
|
+
continue;
|
|
61
61
|
}
|
|
62
|
+
if ((0, build_file_include_schema_1.isGitSource)(include)) {
|
|
63
|
+
ctx.checkGitSource?.(include, scope, block, name);
|
|
64
|
+
}
|
|
65
|
+
const target = await locateInclude(ctx, scope, include, block === 'references'
|
|
66
|
+
? // a remote file's paths resolve within its repository
|
|
67
|
+
remote
|
|
68
|
+
? (0, path_1.dirname)(scope.fileName)
|
|
69
|
+
: readDirectory(ctx, scope.cwd)
|
|
70
|
+
: (0, path_1.dirname)(scope.fileName), environment);
|
|
71
|
+
const targetScope = await appendBuildFile(
|
|
72
|
+
// a reference works in its own directory, an include in the one of the including file
|
|
73
|
+
block === 'references' ? workDirectory(ctx, (0, path_1.dirname)(target.fileName)) : cwd, ctx, environment, target.fileName, [...namePrefix, name], target.remote, target.inputs, leaveOut);
|
|
74
|
+
scope.references[name] = { type, scope: targetScope, publish };
|
|
62
75
|
}
|
|
63
|
-
return scope;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
throw new parse_error_1.ParseError(result.error, fileName);
|
|
67
76
|
}
|
|
77
|
+
return scope;
|
|
68
78
|
}
|
|
69
79
|
exports.appendBuildFile = appendBuildFile;
|
|
80
|
+
// The scope of a git source a server does not fetch, see ParseScope.notFetched
|
|
81
|
+
function notFetched(cwd, namePrefix, include) {
|
|
82
|
+
return {
|
|
83
|
+
fileName: `${include.git}${include.path ? ` ${include.path}` : ''}`,
|
|
84
|
+
cwd,
|
|
85
|
+
schema: {},
|
|
86
|
+
namePrefix: namePrefix.join(':'),
|
|
87
|
+
references: {},
|
|
88
|
+
notFetched: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
70
91
|
// The directory a build file works in, for the directory it was read from: the same
|
|
71
92
|
// one, unless the files were read from elsewhere (see BuildFileDirectory).
|
|
72
93
|
function workDirectory(ctx, directory) {
|
|
@@ -89,13 +110,45 @@ async function locateInclude(ctx, scope, include, base, environment) {
|
|
|
89
110
|
if (!(0, resolve_git_source_1.isWithin)(remote.root, path)) {
|
|
90
111
|
throw new Error(`${include.path} in ${include.git} points outside the repository`);
|
|
91
112
|
}
|
|
92
|
-
return { fileName: await (0, default_build_file_1.getBuildFilename)(path, environment), remote, inputs: include.with };
|
|
113
|
+
return { fileName: await (0, default_build_file_1.getBuildFilename)(path, environment), remote, inputs: passEnvs(include.with, scope) };
|
|
93
114
|
}
|
|
94
|
-
const
|
|
115
|
+
const localPath = typeof include === 'string' ? include : include.path;
|
|
116
|
+
const path = (0, path_1.join)(base, localPath);
|
|
95
117
|
if (scope.remote && !(0, resolve_git_source_1.isWithin)(scope.remote.root, path)) {
|
|
96
|
-
throw new Error(`${
|
|
118
|
+
throw new Error(`${localPath} in ${scope.fileName} points outside ${scope.remote.git}`);
|
|
119
|
+
}
|
|
120
|
+
const inputs = typeof include === 'string' ? undefined : passEnvs(include.with, scope);
|
|
121
|
+
return { fileName: await (0, default_build_file_1.getBuildFilename)(path, environment), remote: scope.remote, inputs };
|
|
122
|
+
}
|
|
123
|
+
// `${NAME}` (or `${NAME:-default}`) in a `with` value reads as it does in the including
|
|
124
|
+
// file: the environment or a `.env` file first, then that file's own `envs` value. So one
|
|
125
|
+
// file holds a value such as NODE_VERSION for all it includes, and CI can still set it. A
|
|
126
|
+
// name the file does not declare is left to the included file, which reads it the same way.
|
|
127
|
+
function passEnvs(inputs, scope) {
|
|
128
|
+
if (!inputs) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
const envs = scope.schema.envs ?? {};
|
|
132
|
+
const result = {};
|
|
133
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
134
|
+
result[key] =
|
|
135
|
+
typeof value === 'string'
|
|
136
|
+
? value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (match, name, fallback) => name in envs ? passEnv(name, `${envs[name]}`, fallback) : match)
|
|
137
|
+
: value;
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
// The including file's value as an interpolation the included file resolves in the same
|
|
142
|
+
// order. A value built from other variables is passed on as it is written.
|
|
143
|
+
function passEnv(name, value, fallback) {
|
|
144
|
+
const reference = /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);
|
|
145
|
+
if (reference) {
|
|
146
|
+
return fallback === undefined ? `\${${reference[1]}}` : `\${${reference[1]}:-${fallback}}`;
|
|
147
|
+
}
|
|
148
|
+
if (value.includes('$') || value.includes('}')) {
|
|
149
|
+
return value;
|
|
97
150
|
}
|
|
98
|
-
return {
|
|
151
|
+
return `\${${name}:-${value}}`;
|
|
99
152
|
}
|
|
100
153
|
// `includes pull` fetches each source once, however often the build files name it.
|
|
101
154
|
async function resolveRemote(ctx, source, environment) {
|
|
@@ -47,19 +47,27 @@ function getWorkContext(context, scope, environment) {
|
|
|
47
47
|
// its init task's need on it, which narrowing follows
|
|
48
48
|
const tasks = Object.values(context.tasks).map((task) => (0, append_work_task_1.appendWorkTask)(workTree, task.cwd, task, environment, context));
|
|
49
49
|
const services = Object.values(context.services).map((service) => (0, append_work_service_1.appendWorkService)(workTree, service, environment, context));
|
|
50
|
+
const apply = { servicesAlone: !(0, work_scope_1.isContextTaskFilter)(scope) && !!scope.names && !!scope.stop };
|
|
50
51
|
const selectedTasks = [];
|
|
51
52
|
const selectedServices = [];
|
|
52
53
|
for (const item of tasks) {
|
|
53
54
|
if (matchesTaskWorkScope(item, scope)) {
|
|
54
55
|
requested.push(item.name);
|
|
55
56
|
selectedTasks.push(item.data);
|
|
56
|
-
applyTask(filteredWorkTree, item);
|
|
57
|
+
applyTask(filteredWorkTree, item, apply);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
for (const item of services) {
|
|
60
61
|
if (matchesServiceWorkScope(item.data, scope)) {
|
|
61
62
|
selectedServices.push(item.data);
|
|
62
|
-
applyService(filteredWorkTree, item);
|
|
63
|
+
applyService(filteredWorkTree, item, apply);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!(0, work_scope_1.isContextTaskFilter)(scope) && scope.names) {
|
|
67
|
+
const known = new Set([...tasks.map((task) => task.name), ...services.map((service) => service.data.name)]);
|
|
68
|
+
const unknown = scope.names.filter((name) => !known.has(name));
|
|
69
|
+
if (unknown.length > 0) {
|
|
70
|
+
throw new Error(`no task or service named ${unknown.join(', ')}, see hammerkit ls`);
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
if (scope.args) {
|
|
@@ -86,43 +94,58 @@ function keepRequirersInScope(workTree) {
|
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
|
-
function applyTask(workTree, item) {
|
|
97
|
+
function applyTask(workTree, item, options) {
|
|
90
98
|
if (workTree.tasks[item.name]) {
|
|
91
99
|
return;
|
|
92
100
|
}
|
|
93
101
|
workTree.tasks[item.name] = item;
|
|
94
|
-
applyNeedsAndDeps(workTree, item);
|
|
102
|
+
applyNeedsAndDeps(workTree, item, options);
|
|
95
103
|
}
|
|
96
|
-
function applyService(workTree, need) {
|
|
104
|
+
function applyService(workTree, need, options) {
|
|
97
105
|
const item = (0, append_work_service_1.getPlannedService)(need);
|
|
98
106
|
if (workTree.services[item.name]) {
|
|
99
107
|
return;
|
|
100
108
|
}
|
|
101
109
|
workTree.services[item.name] = item;
|
|
102
|
-
|
|
110
|
+
if (options.servicesAlone) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
applyNeedsAndDeps(workTree, item, options);
|
|
103
114
|
// a service in the run brings its init task, which runs once it is healthy
|
|
104
115
|
if (item.data.type === 'container-service' && item.data.init) {
|
|
105
|
-
applyTask(workTree, item.data.init.task);
|
|
116
|
+
applyTask(workTree, item.data.init.task, options);
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
|
-
function applyNeedsAndDeps(workTree, item) {
|
|
119
|
+
function applyNeedsAndDeps(workTree, item, options) {
|
|
109
120
|
for (const dep of item.deps) {
|
|
110
|
-
applyTask(workTree, dep);
|
|
121
|
+
applyTask(workTree, dep, options);
|
|
111
122
|
}
|
|
112
123
|
for (const need of item.needs) {
|
|
113
|
-
applyService(workTree, need.service);
|
|
124
|
+
applyService(workTree, need.service, options);
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
127
|
function matchesTaskWorkScope(task, scope) {
|
|
117
128
|
if ((0, work_scope_1.isContextTaskFilter)(scope)) {
|
|
118
129
|
return task.name === scope.taskName;
|
|
119
130
|
}
|
|
131
|
+
if (scope.serviceNames) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (scope.names) {
|
|
135
|
+
return scope.names.includes(task.name);
|
|
136
|
+
}
|
|
120
137
|
return (0, label_values_1.appliesToLabels)(task.data.labels, scope);
|
|
121
138
|
}
|
|
122
139
|
function matchesServiceWorkScope(service, scope) {
|
|
123
140
|
if ((0, work_scope_1.isContextTaskFilter)(scope)) {
|
|
124
141
|
return false;
|
|
125
142
|
}
|
|
143
|
+
if (scope.serviceNames) {
|
|
144
|
+
return scope.serviceNames.includes(service.name);
|
|
145
|
+
}
|
|
146
|
+
if (scope.names) {
|
|
147
|
+
return scope.names.includes(service.name);
|
|
148
|
+
}
|
|
126
149
|
return (0, label_values_1.appliesToLabels)(service.labels, scope);
|
|
127
150
|
}
|
|
128
151
|
//# sourceMappingURL=work-scope-parser.js.map
|
|
@@ -150,9 +150,11 @@ function candidates(command, path) {
|
|
|
150
150
|
.map((dir) => (0, path_1.resolve)(dir, command));
|
|
151
151
|
return bases.flatMap((base) => extensions.map((extension) => base + extension));
|
|
152
152
|
}
|
|
153
|
+
// bigint: a Windows file id has more digits than a number keeps, and two files whose ids
|
|
154
|
+
// round to the same number would be the same file
|
|
153
155
|
function statOf(path) {
|
|
154
156
|
try {
|
|
155
|
-
const stat = (0, fs_1.statSync)(path);
|
|
157
|
+
const stat = (0, fs_1.statSync)(path, { bigint: true });
|
|
156
158
|
return stat.isFile() ? stat : null;
|
|
157
159
|
}
|
|
158
160
|
catch {
|
|
@@ -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
|
}
|
|
@@ -11,10 +11,12 @@ function emptySecretCatalog() {
|
|
|
11
11
|
exports.emptySecretCatalog = emptySecretCatalog;
|
|
12
12
|
const FULL_COMMIT = /^[0-9a-f]{40}$/i;
|
|
13
13
|
// Providers and accounts are global names, like caches, collected from every
|
|
14
|
-
// loaded build file. A name declared twice is an error rather than a silent
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
14
|
+
// loaded build file. A name declared twice is an error rather than a silent override:
|
|
15
|
+
// which definition runs with a service account's credential must not depend on include
|
|
16
|
+
// order. The host config then replaces what the build files declare, name by name (and
|
|
17
|
+
// its default account replaces theirs): the machine that runs a task says how it reads
|
|
18
|
+
// secrets, and a build file that still declares them loads as it is.
|
|
19
|
+
function collectSecretCatalog(files, environment, context, host) {
|
|
18
20
|
const catalog = emptySecretCatalog();
|
|
19
21
|
for (const file of files) {
|
|
20
22
|
const providers = Object.entries(file.schema.secretProviders ?? {});
|
|
@@ -28,25 +30,13 @@ function collectSecretCatalog(files, environment, context) {
|
|
|
28
30
|
if (catalog.providers[name]) {
|
|
29
31
|
throw new Error(`secret provider ${name} is declared in ${catalog.providers[name].declaredIn} and in ${file.fileName}`);
|
|
30
32
|
}
|
|
31
|
-
catalog.providers[name] =
|
|
32
|
-
name,
|
|
33
|
-
type: 'command',
|
|
34
|
-
command: provider.command.map((part) => applyEnvs(part, envs)),
|
|
35
|
-
env: provider.env ?? {},
|
|
36
|
-
timeoutMs: (0, units_1.parseDuration)(provider.timeout ?? DEFAULT_TIMEOUT),
|
|
37
|
-
declaredIn: file.fileName,
|
|
38
|
-
};
|
|
33
|
+
catalog.providers[name] = toProvider(name, provider, envs, file.fileName);
|
|
39
34
|
}
|
|
40
35
|
for (const [name, account] of accounts) {
|
|
41
36
|
if (catalog.accounts[name]) {
|
|
42
37
|
throw new Error(`secret account ${name} is declared in ${catalog.accounts[name].declaredIn} and in ${file.fileName}`);
|
|
43
38
|
}
|
|
44
|
-
catalog.accounts[name] =
|
|
45
|
-
name,
|
|
46
|
-
default: account.default ?? false,
|
|
47
|
-
providers: account.providers,
|
|
48
|
-
declaredIn: file.fileName,
|
|
49
|
-
};
|
|
39
|
+
catalog.accounts[name] = toAccount(name, account, file.fileName);
|
|
50
40
|
}
|
|
51
41
|
}
|
|
52
42
|
const defaults = Object.values(catalog.accounts).filter((account) => account.default);
|
|
@@ -55,14 +45,48 @@ function collectSecretCatalog(files, environment, context) {
|
|
|
55
45
|
}
|
|
56
46
|
for (const account of Object.values(catalog.accounts)) {
|
|
57
47
|
for (const provider of Object.keys(account.providers)) {
|
|
58
|
-
if (!catalog.providers[provider]) {
|
|
48
|
+
if (!catalog.providers[provider] && !host?.secretProviders?.[provider]) {
|
|
59
49
|
throw new Error(`secret account ${account.name} (${account.declaredIn}) names provider ${provider}, which is not declared`);
|
|
60
50
|
}
|
|
61
51
|
}
|
|
62
52
|
}
|
|
53
|
+
if (host) {
|
|
54
|
+
// the host config has no envs of its own: `${NAME}` is read from the process
|
|
55
|
+
for (const [name, provider] of Object.entries(host.secretProviders ?? {})) {
|
|
56
|
+
catalog.providers[name] = toProvider(name, provider, {}, host.fileName);
|
|
57
|
+
}
|
|
58
|
+
const accounts = Object.entries(host.secretAccounts ?? {});
|
|
59
|
+
const hostDefaults = accounts.filter(([, account]) => account.default);
|
|
60
|
+
if (hostDefaults.length > 1) {
|
|
61
|
+
throw new Error(`more than one default secret account: ${hostDefaults.map(([name]) => name).join(', ')} (${host.fileName})`);
|
|
62
|
+
}
|
|
63
|
+
if (hostDefaults.length > 0) {
|
|
64
|
+
for (const account of Object.values(catalog.accounts)) {
|
|
65
|
+
account.default = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// a provider an account of the host names is only checked when a secret uses it:
|
|
69
|
+
// a project that doesn't declare it, and reads no secret, loads anyway
|
|
70
|
+
for (const [name, account] of accounts) {
|
|
71
|
+
catalog.accounts[name] = toAccount(name, account, host.fileName);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
63
74
|
return catalog;
|
|
64
75
|
}
|
|
65
76
|
exports.collectSecretCatalog = collectSecretCatalog;
|
|
77
|
+
function toProvider(name, provider, envs, declaredIn) {
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
type: 'command',
|
|
81
|
+
command: provider.command.map((part) => applyEnvs(part, envs)),
|
|
82
|
+
env: provider.env ?? {},
|
|
83
|
+
timeoutMs: (0, units_1.parseDuration)(provider.timeout ?? DEFAULT_TIMEOUT),
|
|
84
|
+
declaredIn,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function toAccount(name, account, declaredIn) {
|
|
88
|
+
return { name, default: account.default ?? false, providers: account.providers, declaredIn };
|
|
89
|
+
}
|
|
66
90
|
// A tag or branch can be moved, and a moved file runs with the credentials of a
|
|
67
91
|
// service account: providers and accounts from a git include are pinned.
|
|
68
92
|
function requirePinnedSource(file) {
|
|
@@ -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,10 @@ 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,
|
|
37
|
+
observe: grant.observe,
|
|
35
38
|
});
|
|
36
39
|
return true;
|
|
37
40
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// How CPU and memory read, for the dashboard and the CLI (without the clients of capacity.ts).
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.formatMemory = exports.plural = exports.formatCpu = void 0;
|
|
5
|
+
// cores as a person reads them: 0.25, 1.5, 12
|
|
6
|
+
function formatCpu(cores) {
|
|
7
|
+
if (cores === undefined) {
|
|
8
|
+
return '?';
|
|
9
|
+
}
|
|
10
|
+
if (cores === 0) {
|
|
11
|
+
return '0';
|
|
12
|
+
}
|
|
13
|
+
return cores < 10 ? String(Math.round(cores * 100) / 100) : String(Math.round(cores * 10) / 10);
|
|
14
|
+
}
|
|
15
|
+
exports.formatCpu = formatCpu;
|
|
16
|
+
// a count and what it counts: 1 pod, 2 pods
|
|
17
|
+
function plural(count, one, more = `${one}s`) {
|
|
18
|
+
return `${count} ${count === 1 ? one : more}`;
|
|
19
|
+
}
|
|
20
|
+
exports.plural = plural;
|
|
21
|
+
// bytes as a person reads them: 512 MiB, 3.4 GiB
|
|
22
|
+
function formatMemory(bytes) {
|
|
23
|
+
if (bytes === undefined) {
|
|
24
|
+
return '?';
|
|
25
|
+
}
|
|
26
|
+
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
|
|
27
|
+
let value = bytes;
|
|
28
|
+
let unit = 0;
|
|
29
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
30
|
+
value /= 1024;
|
|
31
|
+
unit++;
|
|
32
|
+
}
|
|
33
|
+
return `${value < 10 && unit > 0 ? Math.round(value * 10) / 10 : Math.round(value)} ${units[unit]}`;
|
|
34
|
+
}
|
|
35
|
+
exports.formatMemory = formatMemory;
|
|
36
|
+
//# sourceMappingURL=capacity-format.js.map
|