github.com/rish1988/moby@v25.0.2+incompatible/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/api/types/image"
    14  	"github.com/docker/docker/api/types/system"
    15  	"github.com/docker/docker/client"
    16  	"github.com/docker/docker/testutil/fixtures/load"
    17  	"github.com/pkg/errors"
    18  	"gotest.tools/v3/assert"
    19  )
    20  
    21  // Execution contains information about the current test execution and daemon
    22  // under test
    23  type Execution struct {
    24  	client            client.APIClient
    25  	DaemonInfo        system.Info
    26  	DaemonVersion     types.Version
    27  	PlatformDefaults  PlatformDefaults
    28  	protectedElements protectedElements
    29  }
    30  
    31  // PlatformDefaults are defaults values for the platform of the daemon under test
    32  type PlatformDefaults struct {
    33  	BaseImage            string
    34  	VolumesConfigPath    string
    35  	ContainerStoragePath string
    36  }
    37  
    38  // New creates a new Execution struct
    39  // This is configured using the env client (see client.FromEnv)
    40  func New(ctx context.Context) (*Execution, error) {
    41  	c, err := client.NewClientWithOpts(client.FromEnv)
    42  	if err != nil {
    43  		return nil, errors.Wrapf(err, "failed to create client")
    44  	}
    45  	return FromClient(ctx, c)
    46  }
    47  
    48  // FromClient creates a new Execution environment from the passed in client
    49  func FromClient(ctx context.Context, c *client.Client) (*Execution, error) {
    50  	info, err := c.Info(ctx)
    51  	if err != nil {
    52  		return nil, errors.Wrapf(err, "failed to get info from daemon")
    53  	}
    54  	v, err := c.ServerVersion(context.Background())
    55  	if err != nil {
    56  		return nil, errors.Wrapf(err, "failed to get version info from daemon")
    57  	}
    58  
    59  	return &Execution{
    60  		client:            c,
    61  		DaemonInfo:        info,
    62  		DaemonVersion:     v,
    63  		PlatformDefaults:  getPlatformDefaults(info),
    64  		protectedElements: newProtectedElements(),
    65  	}, nil
    66  }
    67  
    68  func getPlatformDefaults(info system.Info) PlatformDefaults {
    69  	volumesPath := filepath.Join(info.DockerRootDir, "volumes")
    70  	containersPath := filepath.Join(info.DockerRootDir, "containers")
    71  
    72  	switch info.OSType {
    73  	case "linux":
    74  		return PlatformDefaults{
    75  			BaseImage:            "scratch",
    76  			VolumesConfigPath:    toSlash(volumesPath),
    77  			ContainerStoragePath: toSlash(containersPath),
    78  		}
    79  	case "windows":
    80  		baseImage := "mcr.microsoft.com/windows/servercore:ltsc2022"
    81  		if overrideBaseImage := os.Getenv("WINDOWS_BASE_IMAGE"); overrideBaseImage != "" {
    82  			baseImage = overrideBaseImage
    83  			if overrideBaseImageTag := os.Getenv("WINDOWS_BASE_IMAGE_TAG"); overrideBaseImageTag != "" {
    84  				baseImage = baseImage + ":" + overrideBaseImageTag
    85  			}
    86  		}
    87  		fmt.Println("INFO: Windows Base image is ", baseImage)
    88  		return PlatformDefaults{
    89  			BaseImage:            baseImage,
    90  			VolumesConfigPath:    filepath.FromSlash(volumesPath),
    91  			ContainerStoragePath: filepath.FromSlash(containersPath),
    92  		}
    93  	default:
    94  		panic(fmt.Sprintf("unknown OSType for daemon: %s", info.OSType))
    95  	}
    96  }
    97  
    98  // Make sure in context of daemon, not the local platform. Note we can't
    99  // use filepath.ToSlash here as that is a no-op on Unix.
   100  func toSlash(path string) string {
   101  	return strings.ReplaceAll(path, `\`, `/`)
   102  }
   103  
   104  // IsLocalDaemon is true if the daemon under test is on the same
   105  // host as the test process.
   106  //
   107  // Deterministically working out the environment in which CI is running
   108  // to evaluate whether the daemon is local or remote is not possible through
   109  // a build tag.
   110  //
   111  // For example Windows to Linux CI under Jenkins tests the 64-bit
   112  // Windows binary build with the daemon build tag, but calls a remote
   113  // Linux daemon.
   114  //
   115  // We can't just say if Windows then assume the daemon is local as at
   116  // some point, we will be testing the Windows CLI against a Windows daemon.
   117  //
   118  // Similarly, it will be perfectly valid to also run CLI tests from
   119  // a Linux CLI (built with the daemon tag) against a Windows daemon.
   120  func (e *Execution) IsLocalDaemon() bool {
   121  	return os.Getenv("DOCKER_REMOTE_DAEMON") == ""
   122  }
   123  
   124  // IsRemoteDaemon is true if the daemon under test is on different host
   125  // as the test process.
   126  func (e *Execution) IsRemoteDaemon() bool {
   127  	return !e.IsLocalDaemon()
   128  }
   129  
   130  // DaemonAPIVersion returns the negotiated daemon api version
   131  func (e *Execution) DaemonAPIVersion() string {
   132  	version, err := e.APIClient().ServerVersion(context.TODO())
   133  	if err != nil {
   134  		return ""
   135  	}
   136  	return version.APIVersion
   137  }
   138  
   139  // Print the execution details to stdout
   140  // TODO: print everything
   141  func (e *Execution) Print() {
   142  	if e.IsLocalDaemon() {
   143  		fmt.Println("INFO: Testing against a local daemon")
   144  	} else {
   145  		fmt.Println("INFO: Testing against a remote daemon")
   146  	}
   147  }
   148  
   149  // APIClient returns an APIClient connected to the daemon under test
   150  func (e *Execution) APIClient() client.APIClient {
   151  	return e.client
   152  }
   153  
   154  // IsUserNamespace returns whether the user namespace remapping is enabled
   155  func (e *Execution) IsUserNamespace() bool {
   156  	root := os.Getenv("DOCKER_REMAP_ROOT")
   157  	return root != ""
   158  }
   159  
   160  // RuntimeIsWindowsContainerd returns whether containerd runtime is used on Windows
   161  func (e *Execution) RuntimeIsWindowsContainerd() bool {
   162  	return os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME") == "1"
   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  // UsingSnapshotter returns whether containerd snapshotters are used for the
   192  // tests by checking if the "TEST_INTEGRATION_USE_SNAPSHOTTER" is set to a
   193  // non-empty value.
   194  func (e *Execution) UsingSnapshotter() bool {
   195  	return os.Getenv("TEST_INTEGRATION_USE_SNAPSHOTTER") != ""
   196  }
   197  
   198  // HasExistingImage checks whether there is an image with the given reference.
   199  // Note that this is done by filtering and then checking whether there were any
   200  // results -- so ambiguous references might result in false-positives.
   201  func (e *Execution) HasExistingImage(t testing.TB, reference string) bool {
   202  	imageList, err := e.APIClient().ImageList(context.Background(), image.ListOptions{
   203  		All: true,
   204  		Filters: filters.NewArgs(
   205  			filters.Arg("dangling", "false"),
   206  			filters.Arg("reference", reference),
   207  		),
   208  	})
   209  	assert.NilError(t, err, "failed to list images")
   210  
   211  	return len(imageList) > 0
   212  }
   213  
   214  // EnsureFrozenImagesLinux loads frozen test images into the daemon
   215  // if they aren't already loaded
   216  func EnsureFrozenImagesLinux(ctx context.Context, testEnv *Execution) error {
   217  	if testEnv.DaemonInfo.OSType == "linux" {
   218  		err := load.FrozenImagesLinux(ctx, testEnv.APIClient(), frozenImages...)
   219  		if err != nil {
   220  			return errors.Wrap(err, "error loading frozen images")
   221  		}
   222  	}
   223  	return nil
   224  }
   225  
   226  // GitHubActions is true if test is executed on a GitHub Runner.
   227  func (e *Execution) GitHubActions() bool {
   228  	return os.Getenv("GITHUB_ACTIONS") != ""
   229  }
   230  
   231  // NotAmd64 returns true if the daemon's architecture is not amd64
   232  func (e *Execution) NotAmd64() bool {
   233  	return e.DaemonVersion.Arch != "amd64"
   234  }