github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/testutil/environment/environment.go (about) 1 package environment // import "github.com/docker/docker/testutil/environment" 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/filters" 13 "github.com/docker/docker/client" 14 "github.com/docker/docker/testutil/fixtures/load" 15 "github.com/pkg/errors" 16 "gotest.tools/v3/assert" 17 ) 18 19 // Execution contains information about the current test execution and daemon 20 // under test 21 type Execution struct { 22 client client.APIClient 23 DaemonInfo types.Info 24 OSType string 25 PlatformDefaults PlatformDefaults 26 protectedElements protectedElements 27 } 28 29 // PlatformDefaults are defaults values for the platform of the daemon under test 30 type PlatformDefaults struct { 31 BaseImage string 32 VolumesConfigPath string 33 ContainerStoragePath string 34 } 35 36 // New creates a new Execution struct 37 // This is configured using the env client (see client.FromEnv) 38 func New() (*Execution, error) { 39 c, err := client.NewClientWithOpts(client.FromEnv) 40 if err != nil { 41 return nil, errors.Wrapf(err, "failed to create client") 42 } 43 return FromClient(c) 44 } 45 46 // FromClient creates a new Execution environment from the passed in client 47 func FromClient(c *client.Client) (*Execution, error) { 48 info, err := c.Info(context.Background()) 49 if err != nil { 50 return nil, errors.Wrapf(err, "failed to get info from daemon") 51 } 52 53 osType := getOSType(info) 54 55 return &Execution{ 56 client: c, 57 DaemonInfo: info, 58 OSType: osType, 59 PlatformDefaults: getPlatformDefaults(info, osType), 60 protectedElements: newProtectedElements(), 61 }, nil 62 } 63 64 func getOSType(info types.Info) string { 65 // Docker EE does not set the OSType so allow the user to override this value. 66 userOsType := os.Getenv("TEST_OSTYPE") 67 if userOsType != "" { 68 return userOsType 69 } 70 return info.OSType 71 } 72 73 func getPlatformDefaults(info types.Info, osType string) PlatformDefaults { 74 volumesPath := filepath.Join(info.DockerRootDir, "volumes") 75 containersPath := filepath.Join(info.DockerRootDir, "containers") 76 77 switch osType { 78 case "linux": 79 return PlatformDefaults{ 80 BaseImage: "scratch", 81 VolumesConfigPath: toSlash(volumesPath), 82 ContainerStoragePath: toSlash(containersPath), 83 } 84 case "windows": 85 baseImage := "microsoft/windowsservercore" 86 if overrideBaseImage := os.Getenv("WINDOWS_BASE_IMAGE"); overrideBaseImage != "" { 87 baseImage = overrideBaseImage 88 if overrideBaseImageTag := os.Getenv("WINDOWS_BASE_IMAGE_TAG"); overrideBaseImageTag != "" { 89 baseImage = baseImage + ":" + overrideBaseImageTag 90 } 91 } 92 fmt.Println("INFO: Windows Base image is ", baseImage) 93 return PlatformDefaults{ 94 BaseImage: baseImage, 95 VolumesConfigPath: filepath.FromSlash(volumesPath), 96 ContainerStoragePath: filepath.FromSlash(containersPath), 97 } 98 default: 99 panic(fmt.Sprintf("unknown OSType for daemon: %s", osType)) 100 } 101 } 102 103 // Make sure in context of daemon, not the local platform. Note we can't 104 // use filepath.FromSlash or ToSlash here as they are a no-op on Unix. 105 func toSlash(path string) string { 106 return strings.Replace(path, `\`, `/`, -1) 107 } 108 109 // IsLocalDaemon is true if the daemon under test is on the same 110 // host as the test process. 111 // 112 // Deterministically working out the environment in which CI is running 113 // to evaluate whether the daemon is local or remote is not possible through 114 // a build tag. 115 // 116 // For example Windows to Linux CI under Jenkins tests the 64-bit 117 // Windows binary build with the daemon build tag, but calls a remote 118 // Linux daemon. 119 // 120 // We can't just say if Windows then assume the daemon is local as at 121 // some point, we will be testing the Windows CLI against a Windows daemon. 122 // 123 // Similarly, it will be perfectly valid to also run CLI tests from 124 // a Linux CLI (built with the daemon tag) against a Windows daemon. 125 func (e *Execution) IsLocalDaemon() bool { 126 return os.Getenv("DOCKER_REMOTE_DAEMON") == "" 127 } 128 129 // IsRemoteDaemon is true if the daemon under test is on different host 130 // as the test process. 131 func (e *Execution) IsRemoteDaemon() bool { 132 return !e.IsLocalDaemon() 133 } 134 135 // DaemonAPIVersion returns the negotiated daemon api version 136 func (e *Execution) DaemonAPIVersion() string { 137 version, err := e.APIClient().ServerVersion(context.TODO()) 138 if err != nil { 139 return "" 140 } 141 return version.APIVersion 142 } 143 144 // Print the execution details to stdout 145 // TODO: print everything 146 func (e *Execution) Print() { 147 if e.IsLocalDaemon() { 148 fmt.Println("INFO: Testing against a local daemon") 149 } else { 150 fmt.Println("INFO: Testing against a remote daemon") 151 } 152 } 153 154 // APIClient returns an APIClient connected to the daemon under test 155 func (e *Execution) APIClient() client.APIClient { 156 return e.client 157 } 158 159 // IsUserNamespace returns whether the user namespace remapping is enabled 160 func (e *Execution) IsUserNamespace() bool { 161 root := os.Getenv("DOCKER_REMAP_ROOT") 162 return root != "" 163 } 164 165 // IsRootless returns whether the rootless mode is enabled 166 func (e *Execution) IsRootless() bool { 167 return os.Getenv("DOCKER_ROOTLESS") != "" 168 } 169 170 // IsUserNamespaceInKernel returns whether the kernel supports user namespaces 171 func (e *Execution) IsUserNamespaceInKernel() bool { 172 if _, err := os.Stat("/proc/self/uid_map"); os.IsNotExist(err) { 173 /* 174 * This kernel-provided file only exists if user namespaces are 175 * supported 176 */ 177 return false 178 } 179 180 // We need extra check on redhat based distributions 181 if f, err := os.Open("/sys/module/user_namespace/parameters/enable"); err == nil { 182 defer f.Close() 183 b := make([]byte, 1) 184 _, _ = f.Read(b) 185 return string(b) != "N" 186 } 187 188 return true 189 } 190 191 // HasExistingImage checks whether there is an image with the given reference. 192 // Note that this is done by filtering and then checking whether there were any 193 // results -- so ambiguous references might result in false-positives. 194 func (e *Execution) HasExistingImage(t testing.TB, reference string) bool { 195 client := e.APIClient() 196 filter := filters.NewArgs() 197 filter.Add("dangling", "false") 198 filter.Add("reference", reference) 199 imageList, err := client.ImageList(context.Background(), types.ImageListOptions{ 200 All: true, 201 Filters: filter, 202 }) 203 assert.NilError(t, err, "failed to list images") 204 205 return len(imageList) > 0 206 } 207 208 // EnsureFrozenImagesLinux loads frozen test images into the daemon 209 // if they aren't already loaded 210 func EnsureFrozenImagesLinux(testEnv *Execution) error { 211 if testEnv.OSType == "linux" { 212 err := load.FrozenImagesLinux(testEnv.APIClient(), frozenImages...) 213 if err != nil { 214 return errors.Wrap(err, "error loading frozen images") 215 } 216 } 217 return nil 218 }