| package 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/api/types/image" |
| "github.com/docker/docker/api/types/system" |
| "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 system.Info |
| DaemonVersion types.Version |
| 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(ctx context.Context) (*Execution, error) { |
| c, err := client.NewClientWithOpts(client.FromEnv) |
| if err != nil { |
| return nil, errors.Wrapf(err, "failed to create client") |
| } |
| return FromClient(ctx, c) |
| } |
| |
| // FromClient creates a new Execution environment from the passed in client |
| func FromClient(ctx context.Context, c *client.Client) (*Execution, error) { |
| info, err := c.Info(ctx) |
| if err != nil { |
| return nil, errors.Wrapf(err, "failed to get info from daemon") |
| } |
| v, err := c.ServerVersion(context.Background()) |
| if err != nil { |
| return nil, errors.Wrapf(err, "failed to get version info from daemon") |
| } |
| |
| return &Execution{ |
| client: c, |
| DaemonInfo: info, |
| DaemonVersion: v, |
| PlatformDefaults: getPlatformDefaults(info), |
| protectedElements: newProtectedElements(), |
| }, nil |
| } |
| |
| func getPlatformDefaults(info system.Info) PlatformDefaults { |
| volumesPath := filepath.Join(info.DockerRootDir, "volumes") |
| containersPath := filepath.Join(info.DockerRootDir, "containers") |
| |
| switch info.OSType { |
| case "linux": |
| return PlatformDefaults{ |
| BaseImage: "scratch", |
| VolumesConfigPath: toSlash(volumesPath), |
| ContainerStoragePath: toSlash(containersPath), |
| } |
| case "windows": |
| baseImage := "mcr.microsoft.com/windows/servercore:ltsc2022" |
| 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", info.OSType)) |
| } |
| } |
| |
| // Make sure in context of daemon, not the local platform. Note we can't |
| // use filepath.ToSlash here as that is 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 |
| } |
| |
| // UsingSnapshotter returns whether containerd snapshotters are used for the |
| // tests by checking if the "TEST_INTEGRATION_USE_SNAPSHOTTER" is set to a |
| // non-empty value. |
| func (e *Execution) UsingSnapshotter() bool { |
| return os.Getenv("TEST_INTEGRATION_USE_SNAPSHOTTER") != "" |
| } |
| |
| // 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(), image.ListOptions{ |
| 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(ctx context.Context, testEnv *Execution) error { |
| if testEnv.DaemonInfo.OSType == "linux" { |
| err := load.FrozenImagesLinux(ctx, 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") != "" |
| } |
| |
| // NotAmd64 returns true if the daemon's architecture is not amd64 |
| func (e *Execution) NotAmd64() bool { |
| return e.DaemonVersion.Arch != "amd64" |
| } |
| |
| // FirewallBackendDriver returns the value of FirewallBackend.Driver from |
| // system Info if set, else the empty string. |
| func (e *Execution) FirewallBackendDriver() string { |
| if e.DaemonInfo.FirewallBackend == nil { |
| return "" |
| } |
| return e.DaemonInfo.FirewallBackend.Driver |
| } |