hammerkit 1.25.0 → 1.27.0-next.1
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 +53 -1
- package/dist/cache/backends/local-cache-backend.js +10 -2
- package/dist/cache/explain.js +13 -11
- package/dist/cli.js +3 -0
- package/dist/docker/item-network.js +471 -0
- package/dist/docker/pull.js +4 -2
- package/dist/docker/remove-orphaned-containers.js +13 -6
- package/dist/environment/replace-env-variables.js +8 -6
- package/dist/executer/await-completed-dependencies.js +2 -1
- package/dist/executer/check-readiness.js +2 -2
- package/dist/executer/container-task-state.js +8 -6
- package/dist/executer/docker-service.js +24 -13
- package/dist/executer/docker-task.js +18 -29
- package/dist/executer/event-cache.js +4 -0
- package/dist/executer/execute-command.js +18 -5
- package/dist/executer/execute-docker.js +4 -1
- package/dist/executer/execution-steps.js +33 -7
- package/dist/executer/get-docker-executor.js +14 -7
- package/dist/executer/scheduler/enqueue-next.js +17 -3
- package/dist/executer/watch-loop.js +2 -2
- package/dist/executer/watch-state-key.js +1 -1
- package/dist/file/within-root.js +42 -0
- package/dist/harness/connect.js +99 -0
- package/dist/harness/format.js +73 -0
- package/dist/harness/mcp-command.js +59 -0
- package/dist/harness/mcp.js +432 -0
- package/dist/harness/opencode.js +492 -0
- package/dist/harness/pi.js +217 -0
- package/dist/harness/sandbox-client.js +373 -0
- package/dist/index.js +33 -36
- package/dist/kubernetes/apply.js +31 -1
- package/dist/kubernetes/await-running-state.js +33 -1
- package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +44 -22
- package/dist/kubernetes/ensure-kubernetes-persistent-volume-claim-exists.js +131 -4
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +25 -4
- package/dist/kubernetes/ensure-persistent-data.js +48 -7
- package/dist/kubernetes/get-service-ip.js +15 -1
- package/dist/kubernetes/job-placement.js +99 -0
- package/dist/kubernetes/job-secrets.js +306 -0
- package/dist/kubernetes/kept-claims.js +191 -0
- package/dist/kubernetes/kubernetes-instance.js +10 -2
- package/dist/kubernetes/network-gate.js +71 -0
- package/dist/kubernetes/network-policy.js +526 -0
- package/dist/kubernetes/resources.js +13 -2
- package/dist/kubernetes/restore-kubernetes-data.js +19 -4
- package/dist/kubernetes/secrets.js +2 -0
- package/dist/kubernetes/store-kubernetes-data.js +1 -1
- package/dist/kubernetes/volumes.js +100 -11
- package/dist/main.js +41 -0
- package/dist/network/egress-proxy.js +368 -0
- package/dist/network/port-relay.js +35 -0
- package/dist/optimizer/get-work-cache-stats.js +16 -0
- package/dist/optimizer/work-task-cache-description.js +3 -2
- package/dist/planner/service-definition.js +8 -2
- package/dist/planner/utils/append-work-environment.js +2 -0
- package/dist/planner/utils/append-work-service.js +4 -0
- package/dist/planner/utils/append-work-task.js +17 -1
- package/dist/planner/utils/assert-item-paths.js +50 -0
- package/dist/planner/utils/hosted-volumes.js +35 -0
- package/dist/planner/utils/task-arguments.js +155 -0
- package/dist/planner/work-cache-id.js +15 -1
- package/dist/planner/work-network.js +193 -0
- package/dist/planner/work-runtime-docker.js +43 -0
- package/dist/planner/work-runtime-kubernetes.js +336 -102
- package/dist/planner/work-runtime-local.js +16 -8
- package/dist/planner/work-service.js +7 -3
- package/dist/planner/work-state-record.js +30 -0
- package/dist/program.js +80 -7
- package/dist/remote/annotations.js +103 -0
- package/dist/remote/bundle-text.js +24 -0
- package/dist/remote/bundle.js +9 -1
- package/dist/remote/remote-commands.js +4 -1
- package/dist/remote/remote-work-commands.js +182 -69
- package/dist/remote/server-client.js +18 -5
- package/dist/remote/source-manifest.js +15 -2
- package/dist/remote/token-source.js +67 -0
- package/dist/schema/build-file-base-task-schema.js +3 -0
- package/dist/schema/build-file-container-service-schema.js +6 -0
- package/dist/schema/build-file-container-task-schema.js +3 -0
- package/dist/schema/build-file-environment-schema.js +9 -0
- package/dist/schema/network-schema.js +78 -0
- package/dist/schema/reference-parser.js +1 -0
- package/dist/schema/schema-parser.js +16 -3
- package/dist/schema/work-scope-parser.js +13 -0
- package/dist/secrets/command-secret-provider.js +73 -10
- package/dist/secrets/github-app-token-command.js +23 -0
- package/dist/secrets/github-app-token.js +191 -0
- package/dist/server/accounts-api.js +9 -2
- package/dist/server/definitions-api.js +69 -47
- package/dist/server/definitions.js +76 -51
- package/dist/server/executor.js +156 -41
- package/dist/server/git-checkout.js +72 -8
- package/dist/server/kubernetes-backend.js +554 -0
- package/dist/server/kubernetes-sandbox-storage.js +996 -0
- package/dist/server/oauth.js +1 -0
- package/dist/server/plan.js +51 -1
- package/dist/server/policy.js +104 -15
- package/dist/server/proposals-api.js +24 -40
- package/dist/server/proposals.js +12 -25
- package/dist/server/repo-access.js +37 -6
- package/dist/server/run-logs.js +217 -0
- package/dist/server/run-plan.js +51 -2
- package/dist/server/run-request.js +23 -10
- package/dist/server/run-store.js +5 -0
- package/dist/server/run-worker-command.js +13 -1
- package/dist/server/run-worker.js +121 -24
- package/dist/server/runs.js +184 -10
- package/dist/server/sandbox-docker.js +62 -0
- package/dist/server/sandbox-files-api.js +269 -0
- package/dist/server/sandbox-files.js +1054 -0
- package/dist/server/sandbox-patch.js +481 -0
- package/dist/server/sandbox-pod-command.js +15 -0
- package/dist/server/sandbox-pod.js +146 -0
- package/dist/server/sandbox-storage.js +462 -0
- package/dist/server/sandboxes-api.js +210 -0
- package/dist/server/sandboxes.js +519 -0
- package/dist/server/server-config.js +412 -22
- package/dist/server/server.js +204 -39
- package/dist/utils/run-id.js +12 -0
- package/harness/opencode.ts +11 -0
- package/harness/pi.ts +7 -0
- package/package.json +8 -2
- package/dist/remote/text-diff.js +0 -89
|
@@ -3,32 +3,54 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.registerWorkCommands = void 0;
|
|
6
|
+
exports.registerWorkCommands = exports.describePlan = exports.describeApproval = void 0;
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
8
|
const colors_1 = __importDefault(require("colors"));
|
|
9
|
-
const
|
|
9
|
+
const token_source_1 = require("./token-source");
|
|
10
10
|
const collect_bundle_1 = require("./collect-bundle");
|
|
11
11
|
const bundle_1 = require("./bundle");
|
|
12
|
+
const annotations_1 = require("./annotations");
|
|
12
13
|
const server_client_1 = require("./server-client");
|
|
13
|
-
const
|
|
14
|
-
const PROPOSAL_NAME = /^([a-z0-9][a-z0-9._-]{0,63})@(sha256:[0-9a-f]{64})$/i;
|
|
15
|
-
// A proposed version is named like `payments@sha256:...`
|
|
16
|
-
function parseProposal(value) {
|
|
17
|
-
const match = PROPOSAL_NAME.exec(value);
|
|
18
|
-
return match && bundle_1.HASH_PATTERN.test(match[2]) ? { name: match[1], hash: match[2] } : undefined;
|
|
19
|
-
}
|
|
14
|
+
const bundle_text_1 = require("./bundle-text");
|
|
20
15
|
const RUN_ID = /^run_[A-Za-z0-9_-]{16}$/;
|
|
16
|
+
// what the publisher said about where a version comes from, one line
|
|
17
|
+
// What a publisher said about where a version comes from: the server takes it as said,
|
|
18
|
+
// so anybody else is told it is not checked.
|
|
19
|
+
function annotationLines(annotations, label = 'from (unverified)') {
|
|
20
|
+
const entries = Object.entries(annotations ?? {});
|
|
21
|
+
return entries.length > 0
|
|
22
|
+
? [` ${label}: ${entries.map(([key, value]) => `${(0, server_client_1.printable)(key)}=${(0, server_client_1.printable)(value)}`).join(' ')}`]
|
|
23
|
+
: [];
|
|
24
|
+
}
|
|
25
|
+
function yankLines(version) {
|
|
26
|
+
return version.yanked
|
|
27
|
+
? [
|
|
28
|
+
` yanked by ${(0, server_client_1.printable)(version.yanked.by.sub)}${version.yanked.reason ? `: ${(0, server_client_1.printable)(version.yanked.reason)}` : ''}`,
|
|
29
|
+
]
|
|
30
|
+
: [];
|
|
31
|
+
}
|
|
32
|
+
const collectOption = (value, previous = []) => [...previous, value];
|
|
21
33
|
// What a person who approves is shown of a run: everything the decision rests on.
|
|
22
|
-
function
|
|
23
|
-
const source = run.source.type === 'ref'
|
|
24
|
-
|
|
34
|
+
function describeApproval(run) {
|
|
35
|
+
const source = run.source.type === 'ref'
|
|
36
|
+
? `ref ${run.source.ref}`
|
|
37
|
+
: run.source.type === 'sandbox'
|
|
38
|
+
? `sandbox of ${run.source.repo} at ${run.source.commit}, ${run.source.clean ? 'unchanged' : `with changes, files ${run.source.tree}`} (files .gitignore leaves out are not shown)`
|
|
39
|
+
: `snapshot ${run.source.snapshot}`;
|
|
40
|
+
// the arguments are shown whole on a line of their own, each as a JSON string,
|
|
41
|
+
// so commas, spaces and quotes inside one stay visible
|
|
42
|
+
const { args, ...settings } = run.options;
|
|
43
|
+
const options = Object.entries(settings)
|
|
25
44
|
.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join(',') : value}`)
|
|
26
45
|
.join(' ');
|
|
27
46
|
return [
|
|
28
47
|
`${colors_1.default.bold((0, server_client_1.printable)(run.id))} requested by ${(0, server_client_1.printable)(run.requestedBy.sub)} (${(0, server_client_1.printable)(run.requestedBy.iss)})`,
|
|
29
|
-
`
|
|
48
|
+
` build file ${(0, server_client_1.printable)(run.definition)}`,
|
|
30
49
|
` tasks: ${run.tasks.length > 0 ? run.tasks.map(server_client_1.printable).join(', ') : 'selected by labels'}`,
|
|
31
50
|
` accounts: ${run.accounts.length > 0 ? run.accounts.map(server_client_1.printable).join(', ') : 'none'}, source ${(0, server_client_1.printable)(source)}${options ? `, ${(0, server_client_1.printable)(options)}` : ''}`,
|
|
51
|
+
...(Array.isArray(args) && args.length > 0
|
|
52
|
+
? [` arguments: ${args.map((arg) => (0, server_client_1.printableWhole)(JSON.stringify(arg))).join(' ')}`]
|
|
53
|
+
: []),
|
|
32
54
|
...(run.approvals ?? []).map((approval) => ` ${(0, server_client_1.printable)(approval.account)}: ${approval.approvedBy
|
|
33
55
|
? `approved by ${(0, server_client_1.printable)(approval.approvedBy.sub)}`
|
|
34
56
|
: 'waits for somebody who uses it to approve'}`),
|
|
@@ -36,6 +58,7 @@ function describe(run) {
|
|
|
36
58
|
` decide before ${(0, server_client_1.printable)(run.approvals?.[0]?.expiresAt ?? '-')}`,
|
|
37
59
|
];
|
|
38
60
|
}
|
|
61
|
+
exports.describeApproval = describeApproval;
|
|
39
62
|
// What to look at first, one line.
|
|
40
63
|
function flagLines(run) {
|
|
41
64
|
const flagged = Object.entries(run.plan ?? {}).filter(([, entry]) => entry.flags.length > 0);
|
|
@@ -51,7 +74,11 @@ function flagLines(run) {
|
|
|
51
74
|
function describePlan(plan) {
|
|
52
75
|
return Object.entries(plan).flatMap(([name, entry]) => [
|
|
53
76
|
` ${colors_1.default.bold((0, server_client_1.printable)(name))} ${entry.kind}, ${(0, server_client_1.printable)(entry.runtime)}${entry.image ? ` ${(0, server_client_1.printable)(entry.image)}` : ''}${entry.flags.length > 0 ? ` [${entry.flags.map(server_client_1.printable).join(', ')}]` : ''}`,
|
|
54
|
-
|
|
77
|
+
// a command is what is decided on: shown whole, however long
|
|
78
|
+
...entry.cmds.map((cmd) => ` $ ${(0, server_client_1.printableWhole)(cmd)}`),
|
|
79
|
+
...(entry.network !== undefined && entry.network !== 'open'
|
|
80
|
+
? [` network: ${(0, server_client_1.printable)(typeof entry.network === 'string' ? entry.network : JSON.stringify(entry.network))}`]
|
|
81
|
+
: []),
|
|
55
82
|
...(entry.deps.length > 0 ? [` after: ${entry.deps.map(server_client_1.printable).join(', ')}`] : []),
|
|
56
83
|
...(entry.needs.length > 0 ? [` needs: ${entry.needs.map(server_client_1.printable).join(', ')}`] : []),
|
|
57
84
|
...entry.secrets.map((secret) => ` secret ${(0, server_client_1.printable)(secret.from)}${secret.account ? ` as ${(0, server_client_1.printable)(secret.account)}` : ''} -> ${(0, server_client_1.printable)(secret.target)}`),
|
|
@@ -60,29 +87,78 @@ function describePlan(plan) {
|
|
|
60
87
|
...(entry.generates.length > 0 ? [` generates: ${entry.generates.map(server_client_1.printable).join(', ')}`] : []),
|
|
61
88
|
]);
|
|
62
89
|
}
|
|
90
|
+
exports.describePlan = describePlan;
|
|
63
91
|
function registerWorkCommands(remote, context) {
|
|
64
92
|
const { environment, buildFile, getServer, guarded } = context;
|
|
65
93
|
const connect = async (name) => {
|
|
66
94
|
const chosen = await getServer(name);
|
|
67
|
-
|
|
95
|
+
// a CI job or an agent brings its own token, a person signs in
|
|
96
|
+
const token = await (0, token_source_1.getTokenSource)(environment, chosen.name, chosen.server).token();
|
|
68
97
|
return { ...chosen, token };
|
|
69
98
|
};
|
|
70
99
|
remote
|
|
71
|
-
.command('publish
|
|
72
|
-
.description('publish the build file and the files it includes to a server,
|
|
73
|
-
.
|
|
100
|
+
.command('publish [server]')
|
|
101
|
+
.description('publish the build file and the files it includes to a server, runs refer to it by its hash')
|
|
102
|
+
.addOption(new commander_1.Option('--annotation <key=value>', 'say more about where it comes from, or leave one out with key=; path, repo, commit, machine and user are filled in').argParser(collectOption))
|
|
103
|
+
.action(guarded('Remote publish was not successful', async (name, options) => {
|
|
74
104
|
const bundle = await (0, collect_bundle_1.collectBundle)(buildFile, environment);
|
|
105
|
+
const annotations = (0, annotations_1.applyAnnotations)(await (0, annotations_1.collectAnnotations)(buildFile, Object.keys(bundle.files)), options.annotation ?? []);
|
|
75
106
|
const { name: serverName, server, token } = await connect(name);
|
|
76
|
-
const answer = await (0, server_client_1.callServer)(server, token, '
|
|
107
|
+
const answer = await (0, server_client_1.callServer)(server, token, 'POST', '/v1/definitions', {
|
|
108
|
+
...bundle,
|
|
109
|
+
annotations,
|
|
110
|
+
});
|
|
77
111
|
const version = answer.body;
|
|
78
112
|
const files = Object.keys(bundle.files).length;
|
|
79
113
|
const proposed = answer.status === 202;
|
|
80
114
|
const verb = proposed ? 'proposed' : answer.status === 201 ? 'published' : 'already published';
|
|
81
|
-
environment.stdout.write(`${verb}
|
|
115
|
+
environment.stdout.write(`${verb} to ${serverName}: ${(0, server_client_1.printable)(version.hash)} (${files} ${files === 1 ? 'file' : 'files'})\n`);
|
|
82
116
|
const names = [...(version.tasks ?? []), ...(version.services ?? [])];
|
|
83
117
|
environment.stdout.write(`tasks and services: ${names.map(server_client_1.printable).join(', ') || 'none'}\n`);
|
|
118
|
+
for (const line of annotationLines(version.annotations, 'from')) {
|
|
119
|
+
environment.stdout.write(`${line.trimStart()}\n`);
|
|
120
|
+
}
|
|
84
121
|
if (proposed) {
|
|
85
|
-
environment.stdout.write(`it can not run before somebody who may publish
|
|
122
|
+
environment.stdout.write(`it can not run before somebody who may publish approves it, until ${(0, server_client_1.printable)(version.expiresAt)}\n`);
|
|
123
|
+
}
|
|
124
|
+
}));
|
|
125
|
+
remote
|
|
126
|
+
.command('versions [server]')
|
|
127
|
+
.description('list the published versions of build files on a server, newest first, with where they come from')
|
|
128
|
+
.addOption(new commander_1.Option('--filter <key=value>', 'only the versions with this annotation, such as repo=github.com/corp/payments').argParser(collectOption))
|
|
129
|
+
.action(guarded('Remote versions was not successful', async (name, options) => {
|
|
130
|
+
const query = new URLSearchParams();
|
|
131
|
+
for (const filter of (options.filter ?? [])) {
|
|
132
|
+
const index = filter.indexOf('=');
|
|
133
|
+
if (index <= 0) {
|
|
134
|
+
throw new Error(`${(0, server_client_1.printable)(filter)} is not a filter, write it as key=value`);
|
|
135
|
+
}
|
|
136
|
+
if (index === filter.length - 1) {
|
|
137
|
+
throw new Error(`${(0, server_client_1.printable)(filter)} matches nothing, an annotation always has a value`);
|
|
138
|
+
}
|
|
139
|
+
query.append(filter.slice(0, index), filter.slice(index + 1));
|
|
140
|
+
}
|
|
141
|
+
const { name: serverName, server, token } = await connect(name);
|
|
142
|
+
const path = `/v1/definitions${query.toString() !== '' ? `?${query}` : ''}`;
|
|
143
|
+
const { body } = await (0, server_client_1.callServer)(server, token, 'GET', path);
|
|
144
|
+
if (body.definitions.length === 0) {
|
|
145
|
+
environment.stdout.write(`${serverName}: no published versions${query.toString() !== '' ? ' match' : ''}\n`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
environment.stdout.write(`${serverName}: ${body.definitions.length} published\n`);
|
|
149
|
+
for (const version of body.definitions) {
|
|
150
|
+
environment.stdout.write(`${colors_1.default.bold((0, server_client_1.printable)(version.hash))} published by ${(0, server_client_1.printable)(version.publishedBy.sub)} at ${(0, server_client_1.printable)(version.publishedAt)}${version.approvedBy ? `, approved by ${(0, server_client_1.printable)(version.approvedBy.sub)}` : ''}\n`);
|
|
151
|
+
for (const line of annotationLines(version.annotations)) {
|
|
152
|
+
environment.stdout.write(`${line}\n`);
|
|
153
|
+
}
|
|
154
|
+
const names = [...version.tasks, ...version.services];
|
|
155
|
+
environment.stdout.write(` tasks and services: ${names.map(server_client_1.printable).join(', ') || 'none'}\n`);
|
|
156
|
+
if (version.accounts.length > 0) {
|
|
157
|
+
environment.stdout.write(` accounts: ${version.accounts.map(server_client_1.printable).join(', ')}\n`);
|
|
158
|
+
}
|
|
159
|
+
for (const line of yankLines(version)) {
|
|
160
|
+
environment.stdout.write(`${line}\n`);
|
|
161
|
+
}
|
|
86
162
|
}
|
|
87
163
|
}));
|
|
88
164
|
remote
|
|
@@ -97,21 +173,84 @@ function registerWorkCommands(remote, context) {
|
|
|
97
173
|
}
|
|
98
174
|
environment.stdout.write(`${serverName}: ${body.approvals.length} waiting for your approval\n`);
|
|
99
175
|
for (const run of body.approvals) {
|
|
100
|
-
for (const line of
|
|
176
|
+
for (const line of describeApproval(run)) {
|
|
101
177
|
environment.stdout.write(`${line}\n`);
|
|
102
178
|
}
|
|
103
179
|
}
|
|
104
180
|
}));
|
|
181
|
+
// A version that is published: who published it, what its tasks do and every file.
|
|
182
|
+
const showPublished = async (hash, serverName, server, token) => {
|
|
183
|
+
const path = `/v1/definitions/${hash}`;
|
|
184
|
+
let version;
|
|
185
|
+
try {
|
|
186
|
+
version = (await (0, server_client_1.callServer)(server, token, 'GET', path)).body;
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
if (e instanceof server_client_1.ServerError && e.status === 404) {
|
|
190
|
+
throw new Error(`${serverName} has no proposal or published version ${(0, server_client_1.printable)(hash)} you may see`);
|
|
191
|
+
}
|
|
192
|
+
throw e;
|
|
193
|
+
}
|
|
194
|
+
const files = (await (0, server_client_1.callServer)(server, token, 'GET', `${path}/files`)).body;
|
|
195
|
+
environment.stdout.write(`${colors_1.default.bold((0, server_client_1.printable)(version.hash))} on ${serverName}, published by ${(0, server_client_1.printable)(version.publishedBy.sub)} (${(0, server_client_1.printable)(version.publishedBy.iss)}) at ${(0, server_client_1.printable)(version.publishedAt)}${version.approvedBy ? `, approved by ${(0, server_client_1.printable)(version.approvedBy.sub)}` : ''}\n`);
|
|
196
|
+
for (const line of [
|
|
197
|
+
...yankLines(version),
|
|
198
|
+
...annotationLines(version.annotations),
|
|
199
|
+
...flagLines(version),
|
|
200
|
+
...(version.plan ? describePlan(version.plan) : [' the server did not record what its tasks do']),
|
|
201
|
+
...(0, bundle_text_1.showBundle)(files),
|
|
202
|
+
]) {
|
|
203
|
+
environment.stdout.write(`${line}\n`);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
105
206
|
remote
|
|
106
|
-
.command('show <
|
|
107
|
-
.description('show a run
|
|
108
|
-
.action(guarded('Remote show was not successful', async (
|
|
109
|
-
if (!RUN_ID.test(
|
|
110
|
-
throw new Error(`${(0, server_client_1.printable)(
|
|
207
|
+
.command('show <target> [server]')
|
|
208
|
+
.description('show a run (run_...) or a proposed or published version (sha256:...) in full: the commands, secrets and accounts of its tasks, and the files of a version')
|
|
209
|
+
.action(guarded('Remote show was not successful', async (target, name) => {
|
|
210
|
+
if (!RUN_ID.test(target) && !bundle_1.HASH_PATTERN.test(target)) {
|
|
211
|
+
throw new Error(`${(0, server_client_1.printable)(target)} is not a run id or a version, list them with: hammerkit remote approvals, remote proposals or remote versions`);
|
|
111
212
|
}
|
|
112
|
-
const { server, token } = await connect(name);
|
|
113
|
-
|
|
114
|
-
|
|
213
|
+
const { name: serverName, server, token } = await connect(name);
|
|
214
|
+
if (bundle_1.HASH_PATTERN.test(target)) {
|
|
215
|
+
const path = `/v1/proposals/${target}`;
|
|
216
|
+
let proposal;
|
|
217
|
+
try {
|
|
218
|
+
proposal = (await (0, server_client_1.callServer)(server, token, 'GET', path)).body;
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
if (e instanceof server_client_1.ServerError && e.status === 404) {
|
|
222
|
+
await showPublished(target, serverName, server, token);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
throw e;
|
|
226
|
+
}
|
|
227
|
+
if (proposal.state === 'approved') {
|
|
228
|
+
await showPublished(target, serverName, server, token);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (proposal.state !== 'waiting') {
|
|
232
|
+
// a decided proposal keeps no files
|
|
233
|
+
environment.stdout.write(`${colors_1.default.bold((0, server_client_1.printable)(proposal.hash))} on ${serverName}, proposed by ${(0, server_client_1.printable)(proposal.proposedBy.sub)}, ${(0, server_client_1.printable)(proposal.state)}${proposal.decidedBy ? ` by ${(0, server_client_1.printable)(proposal.decidedBy.sub)}` : ''}${proposal.reason ? `: ${(0, server_client_1.printable)(proposal.reason)}` : ''}\n`);
|
|
234
|
+
for (const line of annotationLines(proposal.annotations)) {
|
|
235
|
+
environment.stdout.write(`${line}\n`);
|
|
236
|
+
}
|
|
237
|
+
environment.stdout.write(' its files are not kept once it is decided\n');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const files = (await (0, server_client_1.callServer)(server, token, 'GET', `${path}/files`)).body;
|
|
241
|
+
environment.stdout.write(`${colors_1.default.bold((0, server_client_1.printable)(proposal.hash))} on ${serverName}, proposed by ${(0, server_client_1.printable)(proposal.proposedBy.sub)} (${(0, server_client_1.printable)(proposal.proposedBy.iss)}), ${(0, server_client_1.printable)(proposal.state)}\n`);
|
|
242
|
+
for (const line of [
|
|
243
|
+
...annotationLines(proposal.annotations),
|
|
244
|
+
...flagLines(proposal),
|
|
245
|
+
...describePlan(proposal.plan ?? {}),
|
|
246
|
+
...(0, bundle_text_1.showBundle)(files),
|
|
247
|
+
]) {
|
|
248
|
+
environment.stdout.write(`${line}\n`);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const view = (await (0, server_client_1.callServer)(server, token, 'GET', `/v1/approvals/${target}`)).body;
|
|
253
|
+
for (const line of describeApproval(view)) {
|
|
115
254
|
environment.stdout.write(`${line}\n`);
|
|
116
255
|
}
|
|
117
256
|
environment.stdout.write(view.plan ? `${describePlan(view.plan).join('\n')}\n` : 'the server did not record what its tasks do\n');
|
|
@@ -130,7 +269,10 @@ function registerWorkCommands(remote, context) {
|
|
|
130
269
|
for (const proposal of body.proposals) {
|
|
131
270
|
const who = proposal.mine ? 'you' : (0, server_client_1.printable)(proposal.proposedBy.sub);
|
|
132
271
|
const state = proposal.canDecide ? colors_1.default.bold('waits for your approval') : (0, server_client_1.printable)(proposal.state);
|
|
133
|
-
environment.stdout.write(`${colors_1.default.bold(
|
|
272
|
+
environment.stdout.write(`${colors_1.default.bold((0, server_client_1.printable)(proposal.hash))} proposed by ${who}, ${state}\n`);
|
|
273
|
+
for (const line of annotationLines(proposal.annotations)) {
|
|
274
|
+
environment.stdout.write(`${line}\n`);
|
|
275
|
+
}
|
|
134
276
|
const names = [...proposal.tasks, ...proposal.services];
|
|
135
277
|
environment.stdout.write(` tasks and services: ${names.map(server_client_1.printable).join(', ') || 'none'}\n`);
|
|
136
278
|
if (proposal.accounts.length > 0) {
|
|
@@ -141,9 +283,6 @@ function registerWorkCommands(remote, context) {
|
|
|
141
283
|
for (const line of flagLines(proposal)) {
|
|
142
284
|
environment.stdout.write(`${line}\n`);
|
|
143
285
|
}
|
|
144
|
-
if (proposal.stale) {
|
|
145
|
-
environment.stdout.write(` stale: ${(0, server_client_1.printable)(proposal.name)} has a newer version, reject it and propose again on top of it\n`);
|
|
146
|
-
}
|
|
147
286
|
if (proposal.state === 'waiting') {
|
|
148
287
|
environment.stdout.write(` decide before ${(0, server_client_1.printable)(proposal.expiresAt)}\n`);
|
|
149
288
|
}
|
|
@@ -152,43 +291,19 @@ function registerWorkCommands(remote, context) {
|
|
|
152
291
|
}
|
|
153
292
|
}
|
|
154
293
|
}));
|
|
155
|
-
remote
|
|
156
|
-
.command('diff <proposal> [server]')
|
|
157
|
-
.description('show what a proposed version (name@hash) changes against the version it follows')
|
|
158
|
-
.action(guarded('Remote diff was not successful', async (target, name) => {
|
|
159
|
-
const proposal = parseProposal(target);
|
|
160
|
-
if (!proposal) {
|
|
161
|
-
throw new Error(`${(0, server_client_1.printable)(target)} is not a proposal, list them with: hammerkit remote proposals`);
|
|
162
|
-
}
|
|
163
|
-
const { name: serverName, server, token } = await connect(name);
|
|
164
|
-
const path = `/v1/proposals/${encodeURIComponent(proposal.name)}/${proposal.hash}`;
|
|
165
|
-
const view = (await (0, server_client_1.callServer)(server, token, 'GET', path)).body;
|
|
166
|
-
const proposed = (await (0, server_client_1.callServer)(server, token, 'GET', `${path}/files`)).body;
|
|
167
|
-
const base = view.base
|
|
168
|
-
? (await (0, server_client_1.callServer)(server, token, 'GET', `/v1/definitions/${encodeURIComponent(proposal.name)}/${view.base}/files`)).body
|
|
169
|
-
: undefined;
|
|
170
|
-
environment.stdout.write(`${(0, server_client_1.printable)(proposal.name)}@${(0, server_client_1.printable)(proposal.hash)} on ${serverName}, proposed by ${(0, server_client_1.printable)(view.proposedBy.sub)} (${(0, server_client_1.printable)(view.proposedBy.iss)})\n`);
|
|
171
|
-
environment.stdout.write(base ? `compared with ${(0, server_client_1.printable)(view.base)}\n` : 'no version is published yet, everything is new\n');
|
|
172
|
-
if (view.stale) {
|
|
173
|
-
environment.stdout.write(`warning: ${(0, server_client_1.printable)(proposal.name)} has a newer version (${(0, server_client_1.printable)(view.latest)}), this can only be rejected\n`);
|
|
174
|
-
}
|
|
175
|
-
const lines = (0, text_diff_1.diffBundles)(base, proposed);
|
|
176
|
-
environment.stdout.write(lines.length > 0 ? `${lines.join('\n')}\n` : 'the files are the same\n');
|
|
177
|
-
}));
|
|
178
294
|
for (const action of ['yank', 'unyank']) {
|
|
179
295
|
remote
|
|
180
296
|
.command(`${action} <version> [server]`)
|
|
181
297
|
.description(action === 'yank'
|
|
182
|
-
? 'pull a published version (
|
|
183
|
-
: 'let a yanked version (
|
|
298
|
+
? 'pull a published version (sha256:...) back: no run starts from it'
|
|
299
|
+
: 'let a yanked version (sha256:...) go on')
|
|
184
300
|
.addOption(new commander_1.Option('--reason <text>', 'why, shown to whoever tries to run it').argParser((value) => value))
|
|
185
301
|
.action(guarded(`Remote ${action} was not successful`, async (target, name, options) => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
throw new Error(`${(0, server_client_1.printable)(target)} is not a version, name it like payments@sha256:...`);
|
|
302
|
+
if (!bundle_1.HASH_PATTERN.test(target)) {
|
|
303
|
+
throw new Error(`${(0, server_client_1.printable)(target)} is not a version, name it by its hash: sha256:...`);
|
|
189
304
|
}
|
|
190
305
|
const { name: serverName, server, token } = await connect(name);
|
|
191
|
-
await (0, server_client_1.callServer)(server, token, 'POST', `/v1/definitions/${
|
|
306
|
+
await (0, server_client_1.callServer)(server, token, 'POST', `/v1/definitions/${target}/${action}`, options.reason ? { reason: options.reason } : undefined);
|
|
192
307
|
environment.stdout.write(`${action === 'yank' ? 'yanked' : 'let go on'} ${(0, server_client_1.printable)(target)} on ${serverName}\n`);
|
|
193
308
|
}));
|
|
194
309
|
}
|
|
@@ -196,18 +311,16 @@ function registerWorkCommands(remote, context) {
|
|
|
196
311
|
remote
|
|
197
312
|
.command(`${action} <target> [server]`)
|
|
198
313
|
.description(action === 'approve'
|
|
199
|
-
? 'let a run (run_...) or a proposed version (
|
|
200
|
-
: 'end a run (run_...) or drop a proposed version (
|
|
314
|
+
? 'let a run (run_...) or a proposed version (sha256:...) that waits for your approval go ahead'
|
|
315
|
+
: 'end a run (run_...) or drop a proposed version (sha256:...) that waits for your approval')
|
|
201
316
|
.addOption(new commander_1.Option('--reason <text>', 'why, shown to who asked').argParser((value) => value))
|
|
202
317
|
.action(guarded(`Remote ${action} was not successful`, async (target, name, options) => {
|
|
203
|
-
const proposal =
|
|
318
|
+
const proposal = bundle_1.HASH_PATTERN.test(target);
|
|
204
319
|
if (!proposal && !RUN_ID.test(target)) {
|
|
205
320
|
throw new Error(`${(0, server_client_1.printable)(target)} is not a run id or a proposed version, list them with: hammerkit remote approvals or remote proposals`);
|
|
206
321
|
}
|
|
207
322
|
const { name: serverName, server, token } = await connect(name);
|
|
208
|
-
const path = proposal
|
|
209
|
-
? `/v1/proposals/${encodeURIComponent(proposal.name)}/${proposal.hash}/${action}`
|
|
210
|
-
: `/v1/approvals/${target}/${action}`;
|
|
323
|
+
const path = proposal ? `/v1/proposals/${target}/${action}` : `/v1/approvals/${target}/${action}`;
|
|
211
324
|
const { body } = await (0, server_client_1.callServer)(server, token, 'POST', path, options.reason ? { reason: options.reason } : undefined);
|
|
212
325
|
const rejected = body.state === 'rejected';
|
|
213
326
|
environment.stdout.write(proposal
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.callServer = exports.getMe = exports.getDiscovery = exports.printable = exports.ServerError = void 0;
|
|
3
|
+
exports.callServer = exports.getMe = exports.getDiscovery = exports.printableWhole = exports.printable = exports.ServerError = void 0;
|
|
4
4
|
const discovery_1 = require("../server/discovery");
|
|
5
5
|
const TIMEOUT_MS = 10000;
|
|
6
6
|
const WRITE_TIMEOUT_MS = 30000;
|
|
7
7
|
class ServerError extends Error {
|
|
8
|
+
// what the server answered, when it did
|
|
9
|
+
constructor(message, status) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
8
13
|
}
|
|
9
14
|
exports.ServerError = ServerError;
|
|
10
15
|
// Text from the server or the identity provider is shown to the user: no
|
|
11
|
-
// control characters, so it can't rewrite the terminal
|
|
16
|
+
// control characters, so it can't rewrite the terminal, and format characters
|
|
17
|
+
// (bidi overrides, zero-width) written out, so text reads in the order it runs.
|
|
18
|
+
// Long text is cut, and says how much was.
|
|
12
19
|
function printable(value) {
|
|
20
|
+
const text = printableWhole(value);
|
|
21
|
+
return text.length > 300 ? `${text.slice(0, 300)}… (+${text.length - 300} chars)` : text;
|
|
22
|
+
}
|
|
23
|
+
exports.printable = printable;
|
|
24
|
+
// The same, never cut: for what somebody decides on, such as a command.
|
|
25
|
+
function printableWhole(value) {
|
|
13
26
|
return String(value)
|
|
14
27
|
.replace(/\p{Cc}/gu, '?')
|
|
15
|
-
.
|
|
28
|
+
.replace(/\p{Cf}/gu, (char) => `\\u{${char.codePointAt(0)?.toString(16)}}`);
|
|
16
29
|
}
|
|
17
|
-
exports.
|
|
30
|
+
exports.printableWhole = printableWhole;
|
|
18
31
|
async function getJson(url, token) {
|
|
19
32
|
let response;
|
|
20
33
|
try {
|
|
@@ -80,7 +93,7 @@ async function callServer(server, token, method, path, body) {
|
|
|
80
93
|
}
|
|
81
94
|
if (!response.ok) {
|
|
82
95
|
const said = answer && typeof answer.error === 'string' ? `: ${printable(answer.error)}` : '';
|
|
83
|
-
throw new ServerError(`${server.url} answered ${response.status}${said}
|
|
96
|
+
throw new ServerError(`${server.url} answered ${response.status}${said}`, response.status);
|
|
84
97
|
}
|
|
85
98
|
return { status: response.status, body: answer };
|
|
86
99
|
}
|
|
@@ -53,6 +53,16 @@ function hashSource(manifest) {
|
|
|
53
53
|
}
|
|
54
54
|
exports.hashSource = hashSource;
|
|
55
55
|
const DENIED_DIRECTORIES = ['.git', '.ssh', '.aws', '.gnupg', '.kube'];
|
|
56
|
+
// the short names Windows gives these directories, `GIT~1` for `.git`
|
|
57
|
+
const DENIED_SHORT_NAMES = /^(git|ssh|aws|gnupg|kube)~\d+$/;
|
|
58
|
+
// A name as a case-insensitive disk opens it: Windows and macOS open `.SSH` as `.ssh`, and
|
|
59
|
+
// Windows drops trailing dots and spaces (`.git.`) and reads `name:stream` as `name`.
|
|
60
|
+
function canonicalName(name) {
|
|
61
|
+
return name
|
|
62
|
+
.replace(/:.*$/, '')
|
|
63
|
+
.replace(/[. ]+$/, '')
|
|
64
|
+
.toLowerCase();
|
|
65
|
+
}
|
|
56
66
|
const DENIED_NAMES = [
|
|
57
67
|
/^\.env$/i,
|
|
58
68
|
/^\.env\.(?!example$|sample$|template$|dist$)/i,
|
|
@@ -65,12 +75,15 @@ const DENIED_NAMES = [
|
|
|
65
75
|
// must not be what decides that a key leaves the machine.
|
|
66
76
|
function deniedSourcePath(path) {
|
|
67
77
|
const parts = path.split('/');
|
|
68
|
-
const directory = parts.slice(0, -1).find((part) =>
|
|
78
|
+
const directory = parts.slice(0, -1).find((part) => {
|
|
79
|
+
const name = canonicalName(part);
|
|
80
|
+
return DENIED_DIRECTORIES.includes(name) || DENIED_SHORT_NAMES.test(name);
|
|
81
|
+
});
|
|
69
82
|
if (directory !== undefined) {
|
|
70
83
|
return `${directory}/ holds credentials or history`;
|
|
71
84
|
}
|
|
72
85
|
const name = parts[parts.length - 1];
|
|
73
|
-
if (DENIED_NAMES.some((pattern) => pattern.test(name))) {
|
|
86
|
+
if (DENIED_NAMES.some((pattern) => pattern.test(name) || pattern.test(canonicalName(name)))) {
|
|
74
87
|
return `${name} looks like a credential file`;
|
|
75
88
|
}
|
|
76
89
|
return null;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTokenSource = exports.isToken = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const credentials_1 = require("./credentials");
|
|
6
|
+
// A bearer token is one word of visible ASCII; anything else is output of a token command
|
|
7
|
+
// that holds more than the token, which must never reach an error message.
|
|
8
|
+
function isToken(value) {
|
|
9
|
+
return /^[\x21-\x7e]+$/.test(value);
|
|
10
|
+
}
|
|
11
|
+
exports.isToken = isToken;
|
|
12
|
+
/**
|
|
13
|
+
* The token a client of a server sends: a harness, and the `remote` commands. In this order:
|
|
14
|
+
*
|
|
15
|
+
* - `HAMMERKIT_TOKEN_COMMAND`: a command that prints a short-lived token of the agent's own
|
|
16
|
+
* identity, such as a workload identity or a service account of the identity provider. It
|
|
17
|
+
* runs again when the server no longer accepts the token.
|
|
18
|
+
* - `HAMMERKIT_TOKEN`: such a token, as the agent's platform hands it over.
|
|
19
|
+
* - the sign-in of `hammerkit remote login <server>`.
|
|
20
|
+
*
|
|
21
|
+
* The token only goes to the URL the server is registered with in the host config.
|
|
22
|
+
*/
|
|
23
|
+
function getTokenSource(environment, name, server) {
|
|
24
|
+
const command = environment.processEnvs.HAMMERKIT_TOKEN_COMMAND;
|
|
25
|
+
if (command) {
|
|
26
|
+
let current;
|
|
27
|
+
const run = () => new Promise((resolve, reject) => {
|
|
28
|
+
(0, child_process_1.exec)(command, { env: environment.processEnvs, timeout: 60000 }, (error, stdout) => {
|
|
29
|
+
const token = String(stdout).trim();
|
|
30
|
+
if (error || !token) {
|
|
31
|
+
reject(new Error('HAMMERKIT_TOKEN_COMMAND printed no token'));
|
|
32
|
+
}
|
|
33
|
+
else if (!isToken(token)) {
|
|
34
|
+
// never the output itself: it can hold more secrets than the token
|
|
35
|
+
reject(new Error('HAMMERKIT_TOKEN_COMMAND printed more than a token, it has to print the token alone'));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
resolve(token);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
token: () => {
|
|
44
|
+
current = current ?? run();
|
|
45
|
+
current.catch(() => (current = undefined));
|
|
46
|
+
return current;
|
|
47
|
+
},
|
|
48
|
+
renew: async () => {
|
|
49
|
+
current = undefined;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const fixed = environment.processEnvs.HAMMERKIT_TOKEN?.trim();
|
|
54
|
+
if (fixed) {
|
|
55
|
+
return {
|
|
56
|
+
token: async () => {
|
|
57
|
+
if (!isToken(fixed)) {
|
|
58
|
+
throw new Error('HAMMERKIT_TOKEN holds more than a token, it has to be the token alone');
|
|
59
|
+
}
|
|
60
|
+
return fixed;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { token: () => (0, credentials_1.getAccessToken)(environment, name, server) };
|
|
65
|
+
}
|
|
66
|
+
exports.getTokenSource = getTokenSource;
|
|
67
|
+
//# sourceMappingURL=token-source.js.map
|
|
@@ -12,6 +12,7 @@ const duration_schema_1 = require("./duration-schema");
|
|
|
12
12
|
const build_file_secret_schema_1 = require("./build-file-secret-schema");
|
|
13
13
|
const resources_schema_1 = require("./resources-schema");
|
|
14
14
|
const runner_schema_1 = require("./runner-schema");
|
|
15
|
+
const network_schema_1 = require("./network-schema");
|
|
15
16
|
// what `extend` adds to (the rest, e.g. `image` or `shell`, the task just overrides)
|
|
16
17
|
exports.RESET_PROPERTIES = ['deps', 'needs', 'src', 'generates', 'cmds', 'mounts', 'envs', 'labels'];
|
|
17
18
|
exports.buildFileBaseTaskSchema = (0, zod_1.object)({
|
|
@@ -39,6 +40,8 @@ exports.buildFileBaseTaskSchema = (0, zod_1.object)({
|
|
|
39
40
|
account: (0, zod_1.string)().optional(),
|
|
40
41
|
// CPU and memory limits of the container; a local task is not limited
|
|
41
42
|
resources: resources_schema_1.resourcesSchema.optional(),
|
|
43
|
+
// what the container reaches besides its needs; a local task can't be limited
|
|
44
|
+
network: network_schema_1.networkSchema.optional(),
|
|
42
45
|
// Name of the server (see `hammerkit remote`) the task has to run on; defaults
|
|
43
46
|
// to the build file's `runner`. Never a local fallback when it is unknown.
|
|
44
47
|
runner: runner_schema_1.runnerSchema.optional(),
|
|
@@ -8,6 +8,8 @@ const labels_schema_1 = require("./labels-schema");
|
|
|
8
8
|
const build_file_service_container_healthcheck_1 = require("./build-file-service-container-healthcheck");
|
|
9
9
|
const build_file_secret_schema_1 = require("./build-file-secret-schema");
|
|
10
10
|
const resources_schema_1 = require("./resources-schema");
|
|
11
|
+
const size_schema_1 = require("./size-schema");
|
|
12
|
+
const network_schema_1 = require("./network-schema");
|
|
11
13
|
exports.buildFileContainerServiceSchema = (0, zod_1.object)({
|
|
12
14
|
image: (0, zod_1.string)(),
|
|
13
15
|
description: (0, zod_1.string)().optional(),
|
|
@@ -39,6 +41,10 @@ exports.buildFileContainerServiceSchema = (0, zod_1.object)({
|
|
|
39
41
|
// the service account its provider secrets are read as, unless a secret names one
|
|
40
42
|
account: (0, zod_1.string)().optional(),
|
|
41
43
|
resources: resources_schema_1.resourcesSchema.optional(),
|
|
44
|
+
// on Kubernetes, the size of the claim that holds its sources, mounts and volumes
|
|
45
|
+
volumeSize: size_schema_1.sizeSchema.describe('size of its claim on Kubernetes, e.g. 10Gi').optional(),
|
|
46
|
+
// what the service reaches besides its needs; tasks needing it always reach it
|
|
47
|
+
network: network_schema_1.networkSchema.optional(),
|
|
42
48
|
})
|
|
43
49
|
.strict()
|
|
44
50
|
.describe('container service');
|
|
@@ -3,10 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.buildFileContainerTaskSchema = void 0;
|
|
4
4
|
const build_file_base_task_schema_1 = require("./build-file-base-task-schema");
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
|
+
const size_schema_1 = require("./size-schema");
|
|
6
7
|
exports.buildFileContainerTaskSchema = build_file_base_task_schema_1.buildFileBaseTaskSchema
|
|
7
8
|
.extend({
|
|
8
9
|
image: (0, zod_1.string)(),
|
|
9
10
|
mounts: (0, zod_1.array)((0, zod_1.string)()).optional(),
|
|
11
|
+
// on Kubernetes, the size of the claim that holds its sources, mounts and outputs
|
|
12
|
+
volumeSize: size_schema_1.sizeSchema.describe('size of its claim on Kubernetes, e.g. 10Gi').optional(),
|
|
10
13
|
})
|
|
11
14
|
.strict()
|
|
12
15
|
.describe('container task');
|
|
@@ -3,11 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.buildFileEnvironmentSchema = exports.buildFileDockerEnvironmentSchema = exports.buildFileKubernetesEnvironmentSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const build_file_environment_schema_http_route_1 = require("./build-file-environment-schema-http-route");
|
|
6
|
+
const size_schema_1 = require("./size-schema");
|
|
6
7
|
exports.buildFileKubernetesEnvironmentSchema = (0, zod_1.object)({
|
|
7
8
|
namespace: (0, zod_1.string)().optional(),
|
|
8
9
|
context: (0, zod_1.string)(),
|
|
9
10
|
kubeconfig: (0, zod_1.string)().optional(),
|
|
10
11
|
httpRoutes: (0, zod_1.array)(build_file_environment_schema_http_route_1.buildFileEnvironmentSchemaHttpRoute).optional(),
|
|
12
|
+
// Hammerkit can't tell from the API whether the cluster's network plugin
|
|
13
|
+
// enforces NetworkPolicy, so the build file states it before any task or
|
|
14
|
+
// service there may have a limited network.
|
|
15
|
+
networkPolicy: (0, zod_1.literal)('enforced')
|
|
16
|
+
.describe('the cluster enforces NetworkPolicy, which limited networks need')
|
|
17
|
+
.optional(),
|
|
18
|
+
// the size of the claim of a task or service that states none (1Gi when left out)
|
|
19
|
+
volumeSize: size_schema_1.sizeSchema.describe('size of the claim of each task and service, 1Gi by default').optional(),
|
|
11
20
|
// Ingress support was removed in 1.8.0. Fail instead of dropping the routes.
|
|
12
21
|
ingresses: (0, zod_1.never)({
|
|
13
22
|
errorMap: () => ({
|