Projects STRLCPY cdebug Commits 09096b1c
🤬
  • ■ ■ ■ ■ ■ ■
    cmd/exec/exec.go
    skipped 5 lines
    6 6   "errors"
    7 7   "fmt"
    8 8   "io"
     9 + "os"
    9 10   "strings"
    10 11   "text/template"
    11 12   
    skipped 132 lines
    144 145   if err != nil {
    145 146   return err
    146 147   }
    147  - 
    148 148   if target.State == nil || !target.State.Running {
    149 149   return errors.New("target container found but it's not running")
    150 150   }
    151 151   
     152 + cli.PrintAux("Pulling debugger image...\n")
    152 153   if err := client.ImagePullEx(ctx, opts.image, types.ImagePullOptions{}); err != nil {
    153 154   return fmt.Errorf("cannot pull debugger image %q: %w", opts.image, err)
    154 155   }
    skipped 8 lines
    163 164   Cmd: []string{
    164 165   "-c",
    165 166   mustRenderTemplate(
     167 + cli,
    166 168   chrootEntrypoint,
    167 169   map[string]any{
    168 170   "ID": runID,
    skipped 101 lines
    270 272   }
    271 273   
    272 274   if err := s.stream(ctx); err != nil {
    273  - logrus.WithError(err).Warn("ioStreamer.stream() failed")
     275 + logrus.Debugf("ioStreamer.stream() failed: %s", err)
    274 276   }
    275 277   }()
    276 278   
    skipped 67 lines
    344 346   return "cdebug-" + runID
    345 347  }
    346 348   
    347  -func mustRenderTemplate(t *template.Template, data any) string {
     349 +func mustRenderTemplate(cli cliutil.CLI, t *template.Template, data any) string {
    348 350   var buf bytes.Buffer
    349 351   if err := t.Execute(&buf, data); err != nil {
    350  - panic(fmt.Errorf("cannot render template %q: %w", t.Name(), err))
     352 + cli.PrintErr("Cannot render template %q: %w", t.Name(), err)
     353 + os.Exit(1)
    351 354   }
    352 355   return buf.String()
    353 356  }
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    cmd/portforward/portforward.go
    skipped 3 lines
    4 4   "context"
    5 5   "errors"
    6 6   "fmt"
    7  - "net"
    8 7   "os"
    9 8   "os/signal"
    10 9   "strings"
    skipped 1 lines
    12 11   
    13 12   "github.com/docker/docker/api/types"
    14 13   "github.com/docker/docker/api/types/container"
     14 + dockerclient "github.com/docker/docker/client"
    15 15   "github.com/docker/go-connections/nat"
    16 16   "github.com/sirupsen/logrus"
    17 17   "github.com/spf13/cobra"
    skipped 5 lines
    23 23  )
    24 24   
    25 25  // TODO:
    26  -// - parse ports args
    27  -// - handle non-default network case
    28  -// - handle exposing localhost ports
     26 +// - Local port forwarding: handle exposing localhost ports
    29 27  // cdebug exec --name helper --image socat <target> <target-port> <proxy-port>
    30 28  // cdebug port-forward helper <host-port>:<proxy-port>
    31  - 
    32  -// Possible options (kinda sorta as in ssh -L):
    33  -// - TARGET_PORT # binds TARGET_IP:TARGET_PORT to a random port on localhost
    34  -// - TARGET_IP:TARGET_PORT # The second form is needed to:
     29 +//
     30 +// - Remote port forwarding: implement me!
     31 +//
     32 +// Local port forwarding's possible modes (kinda sorta as in ssh -L):
     33 +// - REMOTE_PORT # binds REMOTE_IP:REMOTE_PORT to a random port on localhost
     34 +// - REMOTE_<IP|ALIAS|NET>:REMOTE_PORT # The second form is needed to:
    35 35  // # 1) allow exposing target's localhost ports
    36 36  // # 2) specify a concrete IP for a multi-network target
    37 37  //
    38  -// - LOCAL_PORT:TARGET_PORT # binds TARGET_IP:TARGET_PORT to LOCAL_PORT on localhost
    39  -// - LOCAL_PORT:TARGET_IP:TARGET_PORT
     38 +// - LOCAL_PORT:REMOTE_PORT # binds REMOTE_IP:REMOTE_PORT to LOCAL_PORT on localhost
     39 +// - LOCAL_PORT:REMOTE_<IP|ALIAS|NET>:REMOTE_PORT
    40 40  //
    41  -// - LOCAL_IP:LOCAL_PORT:TARGET_PORT # similar to LOCAL_PORT:TARGET_PORT but LOCAL_IP is used instead of localhost
    42  -// - LOCAL_IP:LOCAL_PORT:TARGET_IP:TARGET_PORT
     41 +// - LOCAL_IP:LOCAL_PORT:REMOTE_PORT # similar to LOCAL_PORT:REMOTE_PORT but LOCAL_IP is used instead of localhost
     42 +// - LOCAL_IP:LOCAL_PORT:REMOTE_<IP|ALIAS|NET>:REMOTE_PORT
     43 +//
     44 +// Remote port forwarding's possible modes (kinda sorta as in ssh -R):
     45 +// - coming soon...
    43 46   
    44 47  const (
    45  - helperImage = "nixery.dev/shell/socat:latest"
     48 + forwarderImage = "nixery.dev/shell/socat:latest"
    46 49   
    47 50   outFormatText = "text"
    48 51   outFormatJSON = "json"
    49 52  )
    50 53   
     54 +var (
     55 + errNoAddr = errors.New("target container must have at least one IP address")
     56 + errBadLocalPort = errors.New("bad local port")
     57 + errBadRemotePort = errors.New("bad remote port")
     58 +)
     59 + 
    51 60  type options struct {
    52  - target string
    53  - forwardings []string
    54  - output string
    55  - quiet bool
     61 + target string
     62 + locals []string
     63 + remotes []string
     64 + output string
     65 + quiet bool
    56 66  }
    57 67   
    58 68  func NewCommand(cli cliutil.CLI) *cobra.Command {
    59 69   var opts options
    60 70   
    61 71   cmd := &cobra.Command{
    62  - Use: "port-forward CONTAINER [[LOCAL_IP:]LOCAL_PORT:]TARGET_PORT [...]",
    63  - Short: `"Publish" one or more ports of an already running container`,
    64  - Args: cobra.MinimumNArgs(2),
     72 + Use: "port-forward CONTAINER -L [LOCAL:]REMOTE [-L ...] | -R [REMOTE:]:LOCAL [-R ...]",
     73 + Short: `Forward one or more local or remote ports`,
     74 + Long: `While the implementation for sure differs, the behavior and semantic of the command
     75 +are meant to be similar to SSH local (-L) and remote (-R) port forwarding. The word "local" always
     76 +refers to the cdebug side. The word "remote" always refers to the target container side.`,
     77 + Args: cobra.ExactArgs(1),
    65 78   RunE: func(cmd *cobra.Command, args []string) error {
     79 + if len(opts.locals)+len(opts.remotes) == 0 {
     80 + return cliutil.NewStatusError(1, "at least one -L or -R flag must be provided")
     81 + }
     82 + if len(opts.remotes) > 0 {
     83 + // TODO: Implement me!
     84 + return cliutil.NewStatusError(1, "remote port forwarding is not implemented yet")
     85 + }
     86 + 
    66 87   cli.SetQuiet(opts.quiet)
    67 88   
    68 89   opts.target = args[0]
    69  - opts.forwardings = args[1:]
     90 + 
    70 91   return cliutil.WrapStatusError(runPortForward(context.Background(), cli, &opts))
    71 92   },
    72 93   }
    73 94   
    74 95   flags := cmd.Flags()
    75  - flags.SetInterspersed(false) // Instead of relying on --
     96 + 
     97 + flags.StringSliceVarP(
     98 + &opts.locals,
     99 + "local",
     100 + "L",
     101 + nil,
     102 + `Local port forwarding in the form [[LOCAL_IP:]LOCAL_PORT:][REMOTE_IP|REMOTE_NETWORK|REMOTE_ALIAS:]REMOTE_PORT`,
     103 + )
     104 + 
     105 + flags.StringSliceVarP(
     106 + &opts.remotes,
     107 + "remote",
     108 + "R",
     109 + nil,
     110 + `Remote port forwarding in the form [REMOTE_IP|REMOTE_NETWORK|REMOTE_ALIAS:]REMOTE_PORT:LOCAL_IP:LOCAL_PORT`,
     111 + )
    76 112   
    77 113   flags.BoolVarP(
    78 114   &opts.quiet,
    skipped 8 lines
    87 123   "output",
    88 124   "o",
    89 125   outFormatText,
    90  - `Output format (plain text or JSON)`,
     126 + `Output format ("text" | "json")`,
    91 127   )
    92 128   
    93 129   return cmd
    skipped 9 lines
    103 139   if err != nil {
    104 140   return err
    105 141   }
    106  - 
    107  - // TODO: Check that target has at least 1 IP!
    108  - 
    109  - if err := client.ImagePullEx(ctx, helperImage, types.ImagePullOptions{}); err != nil {
    110  - return fmt.Errorf("cannot pull port-forwarder helper image %q: %w", helperImage, err)
     142 + if err := validateTarget(target); err != nil {
     143 + return err
    111 144   }
    112 145   
    113  - forwardings, err := parseForwardings(target, opts.forwardings)
    114  - if err != nil {
    115  - return err
     146 + cli.PrintAux("Pulling forwarder image...\n")
     147 + if err := client.ImagePullEx(ctx, forwarderImage, types.ImagePullOptions{}); err != nil {
     148 + return fmt.Errorf("cannot pull forwarder image %q: %w", forwarderImage, err)
    116 149   }
    117 150   
    118  - exposedPorts, portBindings, err := nat.ParsePortSpecs(forwardings.toDockerPortSpecs())
     151 + locals, err := parseLocalForwardings(target, opts.locals)
    119 152   if err != nil {
    120 153   return err
    121 154   }
    122 155   
    123  - // TODO: Iterate over all forwardings.
    124  - resp, err := client.ContainerCreate(
    125  - ctx,
    126  - &container.Config{
    127  - Image: helperImage,
    128  - Entrypoint: []string{"socat"},
    129  - Cmd: []string{
    130  - fmt.Sprintf("TCP-LISTEN:%s,fork", forwardings[0].targetPort),
    131  - fmt.Sprintf("TCP-CONNECT:%s:%s", forwardings[0].targetIP, forwardings[0].targetPort),
    132  - },
    133  - ExposedPorts: exposedPorts,
    134  - },
    135  - &container.HostConfig{
    136  - AutoRemove: true,
    137  - PortBindings: portBindings,
    138  - },
    139  - nil,
    140  - nil,
    141  - "port-forwarder-"+uuid.ShortID(),
    142  - )
    143  - if err != nil {
    144  - return fmt.Errorf("cannot create port-forwarder container: %w", err)
    145  - }
    146  - 
    147  - if err := client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
    148  - return fmt.Errorf("cannot start port-forwarder container: %w", err)
    149  - }
    150  - 
    151  - forwarder, err := client.ContainerInspect(ctx, resp.ID)
    152  - if err != nil {
    153  - return fmt.Errorf("cannot inspect forwarder container: %w", err)
    154  - }
     156 + // TODO: It's probably a good idea to monitor forwarders too.
     157 + for i, l := range locals {
     158 + forwarder, err := startLocalForwarder(ctx, client, target, l)
     159 + if err != nil {
     160 + return err
     161 + }
     162 + defer func() {
     163 + if err := client.ContainerKill(ctx, forwarder.ID, "KILL"); err != nil {
     164 + logrus.Debugf("Cannot kill forwarder container: %s", err)
     165 + }
     166 + }()
    155 167   
    156  - // TODO: Multi-network support.
    157  - targetIP := target.NetworkSettings.Networks["bridge"].IPAddress
    158  - for remotePort, localBindings := range forwarder.NetworkSettings.Ports {
    159  - for _, binding := range localBindings {
    160  - switch opts.output {
    161  - case outFormatText:
    162  - local := net.JoinHostPort(binding.HostIP, binding.HostPort)
    163  - remote := targetIP + ":" + string(remotePort)
    164  - cli.PrintOut("Forwarding %s to %s's %s\n", local, target.Name[1:], remote)
    165  - case outFormatJSON:
    166  - cli.PrintOut(jsonutil.Dump(map[string]string{
    167  - "localHost": binding.HostIP,
    168  - "localPort": binding.HostPort,
    169  - "remoteHost": targetIP,
    170  - "remotePort": string(remotePort),
    171  - }))
    172  - default:
    173  - panic("unreachable!")
     168 + if len(l.localPort) == 0 {
     169 + for _, bindings := range forwarder.NetworkSettings.Ports {
     170 + locals[i].localPort = bindings[0].HostPort
     171 + break
    174 172   }
    175 173   }
    176 174   }
    177 175   
    178  - sigCh := make(chan os.Signal, 128)
    179  - signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
    180  - defer close(sigCh)
     176 + switch opts.output {
     177 + case outFormatJSON:
     178 + cli.PrintOut(localForwardingsToJSON(locals) + "\n")
     179 + case outFormatText:
     180 + cli.PrintOut(localForwardingsToText(locals) + "\n")
     181 + default:
     182 + panic("unreachable!")
     183 + }
    181 184   
    182  - go func() {
    183  - for _ = range sigCh {
    184  - cli.PrintAux("Exiting...")
     185 + signalCh := make(chan os.Signal, 128)
     186 + signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
     187 + defer close(signalCh)
    185 188   
    186  - if err := client.ContainerKill(ctx, resp.ID, "KILL"); err != nil {
    187  - logrus.Debugf("Cannot kill forwarder container: %s", err)
    188  - }
    189  - break
    190  - }
    191  - }()
    192  - 
    193  - forwarderStatusCh, forwarderErrCh := client.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    194  - // targetStatusCh, targetErrCh := client.ContainerWait(ctx, target.ID, container.WaitConditionNotRunning)
     189 + targetStatusCh, targetErrCh := client.ContainerWait(ctx, target.ID, container.WaitConditionNotRunning)
    195 190   select {
    196  - case err := <-forwarderErrCh:
     191 + case <-signalCh:
     192 + cli.PrintAux("Exiting...")
     193 + 
     194 + case err := <-targetErrCh:
    197 195   if err != nil {
    198  - return fmt.Errorf("waiting for port-forwarder container failed: %w", err)
     196 + return fmt.Errorf("waiting for target container failed: %w", err)
    199 197   }
    200  - case <-forwarderStatusCh:
     198 + 
     199 + case <-targetStatusCh:
     200 + }
     201 + 
     202 + return nil
     203 +}
     204 + 
     205 +func validateTarget(target types.ContainerJSON) error {
     206 + if target.State == nil || !target.State.Running {
     207 + return errors.New("target container found but it's not running")
     208 + }
     209 + 
     210 + hasIP := false
     211 + for _, net := range target.NetworkSettings.Networks {
     212 + hasIP = hasIP || len(net.IPAddress) > 0
     213 + }
     214 + if !hasIP {
     215 + return errNoAddr
    201 216   }
    202 217   
    203 218   return nil
    skipped 2 lines
    206 221  type forwarding struct {
    207 222   localIP string
    208 223   localPort string
    209  - targetIP string
    210  - targetPort string
     224 + remoteIP string
     225 + remotePort string
    211 226  }
    212 227   
    213  -type forwardingList []forwarding
     228 +func (f forwarding) toDockerPortSpec() string {
     229 + // ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
     230 + return f.localIP + ":" + f.localPort + ":" + f.remotePort
     231 +}
    214 232   
    215  -func (list forwardingList) toDockerPortSpecs() []string {
    216  - // ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
    217  - var spec []string
    218  - for _, f := range list {
    219  - spec = append(spec, fmt.Sprintf("%s:%s:%s", f.localIP, f.localPort, f.targetPort))
     233 +func parseLocalForwardings(
     234 + target types.ContainerJSON,
     235 + locals []string,
     236 +) ([]forwarding, error) {
     237 + var parsed []forwarding
     238 + for _, l := range locals {
     239 + next, err := parseLocalForwarding(target, l)
     240 + if err != nil {
     241 + return nil, err
     242 + }
     243 + parsed = append(parsed, next)
    220 244   }
    221  - return spec
     245 + return parsed, nil
    222 246  }
    223 247   
    224  -func parseForwardings(
     248 +func parseLocalForwarding(
    225 249   target types.ContainerJSON,
    226  - forwardings []string,
    227  -) (forwardingList, error) {
    228  - var list forwardingList
     250 + local string,
     251 +) (forwarding, error) {
     252 + parts := strings.Split(local, ":")
     253 + if len(parts) == 1 {
     254 + // Case 1: REMOTE_PORT only
     255 + if _, err := nat.ParsePort(parts[0]); err != nil {
     256 + return forwarding{}, errBadRemotePort
     257 + }
    229 258   
    230  - targetIP := target.NetworkSettings.Networks["bridge"].IPAddress
     259 + remoteIP, err := unambiguousIP(target)
     260 + if err != nil {
     261 + return forwarding{}, err
     262 + }
    231 263   
    232  - for _, f := range forwardings {
    233  - parts := strings.Split(f, ":")
    234  - if len(parts) == 1 {
    235  - // Case 1: TARGET_PORT
     264 + // localPort will be later assigned by Docker dynamically
     265 + return forwarding{
     266 + localIP: "127.0.0.1",
     267 + remoteIP: remoteIP,
     268 + remotePort: parts[0],
     269 + }, nil
     270 + }
    236 271   
    237  - if _, err := nat.ParsePort(parts[0]); err != nil {
    238  - // TODO: Return a user-friendly error.
    239  - return nil, err
     272 + if len(parts) == 2 {
     273 + // Either LOCAL_PORT:REMOTE_PORT or REMOTE_<IP|ALIAS|NETWORK>:REMOTE_PORT
     274 + 
     275 + if _, err := nat.ParsePort(parts[1]); err != nil {
     276 + return forwarding{}, errBadRemotePort
     277 + }
     278 + 
     279 + if _, err := nat.ParsePort(parts[0]); err == nil {
     280 + // Case 2: LOCAL_PORT:REMOTE_PORT
     281 + remoteIP, err := unambiguousIP(target)
     282 + if err != nil {
     283 + return forwarding{}, err
    240 284   }
    241 285   
    242  - // TODO: if "target has more than 1 IP" return err
     286 + return forwarding{
     287 + localIP: "127.0.0.1",
     288 + localPort: parts[0],
     289 + remoteIP: remoteIP,
     290 + remotePort: parts[1],
     291 + }, nil
     292 + }
     293 + 
     294 + // Case 3: REMOTE_<IP|ALIAS|NETWORK>:REMOTE_PORT
     295 + remoteIP, err := lookupTargetIP(target, parts[0])
     296 + if err != nil {
     297 + return forwarding{}, err
     298 + }
     299 + 
     300 + // localPort will be later assigned by Docker dynamically
     301 + return forwarding{
     302 + localIP: "127.0.0.1",
     303 + remotePort: parts[1],
     304 + remoteIP: remoteIP,
     305 + }, nil
     306 + }
     307 + 
     308 + if len(parts) == 3 {
     309 + // Either LOCAL_PORT:REMOTE_<IP|ALIAS|NET>:REMOTE_PORT or LOCAL_IP:LOCAL_PORT:REMOTE_PORT
    243 310   
    244  - list = append(list, forwarding{
     311 + if _, err := nat.ParsePort(parts[0]); err == nil {
     312 + // Case 4: LOCAL_PORT:REMOTE_<IP|ALIAS|NET>:REMOTE_PORT
     313 + remoteIP, err := lookupTargetIP(target, parts[1])
     314 + if err != nil {
     315 + return forwarding{}, err
     316 + }
     317 + 
     318 + if _, err := nat.ParsePort(parts[2]); err != nil {
     319 + return forwarding{}, errBadRemotePort
     320 + }
     321 + 
     322 + return forwarding{
    245 323   localIP: "127.0.0.1",
    246  - targetPort: parts[0],
    247  - targetIP: targetIP,
    248  - })
    249  - continue
     324 + localPort: parts[0],
     325 + remoteIP: remoteIP,
     326 + remotePort: parts[2],
     327 + }, nil
    250 328   }
    251 329   
    252  - if len(parts) == 2 {
    253  - if _, err := nat.ParsePort(parts[0]); err == nil {
    254  - // Case 2: LOCAL_PORT:TARGET_PORT
     330 + // Case 5: LOCAL_IP:LOCAL_PORT:REMOTE_PORT
     331 + remoteIP, err := unambiguousIP(target)
     332 + if err != nil {
     333 + return forwarding{}, err
     334 + }
     335 + 
     336 + return forwarding{
     337 + localIP: parts[0],
     338 + localPort: parts[1],
     339 + remoteIP: remoteIP,
     340 + remotePort: parts[2],
     341 + }, nil
     342 + }
    255 343   
    256  - // TODO: if "target has more than 1 IP" return err
     344 + // Case 6: LOCAL_IP:LOCAL_PORT:REMOTE_<IP|ALIAS|NET>:REMOTE_PORT
     345 + if _, err := nat.ParsePort(parts[1]); err != nil {
     346 + return forwarding{}, errBadLocalPort
     347 + }
     348 + if _, err := nat.ParsePort(parts[3]); err != nil {
     349 + return forwarding{}, errBadRemotePort
     350 + }
    257 351   
    258  - list = append(list, forwarding{
    259  - localPort: parts[0],
    260  - localIP: "127.0.0.1",
    261  - targetPort: parts[1],
    262  - targetIP: targetIP,
    263  - })
    264  - } else {
    265  - // Case 3: TARGET_IP:TARGET_PORT
     352 + remoteIP, err := lookupTargetIP(target, parts[2])
     353 + if err != nil {
     354 + return forwarding{}, err
     355 + }
    266 356   
    267  - // TODO: if "parts[0] not in target IP list" return err
     357 + return forwarding{
     358 + localIP: parts[0],
     359 + localPort: parts[1],
     360 + remoteIP: remoteIP,
     361 + remotePort: parts[3],
     362 + }, nil
     363 +}
    268 364   
    269  - list = append(list, forwarding{
    270  - localIP: "127.0.0.1",
    271  - targetPort: parts[1],
    272  - targetIP: parts[0],
    273  - })
     365 +func unambiguousIP(target types.ContainerJSON) (string, error) {
     366 + var found string
     367 + for _, net := range target.NetworkSettings.Networks {
     368 + if len(net.IPAddress) > 0 {
     369 + if len(found) > 0 {
     370 + return "", errors.New("remote IP must be specified explicitly for targets with multiple network interfaces")
    274 371   }
     372 + found = net.IPAddress
     373 + }
     374 + }
     375 + 
     376 + if len(found) == 0 {
     377 + // This cannot really happen unless there is a mistake in validateTarget().
     378 + return "", errNoAddr
     379 + }
     380 + 
     381 + return found, nil
     382 +}
     383 + 
     384 +func lookupTargetIP(target types.ContainerJSON, ipAliasNetwork string) (string, error) {
     385 + for name, net := range target.NetworkSettings.Networks {
     386 + if len(net.IPAddress) == 0 {
    275 387   continue
    276 388   }
    277 389   
    278  - // TODO: other cases
    279  - return nil, errors.New("implement me")
     390 + if net.IPAddress == ipAliasNetwork {
     391 + return net.IPAddress, nil
     392 + }
     393 + 
     394 + for _, alias := range net.Aliases {
     395 + if alias == ipAliasNetwork {
     396 + return net.IPAddress, nil
     397 + }
     398 + }
     399 + 
     400 + if name == ipAliasNetwork {
     401 + return net.IPAddress, nil
     402 + }
     403 + }
     404 + 
     405 + return "", errors.New("cannot derive remote host")
     406 +}
     407 + 
     408 +func targetNetworkByIP(target types.ContainerJSON, ip string) (string, error) {
     409 + for name, net := range target.NetworkSettings.Networks {
     410 + if net.IPAddress == ip {
     411 + return name, nil
     412 + }
     413 + }
     414 + return "", errors.New("cannot deduce target network by IP")
     415 +}
     416 + 
     417 +func startLocalForwarder(
     418 + ctx context.Context,
     419 + client dockerclient.CommonAPIClient,
     420 + target types.ContainerJSON,
     421 + fwd forwarding,
     422 +) (types.ContainerJSON, error) {
     423 + exposedPorts, portBindings, err := nat.ParsePortSpecs([]string{fwd.toDockerPortSpec()})
     424 + if err != nil {
     425 + return types.ContainerJSON{}, err
     426 + }
     427 + 
     428 + network, err := targetNetworkByIP(target, fwd.remoteIP)
     429 + if err != nil {
     430 + return types.ContainerJSON{}, err
     431 + }
     432 + 
     433 + resp, err := client.ContainerCreate(
     434 + ctx,
     435 + &container.Config{
     436 + Image: forwarderImage,
     437 + Entrypoint: []string{"socat"},
     438 + Cmd: []string{
     439 + fmt.Sprintf("TCP-LISTEN:%s,fork", fwd.remotePort),
     440 + fmt.Sprintf("TCP-CONNECT:%s:%s", fwd.remoteIP, fwd.remotePort),
     441 + },
     442 + ExposedPorts: exposedPorts,
     443 + },
     444 + &container.HostConfig{
     445 + AutoRemove: true,
     446 + PortBindings: portBindings,
     447 + NetworkMode: container.NetworkMode(network),
     448 + },
     449 + nil,
     450 + nil,
     451 + "cdebug-fwd-"+uuid.ShortID(),
     452 + )
     453 + if err != nil {
     454 + return types.ContainerJSON{}, fmt.Errorf("cannot create forwarder container: %w", err)
    280 455   }
    281 456   
    282  - return list, nil
     457 + if err := client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
     458 + return types.ContainerJSON{}, fmt.Errorf("cannot start forwarder container: %w", err)
     459 + }
     460 + 
     461 + forwarder, err := client.ContainerInspect(ctx, resp.ID)
     462 + if err != nil {
     463 + return forwarder, fmt.Errorf("cannot inspect forwarder container: %w", err)
     464 + }
     465 + return forwarder, nil
     466 +}
     467 + 
     468 +func localForwardingsToJSON(fwds []forwarding) string {
     469 + out := []map[string]string{}
     470 + for _, f := range fwds {
     471 + out = append(out, map[string]string{
     472 + "localHost": f.localIP,
     473 + "localPort": f.localPort,
     474 + "remoteHost": f.remoteIP,
     475 + "remotePort": f.remotePort,
     476 + })
     477 + }
     478 + return jsonutil.DumpIndent(out)
     479 +}
     480 + 
     481 +func localForwardingsToText(fwds []forwarding) string {
     482 + out := []string{}
     483 + for _, f := range fwds {
     484 + out = append(out, fmt.Sprintf(
     485 + "Forwarding %s:%s to %s:%s",
     486 + f.localIP, f.localPort, f.remoteIP, f.remotePort,
     487 + ))
     488 + }
     489 + return strings.Join(out, "\n")
    283 490  }
    284 491   
  • ■ ■ ■ ■
    e2e/portforward/docker_test.go
    skipped 47 lines
    48 48   return poll.Continue("waiting for `cdebug port-forward` to start up...")
    49 49   },
    50 50   poll.WithDelay(500*time.Millisecond),
    51  - poll.WithTimeout(3*time.Second),
     51 + poll.WithTimeout(30*time.Second),
    52 52   )
    53 53   
    54 54   // Probe target through forwarded port.
    skipped 13 lines
  • ■ ■ ■ ■
    pkg/jsonutil/jsonutil.go
    skipped 12 lines
    13 13  }
    14 14   
    15 15  func DumpIndent(v any) string {
    16  - b, _ := json.MarshalIndent(v, "", " ")
     16 + b, err := json.MarshalIndent(v, "", " ")
     17 + if err != nil {
     18 + panic(err)
     19 + }
    17 20   return string(b)
    18 21  }
    19 22   
Please wait...
Page is in error, reload to recover