github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/dockertest/common.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package dockertest
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/m3db/m3/src/x/instrument"
    28  
    29  	"github.com/ory/dockertest/v3"
    30  	dc "github.com/ory/dockertest/v3/docker"
    31  )
    32  
    33  var (
    34  	networkName = "d-test"
    35  	volumeName  = "d-test"
    36  
    37  	// ErrClosed is a common error for use when a container has been closed.
    38  	ErrClosed = errors.New("container has been closed")
    39  )
    40  
    41  // Image represents a docker image.
    42  type Image struct {
    43  	Name string
    44  	Tag  string
    45  }
    46  
    47  // GoalStateVerifier asserts that a resource is in a particular state.
    48  // TODO: more info here; this interface is unclear from usage
    49  type GoalStateVerifier func(output string, err error) error
    50  
    51  // ResourceOptions returns options for creating
    52  // a Resource.
    53  //nolint:maligned
    54  type ResourceOptions struct {
    55  	OverrideDefaults bool
    56  	Source           string
    57  	ContainerName    string
    58  	Image            Image
    59  	PortList         []int
    60  	PortMappings     map[dc.Port][]dc.PortBinding
    61  
    62  	// NoNetworkOverlay if set, disables use of the default integration testing network we create (networkName).
    63  	NoNetworkOverlay bool
    64  
    65  	Cmd []string
    66  
    67  	// Env is the environment for the docker container; it corresponds 1:1 with dockertest.RunOptions.
    68  	// Format should be: VAR=value
    69  	Env []string
    70  	// Mounts creates mounts in the container that map back to a resource
    71  	// on the host system.
    72  	Mounts []string
    73  	// TmpfsMounts creates mounts to the container's temporary file system
    74  	TmpfsMounts    []string
    75  	InstrumentOpts instrument.Options
    76  }
    77  
    78  // NB: this will fill unset fields with given default values.
    79  func (o ResourceOptions) WithDefaults(
    80  	defaultOpts ResourceOptions) ResourceOptions {
    81  	if o.OverrideDefaults {
    82  		return o
    83  	}
    84  
    85  	if len(o.Source) == 0 {
    86  		o.Source = defaultOpts.Source
    87  	}
    88  
    89  	if len(o.ContainerName) == 0 {
    90  		o.ContainerName = defaultOpts.ContainerName
    91  	}
    92  
    93  	if o.Image == (Image{}) {
    94  		o.Image = defaultOpts.Image
    95  	}
    96  
    97  	if len(o.PortList) == 0 {
    98  		o.PortList = defaultOpts.PortList
    99  	}
   100  
   101  	if len(o.TmpfsMounts) == 0 {
   102  		o.TmpfsMounts = defaultOpts.TmpfsMounts
   103  	}
   104  
   105  	if len(o.Mounts) == 0 {
   106  		o.Mounts = defaultOpts.Mounts
   107  	}
   108  
   109  	if o.InstrumentOpts == nil {
   110  		o.InstrumentOpts = defaultOpts.InstrumentOpts
   111  	}
   112  
   113  	return o
   114  }
   115  
   116  func newOptions(name string) *dockertest.RunOptions {
   117  	return &dockertest.RunOptions{
   118  		Name: name,
   119  	}
   120  }
   121  
   122  func useImage(opts *dockertest.RunOptions, image Image) *dockertest.RunOptions {
   123  	opts.Repository = image.Name
   124  	opts.Tag = image.Tag
   125  	return opts
   126  }
   127  
   128  // SetupNetwork sets up a network within docker.
   129  func SetupNetwork(pool *dockertest.Pool, cleanIfExists bool) error {
   130  	networks, err := pool.Client.ListNetworks()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	for _, n := range networks {
   136  		if n.Name == networkName {
   137  			if !cleanIfExists {
   138  				return nil
   139  			}
   140  			if err := pool.Client.RemoveNetwork(networkName); err != nil {
   141  				return err
   142  			}
   143  
   144  			break
   145  		}
   146  	}
   147  
   148  	_, err = pool.Client.CreateNetwork(dc.CreateNetworkOptions{Name: networkName})
   149  	return err
   150  }
   151  
   152  // SetupVolume creates a default docker volume, with name volumeName (in this package)
   153  func SetupVolume(pool *dockertest.Pool) error {
   154  	volumes, err := pool.Client.ListVolumes(dc.ListVolumesOptions{})
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	for _, v := range volumes {
   160  		if volumeName == v.Name {
   161  			if err := pool.Client.RemoveVolume(volumeName); err != nil {
   162  				return err
   163  			}
   164  
   165  			break
   166  		}
   167  	}
   168  
   169  	_, err = pool.Client.CreateVolume(dc.CreateVolumeOptions{
   170  		Name: volumeName,
   171  	})
   172  
   173  	return err
   174  }
   175  
   176  func exposePorts(
   177  	opts *dockertest.RunOptions,
   178  	portList []int,
   179  	mappings map[dc.Port][]dc.PortBinding,
   180  ) (*dockertest.RunOptions, error) {
   181  	ports := make(map[dc.Port][]dc.PortBinding, len(portList))
   182  	for _, p := range portList {
   183  		port := fmt.Sprintf("%d", p)
   184  
   185  		portRepresentation := dc.Port(fmt.Sprintf("%s/tcp", port))
   186  		binding := dc.PortBinding{HostIP: "0.0.0.0", HostPort: port}
   187  		entry, found := ports[portRepresentation]
   188  		if !found {
   189  			entry = []dc.PortBinding{binding}
   190  		} else {
   191  			entry = append(entry, binding)
   192  		}
   193  
   194  		ports[portRepresentation] = entry
   195  	}
   196  
   197  	for k, v := range mappings {
   198  		if _, ok := ports[k]; ok {
   199  			return nil, fmt.Errorf("mapping %s already specified by PortList; "+
   200  				"mappings should be in PortList or PortMappings but not both",
   201  				k,
   202  			)
   203  		}
   204  		ports[k] = v
   205  	}
   206  
   207  	opts.PortBindings = ports
   208  	return opts, nil
   209  }