environment.go 7.2 KB

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