Projects STRLCPY cdebug Commits 8bb5d5f9
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■
    cmd/portforward/portforward.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "context"
     5 + "errors"
    5 6   "fmt"
    6 7   "io"
     8 + "strings"
    7 9   
    8 10   "github.com/docker/docker/api/types"
    9 11   "github.com/docker/docker/api/types/container"
    skipped 15 lines
    25 27  // Possible options (kinda sorta as in ssh -L):
    26 28  // - TARGET_PORT # binds TARGET_IP:TARGET_PORT to a random port on localhost
    27 29  // - TARGET_IP:TARGET_PORT # The second form is needed to:
    28  -// # 1) allow target's localhost ports expose
    29  -// # 2) specify a concrete IP if a multi-network target listens on 0.0.0.0
     30 +// # 1) allow exposing target's localhost ports
     31 +// # 2) specify a concrete IP for a multi-network target
    30 32  //
    31 33  // - LOCAL_PORT:TARGET_PORT # binds TARGET_IP:TARGET_PORT to LOCAL_PORT on localhost
    32 34  // - LOCAL_PORT:TARGET_IP:TARGET_PORT
    33 35  //
    34  -// - LOCAL_IP:LOCAL_PORT:TARGET_PORT # binds TARGET_IP:TARGET_PORT to LOCAL_PORT on LOCAL_IP
     36 +// - LOCAL_IP:LOCAL_PORT:TARGET_PORT # similar to LOCAL_PORT:TARGET_PORT but LOCAL_IP is used instead of localhost
    35 37  // - LOCAL_IP:LOCAL_PORT:TARGET_IP:TARGET_PORT
    36 38   
    37 39  const (
    38  - helperImage = "nixery.dev/socat:latest"
     40 + helperImage = "nixery.dev/shell/socat:latest"
    39 41  )
    40 42   
    41 43  type options struct {
    42  - target string
    43  - address string
    44  - ports []string
     44 + target string
     45 + forwardings []string
    45 46  }
    46 47   
    47 48  func NewCommand(cli cmd.CLI) *cobra.Command {
    48 49   var opts options
    49 50   
    50 51   cmd := &cobra.Command{
    51  - Use: "port-forward [OPTIONS] CONTAINER [LOCAL_PORT:]TARGET_PORT [...[LOCAL_PORT_N:]TARGET_PORT_N]",
    52  - Short: "Publish a port of an already running container (kind of)",
     52 + Use: "port-forward CONTAINER [[LOCAL_IP:]LOCAL_PORT:]TARGET_PORT [...]",
     53 + Short: `"Publish" one or more ports of an already running container`,
    53 54   Args: cobra.MinimumNArgs(2),
    54 55   RunE: func(cmd *cobra.Command, args []string) error {
    55 56   opts.target = args[0]
    56  - if len(args) > 1 {
    57  - opts.ports = args[1:]
    58  - }
     57 + opts.forwardings = args[1:]
    59 58   return runPortForward(context.Background(), cli, &opts)
    60 59   },
    61 60   }
    62 61   
    63  - flags := cmd.Flags()
    64  - flags.SetInterspersed(false) // Instead of relying on --
    65  - 
    66  - flags.StringVar(
    67  - &opts.address,
    68  - "address",
    69  - "127.0.0.1",
    70  - "Host's interface address to bind the port to",
    71  - )
    72  - 
    73 62   return cmd
    74 63  }
    75 64   
    skipped 15 lines
    91 80   return err
    92 81   }
    93 82   
    94  - ports, portBindings, err := nat.ParsePortSpecs([]string{"8080:80"})
     83 + forwardings, err := prepareForwardings(target, opts.forwardings)
    95 84   if err != nil {
    96 85   return err
    97 86   }
    98 87   
    99  - contIP := target.NetworkSettings.Networks["bridge"].IPAddress
     88 + exposedPorts, portBindings, err := nat.ParsePortSpecs(forwardings.toDockerPortSpecs())
     89 + if err != nil {
     90 + return err
     91 + }
     92 + 
     93 + // TODO: Iterate over all forwardings.
    100 94   resp, err := client.ContainerCreate(
    101 95   ctx,
    102 96   &container.Config{
    103 97   Image: helperImage,
    104 98   Entrypoint: []string{"socat"},
    105 99   Cmd: []string{
    106  - "TCP-LISTEN:80,fork",
    107  - fmt.Sprintf("TCP-CONNECT:%s:%d", contIP, 80),
     100 + fmt.Sprintf("TCP-LISTEN:%s,fork", forwardings[0].targetPort),
     101 + fmt.Sprintf("TCP-CONNECT:%s:%d", forwardings[0].targetIP, forwardings[0].targetPort),
    108 102   },
    109  - ExposedPorts: ports,
     103 + ExposedPorts: exposedPorts,
    110 104   },
    111 105   &container.HostConfig{
    112 106   AutoRemove: true,
    skipped 11 lines
    124 118   return fmt.Errorf("cannot start port-forwarder container: %w", err)
    125 119   }
    126 120   
     121 + // TODO: Handle ctrl + C and other signals.
    127 122   forwarderStatusCh, forwarderErrCh := client.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    128 123   // targetStatusCh, targetErrCh := client.ContainerWait(ctx, target.ID, container.WaitConditionNotRunning)
    129 124   select {
    skipped 23 lines
    153 148   return err
    154 149  }
    155 150   
     151 +type forwarding struct {
     152 + localIP string
     153 + localPort string
     154 + targetIP string
     155 + targetPort string
     156 +}
     157 + 
     158 +type forwardingList []forwarding
     159 + 
     160 +func (list forwardingList) toDockerPortSpecs() []string {
     161 + // ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
     162 + var spec []string
     163 + for _, f := range list {
     164 + spec = append(spec, fmt.Sprintf("%s:%s:%s", f.localIP, f.localPort, f.targetPort))
     165 + }
     166 + return spec
     167 +}
     168 + 
     169 +func prepareForwardings(
     170 + target types.ContainerJSON,
     171 + forwardings []string,
     172 +) (forwardingList, error) {
     173 + var list forwardingList
     174 + 
     175 + targetIP := target.NetworkSettings.Networks["bridge"].IPAddress
     176 + 
     177 + for _, f := range forwardings {
     178 + parts := strings.Split(f, ":")
     179 + if len(parts) == 1 {
     180 + // Case 1: TARGET_PORT
     181 + 
     182 + if _, err := nat.ParsePort(parts[0]); err != nil {
     183 + // TODO: Return a user-friendly error.
     184 + return nil, err
     185 + }
     186 + 
     187 + // TODO: if "target has more than 1 IP" return err
     188 + 
     189 + list = append(list, forwarding{
     190 + localIP: "127.0.0.1",
     191 + targetPort: parts[0],
     192 + targetIP: targetIP,
     193 + })
     194 + continue
     195 + }
     196 + 
     197 + if len(parts) == 2 {
     198 + if _, err := nat.ParsePort(parts[0]); err == nil {
     199 + // Case 2: LOCAL_PORT:TARGET_PORT
     200 + 
     201 + // TODO: if "target has more than 1 IP" return err
     202 + 
     203 + list = append(list, forwarding{
     204 + localPort: parts[0],
     205 + localIP: "127.0.0.1",
     206 + targetPort: parts[1],
     207 + targetIP: targetIP,
     208 + })
     209 + } else {
     210 + // Case 3: TARGET_IP:TARGET_PORT
     211 + 
     212 + // TODO: if "parts[0] not in target IP list" return err
     213 + 
     214 + list = append(list, forwarding{
     215 + localIP: "127.0.0.1",
     216 + targetPort: parts[1],
     217 + targetIP: parts[0],
     218 + })
     219 + }
     220 + continue
     221 + }
     222 + 
     223 + // TODO: other cases
     224 + return nil, errors.New("implement me")
     225 + }
     226 + 
     227 + return list, nil
     228 +}
     229 + 
Please wait...
Page is in error, reload to recover