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

Commit3c87801

Browse files
committed
feat: Parse and prompt to re-use previous configuration
1 parentd2992ad commit3c87801

File tree

1 file changed

+132
-40
lines changed

1 file changed

+132
-40
lines changed

‎cli/configssh.go

Lines changed: 132 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"bufio"
45
"bytes"
56
"errors"
67
"fmt"
@@ -33,9 +34,9 @@ const (
3334
sshCoderConfigDocsHeader=`
3435
#
3536
# You should not hand-edit this file, all changes will be lost upon workspace
36-
# creation, deletion or when running "coder config-ssh".
37-
`
38-
sshCoderConfigOptionsHeader=`#
37+
# creation, deletion or when running "coder config-ssh".`
38+
sshCoderConfigOptionsHeader=`
39+
#
3940
# Last config-ssh options:
4041
`
4142
// Relative paths are assumed to be in ~/.ssh, except when
@@ -53,11 +54,50 @@ var (
5354
sshCoderIncludedRe=regexp.MustCompile(`^\s*((?i)Include) coder(\s|$)`)
5455
)
5556

57+
// sshCoderConfigOptions represents options that can be stored and read
58+
// from the coder config in ~/.ssh/coder.
59+
typesshCoderConfigOptionsstruct {
60+
sshConfigFilestring
61+
sshOptions []string
62+
}
63+
64+
func (osshCoderConfigOptions)isZero()bool {
65+
returno.sshConfigFile==sshDefaultConfigFileName&&len(o.sshOptions)==0
66+
}
67+
68+
func (osshCoderConfigOptions)equal(othersshCoderConfigOptions)bool {
69+
// Compare without side-effects or regard to order.
70+
opt1:=slices.Clone(o.sshOptions)
71+
sort.Strings(opt1)
72+
opt2:=slices.Clone(other.sshOptions)
73+
sort.Strings(opt2)
74+
returno.sshConfigFile==other.sshConfigFile&&slices.Equal(opt1,opt2)
75+
}
76+
77+
func (osshCoderConfigOptions)asArgs() (args []string) {
78+
ifo.sshConfigFile!=sshDefaultConfigFileName {
79+
args=append(args,"--ssh-config-file",o.sshConfigFile)
80+
}
81+
for_,opt:=rangeo.sshOptions {
82+
args=append(args,"--ssh-option",fmt.Sprintf("%q",opt))
83+
}
84+
returnargs
85+
}
86+
87+
func (osshCoderConfigOptions)asList() (list []string) {
88+
ifo.sshConfigFile!=sshDefaultConfigFileName {
89+
list=append(list,fmt.Sprintf("ssh-config-file: %s",o.sshConfigFile))
90+
}
91+
for_,opt:=rangeo.sshOptions {
92+
list=append(list,fmt.Sprintf("ssh-option: %s",opt))
93+
}
94+
returnlist
95+
}
96+
5697
funcconfigSSH()*cobra.Command {
5798
var (
58-
sshConfigFilestring
99+
coderConfigsshCoderConfigOptions
59100
coderConfigFilestring
60-
sshOptions []string
61101
showDiffbool
62102
skipProxyCommandbool
63103

@@ -99,30 +139,26 @@ func configSSH() *cobra.Command {
99139
returnerr
100140
}
101141

142+
out:=cmd.OutOrStdout()
143+
ifshowDiff {
144+
out=cmd.OutOrStderr()
145+
}
146+
binaryFile,err:=currentBinPath(out)
147+
iferr!=nil {
148+
returnerr
149+
}
150+
102151
dirname,err:=os.UserHomeDir()
103152
iferr!=nil {
104153
returnxerrors.Errorf("user home dir failed: %w",err)
105154
}
106155

107-
sshConfigFileOrig:=sshConfigFile// Store the pre ~/ replacement name for serializing options.
156+
sshConfigFile:=coderConfig.sshConfigFile// Store the pre ~/ replacement name for serializing options.
108157
ifstrings.HasPrefix(sshConfigFile,"~/") {
109158
sshConfigFile=filepath.Join(dirname,sshConfigFile[2:])
110159
}
111-
coderConfigFileOrig:=coderConfigFile
112160
coderConfigFile=filepath.Join(dirname,coderConfigFile[2:])// Replace ~/ with home dir.
113161

114-
// TODO(mafredri): Check coderConfigFile for previous options
115-
// coderConfigFile.
116-
117-
out:=cmd.OutOrStdout()
118-
ifshowDiff {
119-
out=cmd.OutOrStderr()
120-
}
121-
binaryFile,err:=currentBinPath(out)
122-
iferr!=nil {
123-
returnerr
124-
}
125-
126162
// Only allow not-exist errors to avoid trashing
127163
// the users SSH config.
128164
configRaw,err:=os.ReadFile(sshConfigFile)
@@ -139,6 +175,25 @@ func configSSH() *cobra.Command {
139175
returnxerrors.Errorf("unexpected content in %s: remove the file and rerun the command to continue",coderConfigFile)
140176
}
141177
}
178+
lastCoderConfig:=sshCoderConfigParseLastOptions(bytes.NewReader(coderConfigRaw))
179+
180+
// Only prompt when no arguments are provided and avoid
181+
// prompting in diff mode (unexpected behavior).
182+
if!showDiff&&coderConfig.isZero()&&!lastCoderConfig.isZero() {
183+
line,err:=cliui.Prompt(cmd, cliui.PromptOptions{
184+
Text:fmt.Sprintf("Found previous configuration option(s):\n\n - %s\n\n Use previous option(s)?",strings.Join(lastCoderConfig.asList(),"\n - ")),
185+
IsConfirm:true,
186+
})
187+
iferr!=nil {
188+
// TODO(mafredri): Better way to differ between "no" and Ctrl+C?
189+
ifline==""&&xerrors.Is(err,cliui.Canceled) {
190+
returnnil
191+
}
192+
}else {
193+
coderConfig=lastCoderConfig
194+
}
195+
_,_=fmt.Fprint(out,"\n")
196+
}
142197

143198
// Keep track of changes we are making.
144199
varchanges []string
@@ -147,14 +202,14 @@ func configSSH() *cobra.Command {
147202
// remove if present.
148203
configModified,ok:=stripOldConfigBlock(configRaw)
149204
ifok {
150-
changes=append(changes,fmt.Sprintf("Remove old config block (START-CODER/END-CODER) from %s",sshConfigFileOrig))
205+
changes=append(changes,fmt.Sprintf("Remove old config block (START-CODER/END-CODER) from %s",sshConfigFile))
151206
}
152207

153208
// Check for the presence of the coder Include
154209
// statement is present and add if missing.
155210
configModified,ok=sshConfigAddCoderInclude(configModified)
156211
ifok {
157-
changes=append(changes,fmt.Sprintf("Add %q to %s","Include coder",sshConfigFileOrig))
212+
changes=append(changes,fmt.Sprintf("Add %q to %s","Include coder",sshConfigFile))
158213
}
159214

160215
root:=createConfig(cmd)
@@ -197,19 +252,13 @@ func configSSH() *cobra.Command {
197252
}
198253

199254
buf:=&bytes.Buffer{}
200-
_,_=buf.WriteString(sshCoderConfigHeader)
201-
_,_=buf.WriteString(sshCoderConfigDocsHeader)
202-
203-
// Store the provided flags as part of the
204-
// config for future (re)use.
205-
_,_=buf.WriteString(sshCoderConfigOptionsHeader)
206-
ifsshConfigFileOrig!=sshDefaultConfigFileName {
207-
_,_=fmt.Fprintf(buf,"# :%s=%s\n","ssh-config-file",sshConfigFileOrig)
208-
}
209-
for_,opt:=rangesshOptions {
210-
_,_=fmt.Fprintf(buf,"# :%s=%s\n","ssh-option",opt)
255+
256+
// Write header and store the provided options as part
257+
// of the config for future (re)use.
258+
err=sshCoderConfigWriteHeader(buf,coderConfig)
259+
iferr!=nil {
260+
returnxerrors.Errorf("write coder config header failed: %w",err)
211261
}
212-
_,_=buf.WriteString("#\n")
213262

214263
// Ensure stable sorting of output.
215264
slices.SortFunc(workspaceConfigs,func(a,bworkspaceConfig)bool {
@@ -222,7 +271,7 @@ func configSSH() *cobra.Command {
222271
configOptions:= []string{
223272
"Host coder."+hostname,
224273
}
225-
for_,option:=rangesshOptions {
274+
for_,option:=rangecoderConfig.sshOptions {
226275
configOptions=append(configOptions,"\t"+option)
227276
}
228277
configOptions=append(configOptions,
@@ -248,9 +297,9 @@ func configSSH() *cobra.Command {
248297
modifyCoderConfig:=!bytes.Equal(coderConfigRaw,buf.Bytes())
249298
ifmodifyCoderConfig {
250299
iflen(coderConfigRaw)==0 {
251-
changes=append(changes,fmt.Sprintf("Write auto-generated coder config file to %s",coderConfigFileOrig))
300+
changes=append(changes,fmt.Sprintf("Write auto-generated coder config file to %s",coderConfigFile))
252301
}else {
253-
changes=append(changes,fmt.Sprintf("Update auto-generated coder config file in %s",coderConfigFileOrig))
302+
changes=append(changes,fmt.Sprintf("Update auto-generated coder config file in %s",coderConfigFile))
254303
}
255304
}
256305

@@ -259,7 +308,7 @@ func configSSH() *cobra.Command {
259308
// Write to stderr to avoid dirtying the diff output.
260309
_,_=fmt.Fprint(out,"Changes:\n\n")
261310
for_,change:=rangechanges {
262-
_,_=fmt.Fprintf(out,"* %s\n",change)
311+
_,_=fmt.Fprintf(out,"* %s\n",change)
263312
}
264313
}
265314

@@ -283,8 +332,11 @@ func configSSH() *cobra.Command {
283332
}
284333

285334
iflen(changes)>0 {
335+
// In diff mode we don't prompt re-using the previous
336+
// configuration, so we output the entire command.
337+
diffCommand:=fmt.Sprintf("$ %s %s",cmd.CommandPath(),strings.Join(append(coderConfig.asArgs(),"--diff")," "))
286338
_,err=cliui.Prompt(cmd, cliui.PromptOptions{
287-
Text:fmt.Sprintf("The following changes will be made to your SSH configuration (use --diff tosee changes):\n\n*%s\n\n Continue?",strings.Join(changes,"\n * ")),
339+
Text:fmt.Sprintf("The following changes will be made to your SSH configuration:\n\n * %s\n\n Tosee changes, run with --diff:\n\n%s\n\n Continue?",strings.Join(changes,"\n* "),diffCommand),
288340
IsConfirm:true,
289341
})
290342
iferr!=nil {
@@ -315,10 +367,10 @@ func configSSH() *cobra.Command {
315367
returnnil
316368
},
317369
}
318-
cliflag.StringVarP(cmd.Flags(),&sshConfigFile,"ssh-config-file","","CODER_SSH_CONFIG_FILE",sshDefaultConfigFileName,"Specifies the path to an SSH config.")
370+
cliflag.StringVarP(cmd.Flags(),&coderConfig.sshConfigFile,"ssh-config-file","","CODER_SSH_CONFIG_FILE",sshDefaultConfigFileName,"Specifies the path to an SSH config.")
319371
cmd.Flags().StringVar(&coderConfigFile,"ssh-coder-config-file",sshDefaultCoderConfigFileName,"Specifies the path to an Coder SSH config file. Useful for testing.")
320372
_=cmd.Flags().MarkHidden("ssh-coder-config-file")
321-
cmd.Flags().StringArrayVarP(&sshOptions,"ssh-option","o", []string{},"Specifies additional SSH options to embed in each host stanza.")
373+
cmd.Flags().StringArrayVarP(&coderConfig.sshOptions,"ssh-option","o", []string{},"Specifies additional SSH options to embed in each host stanza.")
322374
cmd.Flags().BoolVarP(&showDiff,"diff","D",false,"Show diff of changes that will be made.")
323375
cmd.Flags().BoolVarP(&skipProxyCommand,"skip-proxy-command","",false,"Specifies whether the ProxyCommand option should be skipped. Useful for testing.")
324376
_=cmd.Flags().MarkHidden("skip-proxy-command")
@@ -356,6 +408,46 @@ func sshConfigAddCoderInclude(data []byte) (modifiedData []byte, modified bool)
356408
returndata,true
357409
}
358410

411+
funcsshCoderConfigWriteHeader(w io.Writer,osshCoderConfigOptions)error {
412+
_,_=fmt.Fprint(w,sshCoderConfigHeader)
413+
_,_=fmt.Fprint(w,sshCoderConfigDocsHeader)
414+
_,_=fmt.Fprint(w,sshCoderConfigOptionsHeader)
415+
ifo.sshConfigFile!=sshDefaultConfigFileName {
416+
_,_=fmt.Fprintf(w,"# :%s=%s\n","ssh-config-file",o.sshConfigFile)
417+
}
418+
for_,opt:=rangeo.sshOptions {
419+
_,_=fmt.Fprintf(w,"# :%s=%s\n","ssh-option",opt)
420+
}
421+
_,_=fmt.Fprint(w,"#\n")
422+
returnnil
423+
}
424+
425+
funcsshCoderConfigParseLastOptions(r io.Reader) (osshCoderConfigOptions) {
426+
o.sshConfigFile=sshDefaultConfigFileName// Default value is not written.
427+
428+
s:=bufio.NewScanner(r)
429+
fors.Scan() {
430+
line:=s.Text()
431+
ifstrings.HasPrefix(line,"# :") {
432+
line=strings.TrimPrefix(line,"# :")
433+
parts:=strings.SplitN(line,"=",2)
434+
switchparts[0] {
435+
case"ssh-config-file":
436+
o.sshConfigFile=parts[1]
437+
case"ssh-option":
438+
o.sshOptions=append(o.sshOptions,parts[1])
439+
default:
440+
// Unknown option, ignore.
441+
}
442+
}
443+
}
444+
iferr:=s.Err();err!=nil {
445+
panic(err)
446+
}
447+
448+
returno
449+
}
450+
359451
// writeWithTempFileAndMove writes to a temporary file in the same
360452
// directory as path and renames the temp file to the file provided in
361453
// path. This ensure we avoid trashing the file we are writing due to

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp