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

Commit2625f25

Browse files
committed
Add header command setting
This will be called before requests and added to the SSH config.
1 parent697b30e commit2625f25

File tree

7 files changed

+157
-4
lines changed

7 files changed

+157
-4
lines changed

‎package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
"markdownDescription":"The full path of the directory into which the Coder CLI will be downloaded. Defaults to the extension's global storage directory.",
5858
"type":"string",
5959
"default":""
60+
},
61+
"coder.headerCommand": {
62+
"markdownDescription":"An external command that outputs additional HTTP headers added to all requests. The command must output each header as `key=value` on its own line. The following environment variables will be available to the process: `CODER_URL`.",
63+
"type":"string",
64+
"default":""
6065
}
6166
}
6267
},

‎src/commands.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ export class Commands {
7070
severity:vscode.InputBoxValidationSeverity.Error,
7171
}
7272
}
73+
// This could be something like the header command erroring or an
74+
// invalid session token.
7375
return{
74-
message:"Invalid session token! ("+message+")",
76+
message:"Failed to authenticate:"+message,
7577
severity:vscode.InputBoxValidationSeverity.Error,
7678
}
7779
})

‎src/extension.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
5959
conststorage=newStorage(output,ctx.globalState,ctx.secrets,ctx.globalStorageUri,ctx.logUri)
6060
awaitstorage.init()
6161

62+
// Add headers from the header command.
63+
axios.interceptors.request.use(async(config)=>{
64+
return{
65+
...config,
66+
headers:{
67+
...(awaitstorage.getHeaders()),
68+
...creds.headers,
69+
},
70+
}
71+
})
72+
6273
constmyWorkspacesProvider=newWorkspaceProvider(WorkspaceQuery.Mine,storage)
6374
constallWorkspacesProvider=newWorkspaceProvider(WorkspaceQuery.All,storage)
6475

