| 310 | } |
| 311 | |
| 312 | func (a *App) ForwardPorts(ctx context.Context, selector *CodespaceSelector, ports []string) (err error) { |
| 313 | portPairs, err := getPortPairs(ports) |
| 314 | if err != nil { |
| 315 | return fmt.Errorf("get port pairs: %w", err) |
| 316 | } |
| 317 | |
| 318 | codespace, err := selector.Select(ctx) |
| 319 | if err != nil { |
| 320 | return err |
| 321 | } |
| 322 | |
| 323 | codespaceConnection, err := codespaces.GetCodespaceConnection(ctx, a, a.apiClient, codespace) |
| 324 | if err != nil { |
| 325 | return fmt.Errorf("error connecting to codespace: %w", err) |
| 326 | } |
| 327 | |
| 328 | // Run forwarding of all ports concurrently, aborting all of |
| 329 | // them at the first failure, including cancellation of the context. |
| 330 | group, ctx := errgroup.WithContext(ctx) |
| 331 | for _, pair := range portPairs { |
| 332 | group.Go(func() error { |
| 333 | listen, _, err := codespaces.ListenTCP(pair.local, true) |
| 334 | if err != nil { |
| 335 | return err |
| 336 | } |
| 337 | defer listen.Close() |
| 338 | |
| 339 | a.errLogger.Printf("Forwarding ports: remote %d <=> local %d", pair.remote, pair.local) |
| 340 | fwd, err := portforwarder.NewPortForwarder(ctx, codespaceConnection) |
| 341 | if err != nil { |
| 342 | return fmt.Errorf("failed to create port forwarder: %w", err) |
| 343 | } |
| 344 | defer safeClose(fwd, &err) |
| 345 | |
| 346 | opts := portforwarder.ForwardPortOpts{ |
| 347 | Port: pair.remote, |
| 348 | } |
| 349 | return fwd.ForwardPortToListener(ctx, opts, listen) |
| 350 | }) |
| 351 | } |
| 352 | return group.Wait() // first error |
| 353 | } |
| 354 | |
| 355 | type portPair struct { |
| 356 | remote, local int |