github.com/seilagamo/poc-lava-release@v0.3.3-rc3/internal/dockerutil/dockerutil.go (about) 1 // Copyright 2023 Adevinta 2 3 // Package dockerutil provides Docker utility functions. 4 package dockerutil 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 "net" 11 "os" 12 "path/filepath" 13 14 "github.com/docker/cli/cli/command" 15 "github.com/docker/cli/cli/config" 16 "github.com/docker/cli/cli/flags" 17 "github.com/docker/docker/api/types" 18 "github.com/docker/docker/client" 19 "github.com/docker/go-connections/tlsconfig" 20 ) 21 22 // DefaultBridgeNetwork is the name of the default bridge network in 23 // Docker. 24 const DefaultBridgeNetwork = "bridge" 25 26 // NewAPIClient returns a new Docker API client. This client behaves 27 // as close as possible to the Docker CLI. It gets its configuration 28 // from the Docker config file and honors the [Docker CLI environment 29 // variables]. It also sets up TLS authentication if TLS is enabled. 30 // 31 // [Docker CLI environment variables]: https://docs.docker.com/engine/reference/commandline/cli/#environment-variables 32 func NewAPIClient() (client.APIClient, error) { 33 tlsVerify := os.Getenv(client.EnvTLSVerify) != "" 34 35 var tlsopts *tlsconfig.Options 36 if tlsVerify { 37 certPath := os.Getenv(client.EnvOverrideCertPath) 38 if certPath == "" { 39 certPath = config.Dir() 40 } 41 tlsopts = &tlsconfig.Options{ 42 CAFile: filepath.Join(certPath, flags.DefaultCaFile), 43 CertFile: filepath.Join(certPath, flags.DefaultCertFile), 44 KeyFile: filepath.Join(certPath, flags.DefaultKeyFile), 45 } 46 } 47 48 opts := &flags.ClientOptions{ 49 TLS: tlsVerify, 50 TLSVerify: tlsVerify, 51 TLSOptions: tlsopts, 52 } 53 54 return command.NewAPIClientFromFlags(opts, config.LoadDefaultConfigFile(io.Discard)) 55 } 56 57 // Gateways returns the gateways of the specified Docker network. 58 func Gateways(ctx context.Context, cli client.APIClient, network string) ([]*net.IPNet, error) { 59 resp, err := cli.NetworkInspect(ctx, network, types.NetworkInspectOptions{}) 60 if err != nil { 61 return nil, fmt.Errorf("network inspect: %w", err) 62 } 63 64 var gws []*net.IPNet 65 for _, cfg := range resp.IPAM.Config { 66 _, subnet, err := net.ParseCIDR(cfg.Subnet) 67 if err != nil { 68 return nil, fmt.Errorf("invalid subnet: %v", cfg.Subnet) 69 } 70 71 ip := net.ParseIP(cfg.Gateway) 72 if ip == nil { 73 return nil, fmt.Errorf("invalid IP: %v", cfg.Gateway) 74 } 75 76 if !subnet.Contains(ip) { 77 return nil, fmt.Errorf("subnet mismatch: ip: %v, subnet: %v", ip, subnet) 78 } 79 80 subnet.IP = ip 81 gws = append(gws, subnet) 82 } 83 return gws, nil 84 } 85 86 // BridgeGateway returns the gateway of the default Docker bridge 87 // network. 88 func BridgeGateway(cli client.APIClient) (*net.IPNet, error) { 89 gws, err := Gateways(context.Background(), cli, DefaultBridgeNetwork) 90 if err != nil { 91 return nil, fmt.Errorf("could not get Docker network gateway: %w", err) 92 } 93 if len(gws) != 1 { 94 return nil, fmt.Errorf("unexpected number of gateways: %v", len(gws)) 95 } 96 return gws[0], nil 97 } 98 99 // BridgeHost returns a host that points to the Docker host and is 100 // reachable from the containers running in the default bridge. 101 func BridgeHost(cli client.APIClient) (string, error) { 102 return bridgeHost(cli, net.InterfaceAddrs) 103 } 104 105 // ifaceAddrsResolver returns a list of the system's unicast interface 106 // addresses. For instance, [net.InterfaceAddrs]. 107 type ifaceAddrsResolver func() ([]net.Addr, error) 108 109 // bridgeHost is wrapped by [BridgeHost] using [net.InterfaceAddrs]. 110 // The resolver allows tests to simulate different setups. 111 func bridgeHost(cli client.APIClient, r ifaceAddrsResolver) (string, error) { 112 isDesktop, err := isDockerDesktop(cli, r) 113 if err != nil { 114 return "", fmt.Errorf("detect Docker Desktop: %w", err) 115 } 116 117 if isDesktop { 118 return "127.0.0.1", nil 119 } 120 121 gw, err := BridgeGateway(cli) 122 if err != nil { 123 return "", fmt.Errorf("get bridge gateway: %w", err) 124 } 125 return gw.IP.String(), nil 126 } 127 128 // isDockerDesktop returns true if the Docker daemon is part of Docker 129 // Desktop. That means that the IP of the gateway of the default 130 // Docker bridge network is not assigned to any network interface. The 131 // provided resolver is used to list the system's unicast interface 132 // addresses. 133 func isDockerDesktop(cli client.APIClient, r ifaceAddrsResolver) (bool, error) { 134 addrs, err := r() 135 if err != nil { 136 return false, fmt.Errorf("interface addrs: %w", err) 137 } 138 139 gw, err := BridgeGateway(cli) 140 if err != nil { 141 return false, fmt.Errorf("get bridge gateway: %w", err) 142 } 143 144 for _, addr := range addrs { 145 if gw.String() == addr.String() { 146 return false, nil 147 } 148 } 149 return true, nil 150 }