Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf4eeb36

Browse files
authored
Start workspaces by shelling out to CLI (#400)
Signed-off-by: Aaron Lehmann <alehmann@netflix.com>
1 parentda1aaed commitf4eeb36

File tree

3 files changed

+133
-37
lines changed

3 files changed

+133
-37
lines changed

‎src/api.ts

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import{spawn}from"child_process"
12
import{Api}from"coder/site/src/api/api"
23
import{ProvisionerJobLog,Workspace}from"coder/site/src/api/typesGenerated"
34
importfsfrom"fs/promises"
@@ -122,29 +123,66 @@ export async function makeCoderSdk(baseUrl: string, token: string | undefined, s
122123
/**
123124
* Start or update a workspace and return the updated workspace.
124125
*/
125-
exportasyncfunctionstartWorkspaceIfStoppedOrFailed(restClient:Api,workspace:Workspace):Promise<Workspace>{
126-
// If the workspace requires the latest active template version, we should attempt
127-
// to update that here.
128-
// TODO: If param set changes, what do we do??
129-
constversionID=workspace.template_require_active_version
130-
?// Use the latest template version
131-
workspace.template_active_version_id
132-
:// Default to not updating the workspace if not required.
133-
workspace.latest_build.template_version_id
134-
126+
exportasyncfunctionstartWorkspaceIfStoppedOrFailed(
127+
restClient:Api,
128+
globalConfigDir:string,
129+
binPath:string,
130+
workspace:Workspace,
131+
writeEmitter:vscode.EventEmitter<string>,
132+
):Promise<Workspace>{
135133
// Before we start a workspace, we make an initial request to check it's not already started
136134
constupdatedWorkspace=awaitrestClient.getWorkspace(workspace.id)
137135

138136
if(!["stopped","failed"].includes(updatedWorkspace.latest_build.status)){
139137
returnupdatedWorkspace
140138
}
141139

142-
constlatestBuild=awaitrestClient.startWorkspace(updatedWorkspace.id,versionID)
140+
returnnewPromise((resolve,reject)=>{
141+
conststartArgs=[
142+
"--global-config",
143+
globalConfigDir,
144+
"start",
145+
"--yes",
146+
workspace.owner_name+"/"+workspace.name,
147+
]
148+
conststartProcess=spawn(binPath,startArgs)
149+
150+
startProcess.stdout.on("data",(data:Buffer)=>{
151+
data
152+
.toString()
153+
.split(/\r*\n/)
154+
.forEach((line:string)=>{
155+
if(line!==""){
156+
writeEmitter.fire(line.toString()+"\r\n")
157+
}
158+
})
159+
})
160+
161+
letcapturedStderr=""
162+
startProcess.stderr.on("data",(data:Buffer)=>{
163+
data
164+
.toString()
165+
.split(/\r*\n/)
166+
.forEach((line:string)=>{
167+
if(line!==""){
168+
writeEmitter.fire(line.toString()+"\r\n")
169+
capturedStderr+=line.toString()+"\n"
170+
}
171+
})
172+
})
143173

144-
return{
145-
...updatedWorkspace,
146-
latest_build:latestBuild,
147-
}
174+
startProcess.on("close",(code:number)=>{
175+
if(code===0){
176+
resolve(restClient.getWorkspace(workspace.id))
177+
}else{
178+
leterrorText=`"${startArgs.join(" ")}" exited with code${code}`
179+
if(capturedStderr!==""){
180+
errorText+=`:${capturedStderr}`
181+
}
182+
reject(newError(errorText))
183+
}
184+
})
185+
})
148186
}
149187

150188
/**

‎src/remote.ts

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,12 @@ export class Remote {
5050
/**
5151
* Try to get the workspace running. Return undefined if the user canceled.
5252
*/
53-
privateasyncmaybeWaitForRunning(restClient:Api,workspace:Workspace):Promise<Workspace|undefined>{
53+
privateasyncmaybeWaitForRunning(
54+
restClient:Api,
55+
workspace:Workspace,
56+
label:string,
57+
binPath:string,
58+
):Promise<Workspace|undefined>{
5459
// Maybe already running?
5560
if(workspace.latest_build.status==="running"){
5661
returnworkspace
@@ -63,6 +68,28 @@ export class Remote {
6368
letterminal:undefined|vscode.Terminal
6469
letattempts=0
6570

71+
functioninitWriteEmitterAndTerminal():vscode.EventEmitter<string>{
72+
if(!writeEmitter){
73+
writeEmitter=newvscode.EventEmitter<string>()
74+
}
75+
if(!terminal){
76+
terminal=vscode.window.createTerminal({
77+
name:"Build Log",
78+
location:vscode.TerminalLocation.Panel,
79+
// Spin makes this gear icon spin!
80+
iconPath:newvscode.ThemeIcon("gear~spin"),
81+
pty:{
82+
onDidWrite:writeEmitter.event,
83+
close:()=>undefined,
84+
open:()=>undefined,
85+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
86+
}asPartial<vscode.Pseudoterminal>asany,
87+
})
88+
terminal.show(true)
89+
}
90+
returnwriteEmitter
91+
}
92+
6693
try{
6794
// Show a notification while we wait.
6895
returnawaitthis.vscodeProposed.window.withProgress(
@@ -72,39 +99,30 @@ export class Remote {
7299
title:"Waiting for workspace build...",
73100
},
74101
async()=>{
102+
constglobalConfigDir=path.dirname(this.storage.getSessionTokenPath(label))
75103
while(workspace.latest_build.status!=="running"){
76104
++attempts
77105
switch(workspace.latest_build.status){
78106
case"pending":
79107
case"starting":
80108
case"stopping":
81-
if(!writeEmitter){
82-
writeEmitter=newvscode.EventEmitter<string>()
83-
}
84-
if(!terminal){
85-
terminal=vscode.window.createTerminal({
86-
name:"Build Log",
87-
location:vscode.TerminalLocation.Panel,
88-
// Spin makes this gear icon spin!
89-
iconPath:newvscode.ThemeIcon("gear~spin"),
90-
pty:{
91-
onDidWrite:writeEmitter.event,
92-
close:()=>undefined,
93-
open:()=>undefined,
94-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
95-
}asPartial<vscode.Pseudoterminal>asany,
96-
})
97-
terminal.show(true)
98-
}
109+
writeEmitter=initWriteEmitterAndTerminal()
99110
this.storage.writeToCoderOutputChannel(`Waiting for${workspaceName}...`)
100111
workspace=awaitwaitForBuild(restClient,writeEmitter,workspace)
101112
break
102113
case"stopped":
103114
if(!(awaitthis.confirmStart(workspaceName))){
104115
returnundefined
105116
}
117+
writeEmitter=initWriteEmitterAndTerminal()
106118
this.storage.writeToCoderOutputChannel(`Starting${workspaceName}...`)
107-
workspace=awaitstartWorkspaceIfStoppedOrFailed(restClient,workspace)
119+
workspace=awaitstartWorkspaceIfStoppedOrFailed(
120+
restClient,
121+
globalConfigDir,
122+
binPath,
123+
workspace,
124+
writeEmitter,
125+
)
108126
break
109127
case"failed":
110128
// On a first attempt, we will try starting a failed workspace
@@ -113,8 +131,15 @@ export class Remote {
113131
if(!(awaitthis.confirmStart(workspaceName))){
114132
returnundefined
115133
}
134+
writeEmitter=initWriteEmitterAndTerminal()
116135
this.storage.writeToCoderOutputChannel(`Starting${workspaceName}...`)
117-
workspace=awaitstartWorkspaceIfStoppedOrFailed(restClient,workspace)
136+
workspace=awaitstartWorkspaceIfStoppedOrFailed(
137+
restClient,
138+
globalConfigDir,
139+
binPath,
140+
workspace,
141+
writeEmitter,
142+
)
118143
break
119144
}
120145
// Otherwise fall through and error.
@@ -156,6 +181,9 @@ export class Remote {
156181

157182
constworkspaceName=`${parts.username}/${parts.workspace}`
158183

184+
// Migrate "session_token" file to "session", if needed.
185+
awaitthis.storage.migrateSessionToken(parts.label)
186+
159187
// Get the URL and token belonging to this host.
160188
const{url:baseUrlRaw, token}=awaitthis.storage.readCliConfig(parts.label)
161189

@@ -292,7 +320,7 @@ export class Remote {
292320
disposables.push(this.registerLabelFormatter(remoteAuthority,workspace.owner_name,workspace.name))
293321

294322
// If the workspace is not in a running state, try to get it running.
295-
constupdatedWorkspace=awaitthis.maybeWaitForRunning(workspaceRestClient,workspace)
323+
constupdatedWorkspace=awaitthis.maybeWaitForRunning(workspaceRestClient,workspace,parts.label,binaryPath)
296324
if(!updatedWorkspace){
297325
// User declined to start the workspace.
298326
awaitthis.closeRemote()

‎src/storage.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,20 @@ export class Storage {
405405
* The caller must ensure this directory exists before use.
406406
*/
407407
publicgetSessionTokenPath(label:string):string{
408+
returnlabel
409+
?path.join(this.globalStorageUri.fsPath,label,"session")
410+
:path.join(this.globalStorageUri.fsPath,"session")
411+
}
412+
413+
/**
414+
* Return the directory for the deployment with the provided label to where
415+
* its session token was stored by older code.
416+
*
417+
* If the label is empty, read the old deployment-unaware config instead.
418+
*
419+
* The caller must ensure this directory exists before use.
420+
*/
421+
publicgetLegacySessionTokenPath(label:string):string{
408422
returnlabel
409423
?path.join(this.globalStorageUri.fsPath,label,"session_token")
410424
:path.join(this.globalStorageUri.fsPath,"session_token")
@@ -488,6 +502,22 @@ export class Storage {
488502
}
489503
}
490504

505+
/**
506+
* Migrate the session token file from "session_token" to "session", if needed.
507+
*/
508+
publicasyncmigrateSessionToken(label:string){
509+
constoldTokenPath=this.getLegacySessionTokenPath(label)
510+
constnewTokenPath=this.getSessionTokenPath(label)
511+
try{
512+
awaitfs.rename(oldTokenPath,newTokenPath)
513+
}catch(error){
514+
if((errorasNodeJS.ErrnoException)?.code==="ENOENT"){
515+
return
516+
}
517+
throwerror
518+
}
519+
}
520+
491521
/**
492522
* Run the header command and return the generated headers.
493523
*/

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp