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  }