| package environment // import "github.com/docker/docker/testutil/environment" |
| |
| import ( |
| "context" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "github.com/docker/docker/api/types" |
| "github.com/docker/docker/api/types/filters" |
| "github.com/docker/docker/client" |
| "github.com/docker/docker/testutil/fixtures/load" |
| "github.com/pkg/errors" |
| "gotest.tools/v3/assert" |
| ) |
| |
| // Execution contains information about the current test execution and daemon |
| // under test |
| type Execution struct { |
| client client.APIClient |
| DaemonInfo types.Info |
| OSType string |
| PlatformDefaults PlatformDefaults |
| protectedElements protectedElements |
| } |
| |
| // PlatformDefaults are defaults values for the platform of the daemon under test |
| type PlatformDefaults struct { |
| BaseImage string |
| VolumesConfigPath string |
| ContainerStoragePath string |
| } |
| |
| // New creates a new Execution struct |
| // This is configured using the env client (see client.FromEnv) |
| func New() (*Execution, error) { |
| c, err := client.NewClientWithOpts(client.FromEnv) |
| if err != nil { |
| return nil, errors.Wrapf(err, "failed to create client") |
| } |
| return FromClient(c) |
| } |
| |
| // FromClient creates a new Execution environment from the passed in client |
| func FromClient(c *client.Client) (*Execution, error) { |
| info, err := c.Info(context.Background()) |
| if err != nil { |
| return nil, errors.Wrapf(err, "failed to get info from daemon") |
| } |
| |
| osType := getOSType(info) |
| |
| return &Execution{ |
| client: c, |
| DaemonInfo: info, |
| OSType: osType, |
| PlatformDefaults: getPlatformDefaults(info, osType), |
| protectedElements: newProtectedElements(), |
| }, nil |
| } |
| |
| func getOSType(info types.Info) string { |
| // Docker EE does not set the OSType so allow the user to override this value. |
| userOsType := os.Getenv("TEST_OSTYPE") |
| if userOsType != "" { |
| return userOsType |
| } |
| return info.OSType |
| } |
| |
| func getPlatformDefaults(info types.Info, osType string) PlatformDefaults { |
| volumesPath := filepath.Join(info.DockerRootDir, "volumes") |
| containersPath := filepath.Join(info.DockerRootDir, "containers") |
| |
| switch osType { |
| case "linux": |
| return PlatformDefaults{ |
| BaseImage: "scratch", |
| VolumesConfigPath: toSlash(volumesPath), |
| ContainerStoragePath: toSlash(containersPath), |
| } |
| case "windows": |
| baseImage := "microsoft/windowsservercore" |
| if overrideBaseImage := os.Getenv("WINDOWS_BASE_IMAGE"); overrideBaseImage != "" { |
| baseImage = overrideBaseImage |
| if overrideBaseImageTag := os.Getenv("WINDOWS_BASE_IMAGE_TAG"); overrideBaseImageTag != "" { |
| baseImage = baseImage + ":" + overrideBaseImageTag |
| } |
| } |
| fmt.Println("INFO: Windows Base image is ", baseImage) |
| return PlatformDefaults{ |
| BaseImage: baseImage, |
| VolumesConfigPath: filepath.FromSlash(volumesPath), |
| ContainerStoragePath: filepath.FromSlash(containersPath), |
| } |
| default: |
| panic(fmt.Sprintf("unknown OSType for daemon: %s", osType)) |
| } |
| } |
| |
| // Make sure in context of daemon, not the local platform. Note we can't |
| // use filepath.FromSlash or ToSlash here as they are a no-op on Unix. |
| func toSlash(path string) string { |
| return strings.ReplaceAll(path, `\`, `/`) |
| } |
| |
| // IsLocalDaemon is true if the daemon under test is on the same |
| // host as the test process. |
| // |
| // Deterministically working out the environment in which CI is running |
| // to evaluate whether the daemon is local or remote is not possible through |
| // a build tag. |
| // |
| // For example Windows to Linux CI under Jenkins tests the 64-bit |
| // Windows binary build with the daemon build tag, but calls a remote |
| // Linux daemon. |
| // |
| // We can't just say if Windows then assume the daemon is local as at |
| // some point, we will be testing the Windows CLI against a Windows daemon. |
| // |
| // Similarly, it will be perfectly valid to also run CLI tests from |
| // a Linux CLI (built with the daemon tag) against a Windows daemon. |
| func (e *Execution) IsLocalDaemon() bool { |
| return os.Getenv("DOCKER_REMOTE_DAEMON") == "" |
| } |
| |
| // IsRemoteDaemon is true if the daemon under test is on different host |
| // as the test process. |
| func (e *Execution) IsRemoteDaemon() bool { |
| return !e.IsLocalDaemon() |
| } |
| |
| // DaemonAPIVersion returns the negotiated daemon api version |
| func (e *Execution) DaemonAPIVersion() string { |
| version, err := e.APIClient().ServerVersion(context.TODO()) |
| if err != nil { |
| return "" |
| } |
| return version.APIVersion |
| } |
| |
| // Print the execution details to stdout |
| // TODO: print everything |
| func (e *Execution) Print() { |
| if e.IsLocalDaemon() { |
| fmt.Println("INFO: Testing against a local daemon") |
| } else { |
| fmt.Println("INFO: Testing against a remote daemon") |
| } |
| } |
| |
| // APIClient returns an APIClient connected to the daemon under test |
| func (e *Execution) APIClient() client.APIClient { |
| return e.client |
| } |
| |
| // IsUserNamespace returns whether the user namespace remapping is enabled |
| func (e *Execution) IsUserNamespace() bool { |
| root := os.Getenv("DOCKER_REMAP_ROOT") |
| return root != "" |
| } |
| |
| // RuntimeIsWindowsContainerd returns whether containerd runtime is used on Windows |
| func (e *Execution) RuntimeIsWindowsContainerd() bool { |
| return os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME") == "1" |
| } |
| |
| // IsRootless returns whether the rootless mode is enabled |
| func (e *Execution) IsRootless() bool { |
| return os.Getenv("DOCKER_ROOTLESS") != "" |
| } |
| |
| // IsUserNamespaceInKernel returns whether the kernel supports user namespaces |
| func (e *Execution) IsUserNamespaceInKernel() bool { |
| if _, err := os.Stat("/proc/self/uid_map"); os.IsNotExist(err) { |
| /* |
| * This kernel-provided file only exists if user namespaces are |
| * supported |
| */ |
| return false |
| } |
| |
| // We need extra check on redhat based distributions |
| if f, err := os.Open("/sys/module/user_namespace/parameters/enable"); err == nil { |
| defer f.Close() |
| b := make([]byte, 1) |
| _, _ = f.Read(b) |
| return string(b) != "N" |
| } |
| |
| return true |
| } |
| |
| // HasExistingImage checks whether there is an image with the given reference. |
| // Note that this is done by filtering and then checking whether there were any |
| // results -- so ambiguous references might result in false-positives. |
| func (e *Execution) HasExistingImage(t testing.TB, reference string) bool { |
| imageList, err := e.APIClient().ImageList(context.Background(), types.ImageListOptions{ |
| All: true, |
| Filters: filters.NewArgs( |
| filters.Arg("dangling", "false"), |
| filters.Arg("reference", reference), |
| ), |
| }) |
| assert.NilError(t, err, "failed to list images") |
| |
| return len(imageList) > 0 |
| } |
| |
| // EnsureFrozenImagesLinux loads frozen test images into the daemon |
| // if they aren't already loaded |
| func EnsureFrozenImagesLinux(testEnv *Execution) error { |
| if testEnv.OSType == "linux" { |
| err := load.FrozenImagesLinux(testEnv.APIClient(), frozenImages...) |
| if err != nil { |
| return errors.Wrap(err, "error loading frozen images") |
| } |
| } |
| return nil |
| } |
| |
| // GitHubActions is true if test is executed on a GitHub Runner. |
| func (e *Execution) GitHubActions() bool { |
| return os.Getenv("GITHUB_ACTIONS") != "" |
| } |