Skip to content

Commit 06eb5ad

Browse files
committed
fetch remote ssh usernames in parallel
1 parent 2ee88da commit 06eb5ad

File tree

1 file changed

+58
-45
lines changed

1 file changed

+58
-45
lines changed

pkg/cmd/codespace/ssh.go

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ func (a *App) SSH(ctx context.Context, sshArgs []string, opts sshOptions) (err e
162162
}
163163

164164
func (a *App) printOpenSSHConfig(ctx context.Context, opts configOptions) error {
165-
// Ensure all child tasks (e.g. port forwarding) terminate before return.
166165
ctx, cancel := context.WithCancel(ctx)
167166
defer cancel()
168167

@@ -181,68 +180,76 @@ func (a *App) printOpenSSHConfig(ctx context.Context, opts configOptions) error
181180
return fmt.Errorf("error getting codespace info: %w", err)
182181
}
183182

184-
t, err := template.New("ssh_config").Parse(`Host cs.{{.Name}}.{{.EscapedRef}}
185-
User {{.SSHUser}}
186-
ProxyCommand {{.GHExec}} cs ssh -c {{.Name}} --stdio
187-
UserKnownHostsFile=/dev/null
188-
StrictHostKeyChecking no
189-
LogLevel quiet
190-
ControlMaster auto
191-
192-
`)
193-
if err != nil {
194-
return fmt.Errorf("error formatting template: %w", err)
195-
}
196-
197-
ghexec, err := os.Executable()
198-
if err != nil {
199-
return err
200-
}
201-
202-
// store a mapping of repository -> remote ssh username. This is
203-
// necessary because the username can vary between codespaces, but
204-
// since fetching it is slow, we store it here so we at least only do
205-
// it once per repository.
206-
sshUsers := map[string]string{}
207-
183+
sshUsers := make(chan sshResult)
184+
fetches := 0
208185
var status error
209186
for _, cs := range codespaces {
210-
211187
if cs.State != "Available" {
212188
fmt.Fprintf(os.Stderr, "skipping unavailable codespace %s: %s\n", cs.Name, cs.State)
213189
status = cmdutil.SilentError
214190
continue
215191
}
216192

217-
sshUser, ok := sshUsers[cs.Repository.FullName]
218-
if !ok {
193+
cs := cs
194+
fetches += 1
195+
go func() {
196+
result := sshResult{}
197+
defer func() {
198+
select {
199+
case sshUsers <- result:
200+
case <-ctx.Done():
201+
}
202+
}()
203+
219204
session, err := openSSHSession(ctx, a, cs, nil)
220205
if err != nil {
221-
fmt.Fprintf(os.Stderr, "error connecting to codespace: %v\n", err)
222-
223-
// Move on to the next codespace. We don't want to bail here - just because we're not
224-
// able to set up connectivity to one doesn't mean we shouldn't make a best effort to
225-
// generate configs for the rest of them.
226-
status = cmdutil.SilentError
227-
continue
206+
result.err = fmt.Errorf("error connecting to codespace: %w", err)
207+
return
228208
}
229209
defer session.Close()
230210

231-
a.StartProgressIndicatorWithLabel(fmt.Sprintf("Fetching SSH Details for %s", cs.Name))
232-
_, sshUser, err = session.StartSSHServer(ctx)
233-
a.StopProgressIndicator()
211+
//a.StartProgressIndicatorWithLabel(fmt.Sprintf("Fetching SSH Details for %s", cs.Name))
212+
_, result.user, err = session.StartSSHServer(ctx)
213+
//a.StopProgressIndicator()
234214
if err != nil {
235-
fmt.Fprintf(os.Stderr, "error getting ssh server details: %v", err)
236-
status = cmdutil.SilentError
237-
continue // see above
215+
result.err = fmt.Errorf("error getting ssh server details: %w", err)
216+
return
238217
}
239-
sshUsers[cs.Repository.FullName] = sshUser
218+
219+
result.codespace = cs
220+
}()
221+
}
222+
223+
ghexec, err := os.Executable()
224+
if err != nil {
225+
return err
226+
}
227+
228+
t, err := template.New("ssh_config").Parse(`Host cs.{{.Name}}.{{.EscapedRef}}
229+
User {{.SSHUser}}
230+
ProxyCommand {{.GHExec}} cs ssh -c {{.Name}} --stdio
231+
UserKnownHostsFile=/dev/null
232+
StrictHostKeyChecking no
233+
LogLevel quiet
234+
ControlMaster auto
235+
236+
`)
237+
if err != nil {
238+
return fmt.Errorf("error formatting template: %w", err)
239+
}
240+
241+
for i := 0; i < fetches; i++ {
242+
result := <-sshUsers
243+
if result.err != nil {
244+
fmt.Fprintf(os.Stderr, "%v\n", result.err)
245+
status = cmdutil.SilentError
246+
continue
240247
}
241248

242249
conf := codespaceSSHConfig{
243-
Name: cs.Name,
244-
EscapedRef: strings.ReplaceAll(cs.GitStatus.Ref, "/", "-"),
245-
SSHUser: sshUser,
250+
Name: result.codespace.Name,
251+
EscapedRef: strings.ReplaceAll(result.codespace.GitStatus.Ref, "/", "-"),
252+
SSHUser: result.user,
246253
GHExec: ghexec,
247254
}
248255
if err := t.Execute(a.io.Out, conf); err != nil {
@@ -253,6 +260,12 @@ func (a *App) printOpenSSHConfig(ctx context.Context, opts configOptions) error
253260
return status
254261
}
255262

263+
type sshResult struct {
264+
codespace *api.Codespace
265+
user string // on success, the remote ssh username; else nil
266+
err error
267+
}
268+
256269
// codespaceSSHConfig contains values needed to write an OpenSSH host
257270
// configuration for a single codespace. For example:
258271
//

0 commit comments

Comments
 (0)