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 }