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  }