@@ -74,8 +85,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
7485
}
7586
}
7687
})
77-
.catch(()=>{
78-
// Not authenticated!
88+
.catch((error)=>{
89+
// This should be a failure to make the request, like the header command
90+
// errored.
91+
vscodeProposed.window.showErrorMessage("Failed to check user authentication: "+error.message)
7992
})
8093
.finally(()=>{
8194
vscode.commands.executeCommand("setContext","coder.loaded",true)

‎src/headers.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import*asosfrom"os"
2+
import{it,expect}from"vitest"
3+
import{getHeaders}from"./headers"
4+
5+
constlogger={
6+
writeToCoderOutputChannel(){
7+
// no-op
8+
},
9+
}
10+
11+
it("should return no headers",async()=>{
12+
awaitexpect(getHeaders(undefined,undefined,logger)).resolves.toStrictEqual({})
13+
awaitexpect(getHeaders("localhost",undefined,logger)).resolves.toStrictEqual({})
14+
awaitexpect(getHeaders(undefined,"command",logger)).resolves.toStrictEqual({})
15+
awaitexpect(getHeaders("localhost","",logger)).resolves.toStrictEqual({})
16+
awaitexpect(getHeaders("","command",logger)).resolves.toStrictEqual({})
17+
awaitexpect(getHeaders("localhost"," ",logger)).resolves.toStrictEqual({})
18+
awaitexpect(getHeaders(" ","command",logger)).resolves.toStrictEqual({})
19+
})
20+
21+
it("should return headers",async()=>{
22+
awaitexpect(getHeaders("localhost","printf foo=bar'\n'baz=qux",logger)).resolves.toStrictEqual({
23+
foo:"bar",
24+
baz:"qux",
25+
})
26+
awaitexpect(getHeaders("localhost","printf foo=bar'\r\n'baz=qux",logger)).resolves.toStrictEqual({
27+
foo:"bar",
28+
baz:"qux",
29+
})
30+
awaitexpect(getHeaders("localhost","printf foo=bar'\r\n'",logger)).resolves.toStrictEqual({foo:"bar"})
31+
awaitexpect(getHeaders("localhost","printf foo=bar",logger)).resolves.toStrictEqual({foo:"bar"})
32+
awaitexpect(getHeaders("localhost","printf foo=bar=",logger)).resolves.toStrictEqual({foo:"bar="})
33+
awaitexpect(getHeaders("localhost","printf foo=bar=baz",logger)).resolves.toStrictEqual({foo:"bar=baz"})
34+
awaitexpect(getHeaders("localhost","printf foo=",logger)).resolves.toStrictEqual({foo:""})
35+
})
36+
37+
it("should error on malformed or empty lines",async()=>{
38+
awaitexpect(getHeaders("localhost","printf foo=bar'\r\n\r\n'",logger)).rejects.toMatch(/Malformed/)
39+
awaitexpect(getHeaders("localhost","printf '\r\n'foo=bar",logger)).rejects.toMatch(/Malformed/)
40+
awaitexpect(getHeaders("localhost","printf =foo",logger)).rejects.toMatch(/Malformed/)
41+
awaitexpect(getHeaders("localhost","printf foo",logger)).rejects.toMatch(/Malformed/)
42+
awaitexpect(getHeaders("localhost","printf ' =foo'",logger)).rejects.toMatch(/Malformed/)
43+
awaitexpect(getHeaders("localhost","printf 'foo =bar'",logger)).rejects.toMatch(/Malformed/)
44+
awaitexpect(getHeaders("localhost","printf 'foo foo=bar'",logger)).rejects.toMatch(/Malformed/)
45+
awaitexpect(getHeaders("localhost","printf ''",logger)).rejects.toMatch(/Malformed/)
46+
})
47+
48+
it("should have access to environment variables",async()=>{
49+
constcoderUrl="dev.coder.com"
50+
awaitexpect(
51+
getHeaders(coderUrl,os.platform()==="win32" ?"printf url=%CODER_URL" :"printf url=$CODER_URL",logger),
52+
).resolves.toStrictEqual({url:coderUrl})
53+
})
54+
55+
it("should error on non-zero exit",async()=>{
56+
awaitexpect(getHeaders("localhost","exit 10",logger)).rejects.toMatch(/exitedunexpectedlywithcode10/)
57+
})

‎src/headers.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import*ascpfrom"child_process"
2+
import*asutilfrom"util"
3+
4+
exportinterfaceLogger{
5+
writeToCoderOutputChannel(message:string):void
6+
}
7+
8+
interfaceExecException{
9+
code?:number
10+
stderr?:string
11+
stdout?:string
12+
}
13+
14+
functionisExecException(err:unknown):err isExecException{
15+
returntypeof(errasExecException).code!=="undefined"
16+
}
17+
18+
// TODO: getHeaders might make more sense to directly implement on Storage
19+
// but it is difficult to test Storage right now since we use vitest instead of
20+
// the standard extension testing framework which would give us access to vscode
21+
// APIs. We should revert the testing framework then consider moving this.
22+
23+
// getHeaders executes the header command and parses the headers from stdout.
24+
// Both stdout and stderr are logged on error but stderr is otherwise ignored.
25+
// Throws an error if the process exits with non-zero or the JSON is invalid.
26+
// Returns undefined if there is no header command set. No effort is made to
27+
// validate the JSON other than making sure it can be parsed.
28+
exportasyncfunctiongetHeaders(
29+
url:string|undefined,
30+
command:string|undefined,
31+
logger:Logger,
32+
):Promise<Record<string,string>>{
33+
constheaders:Record<string,string>={}
34+
if(typeofurl==="string"&&url.trim().length>0&&typeofcommand==="string"&&command.trim().length>0){
35+
letresult:{stdout:string;stderr:string}
36+
try{
37+
result=awaitutil.promisify(cp.exec)(command,{
38+
env:{
39+
...process.env,
40+
CODER_URL:url,
41+
},
42+
})
43+
}catch(error){
44+
if(isExecException(error)){
45+
logger.writeToCoderOutputChannel(`Header command exited unexpectedly with code${error.code}`)
46+
logger.writeToCoderOutputChannel(`stdout:${error.stdout}`)
47+
logger.writeToCoderOutputChannel(`stderr:${error.stderr}`)
48+
thrownewError(`Header command exited unexpectedly with code${error.code}`)
49+
}
50+
thrownewError(`Header command exited unexpectedly:${error}`)
51+
}
52+
constlines=result.stdout.replace(/\r?\n$/,"").split(/\r?\n/)
53+
for(leti=0;i<lines.length;++i){
54+
const[key,value]=lines[i].split(/=(.*)/)
55+
// Header names cannot be blank or contain whitespace and the Coder CLI
56+
// requires that there be an equals sign (the value can be blank though).
57+
if(key.length===0||key.indexOf(" ")!==-1||typeofvalue==="undefined"){
58+
thrownewError(`Malformed line from header command: [${lines[i]}] (out:${result.stdout})`)
59+
}
60+
headers[key]=value
61+
}
62+
}
63+
returnheaders
64+
}

‎src/remote.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,9 +508,16 @@ export class Remote {
508508
}
509509

510510
constescape=(str:string):string=>`"${str.replace(/"/g,'\\"')}"`
511+
512+
// Add headers from the header command.
513+
letheaderCommand=vscode.workspace.getConfiguration().get("coder.headerCommand")
514+
if(typeofheaderCommand==="string"&&headerCommand.trim().length>0){
515+
headerCommand=` --header-command "${escape(headerCommand)}"`
516+
}
517+
511518
constsshValues:SSHValues={
512519
Host:`${Remote.Prefix}*`,
513-
ProxyCommand:`${escape(binaryPath)} vscodessh --network-info-dir${escape(
520+
ProxyCommand:`${escape(binaryPath)}${headerCommand} vscodessh --network-info-dir${escape(
514521
this.storage.getNetworkInfoPath(),
515522
)} --session-token-file${escape(this.storage.getSessionTokenPath())} --url-file${escape(
516523
this.storage.getURLPath(),

‎src/storage.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import os from "os"
1111
importpathfrom"path"
1212
importprettyBytesfrom"pretty-bytes"
1313
import*asvscodefrom"vscode"
14+
import{getHeaders}from"./headers"
1415

1516
exportclassStorage{
1617
publicworkspace?:Workspace
@@ -391,6 +392,10 @@ export class Storage {
391392
awaitfs.rm(this.getSessionTokenPath(),{force:true})
392393
}
393394
}
395+
396+
publicasyncgetHeaders(url=this.getURL()):Promise<Record<string,string>|undefined>{
397+
returngetHeaders(url,vscode.workspace.getConfiguration().get("coder.headerCommand"),this)
398+
}
394399
}
395400

396401
// goos returns the Go format for the current platform.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp