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