Skip to content

Commit 359027b

Browse files
authored
Merge pull request #7676 from schrodingersket/openapi/compute-server-exec
OpenAPI docs: Added schema for `/api/v2/exec` endpoint for arbitrary project command execution.
2 parents f2d0515 + 5f52d01 commit 359027b

File tree

4 files changed

+115
-3
lines changed

4 files changed

+115
-3
lines changed

src/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ There are two types of file system build caching. These greatly improve the time
192192
It is handy to have a user with admin rights in your dev cocalc server. With the database running you can make a `[email protected]` an admin as follows:
193193

194194
```sh
195-
~/cocalc/src$ pnpm run c
195+
~/cocalc/src$ pnpm install -D express && pnpm run c
196196
...
197197
> db.make_user_admin({email_address:'[email protected]', cb:console.log})
198198
...

src/packages/next/lib/api/schema/compute/compute-server-action.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const ComputeServerActionInputSchema = z
1414
.describe("Action to be performed on the compute server."),
1515
})
1616
.describe(
17-
"Perform various action on a specific compute server (e.g., power off, deprovision, etc.).",
17+
`Perform various actions on a specific compute server (e.g., power off, deprovision,
18+
etc.).`,
1819
);
1920

2021
export const ComputeServerActionOutputSchema = z.union([
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { z } from "../framework";
2+
3+
import { FailedAPIOperationSchema } from "./common";
4+
import { ProjectIdSchema } from "./projects/common";
5+
import { ComputeServerIdSchema } from "./compute/common";
6+
7+
// OpenAPI spec
8+
//
9+
export const ExecInputSchema = z
10+
.object({
11+
project_id: ProjectIdSchema,
12+
compute_server_id: ComputeServerIdSchema.describe(
13+
`If provided, the desired shell command will be run on the compute server whose id
14+
is specified in this field (if available).`,
15+
).optional(),
16+
filesystem: z
17+
.boolean()
18+
.optional()
19+
.describe(
20+
`If \`true\`, this shell command runs in the fileserver container on the compute
21+
server; otherwise, it runs on the main compute container.`,
22+
),
23+
path: z
24+
.string()
25+
.optional()
26+
.describe(
27+
"Path to working directory in which the shell command should be executed.",
28+
),
29+
command: z.string().describe("The shell command to execute."),
30+
args: z
31+
.array(z.string())
32+
.optional()
33+
.describe("An array of arguments to pass to the shell command."),
34+
timeout: z
35+
.number()
36+
.min(0)
37+
.default(60)
38+
.optional()
39+
.describe("Number of seconds before this shell command times out."),
40+
max_output: z
41+
.number()
42+
.min(0)
43+
.optional()
44+
.describe("Maximum number of bytes to return from shell command output."),
45+
bash: z
46+
.boolean()
47+
.optional()
48+
.describe(
49+
`If \`true\`, this command runs in a \`bash\` shell. To do so, the provided shell
50+
command is written to a file and then executed via the \`bash\` command.`,
51+
),
52+
aggregate: z
53+
.union([
54+
z.number(),
55+
z.string(),
56+
z.object({ value: z.union([z.string(), z.number()]) }),
57+
])
58+
.optional()
59+
.describe(
60+
`If provided, this shell command is aggregated as in
61+
\`src/packages/backend/aggregate.js\`. This parameter allows one to specify
62+
multiple callbacks to be executed against the output of the same command
63+
(given identical arguments) within a 60-second window.`,
64+
),
65+
err_on_exit: z
66+
.boolean()
67+
.optional()
68+
.describe(
69+
`When \`true\`, this call will throw an error whenever the provided shell command
70+
exits with a non-zero exit code.`,
71+
),
72+
env: z
73+
.record(z.string(), z.string())
74+
.optional()
75+
.describe(
76+
"Environment variables to be passed to the shell command upon execution.",
77+
),
78+
})
79+
.describe("Perform arbitrary shell commands in a compute server or project.");
80+
81+
export const ExecOutputSchema = z.union([
82+
FailedAPIOperationSchema,
83+
z.any().describe("Output of executed command."),
84+
]);
85+
86+
export type ExecInput = z.infer<typeof ExecInputSchema>;
87+
export type ExecOutput = z.infer<typeof ExecOutputSchema>;

src/packages/next/pages/api/v2/exec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import getParams from "lib/api/get-params";
77
import callProject from "@cocalc/server/projects/call";
88
import isCollaborator from "@cocalc/server/projects/is-collaborator";
99

10-
export default async function handle(req, res) {
10+
import { apiRoute, apiRouteOperation } from "lib/api";
11+
import { ExecInputSchema, ExecOutputSchema } from "lib/api/schema/exec";
12+
13+
async function handle(req, res) {
1114
try {
1215
res.json(await get(req));
1316
} catch (err) {
@@ -65,3 +68,24 @@ async function get(req) {
6568
delete resp.id;
6669
return resp;
6770
}
71+
72+
export default apiRoute({
73+
exec: apiRouteOperation({
74+
method: "POST",
75+
openApiOperation: {
76+
tags: ["Utils"],
77+
},
78+
})
79+
.input({
80+
contentType: "application/json",
81+
body: ExecInputSchema,
82+
})
83+
.outputs([
84+
{
85+
status: 200,
86+
contentType: "application/octet-stream",
87+
body: ExecOutputSchema,
88+
},
89+
])
90+
.handler(handle),
91+
});

0 commit comments

Comments
 (0)