@@ -162,7 +162,6 @@ func (a *App) SSH(ctx context.Context, sshArgs []string, opts sshOptions) (err e
162162}
163163
164164func (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