Skip to content

Commit 211a07d

Browse files
things
1 parent 58558da commit 211a07d

File tree

13 files changed

+215
-138
lines changed

13 files changed

+215
-138
lines changed

openapi.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,14 @@
10611061
"description": "GitHub token for the session",
10621062
"type": "string"
10631063
},
1064+
"git_user_email": {
1065+
"description": "Git user email to configure for this session",
1066+
"type": "string"
1067+
},
1068+
"git_user_name": {
1069+
"description": "Git user name to configure for this session",
1070+
"type": "string"
1071+
},
10641072
"permission": {
10651073
"description": "Permission level for the session",
10661074
"enum": ["read", "write"],
@@ -1962,7 +1970,7 @@
19621970
"/vm/{id}/sessions": {
19631971
"post": {
19641972
"callbacks": {},
1965-
"description": "Creates a new session on a running VM. A session represents an isolated Linux user, with their own container.\nA session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write).\nThe session is identified by a unique session ID, and the Linux username is based on the session ID.\n\nThis endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.\n",
1973+
"description": "Creates a new session on a running VM. A session represents an isolated Linux user, with their own container.\nA session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write).\nThe session is identified by a unique session ID, and the Linux username is based on the session ID.\n\nThe Git user name and email can be configured via parameters.\n\nThis endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.\n",
19661974
"operationId": "vm/create_session",
19671975
"parameters": [
19681976
{

src/SandboxClient.ts

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,34 @@ import {
2828
} from "./types";
2929
import { PitcherManagerResponse } from "@codesandbox/pitcher-client";
3030

31+
export async function startVm(
32+
apiClient: Client,
33+
sandboxId: string,
34+
startOpts?: StartSandboxOpts
35+
): Promise<PitcherManagerResponse> {
36+
const startResult = await vmStart({
37+
client: apiClient,
38+
body: startOpts
39+
? {
40+
ipcountry: startOpts.ipcountry,
41+
tier: startOpts.vmTier?.name,
42+
hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds,
43+
automatic_wakeup_config: startOpts.automaticWakeupConfig,
44+
}
45+
: undefined,
46+
path: {
47+
id: sandboxId,
48+
},
49+
});
50+
51+
const response = handleResponse(
52+
startResult,
53+
`Failed to start sandbox ${sandboxId}`
54+
);
55+
56+
return getStartResponse(response);
57+
}
58+
3159
export class SandboxClient {
3260
get defaultTemplateId() {
3361
return getDefaultTemplateId(this.apiClient);
@@ -48,7 +76,7 @@ export class SandboxClient {
4876
// We do not want users to pass gitAccessToken on global user, because it
4977
// can be read by other users
5078
{
51-
id: "clone-admin",
79+
id: "clone-repo-user",
5280
permission: "write",
5381
...(opts.config
5482
? {
@@ -112,38 +140,11 @@ export class SandboxClient {
112140
);
113141
}
114142

115-
private async start(
116-
sandboxId: string,
117-
startOpts?: StartSandboxOpts
118-
): Promise<PitcherManagerResponse> {
119-
const startResult = await vmStart({
120-
client: this.apiClient,
121-
body: startOpts
122-
? {
123-
ipcountry: startOpts.ipcountry,
124-
tier: startOpts.vmTier?.name,
125-
hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds,
126-
automatic_wakeup_config: startOpts.automaticWakeupConfig,
127-
}
128-
: undefined,
129-
path: {
130-
id: sandboxId,
131-
},
132-
});
133-
134-
const response = handleResponse(
135-
startResult,
136-
`Failed to start sandbox ${sandboxId}`
137-
);
138-
139-
return getStartResponse(response);
140-
}
141-
142143
/**
143144
*
144145
*/
145146
async resume(sandboxId: string) {
146-
const startResponse = await this.start(sandboxId);
147+
const startResponse = await startVm(this.apiClient, sandboxId);
147148
return new Sandbox(sandboxId, this.apiClient, startResponse);
148149
}
149150

@@ -171,7 +172,7 @@ export class SandboxClient {
171172
*/
172173
public async restart(sandboxId: string, opts?: StartSandboxOpts) {
173174
await this.shutdown(sandboxId);
174-
const startResponse = await this.start(sandboxId, opts);
175+
const startResponse = await startVm(this.apiClient, sandboxId, opts);
175176

176177
return new Sandbox(sandboxId, this.apiClient, startResponse);
177178
}

src/api-clients/client/sdk.gen.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ export const vmUpdateHibernationTimeout = <ThrowOnError extends boolean = false>
349349
* A session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write).
350350
* The session is identified by a unique session ID, and the Linux username is based on the session ID.
351351
*
352+
* The Git user name and email can be configured via parameters.
353+
*
352354
* This endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.
353355
*
354356
*/

src/api-clients/client/types.gen.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,14 @@ export type VmCreateSessionRequest = {
523523
* GitHub token for the session
524524
*/
525525
git_access_token?: string;
526+
/**
527+
* Git user email to configure for this session
528+
*/
529+
git_user_email?: string;
530+
/**
531+
* Git user name to configure for this session
532+
*/
533+
git_user_name?: string;
526534
/**
527535
* Permission level for the session
528536
*/

src/bin/commands/build.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { getDefaultTemplateId, handleResponse } from "../../utils/api";
2020
import { BASE_URL, getApiKey } from "../utils/constants";
2121
import { hashDirectory } from "../utils/hash";
22+
import { startVm } from "../../SandboxClient";
2223

2324
export type BuildCommandArgs = {
2425
directory: string;
@@ -172,10 +173,10 @@ export const buildCommand: yargs.CommandModule<
172173
createSpinnerMessage("Starting sandbox...", sandboxId)
173174
);
174175

175-
const startResponse = await sdk.sandbox["start"](sandboxId, {
176+
const startResponse = await startVm(apiClient, sandboxId, {
176177
vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined,
177178
});
178-
let sandbox = new Sandbox(sandboxId, startResponse, apiClient);
179+
let sandbox = new Sandbox(sandboxId, apiClient, startResponse);
179180
let session = await sandbox.connect();
180181

181182
spinner.start(
@@ -227,7 +228,7 @@ export const buildCommand: yargs.CommandModule<
227228

228229
buffer.push(...output.split("\n"));
229230

230-
await step.waitForFinish();
231+
await step.waitUntilComplete();
231232
} catch (error) {
232233
spinner.fail(
233234
createSpinnerMessage(

src/browser.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export async function connectToSandbox(options: {
4747
onFocusChange?: (cb: (isFocused: boolean) => void) => () => void;
4848
initStatusCb?: (event: protocol.system.InitStatus) => void;
4949
}): Promise<WebSocketSession> {
50+
let env: Record<string, string> = {};
51+
5052
const pitcherClient = await initPitcherClient(
5153
{
5254
appId: "sdk",
@@ -56,11 +58,19 @@ export async function connectToSandbox(options: {
5658
(() => {
5759
return () => {};
5860
}),
59-
requestPitcherInstance: options.getSession,
61+
requestPitcherInstance: async (id) => {
62+
const session = await options.getSession(id);
63+
64+
if (session.env) {
65+
env = session.env;
66+
}
67+
68+
return session;
69+
},
6070
subscriptions: DEFAULT_SUBSCRIPTIONS,
6171
},
6272
options.initStatusCb || (() => {})
6373
);
6474

65-
return new WebSocketSession(pitcherClient);
75+
return new WebSocketSession(pitcherClient, () => env);
6676
}

src/sandbox.ts

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {
22
Disposable,
3+
initPitcherClient,
34
PitcherManagerResponse,
45
type protocol as _protocol,
56
} from "@codesandbox/pitcher-client";
6-
import type {
7-
SandboxSession,
8-
SessionCreateOptions,
9-
SandboxBrowserSession,
7+
import {
8+
type SandboxSession,
9+
type SessionCreateOptions,
10+
type SandboxBrowserSession,
11+
DEFAULT_SUBSCRIPTIONS,
1012
} from "./types";
1113
import { Client } from "@hey-api/client-fetch";
1214
import {
@@ -18,6 +20,7 @@ import { handleResponse } from "./utils/api";
1820
import { VMTier } from "./VMTier";
1921
import { WebSocketSession } from "./sessions/WebSocketSession";
2022
import { RestSession } from "./sessions/RestSession";
23+
import { SandboxClient, startVm } from "./SandboxClient";
2124

2225
export class Sandbox {
2326
get bootupType() {
@@ -32,20 +35,19 @@ export class Sandbox {
3235
this.pitcherManagerResponse.pitcherVersion
3336
);
3437
}
35-
private defaultSession: SandboxSession;
36-
constructor(
37-
public id: string,
38-
private apiClient: Client,
39-
private pitcherManagerResponse: PitcherManagerResponse,
40-
customSession?: SandboxSession
41-
) {
42-
this.defaultSession = customSession || {
38+
private get globalSession() {
39+
return {
4340
sandboxId: this.id,
4441
pitcherToken: this.pitcherManagerResponse.pitcherToken,
4542
pitcherUrl: this.pitcherManagerResponse.pitcherURL,
4643
userWorkspacePath: this.pitcherManagerResponse.userWorkspacePath,
4744
};
4845
}
46+
constructor(
47+
public id: string,
48+
private apiClient: Client,
49+
private pitcherManagerResponse: PitcherManagerResponse
50+
) {}
4951

5052
/**
5153
* Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the
@@ -91,6 +93,13 @@ export class Sandbox {
9193
body: {
9294
session_id: opts.id,
9395
permission: opts.permission ?? "write",
96+
...(opts.git
97+
? {
98+
git_access_token: opts.git.accessToken,
99+
git_user_email: opts.git.email,
100+
git_user_name: opts.git.name,
101+
}
102+
: {}),
94103
},
95104
path: {
96105
id: this.id,
@@ -107,6 +116,7 @@ export class Sandbox {
107116
pitcherToken: handledResponse.pitcher_token,
108117
pitcherUrl: handledResponse.pitcher_url,
109118
userWorkspacePath: handledResponse.user_workspace_path,
119+
env: opts.env,
110120
};
111121

112122
return session;
@@ -115,17 +125,74 @@ export class Sandbox {
115125
async connect(
116126
customSession?: SessionCreateOptions
117127
): Promise<WebSocketSession> {
118-
const session = customSession
119-
? await this.createSession(customSession)
120-
: this.defaultSession;
128+
let hasConnected = false;
129+
const pitcherClient = await initPitcherClient(
130+
{
131+
appId: "sdk",
132+
instanceId: this.id,
133+
onFocusChange() {
134+
return () => {};
135+
},
136+
requestPitcherInstance: async () => {
137+
// If we reconnect we have to resume the Sandbox and get new session details
138+
if (hasConnected) {
139+
this.pitcherManagerResponse = await startVm(
140+
this.apiClient,
141+
this.id
142+
);
143+
}
144+
145+
const session = customSession
146+
? await this.createSession(customSession)
147+
: this.globalSession;
148+
149+
const headers = this.apiClient.getConfig().headers as Headers;
150+
151+
if (headers.get("x-pitcher-manager-url")) {
152+
// This is a hack, we need to tell the global scheduler that the VM is running
153+
// in a different cluster than the one it'd like to default to.
154+
155+
const preferredManager = headers
156+
.get("x-pitcher-manager-url")
157+
?.replace("/api/v1", "")
158+
.replace("https://", "");
159+
const baseUrl = this.apiClient
160+
.getConfig()
161+
.baseUrl?.replace("api", "global-scheduler");
162+
163+
await fetch(
164+
`${baseUrl}/api/v1/cluster/${session.sandboxId}?preferredManager=${preferredManager}`
165+
).then((res) => res.json());
166+
}
167+
168+
hasConnected = true;
169+
170+
return {
171+
bootupType: this.bootupType,
172+
pitcherURL: session.pitcherUrl,
173+
workspacePath: session.userWorkspacePath,
174+
userWorkspacePath: session.userWorkspacePath,
175+
pitcherManagerVersion:
176+
this.pitcherManagerResponse.pitcherManagerVersion,
177+
pitcherVersion: this.pitcherManagerResponse.pitcherVersion,
178+
latestPitcherVersion:
179+
this.pitcherManagerResponse.latestPitcherVersion,
180+
pitcherToken: session.pitcherToken,
181+
cluster: this.cluster,
182+
};
183+
},
184+
subscriptions: DEFAULT_SUBSCRIPTIONS,
185+
},
186+
() => {}
187+
);
121188

122-
return WebSocketSession.init(session, this.apiClient);
189+
return new WebSocketSession(pitcherClient, () => customSession?.env ?? {});
123190
}
124191

125192
async createRestSession(customSession?: SessionCreateOptions) {
126193
const session = customSession
127194
? await this.createSession(customSession)
128-
: this.defaultSession;
195+
: this.globalSession;
129196

130197
return new RestSession(session);
131198
}
@@ -135,10 +202,11 @@ export class Sandbox {
135202
): Promise<SandboxBrowserSession> {
136203
const session = customSession
137204
? await this.createSession(customSession)
138-
: this.defaultSession;
205+
: this.globalSession;
139206

140207
return {
141208
id: this.id,
209+
env: customSession?.env,
142210
bootupType: this.bootupType,
143211
cluster: this.cluster,
144212
latestPitcherVersion: this.pitcherManagerResponse.latestPitcherVersion,

0 commit comments

Comments
 (0)