Signed-off-by: Vincent Demeester <vincent@sbr.pm>
| ... | ... |
@@ -4,27 +4,136 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"net/http/httptest" |
| 6 | 6 |
"os" |
| 7 |
+ "os/exec" |
|
| 7 | 8 |
"path/filepath" |
| 9 |
+ "strings" |
|
| 8 | 10 |
"sync" |
| 9 | 11 |
"syscall" |
| 10 | 12 |
"testing" |
| 11 | 13 |
|
| 14 |
+ "github.com/docker/docker/api/types/container" |
|
| 12 | 15 |
"github.com/docker/docker/api/types/swarm" |
| 13 | 16 |
cliconfig "github.com/docker/docker/cli/config" |
| 14 | 17 |
"github.com/docker/docker/integration-cli/daemon" |
| 18 |
+ "github.com/docker/docker/integration-cli/environment" |
|
| 15 | 19 |
"github.com/docker/docker/pkg/reexec" |
| 16 | 20 |
"github.com/go-check/check" |
| 17 | 21 |
) |
| 18 | 22 |
|
| 19 |
-func Test(t *testing.T) {
|
|
| 23 |
+const ( |
|
| 24 |
+ // the private registry to use for tests |
|
| 25 |
+ privateRegistryURL = "127.0.0.1:5000" |
|
| 26 |
+ |
|
| 27 |
+ // path to containerd's ctr binary |
|
| 28 |
+ ctrBinary = "docker-containerd-ctr" |
|
| 29 |
+ |
|
| 30 |
+ // the docker daemon binary to use |
|
| 31 |
+ dockerdBinary = "dockerd" |
|
| 32 |
+) |
|
| 33 |
+ |
|
| 34 |
+var ( |
|
| 35 |
+ testEnv *environment.Execution |
|
| 36 |
+ |
|
| 37 |
+ // FIXME(vdemeester) remove these and use environmentdaemonPid |
|
| 38 |
+ protectedImages = map[string]struct{}{}
|
|
| 39 |
+ |
|
| 40 |
+ // the docker client binary to use |
|
| 41 |
+ dockerBinary = "docker" |
|
| 42 |
+ |
|
| 43 |
+ // isLocalDaemon is true if the daemon under test is on the same |
|
| 44 |
+ // host as the CLI. |
|
| 45 |
+ isLocalDaemon bool |
|
| 46 |
+ // daemonPlatform is held globally so that tests can make intelligent |
|
| 47 |
+ // decisions on how to configure themselves according to the platform |
|
| 48 |
+ // of the daemon. This is initialized in docker_utils by sending |
|
| 49 |
+ // a version call to the daemon and examining the response header. |
|
| 50 |
+ daemonPlatform string |
|
| 51 |
+ |
|
| 52 |
+ // WindowsBaseImage is the name of the base image for Windows testing |
|
| 53 |
+ // Environment variable WINDOWS_BASE_IMAGE can override this |
|
| 54 |
+ WindowsBaseImage string |
|
| 55 |
+ |
|
| 56 |
+ // For a local daemon on Linux, these values will be used for testing |
|
| 57 |
+ // user namespace support as the standard graph path(s) will be |
|
| 58 |
+ // appended with the root remapped uid.gid prefix |
|
| 59 |
+ dockerBasePath string |
|
| 60 |
+ volumesConfigPath string |
|
| 61 |
+ containerStoragePath string |
|
| 62 |
+ |
|
| 63 |
+ // daemonStorageDriver is held globally so that tests can know the storage |
|
| 64 |
+ // driver of the daemon. This is initialized in docker_utils by sending |
|
| 65 |
+ // a version call to the daemon and examining the response header. |
|
| 66 |
+ daemonStorageDriver string |
|
| 67 |
+ |
|
| 68 |
+ // isolation is the isolation mode of the daemon under test |
|
| 69 |
+ isolation container.Isolation |
|
| 70 |
+ |
|
| 71 |
+ // experimentalDaemon tell whether the main daemon has |
|
| 72 |
+ // experimental features enabled or not |
|
| 73 |
+ experimentalDaemon bool |
|
| 74 |
+ |
|
| 75 |
+ daemonKernelVersion string |
|
| 76 |
+) |
|
| 77 |
+ |
|
| 78 |
+func init() {
|
|
| 79 |
+ var err error |
|
| 80 |
+ |
|
| 20 | 81 |
reexec.Init() // This is required for external graphdriver tests |
| 21 | 82 |
|
| 83 |
+ testEnv, err = environment.New() |
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ fmt.Println(err) |
|
| 86 |
+ os.Exit(1) |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ assignGlobalVariablesFromTestEnv(testEnv) |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+// FIXME(vdemeester) remove this and use environment |
|
| 93 |
+func assignGlobalVariablesFromTestEnv(testEnv *environment.Execution) {
|
|
| 94 |
+ isLocalDaemon = testEnv.LocalDaemon() |
|
| 95 |
+ daemonPlatform = testEnv.DaemonPlatform() |
|
| 96 |
+ dockerBasePath = testEnv.DockerBasePath() |
|
| 97 |
+ volumesConfigPath = testEnv.VolumesConfigPath() |
|
| 98 |
+ containerStoragePath = testEnv.ContainerStoragePath() |
|
| 99 |
+ daemonStorageDriver = testEnv.DaemonStorageDriver() |
|
| 100 |
+ isolation = testEnv.Isolation() |
|
| 101 |
+ experimentalDaemon = testEnv.ExperimentalDaemon() |
|
| 102 |
+ daemonKernelVersion = testEnv.DaemonKernelVersion() |
|
| 103 |
+ WindowsBaseImage = testEnv.MinimalBaseImage() |
|
| 104 |
+} |
|
| 105 |
+ |
|
| 106 |
+func TestMain(m *testing.M) {
|
|
| 107 |
+ var err error |
|
| 108 |
+ if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" {
|
|
| 109 |
+ dockerBinary = dockerBin |
|
| 110 |
+ } |
|
| 111 |
+ dockerBinary, err = exec.LookPath(dockerBinary) |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ fmt.Printf("ERROR: couldn't resolve full path to the Docker binary (%v)\n", err)
|
|
| 114 |
+ os.Exit(1) |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ cmd := exec.Command(dockerBinary, "images", "-f", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}")
|
|
| 118 |
+ cmd.Env = appendBaseEnv(true) |
|
| 119 |
+ out, err := cmd.CombinedOutput() |
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ panic(fmt.Errorf("err=%v\nout=%s\n", err, out))
|
|
| 122 |
+ } |
|
| 123 |
+ images := strings.Split(strings.TrimSpace(string(out)), "\n") |
|
| 124 |
+ for _, img := range images {
|
|
| 125 |
+ protectedImages[img] = struct{}{}
|
|
| 126 |
+ } |
|
| 22 | 127 |
if !isLocalDaemon {
|
| 23 | 128 |
fmt.Println("INFO: Testing against a remote daemon")
|
| 24 | 129 |
} else {
|
| 25 | 130 |
fmt.Println("INFO: Testing against a local daemon")
|
| 26 | 131 |
} |
| 132 |
+ exitCode := m.Run() |
|
| 133 |
+ os.Exit(exitCode) |
|
| 134 |
+} |
|
| 27 | 135 |
|
| 136 |
+func Test(t *testing.T) {
|
|
| 28 | 137 |
if daemonPlatform == "linux" {
|
| 29 | 138 |
ensureFrozenImagesLinux(t) |
| 30 | 139 |
} |
| ... | ... |
@@ -39,8 +148,8 @@ type DockerSuite struct {
|
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 | 41 |
func (s *DockerSuite) OnTimeout(c *check.C) {
|
| 42 |
- if daemonPid > 0 && isLocalDaemon {
|
|
| 43 |
- daemon.SignalDaemonDump(daemonPid) |
|
| 42 |
+ if testEnv.DaemonPID() > 0 && isLocalDaemon {
|
|
| 43 |
+ daemon.SignalDaemonDump(testEnv.DaemonPID()) |
|
| 44 | 44 |
} |
| 45 | 45 |
} |
| 46 | 46 |
|
| ... | ... |
@@ -324,7 +324,7 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
|
| 324 | 324 |
func (d *Daemon) StartWithBusybox(t testingT, arg ...string) {
|
| 325 | 325 |
d.Start(t, arg...) |
| 326 | 326 |
if err := d.LoadBusybox(); err != nil {
|
| 327 |
- t.Fatalf("Error loading busybox image to current daeom: %s", d.id)
|
|
| 327 |
+ t.Fatalf("Error loading busybox image to current daemon: %s\n%v", d.id, err)
|
|
| 328 | 328 |
} |
| 329 | 329 |
} |
| 330 | 330 |
|
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
|
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 | 12 |
"github.com/docker/docker/api/types/container" |
| 13 |
+ "github.com/docker/docker/integration-cli/environment" |
|
| 13 | 14 |
"github.com/docker/docker/pkg/integration/checker" |
| 14 | 15 |
icmd "github.com/docker/docker/pkg/integration/cmd" |
| 15 | 16 |
"github.com/go-check/check" |
| ... | ... |
@@ -212,7 +213,7 @@ func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) {
|
| 212 | 212 |
if daemonPlatform == "windows" {
|
| 213 | 213 |
modifier = "" |
| 214 | 214 |
// TODO Windows: Temporary check - remove once TP5 support is dropped |
| 215 |
- if windowsDaemonKV < 14350 {
|
|
| 215 |
+ if environment.WindowsKernelVersion(testEnv.DaemonKernelVersion()) < 14350 {
|
|
| 216 | 216 |
c.Skip("Needs later Windows build for RO volumes")
|
| 217 | 217 |
} |
| 218 | 218 |
// Linux creates the host directory if it doesn't exist. Windows does not. |
| 219 | 219 |
deleted file mode 100644 |
| ... | ... |
@@ -1,142 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "encoding/json" |
|
| 5 |
- "fmt" |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "os" |
|
| 8 |
- "os/exec" |
|
| 9 |
- "path/filepath" |
|
| 10 |
- "strconv" |
|
| 11 |
- |
|
| 12 |
- "github.com/docker/docker/api/types/container" |
|
| 13 |
- "github.com/docker/docker/pkg/reexec" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-const ( |
|
| 17 |
- // the private registry to use for tests |
|
| 18 |
- privateRegistryURL = "127.0.0.1:5000" |
|
| 19 |
- |
|
| 20 |
- // the docker daemon binary to use |
|
| 21 |
- dockerdBinary = "dockerd" |
|
| 22 |
-) |
|
| 23 |
- |
|
| 24 |
-var ( |
|
| 25 |
- // the docker client binary to use |
|
| 26 |
- dockerBinary = "docker" |
|
| 27 |
- |
|
| 28 |
- // path to containerd's ctr binary |
|
| 29 |
- ctrBinary = "docker-containerd-ctr" |
|
| 30 |
- |
|
| 31 |
- // isLocalDaemon is true if the daemon under test is on the same |
|
| 32 |
- // host as the CLI. |
|
| 33 |
- isLocalDaemon bool |
|
| 34 |
- |
|
| 35 |
- // daemonPlatform is held globally so that tests can make intelligent |
|
| 36 |
- // decisions on how to configure themselves according to the platform |
|
| 37 |
- // of the daemon. This is initialized in docker_utils by sending |
|
| 38 |
- // a version call to the daemon and examining the response header. |
|
| 39 |
- daemonPlatform string |
|
| 40 |
- |
|
| 41 |
- // windowsDaemonKV is used on Windows to distinguish between different |
|
| 42 |
- // versions. This is necessary to enable certain tests based on whether |
|
| 43 |
- // the platform supports it. For example, Windows Server 2016 TP3 did |
|
| 44 |
- // not support volumes, but TP4 did. |
|
| 45 |
- windowsDaemonKV int |
|
| 46 |
- |
|
| 47 |
- // For a local daemon on Linux, these values will be used for testing |
|
| 48 |
- // user namespace support as the standard graph path(s) will be |
|
| 49 |
- // appended with the root remapped uid.gid prefix |
|
| 50 |
- dockerBasePath string |
|
| 51 |
- volumesConfigPath string |
|
| 52 |
- containerStoragePath string |
|
| 53 |
- |
|
| 54 |
- // experimentalDaemon tell whether the main daemon has |
|
| 55 |
- // experimental features enabled or not |
|
| 56 |
- experimentalDaemon bool |
|
| 57 |
- |
|
| 58 |
- // daemonStorageDriver is held globally so that tests can know the storage |
|
| 59 |
- // driver of the daemon. This is initialized in docker_utils by sending |
|
| 60 |
- // a version call to the daemon and examining the response header. |
|
| 61 |
- daemonStorageDriver string |
|
| 62 |
- |
|
| 63 |
- // WindowsBaseImage is the name of the base image for Windows testing |
|
| 64 |
- // Environment variable WINDOWS_BASE_IMAGE can override this |
|
| 65 |
- WindowsBaseImage = "microsoft/windowsservercore" |
|
| 66 |
- |
|
| 67 |
- // isolation is the isolation mode of the daemon under test |
|
| 68 |
- isolation container.Isolation |
|
| 69 |
- |
|
| 70 |
- // daemonPid is the pid of the main test daemon |
|
| 71 |
- daemonPid int |
|
| 72 |
- |
|
| 73 |
- daemonKernelVersion string |
|
| 74 |
-) |
|
| 75 |
- |
|
| 76 |
-func init() {
|
|
| 77 |
- reexec.Init() |
|
| 78 |
- if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" {
|
|
| 79 |
- dockerBinary = dockerBin |
|
| 80 |
- } |
|
| 81 |
- var err error |
|
| 82 |
- dockerBinary, err = exec.LookPath(dockerBinary) |
|
| 83 |
- if err != nil {
|
|
| 84 |
- fmt.Printf("ERROR: couldn't resolve full path to the Docker binary (%v)\n", err)
|
|
| 85 |
- os.Exit(1) |
|
| 86 |
- } |
|
| 87 |
- |
|
| 88 |
- // Deterministically working out the environment in which CI is running |
|
| 89 |
- // to evaluate whether the daemon is local or remote is not possible through |
|
| 90 |
- // a build tag. |
|
| 91 |
- // |
|
| 92 |
- // For example Windows to Linux CI under Jenkins tests the 64-bit |
|
| 93 |
- // Windows binary build with the daemon build tag, but calls a remote |
|
| 94 |
- // Linux daemon. |
|
| 95 |
- // |
|
| 96 |
- // We can't just say if Windows then assume the daemon is local as at |
|
| 97 |
- // some point, we will be testing the Windows CLI against a Windows daemon. |
|
| 98 |
- // |
|
| 99 |
- // Similarly, it will be perfectly valid to also run CLI tests from |
|
| 100 |
- // a Linux CLI (built with the daemon tag) against a Windows daemon. |
|
| 101 |
- if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
|
|
| 102 |
- isLocalDaemon = false |
|
| 103 |
- } else {
|
|
| 104 |
- isLocalDaemon = true |
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- // TODO Windows CI. This are incorrect and need fixing into |
|
| 108 |
- // platform specific pieces. |
|
| 109 |
- // This is only used for a tests with local daemon true (Linux-only today) |
|
| 110 |
- // default is "/var/lib/docker", but we'll try and ask the |
|
| 111 |
- // /info endpoint for the specific root dir |
|
| 112 |
- dockerBasePath = "/var/lib/docker" |
|
| 113 |
- type Info struct {
|
|
| 114 |
- DockerRootDir string |
|
| 115 |
- ExperimentalBuild bool |
|
| 116 |
- KernelVersion string |
|
| 117 |
- } |
|
| 118 |
- var i Info |
|
| 119 |
- status, b, err := sockRequest("GET", "/info", nil)
|
|
| 120 |
- if err == nil && status == 200 {
|
|
| 121 |
- if err = json.Unmarshal(b, &i); err == nil {
|
|
| 122 |
- dockerBasePath = i.DockerRootDir |
|
| 123 |
- experimentalDaemon = i.ExperimentalBuild |
|
| 124 |
- daemonKernelVersion = i.KernelVersion |
|
| 125 |
- } |
|
| 126 |
- } |
|
| 127 |
- volumesConfigPath = dockerBasePath + "/volumes" |
|
| 128 |
- containerStoragePath = dockerBasePath + "/containers" |
|
| 129 |
- |
|
| 130 |
- if len(os.Getenv("WINDOWS_BASE_IMAGE")) > 0 {
|
|
| 131 |
- WindowsBaseImage = os.Getenv("WINDOWS_BASE_IMAGE")
|
|
| 132 |
- fmt.Println("INFO: Windows Base image is ", WindowsBaseImage)
|
|
| 133 |
- } |
|
| 134 |
- |
|
| 135 |
- dest := os.Getenv("DEST")
|
|
| 136 |
- b, err = ioutil.ReadFile(filepath.Join(dest, "docker.pid")) |
|
| 137 |
- if err == nil {
|
|
| 138 |
- if p, err := strconv.ParseInt(string(b), 10, 32); err == nil {
|
|
| 139 |
- daemonPid = int(p) |
|
| 140 |
- } |
|
| 141 |
- } |
|
| 142 |
-} |
| 143 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,1368 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bufio" |
|
| 5 |
- "bytes" |
|
| 6 |
- "encoding/json" |
|
| 7 |
- "errors" |
|
| 8 |
- "fmt" |
|
| 9 |
- "io" |
|
| 10 |
- "io/ioutil" |
|
| 11 |
- "net" |
|
| 12 |
- "net/http" |
|
| 13 |
- "net/http/httptest" |
|
| 14 |
- "net/http/httputil" |
|
| 15 |
- "net/url" |
|
| 16 |
- "os" |
|
| 17 |
- "os/exec" |
|
| 18 |
- "path" |
|
| 19 |
- "path/filepath" |
|
| 20 |
- "strconv" |
|
| 21 |
- "strings" |
|
| 22 |
- "time" |
|
| 23 |
- |
|
| 24 |
- "github.com/docker/docker/api/types" |
|
| 25 |
- volumetypes "github.com/docker/docker/api/types/volume" |
|
| 26 |
- "github.com/docker/docker/integration-cli/daemon" |
|
| 27 |
- "github.com/docker/docker/opts" |
|
| 28 |
- "github.com/docker/docker/pkg/httputils" |
|
| 29 |
- "github.com/docker/docker/pkg/integration" |
|
| 30 |
- "github.com/docker/docker/pkg/integration/checker" |
|
| 31 |
- icmd "github.com/docker/docker/pkg/integration/cmd" |
|
| 32 |
- "github.com/docker/docker/pkg/ioutils" |
|
| 33 |
- "github.com/docker/docker/pkg/stringutils" |
|
| 34 |
- "github.com/go-check/check" |
|
| 35 |
-) |
|
| 36 |
- |
|
| 37 |
-func init() {
|
|
| 38 |
- cmd := exec.Command(dockerBinary, "images", "-f", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}")
|
|
| 39 |
- cmd.Env = appendBaseEnv(true) |
|
| 40 |
- out, err := cmd.CombinedOutput() |
|
| 41 |
- if err != nil {
|
|
| 42 |
- panic(fmt.Errorf("err=%v\nout=%s\n", err, out))
|
|
| 43 |
- } |
|
| 44 |
- images := strings.Split(strings.TrimSpace(string(out)), "\n") |
|
| 45 |
- for _, img := range images {
|
|
| 46 |
- protectedImages[img] = struct{}{}
|
|
| 47 |
- } |
|
| 48 |
- |
|
| 49 |
- res, body, err := sockRequestRaw("GET", "/info", nil, "application/json")
|
|
| 50 |
- if err != nil {
|
|
| 51 |
- panic(fmt.Errorf("Init failed to get /info: %v", err))
|
|
| 52 |
- } |
|
| 53 |
- defer body.Close() |
|
| 54 |
- if res.StatusCode != http.StatusOK {
|
|
| 55 |
- panic(fmt.Errorf("Init failed to get /info. Res=%v", res))
|
|
| 56 |
- } |
|
| 57 |
- |
|
| 58 |
- svrHeader, _ := httputils.ParseServerHeader(res.Header.Get("Server"))
|
|
| 59 |
- daemonPlatform = svrHeader.OS |
|
| 60 |
- if daemonPlatform != "linux" && daemonPlatform != "windows" {
|
|
| 61 |
- panic("Cannot run tests against platform: " + daemonPlatform)
|
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- // Now we know the daemon platform, can set paths used by tests. |
|
| 65 |
- var info types.Info |
|
| 66 |
- err = json.NewDecoder(body).Decode(&info) |
|
| 67 |
- if err != nil {
|
|
| 68 |
- panic(fmt.Errorf("Init failed to unmarshal docker info: %v", err))
|
|
| 69 |
- } |
|
| 70 |
- |
|
| 71 |
- daemonStorageDriver = info.Driver |
|
| 72 |
- dockerBasePath = info.DockerRootDir |
|
| 73 |
- volumesConfigPath = filepath.Join(dockerBasePath, "volumes") |
|
| 74 |
- containerStoragePath = filepath.Join(dockerBasePath, "containers") |
|
| 75 |
- // Make sure in context of daemon, not the local platform. Note we can't |
|
| 76 |
- // use filepath.FromSlash or ToSlash here as they are a no-op on Unix. |
|
| 77 |
- if daemonPlatform == "windows" {
|
|
| 78 |
- volumesConfigPath = strings.Replace(volumesConfigPath, `/`, `\`, -1) |
|
| 79 |
- containerStoragePath = strings.Replace(containerStoragePath, `/`, `\`, -1) |
|
| 80 |
- // On Windows, extract out the version as we need to make selective |
|
| 81 |
- // decisions during integration testing as and when features are implemented. |
|
| 82 |
- // e.g. in "10.0 10550 (10550.1000.amd64fre.branch.date-time)" we want 10550 |
|
| 83 |
- windowsDaemonKV, _ = strconv.Atoi(strings.Split(info.KernelVersion, " ")[1]) |
|
| 84 |
- } else {
|
|
| 85 |
- volumesConfigPath = strings.Replace(volumesConfigPath, `\`, `/`, -1) |
|
| 86 |
- containerStoragePath = strings.Replace(containerStoragePath, `\`, `/`, -1) |
|
| 87 |
- } |
|
| 88 |
- isolation = info.Isolation |
|
| 89 |
-} |
|
| 90 |
- |
|
| 91 |
-func daemonHost() string {
|
|
| 92 |
- daemonURLStr := "unix://" + opts.DefaultUnixSocket |
|
| 93 |
- if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
|
|
| 94 |
- daemonURLStr = daemonHostVar |
|
| 95 |
- } |
|
| 96 |
- return daemonURLStr |
|
| 97 |
-} |
|
| 98 |
- |
|
| 99 |
-// FIXME(vdemeester) should probably completely move to daemon struct/methods |
|
| 100 |
-func sockConn(timeout time.Duration, daemonStr string) (net.Conn, error) {
|
|
| 101 |
- if daemonStr == "" {
|
|
| 102 |
- daemonStr = daemonHost() |
|
| 103 |
- } |
|
| 104 |
- return daemon.SockConn(timeout, daemonStr) |
|
| 105 |
-} |
|
| 106 |
- |
|
| 107 |
-func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) {
|
|
| 108 |
- jsonData := bytes.NewBuffer(nil) |
|
| 109 |
- if err := json.NewEncoder(jsonData).Encode(data); err != nil {
|
|
| 110 |
- return -1, nil, err |
|
| 111 |
- } |
|
| 112 |
- |
|
| 113 |
- res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") |
|
| 114 |
- if err != nil {
|
|
| 115 |
- return -1, nil, err |
|
| 116 |
- } |
|
| 117 |
- b, err := integration.ReadBody(body) |
|
| 118 |
- return res.StatusCode, b, err |
|
| 119 |
-} |
|
| 120 |
- |
|
| 121 |
-func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) {
|
|
| 122 |
- return sockRequestRawToDaemon(method, endpoint, data, ct, "") |
|
| 123 |
-} |
|
| 124 |
- |
|
| 125 |
-func sockRequestRawToDaemon(method, endpoint string, data io.Reader, ct, daemon string) (*http.Response, io.ReadCloser, error) {
|
|
| 126 |
- req, client, err := newRequestClient(method, endpoint, data, ct, daemon) |
|
| 127 |
- if err != nil {
|
|
| 128 |
- return nil, nil, err |
|
| 129 |
- } |
|
| 130 |
- |
|
| 131 |
- resp, err := client.Do(req) |
|
| 132 |
- if err != nil {
|
|
| 133 |
- client.Close() |
|
| 134 |
- return nil, nil, err |
|
| 135 |
- } |
|
| 136 |
- body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
|
|
| 137 |
- defer resp.Body.Close() |
|
| 138 |
- return client.Close() |
|
| 139 |
- }) |
|
| 140 |
- |
|
| 141 |
- return resp, body, nil |
|
| 142 |
-} |
|
| 143 |
- |
|
| 144 |
-func sockRequestHijack(method, endpoint string, data io.Reader, ct string) (net.Conn, *bufio.Reader, error) {
|
|
| 145 |
- req, client, err := newRequestClient(method, endpoint, data, ct, "") |
|
| 146 |
- if err != nil {
|
|
| 147 |
- return nil, nil, err |
|
| 148 |
- } |
|
| 149 |
- |
|
| 150 |
- client.Do(req) |
|
| 151 |
- conn, br := client.Hijack() |
|
| 152 |
- return conn, br, nil |
|
| 153 |
-} |
|
| 154 |
- |
|
| 155 |
-func newRequestClient(method, endpoint string, data io.Reader, ct, daemon string) (*http.Request, *httputil.ClientConn, error) {
|
|
| 156 |
- c, err := sockConn(time.Duration(10*time.Second), daemon) |
|
| 157 |
- if err != nil {
|
|
| 158 |
- return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err)
|
|
| 159 |
- } |
|
| 160 |
- |
|
| 161 |
- client := httputil.NewClientConn(c, nil) |
|
| 162 |
- |
|
| 163 |
- req, err := http.NewRequest(method, endpoint, data) |
|
| 164 |
- if err != nil {
|
|
| 165 |
- client.Close() |
|
| 166 |
- return nil, nil, fmt.Errorf("could not create new request: %v", err)
|
|
| 167 |
- } |
|
| 168 |
- |
|
| 169 |
- if ct != "" {
|
|
| 170 |
- req.Header.Set("Content-Type", ct)
|
|
| 171 |
- } |
|
| 172 |
- return req, client, nil |
|
| 173 |
-} |
|
| 174 |
- |
|
| 175 |
-func deleteContainer(container ...string) error {
|
|
| 176 |
- result := icmd.RunCommand(dockerBinary, append([]string{"rm", "-fv"}, container...)...)
|
|
| 177 |
- return result.Compare(icmd.Success) |
|
| 178 |
-} |
|
| 179 |
- |
|
| 180 |
-func getAllContainers() (string, error) {
|
|
| 181 |
- getContainersCmd := exec.Command(dockerBinary, "ps", "-q", "-a") |
|
| 182 |
- out, exitCode, err := runCommandWithOutput(getContainersCmd) |
|
| 183 |
- if exitCode != 0 && err == nil {
|
|
| 184 |
- err = fmt.Errorf("failed to get a list of containers: %v\n", out)
|
|
| 185 |
- } |
|
| 186 |
- |
|
| 187 |
- return out, err |
|
| 188 |
-} |
|
| 189 |
- |
|
| 190 |
-func deleteAllContainers(c *check.C) {
|
|
| 191 |
- containers, err := getAllContainers() |
|
| 192 |
- c.Assert(err, checker.IsNil, check.Commentf("containers: %v", containers))
|
|
| 193 |
- |
|
| 194 |
- if containers != "" {
|
|
| 195 |
- err = deleteContainer(strings.Split(strings.TrimSpace(containers), "\n")...) |
|
| 196 |
- c.Assert(err, checker.IsNil) |
|
| 197 |
- } |
|
| 198 |
-} |
|
| 199 |
- |
|
| 200 |
-func deleteAllNetworks(c *check.C) {
|
|
| 201 |
- networks, err := getAllNetworks() |
|
| 202 |
- c.Assert(err, check.IsNil) |
|
| 203 |
- var errs []string |
|
| 204 |
- for _, n := range networks {
|
|
| 205 |
- if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
|
|
| 206 |
- continue |
|
| 207 |
- } |
|
| 208 |
- if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
|
|
| 209 |
- // nat is a pre-defined network on Windows and cannot be removed |
|
| 210 |
- continue |
|
| 211 |
- } |
|
| 212 |
- status, b, err := sockRequest("DELETE", "/networks/"+n.Name, nil)
|
|
| 213 |
- if err != nil {
|
|
| 214 |
- errs = append(errs, err.Error()) |
|
| 215 |
- continue |
|
| 216 |
- } |
|
| 217 |
- if status != http.StatusNoContent {
|
|
| 218 |
- errs = append(errs, fmt.Sprintf("error deleting network %s: %s", n.Name, string(b)))
|
|
| 219 |
- } |
|
| 220 |
- } |
|
| 221 |
- c.Assert(errs, checker.HasLen, 0, check.Commentf(strings.Join(errs, "\n"))) |
|
| 222 |
-} |
|
| 223 |
- |
|
| 224 |
-func getAllNetworks() ([]types.NetworkResource, error) {
|
|
| 225 |
- var networks []types.NetworkResource |
|
| 226 |
- _, b, err := sockRequest("GET", "/networks", nil)
|
|
| 227 |
- if err != nil {
|
|
| 228 |
- return nil, err |
|
| 229 |
- } |
|
| 230 |
- if err := json.Unmarshal(b, &networks); err != nil {
|
|
| 231 |
- return nil, err |
|
| 232 |
- } |
|
| 233 |
- return networks, nil |
|
| 234 |
-} |
|
| 235 |
- |
|
| 236 |
-func deleteAllPlugins(c *check.C) {
|
|
| 237 |
- plugins, err := getAllPlugins() |
|
| 238 |
- c.Assert(err, checker.IsNil) |
|
| 239 |
- var errs []string |
|
| 240 |
- for _, p := range plugins {
|
|
| 241 |
- pluginName := p.Name |
|
| 242 |
- status, b, err := sockRequest("DELETE", "/plugins/"+pluginName+"?force=1", nil)
|
|
| 243 |
- if err != nil {
|
|
| 244 |
- errs = append(errs, err.Error()) |
|
| 245 |
- continue |
|
| 246 |
- } |
|
| 247 |
- if status != http.StatusOK {
|
|
| 248 |
- errs = append(errs, fmt.Sprintf("error deleting plugin %s: %s", p.Name, string(b)))
|
|
| 249 |
- } |
|
| 250 |
- } |
|
| 251 |
- c.Assert(errs, checker.HasLen, 0, check.Commentf(strings.Join(errs, "\n"))) |
|
| 252 |
-} |
|
| 253 |
- |
|
| 254 |
-func getAllPlugins() (types.PluginsListResponse, error) {
|
|
| 255 |
- var plugins types.PluginsListResponse |
|
| 256 |
- _, b, err := sockRequest("GET", "/plugins", nil)
|
|
| 257 |
- if err != nil {
|
|
| 258 |
- return nil, err |
|
| 259 |
- } |
|
| 260 |
- if err := json.Unmarshal(b, &plugins); err != nil {
|
|
| 261 |
- return nil, err |
|
| 262 |
- } |
|
| 263 |
- return plugins, nil |
|
| 264 |
-} |
|
| 265 |
- |
|
| 266 |
-func deleteAllVolumes(c *check.C) {
|
|
| 267 |
- volumes, err := getAllVolumes() |
|
| 268 |
- c.Assert(err, checker.IsNil) |
|
| 269 |
- var errs []string |
|
| 270 |
- for _, v := range volumes {
|
|
| 271 |
- status, b, err := sockRequest("DELETE", "/volumes/"+v.Name, nil)
|
|
| 272 |
- if err != nil {
|
|
| 273 |
- errs = append(errs, err.Error()) |
|
| 274 |
- continue |
|
| 275 |
- } |
|
| 276 |
- if status != http.StatusNoContent {
|
|
| 277 |
- errs = append(errs, fmt.Sprintf("error deleting volume %s: %s", v.Name, string(b)))
|
|
| 278 |
- } |
|
| 279 |
- } |
|
| 280 |
- c.Assert(errs, checker.HasLen, 0, check.Commentf(strings.Join(errs, "\n"))) |
|
| 281 |
-} |
|
| 282 |
- |
|
| 283 |
-func getAllVolumes() ([]*types.Volume, error) {
|
|
| 284 |
- var volumes volumetypes.VolumesListOKBody |
|
| 285 |
- _, b, err := sockRequest("GET", "/volumes", nil)
|
|
| 286 |
- if err != nil {
|
|
| 287 |
- return nil, err |
|
| 288 |
- } |
|
| 289 |
- if err := json.Unmarshal(b, &volumes); err != nil {
|
|
| 290 |
- return nil, err |
|
| 291 |
- } |
|
| 292 |
- return volumes.Volumes, nil |
|
| 293 |
-} |
|
| 294 |
- |
|
| 295 |
-var protectedImages = map[string]struct{}{}
|
|
| 296 |
- |
|
| 297 |
-func deleteAllImages(c *check.C) {
|
|
| 298 |
- cmd := exec.Command(dockerBinary, "images", "--digests") |
|
| 299 |
- cmd.Env = appendBaseEnv(true) |
|
| 300 |
- out, err := cmd.CombinedOutput() |
|
| 301 |
- c.Assert(err, checker.IsNil) |
|
| 302 |
- lines := strings.Split(string(out), "\n")[1:] |
|
| 303 |
- imgMap := map[string]struct{}{}
|
|
| 304 |
- for _, l := range lines {
|
|
| 305 |
- if l == "" {
|
|
| 306 |
- continue |
|
| 307 |
- } |
|
| 308 |
- fields := strings.Fields(l) |
|
| 309 |
- imgTag := fields[0] + ":" + fields[1] |
|
| 310 |
- if _, ok := protectedImages[imgTag]; !ok {
|
|
| 311 |
- if fields[0] == "<none>" || fields[1] == "<none>" {
|
|
| 312 |
- if fields[2] != "<none>" {
|
|
| 313 |
- imgMap[fields[0]+"@"+fields[2]] = struct{}{}
|
|
| 314 |
- } else {
|
|
| 315 |
- imgMap[fields[3]] = struct{}{}
|
|
| 316 |
- } |
|
| 317 |
- // continue |
|
| 318 |
- } else {
|
|
| 319 |
- imgMap[imgTag] = struct{}{}
|
|
| 320 |
- } |
|
| 321 |
- } |
|
| 322 |
- } |
|
| 323 |
- if len(imgMap) != 0 {
|
|
| 324 |
- imgs := make([]string, 0, len(imgMap)) |
|
| 325 |
- for k := range imgMap {
|
|
| 326 |
- imgs = append(imgs, k) |
|
| 327 |
- } |
|
| 328 |
- dockerCmd(c, append([]string{"rmi", "-f"}, imgs...)...)
|
|
| 329 |
- } |
|
| 330 |
-} |
|
| 331 |
- |
|
| 332 |
-func getPausedContainers() ([]string, error) {
|
|
| 333 |
- getPausedContainersCmd := exec.Command(dockerBinary, "ps", "-f", "status=paused", "-q", "-a") |
|
| 334 |
- out, exitCode, err := runCommandWithOutput(getPausedContainersCmd) |
|
| 335 |
- if exitCode != 0 && err == nil {
|
|
| 336 |
- err = fmt.Errorf("failed to get a list of paused containers: %v\n", out)
|
|
| 337 |
- } |
|
| 338 |
- if err != nil {
|
|
| 339 |
- return nil, err |
|
| 340 |
- } |
|
| 341 |
- |
|
| 342 |
- return strings.Fields(out), nil |
|
| 343 |
-} |
|
| 344 |
- |
|
| 345 |
-func unpauseContainer(c *check.C, container string) {
|
|
| 346 |
- dockerCmd(c, "unpause", container) |
|
| 347 |
-} |
|
| 348 |
- |
|
| 349 |
-func unpauseAllContainers(c *check.C) {
|
|
| 350 |
- containers, err := getPausedContainers() |
|
| 351 |
- c.Assert(err, checker.IsNil, check.Commentf("containers: %v", containers))
|
|
| 352 |
- for _, value := range containers {
|
|
| 353 |
- unpauseContainer(c, value) |
|
| 354 |
- } |
|
| 355 |
-} |
|
| 356 |
- |
|
| 357 |
-func deleteImages(images ...string) error {
|
|
| 358 |
- args := []string{dockerBinary, "rmi", "-f"}
|
|
| 359 |
- return icmd.RunCmd(icmd.Cmd{Command: append(args, images...)}).Error
|
|
| 360 |
-} |
|
| 361 |
- |
|
| 362 |
-func dockerCmdWithError(args ...string) (string, int, error) {
|
|
| 363 |
- if err := validateArgs(args...); err != nil {
|
|
| 364 |
- return "", 0, err |
|
| 365 |
- } |
|
| 366 |
- result := icmd.RunCommand(dockerBinary, args...) |
|
| 367 |
- if result.Error != nil {
|
|
| 368 |
- return result.Combined(), result.ExitCode, result.Compare(icmd.Success) |
|
| 369 |
- } |
|
| 370 |
- return result.Combined(), result.ExitCode, result.Error |
|
| 371 |
-} |
|
| 372 |
- |
|
| 373 |
-func dockerCmdWithStdoutStderr(c *check.C, args ...string) (string, string, int) {
|
|
| 374 |
- if err := validateArgs(args...); err != nil {
|
|
| 375 |
- c.Fatalf(err.Error()) |
|
| 376 |
- } |
|
| 377 |
- |
|
| 378 |
- result := icmd.RunCommand(dockerBinary, args...) |
|
| 379 |
- c.Assert(result, icmd.Matches, icmd.Success) |
|
| 380 |
- return result.Stdout(), result.Stderr(), result.ExitCode |
|
| 381 |
-} |
|
| 382 |
- |
|
| 383 |
-func dockerCmd(c *check.C, args ...string) (string, int) {
|
|
| 384 |
- if err := validateArgs(args...); err != nil {
|
|
| 385 |
- c.Fatalf(err.Error()) |
|
| 386 |
- } |
|
| 387 |
- result := icmd.RunCommand(dockerBinary, args...) |
|
| 388 |
- c.Assert(result, icmd.Matches, icmd.Success) |
|
| 389 |
- return result.Combined(), result.ExitCode |
|
| 390 |
-} |
|
| 391 |
- |
|
| 392 |
-func dockerCmdWithResult(args ...string) *icmd.Result {
|
|
| 393 |
- return icmd.RunCommand(dockerBinary, args...) |
|
| 394 |
-} |
|
| 395 |
- |
|
| 396 |
-func binaryWithArgs(args ...string) []string {
|
|
| 397 |
- return append([]string{dockerBinary}, args...)
|
|
| 398 |
-} |
|
| 399 |
- |
|
| 400 |
-// execute a docker command with a timeout |
|
| 401 |
-func dockerCmdWithTimeout(timeout time.Duration, args ...string) *icmd.Result {
|
|
| 402 |
- if err := validateArgs(args...); err != nil {
|
|
| 403 |
- return &icmd.Result{Error: err}
|
|
| 404 |
- } |
|
| 405 |
- return icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args...), Timeout: timeout})
|
|
| 406 |
-} |
|
| 407 |
- |
|
| 408 |
-// execute a docker command in a directory |
|
| 409 |
-func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error) {
|
|
| 410 |
- if err := validateArgs(args...); err != nil {
|
|
| 411 |
- c.Fatalf(err.Error()) |
|
| 412 |
- } |
|
| 413 |
- result := icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args...), Dir: path})
|
|
| 414 |
- return result.Combined(), result.ExitCode, result.Error |
|
| 415 |
-} |
|
| 416 |
- |
|
| 417 |
-// validateArgs is a checker to ensure tests are not running commands which are |
|
| 418 |
-// not supported on platforms. Specifically on Windows this is 'busybox top'. |
|
| 419 |
-func validateArgs(args ...string) error {
|
|
| 420 |
- if daemonPlatform != "windows" {
|
|
| 421 |
- return nil |
|
| 422 |
- } |
|
| 423 |
- foundBusybox := -1 |
|
| 424 |
- for key, value := range args {
|
|
| 425 |
- if strings.ToLower(value) == "busybox" {
|
|
| 426 |
- foundBusybox = key |
|
| 427 |
- } |
|
| 428 |
- if (foundBusybox != -1) && (key == foundBusybox+1) && (strings.ToLower(value) == "top") {
|
|
| 429 |
- return errors.New("cannot use 'busybox top' in tests on Windows. Use runSleepingContainer()")
|
|
| 430 |
- } |
|
| 431 |
- } |
|
| 432 |
- return nil |
|
| 433 |
-} |
|
| 434 |
- |
|
| 435 |
-// find the State.ExitCode in container metadata |
|
| 436 |
-func findContainerExitCode(c *check.C, name string, vargs ...string) string {
|
|
| 437 |
- args := append(vargs, "inspect", "--format='{{ .State.ExitCode }} {{ .State.Error }}'", name)
|
|
| 438 |
- cmd := exec.Command(dockerBinary, args...) |
|
| 439 |
- out, _, err := runCommandWithOutput(cmd) |
|
| 440 |
- if err != nil {
|
|
| 441 |
- c.Fatal(err, out) |
|
| 442 |
- } |
|
| 443 |
- return out |
|
| 444 |
-} |
|
| 445 |
- |
|
| 446 |
-func findContainerIP(c *check.C, id string, network string) string {
|
|
| 447 |
- out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", network), id)
|
|
| 448 |
- return strings.Trim(out, " \r\n'") |
|
| 449 |
-} |
|
| 450 |
- |
|
| 451 |
-func getContainerCount() (int, error) {
|
|
| 452 |
- const containers = "Containers:" |
|
| 453 |
- |
|
| 454 |
- cmd := exec.Command(dockerBinary, "info") |
|
| 455 |
- out, _, err := runCommandWithOutput(cmd) |
|
| 456 |
- if err != nil {
|
|
| 457 |
- return 0, err |
|
| 458 |
- } |
|
| 459 |
- |
|
| 460 |
- lines := strings.Split(out, "\n") |
|
| 461 |
- for _, line := range lines {
|
|
| 462 |
- if strings.Contains(line, containers) {
|
|
| 463 |
- output := strings.TrimSpace(line) |
|
| 464 |
- output = strings.TrimLeft(output, containers) |
|
| 465 |
- output = strings.Trim(output, " ") |
|
| 466 |
- containerCount, err := strconv.Atoi(output) |
|
| 467 |
- if err != nil {
|
|
| 468 |
- return 0, err |
|
| 469 |
- } |
|
| 470 |
- return containerCount, nil |
|
| 471 |
- } |
|
| 472 |
- } |
|
| 473 |
- return 0, fmt.Errorf("couldn't find the Container count in the output")
|
|
| 474 |
-} |
|
| 475 |
- |
|
| 476 |
-// FakeContext creates directories that can be used as a build context |
|
| 477 |
-type FakeContext struct {
|
|
| 478 |
- Dir string |
|
| 479 |
-} |
|
| 480 |
- |
|
| 481 |
-// Add a file at a path, creating directories where necessary |
|
| 482 |
-func (f *FakeContext) Add(file, content string) error {
|
|
| 483 |
- return f.addFile(file, []byte(content)) |
|
| 484 |
-} |
|
| 485 |
- |
|
| 486 |
-func (f *FakeContext) addFile(file string, content []byte) error {
|
|
| 487 |
- fp := filepath.Join(f.Dir, filepath.FromSlash(file)) |
|
| 488 |
- dirpath := filepath.Dir(fp) |
|
| 489 |
- if dirpath != "." {
|
|
| 490 |
- if err := os.MkdirAll(dirpath, 0755); err != nil {
|
|
| 491 |
- return err |
|
| 492 |
- } |
|
| 493 |
- } |
|
| 494 |
- return ioutil.WriteFile(fp, content, 0644) |
|
| 495 |
- |
|
| 496 |
-} |
|
| 497 |
- |
|
| 498 |
-// Delete a file at a path |
|
| 499 |
-func (f *FakeContext) Delete(file string) error {
|
|
| 500 |
- fp := filepath.Join(f.Dir, filepath.FromSlash(file)) |
|
| 501 |
- return os.RemoveAll(fp) |
|
| 502 |
-} |
|
| 503 |
- |
|
| 504 |
-// Close deletes the context |
|
| 505 |
-func (f *FakeContext) Close() error {
|
|
| 506 |
- return os.RemoveAll(f.Dir) |
|
| 507 |
-} |
|
| 508 |
- |
|
| 509 |
-func fakeContextFromNewTempDir() (*FakeContext, error) {
|
|
| 510 |
- tmp, err := ioutil.TempDir("", "fake-context")
|
|
| 511 |
- if err != nil {
|
|
| 512 |
- return nil, err |
|
| 513 |
- } |
|
| 514 |
- if err := os.Chmod(tmp, 0755); err != nil {
|
|
| 515 |
- return nil, err |
|
| 516 |
- } |
|
| 517 |
- return fakeContextFromDir(tmp), nil |
|
| 518 |
-} |
|
| 519 |
- |
|
| 520 |
-func fakeContextFromDir(dir string) *FakeContext {
|
|
| 521 |
- return &FakeContext{dir}
|
|
| 522 |
-} |
|
| 523 |
- |
|
| 524 |
-func fakeContextWithFiles(files map[string]string) (*FakeContext, error) {
|
|
| 525 |
- ctx, err := fakeContextFromNewTempDir() |
|
| 526 |
- if err != nil {
|
|
| 527 |
- return nil, err |
|
| 528 |
- } |
|
| 529 |
- for file, content := range files {
|
|
| 530 |
- if err := ctx.Add(file, content); err != nil {
|
|
| 531 |
- ctx.Close() |
|
| 532 |
- return nil, err |
|
| 533 |
- } |
|
| 534 |
- } |
|
| 535 |
- return ctx, nil |
|
| 536 |
-} |
|
| 537 |
- |
|
| 538 |
-func fakeContextAddDockerfile(ctx *FakeContext, dockerfile string) error {
|
|
| 539 |
- if err := ctx.Add("Dockerfile", dockerfile); err != nil {
|
|
| 540 |
- ctx.Close() |
|
| 541 |
- return err |
|
| 542 |
- } |
|
| 543 |
- return nil |
|
| 544 |
-} |
|
| 545 |
- |
|
| 546 |
-func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) {
|
|
| 547 |
- ctx, err := fakeContextWithFiles(files) |
|
| 548 |
- if err != nil {
|
|
| 549 |
- return nil, err |
|
| 550 |
- } |
|
| 551 |
- if err := fakeContextAddDockerfile(ctx, dockerfile); err != nil {
|
|
| 552 |
- return nil, err |
|
| 553 |
- } |
|
| 554 |
- return ctx, nil |
|
| 555 |
-} |
|
| 556 |
- |
|
| 557 |
-// FakeStorage is a static file server. It might be running locally or remotely |
|
| 558 |
-// on test host. |
|
| 559 |
-type FakeStorage interface {
|
|
| 560 |
- Close() error |
|
| 561 |
- URL() string |
|
| 562 |
- CtxDir() string |
|
| 563 |
-} |
|
| 564 |
- |
|
| 565 |
-func fakeBinaryStorage(archives map[string]*bytes.Buffer) (FakeStorage, error) {
|
|
| 566 |
- ctx, err := fakeContextFromNewTempDir() |
|
| 567 |
- if err != nil {
|
|
| 568 |
- return nil, err |
|
| 569 |
- } |
|
| 570 |
- for name, content := range archives {
|
|
| 571 |
- if err := ctx.addFile(name, content.Bytes()); err != nil {
|
|
| 572 |
- return nil, err |
|
| 573 |
- } |
|
| 574 |
- } |
|
| 575 |
- return fakeStorageWithContext(ctx) |
|
| 576 |
-} |
|
| 577 |
- |
|
| 578 |
-// fakeStorage returns either a local or remote (at daemon machine) file server |
|
| 579 |
-func fakeStorage(files map[string]string) (FakeStorage, error) {
|
|
| 580 |
- ctx, err := fakeContextWithFiles(files) |
|
| 581 |
- if err != nil {
|
|
| 582 |
- return nil, err |
|
| 583 |
- } |
|
| 584 |
- return fakeStorageWithContext(ctx) |
|
| 585 |
-} |
|
| 586 |
- |
|
| 587 |
-// fakeStorageWithContext returns either a local or remote (at daemon machine) file server |
|
| 588 |
-func fakeStorageWithContext(ctx *FakeContext) (FakeStorage, error) {
|
|
| 589 |
- if isLocalDaemon {
|
|
| 590 |
- return newLocalFakeStorage(ctx) |
|
| 591 |
- } |
|
| 592 |
- return newRemoteFileServer(ctx) |
|
| 593 |
-} |
|
| 594 |
- |
|
| 595 |
-// localFileStorage is a file storage on the running machine |
|
| 596 |
-type localFileStorage struct {
|
|
| 597 |
- *FakeContext |
|
| 598 |
- *httptest.Server |
|
| 599 |
-} |
|
| 600 |
- |
|
| 601 |
-func (s *localFileStorage) URL() string {
|
|
| 602 |
- return s.Server.URL |
|
| 603 |
-} |
|
| 604 |
- |
|
| 605 |
-func (s *localFileStorage) CtxDir() string {
|
|
| 606 |
- return s.FakeContext.Dir |
|
| 607 |
-} |
|
| 608 |
- |
|
| 609 |
-func (s *localFileStorage) Close() error {
|
|
| 610 |
- defer s.Server.Close() |
|
| 611 |
- return s.FakeContext.Close() |
|
| 612 |
-} |
|
| 613 |
- |
|
| 614 |
-func newLocalFakeStorage(ctx *FakeContext) (*localFileStorage, error) {
|
|
| 615 |
- handler := http.FileServer(http.Dir(ctx.Dir)) |
|
| 616 |
- server := httptest.NewServer(handler) |
|
| 617 |
- return &localFileStorage{
|
|
| 618 |
- FakeContext: ctx, |
|
| 619 |
- Server: server, |
|
| 620 |
- }, nil |
|
| 621 |
-} |
|
| 622 |
- |
|
| 623 |
-// remoteFileServer is a containerized static file server started on the remote |
|
| 624 |
-// testing machine to be used in URL-accepting docker build functionality. |
|
| 625 |
-type remoteFileServer struct {
|
|
| 626 |
- host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 |
|
| 627 |
- container string |
|
| 628 |
- image string |
|
| 629 |
- ctx *FakeContext |
|
| 630 |
-} |
|
| 631 |
- |
|
| 632 |
-func (f *remoteFileServer) URL() string {
|
|
| 633 |
- u := url.URL{
|
|
| 634 |
- Scheme: "http", |
|
| 635 |
- Host: f.host} |
|
| 636 |
- return u.String() |
|
| 637 |
-} |
|
| 638 |
- |
|
| 639 |
-func (f *remoteFileServer) CtxDir() string {
|
|
| 640 |
- return f.ctx.Dir |
|
| 641 |
-} |
|
| 642 |
- |
|
| 643 |
-func (f *remoteFileServer) Close() error {
|
|
| 644 |
- defer func() {
|
|
| 645 |
- if f.ctx != nil {
|
|
| 646 |
- f.ctx.Close() |
|
| 647 |
- } |
|
| 648 |
- if f.image != "" {
|
|
| 649 |
- deleteImages(f.image) |
|
| 650 |
- } |
|
| 651 |
- }() |
|
| 652 |
- if f.container == "" {
|
|
| 653 |
- return nil |
|
| 654 |
- } |
|
| 655 |
- return deleteContainer(f.container) |
|
| 656 |
-} |
|
| 657 |
- |
|
| 658 |
-func newRemoteFileServer(ctx *FakeContext) (*remoteFileServer, error) {
|
|
| 659 |
- var ( |
|
| 660 |
- image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10)))
|
|
| 661 |
- container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10)))
|
|
| 662 |
- ) |
|
| 663 |
- |
|
| 664 |
- if err := ensureHTTPServerImage(); err != nil {
|
|
| 665 |
- return nil, err |
|
| 666 |
- } |
|
| 667 |
- |
|
| 668 |
- // Build the image |
|
| 669 |
- if err := fakeContextAddDockerfile(ctx, `FROM httpserver |
|
| 670 |
-COPY . /static`); err != nil {
|
|
| 671 |
- return nil, fmt.Errorf("Cannot add Dockerfile to context: %v", err)
|
|
| 672 |
- } |
|
| 673 |
- if _, err := buildImageFromContext(image, ctx, false); err != nil {
|
|
| 674 |
- return nil, fmt.Errorf("failed building file storage container image: %v", err)
|
|
| 675 |
- } |
|
| 676 |
- |
|
| 677 |
- // Start the container |
|
| 678 |
- runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--name", container, image) |
|
| 679 |
- if out, ec, err := runCommandWithOutput(runCmd); err != nil {
|
|
| 680 |
- return nil, fmt.Errorf("failed to start file storage container. ec=%v\nout=%s\nerr=%v", ec, out, err)
|
|
| 681 |
- } |
|
| 682 |
- |
|
| 683 |
- // Find out the system assigned port |
|
| 684 |
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "port", container, "80/tcp")) |
|
| 685 |
- if err != nil {
|
|
| 686 |
- return nil, fmt.Errorf("failed to find container port: err=%v\nout=%s", err, out)
|
|
| 687 |
- } |
|
| 688 |
- |
|
| 689 |
- fileserverHostPort := strings.Trim(out, "\n") |
|
| 690 |
- _, port, err := net.SplitHostPort(fileserverHostPort) |
|
| 691 |
- if err != nil {
|
|
| 692 |
- return nil, fmt.Errorf("unable to parse file server host:port: %v", err)
|
|
| 693 |
- } |
|
| 694 |
- |
|
| 695 |
- dockerHostURL, err := url.Parse(daemonHost()) |
|
| 696 |
- if err != nil {
|
|
| 697 |
- return nil, fmt.Errorf("unable to parse daemon host URL: %v", err)
|
|
| 698 |
- } |
|
| 699 |
- |
|
| 700 |
- host, _, err := net.SplitHostPort(dockerHostURL.Host) |
|
| 701 |
- if err != nil {
|
|
| 702 |
- return nil, fmt.Errorf("unable to parse docker daemon host:port: %v", err)
|
|
| 703 |
- } |
|
| 704 |
- |
|
| 705 |
- return &remoteFileServer{
|
|
| 706 |
- container: container, |
|
| 707 |
- image: image, |
|
| 708 |
- host: fmt.Sprintf("%s:%s", host, port),
|
|
| 709 |
- ctx: ctx}, nil |
|
| 710 |
-} |
|
| 711 |
- |
|
| 712 |
-func inspectFieldAndMarshall(c *check.C, name, field string, output interface{}) {
|
|
| 713 |
- str := inspectFieldJSON(c, name, field) |
|
| 714 |
- err := json.Unmarshal([]byte(str), output) |
|
| 715 |
- if c != nil {
|
|
| 716 |
- c.Assert(err, check.IsNil, check.Commentf("failed to unmarshal: %v", err))
|
|
| 717 |
- } |
|
| 718 |
-} |
|
| 719 |
- |
|
| 720 |
-func inspectFilter(name, filter string) (string, error) {
|
|
| 721 |
- format := fmt.Sprintf("{{%s}}", filter)
|
|
| 722 |
- inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) |
|
| 723 |
- out, exitCode, err := runCommandWithOutput(inspectCmd) |
|
| 724 |
- if err != nil || exitCode != 0 {
|
|
| 725 |
- return "", fmt.Errorf("failed to inspect %s: %s", name, out)
|
|
| 726 |
- } |
|
| 727 |
- return strings.TrimSpace(out), nil |
|
| 728 |
-} |
|
| 729 |
- |
|
| 730 |
-func inspectFieldWithError(name, field string) (string, error) {
|
|
| 731 |
- return inspectFilter(name, fmt.Sprintf(".%s", field))
|
|
| 732 |
-} |
|
| 733 |
- |
|
| 734 |
-func inspectField(c *check.C, name, field string) string {
|
|
| 735 |
- out, err := inspectFilter(name, fmt.Sprintf(".%s", field))
|
|
| 736 |
- if c != nil {
|
|
| 737 |
- c.Assert(err, check.IsNil) |
|
| 738 |
- } |
|
| 739 |
- return out |
|
| 740 |
-} |
|
| 741 |
- |
|
| 742 |
-func inspectFieldJSON(c *check.C, name, field string) string {
|
|
| 743 |
- out, err := inspectFilter(name, fmt.Sprintf("json .%s", field))
|
|
| 744 |
- if c != nil {
|
|
| 745 |
- c.Assert(err, check.IsNil) |
|
| 746 |
- } |
|
| 747 |
- return out |
|
| 748 |
-} |
|
| 749 |
- |
|
| 750 |
-func inspectFieldMap(c *check.C, name, path, field string) string {
|
|
| 751 |
- out, err := inspectFilter(name, fmt.Sprintf("index .%s %q", path, field))
|
|
| 752 |
- if c != nil {
|
|
| 753 |
- c.Assert(err, check.IsNil) |
|
| 754 |
- } |
|
| 755 |
- return out |
|
| 756 |
-} |
|
| 757 |
- |
|
| 758 |
-func inspectMountSourceField(name, destination string) (string, error) {
|
|
| 759 |
- m, err := inspectMountPoint(name, destination) |
|
| 760 |
- if err != nil {
|
|
| 761 |
- return "", err |
|
| 762 |
- } |
|
| 763 |
- return m.Source, nil |
|
| 764 |
-} |
|
| 765 |
- |
|
| 766 |
-func inspectMountPoint(name, destination string) (types.MountPoint, error) {
|
|
| 767 |
- out, err := inspectFilter(name, "json .Mounts") |
|
| 768 |
- if err != nil {
|
|
| 769 |
- return types.MountPoint{}, err
|
|
| 770 |
- } |
|
| 771 |
- |
|
| 772 |
- return inspectMountPointJSON(out, destination) |
|
| 773 |
-} |
|
| 774 |
- |
|
| 775 |
-var errMountNotFound = errors.New("mount point not found")
|
|
| 776 |
- |
|
| 777 |
-func inspectMountPointJSON(j, destination string) (types.MountPoint, error) {
|
|
| 778 |
- var mp []types.MountPoint |
|
| 779 |
- if err := json.Unmarshal([]byte(j), &mp); err != nil {
|
|
| 780 |
- return types.MountPoint{}, err
|
|
| 781 |
- } |
|
| 782 |
- |
|
| 783 |
- var m *types.MountPoint |
|
| 784 |
- for _, c := range mp {
|
|
| 785 |
- if c.Destination == destination {
|
|
| 786 |
- m = &c |
|
| 787 |
- break |
|
| 788 |
- } |
|
| 789 |
- } |
|
| 790 |
- |
|
| 791 |
- if m == nil {
|
|
| 792 |
- return types.MountPoint{}, errMountNotFound
|
|
| 793 |
- } |
|
| 794 |
- |
|
| 795 |
- return *m, nil |
|
| 796 |
-} |
|
| 797 |
- |
|
| 798 |
-func inspectImage(name, filter string) (string, error) {
|
|
| 799 |
- args := []string{"inspect", "--type", "image"}
|
|
| 800 |
- if filter != "" {
|
|
| 801 |
- format := fmt.Sprintf("{{%s}}", filter)
|
|
| 802 |
- args = append(args, "-f", format) |
|
| 803 |
- } |
|
| 804 |
- args = append(args, name) |
|
| 805 |
- inspectCmd := exec.Command(dockerBinary, args...) |
|
| 806 |
- out, exitCode, err := runCommandWithOutput(inspectCmd) |
|
| 807 |
- if err != nil || exitCode != 0 {
|
|
| 808 |
- return "", fmt.Errorf("failed to inspect %s: %s", name, out)
|
|
| 809 |
- } |
|
| 810 |
- return strings.TrimSpace(out), nil |
|
| 811 |
-} |
|
| 812 |
- |
|
| 813 |
-func getIDByName(name string) (string, error) {
|
|
| 814 |
- return inspectFieldWithError(name, "Id") |
|
| 815 |
-} |
|
| 816 |
- |
|
| 817 |
-func buildImageCmd(name, dockerfile string, useCache bool, buildFlags ...string) *exec.Cmd {
|
|
| 818 |
- return daemon.BuildImageCmdWithHost(dockerBinary, name, dockerfile, "", useCache, buildFlags...) |
|
| 819 |
-} |
|
| 820 |
- |
|
| 821 |
-func buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, error) {
|
|
| 822 |
- buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...) |
|
| 823 |
- out, exitCode, err := runCommandWithOutput(buildCmd) |
|
| 824 |
- if err != nil || exitCode != 0 {
|
|
| 825 |
- return "", out, fmt.Errorf("failed to build the image: %s", out)
|
|
| 826 |
- } |
|
| 827 |
- id, err := getIDByName(name) |
|
| 828 |
- if err != nil {
|
|
| 829 |
- return "", out, err |
|
| 830 |
- } |
|
| 831 |
- return id, out, nil |
|
| 832 |
-} |
|
| 833 |
- |
|
| 834 |
-func buildImageWithStdoutStderr(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, string, error) {
|
|
| 835 |
- buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...) |
|
| 836 |
- result := icmd.RunCmd(transformCmd(buildCmd)) |
|
| 837 |
- err := result.Error |
|
| 838 |
- exitCode := result.ExitCode |
|
| 839 |
- if err != nil || exitCode != 0 {
|
|
| 840 |
- return "", result.Stdout(), result.Stderr(), fmt.Errorf("failed to build the image: %s", result.Combined())
|
|
| 841 |
- } |
|
| 842 |
- id, err := getIDByName(name) |
|
| 843 |
- if err != nil {
|
|
| 844 |
- return "", result.Stdout(), result.Stderr(), err |
|
| 845 |
- } |
|
| 846 |
- return id, result.Stdout(), result.Stderr(), nil |
|
| 847 |
-} |
|
| 848 |
- |
|
| 849 |
-func buildImage(name, dockerfile string, useCache bool, buildFlags ...string) (string, error) {
|
|
| 850 |
- id, _, err := buildImageWithOut(name, dockerfile, useCache, buildFlags...) |
|
| 851 |
- return id, err |
|
| 852 |
-} |
|
| 853 |
- |
|
| 854 |
-func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, error) {
|
|
| 855 |
- id, _, err := buildImageFromContextWithOut(name, ctx, useCache, buildFlags...) |
|
| 856 |
- if err != nil {
|
|
| 857 |
- return "", err |
|
| 858 |
- } |
|
| 859 |
- return id, nil |
|
| 860 |
-} |
|
| 861 |
- |
|
| 862 |
-func buildImageFromContextWithOut(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, string, error) {
|
|
| 863 |
- args := []string{"build", "-t", name}
|
|
| 864 |
- if !useCache {
|
|
| 865 |
- args = append(args, "--no-cache") |
|
| 866 |
- } |
|
| 867 |
- args = append(args, buildFlags...) |
|
| 868 |
- args = append(args, ".") |
|
| 869 |
- buildCmd := exec.Command(dockerBinary, args...) |
|
| 870 |
- buildCmd.Dir = ctx.Dir |
|
| 871 |
- out, exitCode, err := runCommandWithOutput(buildCmd) |
|
| 872 |
- if err != nil || exitCode != 0 {
|
|
| 873 |
- return "", "", fmt.Errorf("failed to build the image: %s", out)
|
|
| 874 |
- } |
|
| 875 |
- id, err := getIDByName(name) |
|
| 876 |
- if err != nil {
|
|
| 877 |
- return "", "", err |
|
| 878 |
- } |
|
| 879 |
- return id, out, nil |
|
| 880 |
-} |
|
| 881 |
- |
|
| 882 |
-func buildImageFromContextWithStdoutStderr(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, string, string, error) {
|
|
| 883 |
- args := []string{"build", "-t", name}
|
|
| 884 |
- if !useCache {
|
|
| 885 |
- args = append(args, "--no-cache") |
|
| 886 |
- } |
|
| 887 |
- args = append(args, buildFlags...) |
|
| 888 |
- args = append(args, ".") |
|
| 889 |
- |
|
| 890 |
- result := icmd.RunCmd(icmd.Cmd{
|
|
| 891 |
- Command: append([]string{dockerBinary}, args...),
|
|
| 892 |
- Dir: ctx.Dir, |
|
| 893 |
- }) |
|
| 894 |
- exitCode := result.ExitCode |
|
| 895 |
- err := result.Error |
|
| 896 |
- if err != nil || exitCode != 0 {
|
|
| 897 |
- return "", result.Stdout(), result.Stderr(), fmt.Errorf("failed to build the image: %s", result.Combined())
|
|
| 898 |
- } |
|
| 899 |
- id, err := getIDByName(name) |
|
| 900 |
- if err != nil {
|
|
| 901 |
- return "", result.Stdout(), result.Stderr(), err |
|
| 902 |
- } |
|
| 903 |
- return id, result.Stdout(), result.Stderr(), nil |
|
| 904 |
-} |
|
| 905 |
- |
|
| 906 |
-func buildImageFromGitWithStdoutStderr(name string, ctx *fakeGit, useCache bool, buildFlags ...string) (string, string, string, error) {
|
|
| 907 |
- args := []string{"build", "-t", name}
|
|
| 908 |
- if !useCache {
|
|
| 909 |
- args = append(args, "--no-cache") |
|
| 910 |
- } |
|
| 911 |
- args = append(args, buildFlags...) |
|
| 912 |
- args = append(args, ctx.RepoURL) |
|
| 913 |
- result := icmd.RunCmd(icmd.Cmd{
|
|
| 914 |
- Command: append([]string{dockerBinary}, args...),
|
|
| 915 |
- }) |
|
| 916 |
- exitCode := result.ExitCode |
|
| 917 |
- err := result.Error |
|
| 918 |
- if err != nil || exitCode != 0 {
|
|
| 919 |
- return "", result.Stdout(), result.Stderr(), fmt.Errorf("failed to build the image: %s", result.Combined())
|
|
| 920 |
- } |
|
| 921 |
- id, err := getIDByName(name) |
|
| 922 |
- if err != nil {
|
|
| 923 |
- return "", result.Stdout(), result.Stderr(), err |
|
| 924 |
- } |
|
| 925 |
- return id, result.Stdout(), result.Stderr(), nil |
|
| 926 |
-} |
|
| 927 |
- |
|
| 928 |
-func buildImageFromPath(name, path string, useCache bool, buildFlags ...string) (string, error) {
|
|
| 929 |
- args := []string{"build", "-t", name}
|
|
| 930 |
- if !useCache {
|
|
| 931 |
- args = append(args, "--no-cache") |
|
| 932 |
- } |
|
| 933 |
- args = append(args, buildFlags...) |
|
| 934 |
- args = append(args, path) |
|
| 935 |
- buildCmd := exec.Command(dockerBinary, args...) |
|
| 936 |
- out, exitCode, err := runCommandWithOutput(buildCmd) |
|
| 937 |
- if err != nil || exitCode != 0 {
|
|
| 938 |
- return "", fmt.Errorf("failed to build the image: %s", out)
|
|
| 939 |
- } |
|
| 940 |
- return getIDByName(name) |
|
| 941 |
-} |
|
| 942 |
- |
|
| 943 |
-type gitServer interface {
|
|
| 944 |
- URL() string |
|
| 945 |
- Close() error |
|
| 946 |
-} |
|
| 947 |
- |
|
| 948 |
-type localGitServer struct {
|
|
| 949 |
- *httptest.Server |
|
| 950 |
-} |
|
| 951 |
- |
|
| 952 |
-func (r *localGitServer) Close() error {
|
|
| 953 |
- r.Server.Close() |
|
| 954 |
- return nil |
|
| 955 |
-} |
|
| 956 |
- |
|
| 957 |
-func (r *localGitServer) URL() string {
|
|
| 958 |
- return r.Server.URL |
|
| 959 |
-} |
|
| 960 |
- |
|
| 961 |
-type fakeGit struct {
|
|
| 962 |
- root string |
|
| 963 |
- server gitServer |
|
| 964 |
- RepoURL string |
|
| 965 |
-} |
|
| 966 |
- |
|
| 967 |
-func (g *fakeGit) Close() {
|
|
| 968 |
- g.server.Close() |
|
| 969 |
- os.RemoveAll(g.root) |
|
| 970 |
-} |
|
| 971 |
- |
|
| 972 |
-func newFakeGit(name string, files map[string]string, enforceLocalServer bool) (*fakeGit, error) {
|
|
| 973 |
- ctx, err := fakeContextWithFiles(files) |
|
| 974 |
- if err != nil {
|
|
| 975 |
- return nil, err |
|
| 976 |
- } |
|
| 977 |
- defer ctx.Close() |
|
| 978 |
- curdir, err := os.Getwd() |
|
| 979 |
- if err != nil {
|
|
| 980 |
- return nil, err |
|
| 981 |
- } |
|
| 982 |
- defer os.Chdir(curdir) |
|
| 983 |
- |
|
| 984 |
- if output, err := exec.Command("git", "init", ctx.Dir).CombinedOutput(); err != nil {
|
|
| 985 |
- return nil, fmt.Errorf("error trying to init repo: %s (%s)", err, output)
|
|
| 986 |
- } |
|
| 987 |
- err = os.Chdir(ctx.Dir) |
|
| 988 |
- if err != nil {
|
|
| 989 |
- return nil, err |
|
| 990 |
- } |
|
| 991 |
- if output, err := exec.Command("git", "config", "user.name", "Fake User").CombinedOutput(); err != nil {
|
|
| 992 |
- return nil, fmt.Errorf("error trying to set 'user.name': %s (%s)", err, output)
|
|
| 993 |
- } |
|
| 994 |
- if output, err := exec.Command("git", "config", "user.email", "fake.user@example.com").CombinedOutput(); err != nil {
|
|
| 995 |
- return nil, fmt.Errorf("error trying to set 'user.email': %s (%s)", err, output)
|
|
| 996 |
- } |
|
| 997 |
- if output, err := exec.Command("git", "add", "*").CombinedOutput(); err != nil {
|
|
| 998 |
- return nil, fmt.Errorf("error trying to add files to repo: %s (%s)", err, output)
|
|
| 999 |
- } |
|
| 1000 |
- if output, err := exec.Command("git", "commit", "-a", "-m", "Initial commit").CombinedOutput(); err != nil {
|
|
| 1001 |
- return nil, fmt.Errorf("error trying to commit to repo: %s (%s)", err, output)
|
|
| 1002 |
- } |
|
| 1003 |
- |
|
| 1004 |
- root, err := ioutil.TempDir("", "docker-test-git-repo")
|
|
| 1005 |
- if err != nil {
|
|
| 1006 |
- return nil, err |
|
| 1007 |
- } |
|
| 1008 |
- repoPath := filepath.Join(root, name+".git") |
|
| 1009 |
- if output, err := exec.Command("git", "clone", "--bare", ctx.Dir, repoPath).CombinedOutput(); err != nil {
|
|
| 1010 |
- os.RemoveAll(root) |
|
| 1011 |
- return nil, fmt.Errorf("error trying to clone --bare: %s (%s)", err, output)
|
|
| 1012 |
- } |
|
| 1013 |
- err = os.Chdir(repoPath) |
|
| 1014 |
- if err != nil {
|
|
| 1015 |
- os.RemoveAll(root) |
|
| 1016 |
- return nil, err |
|
| 1017 |
- } |
|
| 1018 |
- if output, err := exec.Command("git", "update-server-info").CombinedOutput(); err != nil {
|
|
| 1019 |
- os.RemoveAll(root) |
|
| 1020 |
- return nil, fmt.Errorf("error trying to git update-server-info: %s (%s)", err, output)
|
|
| 1021 |
- } |
|
| 1022 |
- err = os.Chdir(curdir) |
|
| 1023 |
- if err != nil {
|
|
| 1024 |
- os.RemoveAll(root) |
|
| 1025 |
- return nil, err |
|
| 1026 |
- } |
|
| 1027 |
- |
|
| 1028 |
- var server gitServer |
|
| 1029 |
- if !enforceLocalServer {
|
|
| 1030 |
- // use fakeStorage server, which might be local or remote (at test daemon) |
|
| 1031 |
- server, err = fakeStorageWithContext(fakeContextFromDir(root)) |
|
| 1032 |
- if err != nil {
|
|
| 1033 |
- return nil, fmt.Errorf("cannot start fake storage: %v", err)
|
|
| 1034 |
- } |
|
| 1035 |
- } else {
|
|
| 1036 |
- // always start a local http server on CLI test machine |
|
| 1037 |
- httpServer := httptest.NewServer(http.FileServer(http.Dir(root))) |
|
| 1038 |
- server = &localGitServer{httpServer}
|
|
| 1039 |
- } |
|
| 1040 |
- return &fakeGit{
|
|
| 1041 |
- root: root, |
|
| 1042 |
- server: server, |
|
| 1043 |
- RepoURL: fmt.Sprintf("%s/%s.git", server.URL(), name),
|
|
| 1044 |
- }, nil |
|
| 1045 |
-} |
|
| 1046 |
- |
|
| 1047 |
-// Write `content` to the file at path `dst`, creating it if necessary, |
|
| 1048 |
-// as well as any missing directories. |
|
| 1049 |
-// The file is truncated if it already exists. |
|
| 1050 |
-// Fail the test when error occurs. |
|
| 1051 |
-func writeFile(dst, content string, c *check.C) {
|
|
| 1052 |
- // Create subdirectories if necessary |
|
| 1053 |
- c.Assert(os.MkdirAll(path.Dir(dst), 0700), check.IsNil) |
|
| 1054 |
- f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) |
|
| 1055 |
- c.Assert(err, check.IsNil) |
|
| 1056 |
- defer f.Close() |
|
| 1057 |
- // Write content (truncate if it exists) |
|
| 1058 |
- _, err = io.Copy(f, strings.NewReader(content)) |
|
| 1059 |
- c.Assert(err, check.IsNil) |
|
| 1060 |
-} |
|
| 1061 |
- |
|
| 1062 |
-// Return the contents of file at path `src`. |
|
| 1063 |
-// Fail the test when error occurs. |
|
| 1064 |
-func readFile(src string, c *check.C) (content string) {
|
|
| 1065 |
- data, err := ioutil.ReadFile(src) |
|
| 1066 |
- c.Assert(err, check.IsNil) |
|
| 1067 |
- |
|
| 1068 |
- return string(data) |
|
| 1069 |
-} |
|
| 1070 |
- |
|
| 1071 |
-func containerStorageFile(containerID, basename string) string {
|
|
| 1072 |
- return filepath.Join(containerStoragePath, containerID, basename) |
|
| 1073 |
-} |
|
| 1074 |
- |
|
| 1075 |
-// docker commands that use this function must be run with the '-d' switch. |
|
| 1076 |
-func runCommandAndReadContainerFile(filename string, cmd *exec.Cmd) ([]byte, error) {
|
|
| 1077 |
- out, _, err := runCommandWithOutput(cmd) |
|
| 1078 |
- if err != nil {
|
|
| 1079 |
- return nil, fmt.Errorf("%v: %q", err, out)
|
|
| 1080 |
- } |
|
| 1081 |
- |
|
| 1082 |
- contID := strings.TrimSpace(out) |
|
| 1083 |
- |
|
| 1084 |
- if err := waitRun(contID); err != nil {
|
|
| 1085 |
- return nil, fmt.Errorf("%v: %q", contID, err)
|
|
| 1086 |
- } |
|
| 1087 |
- |
|
| 1088 |
- return readContainerFile(contID, filename) |
|
| 1089 |
-} |
|
| 1090 |
- |
|
| 1091 |
-func readContainerFile(containerID, filename string) ([]byte, error) {
|
|
| 1092 |
- f, err := os.Open(containerStorageFile(containerID, filename)) |
|
| 1093 |
- if err != nil {
|
|
| 1094 |
- return nil, err |
|
| 1095 |
- } |
|
| 1096 |
- defer f.Close() |
|
| 1097 |
- |
|
| 1098 |
- content, err := ioutil.ReadAll(f) |
|
| 1099 |
- if err != nil {
|
|
| 1100 |
- return nil, err |
|
| 1101 |
- } |
|
| 1102 |
- |
|
| 1103 |
- return content, nil |
|
| 1104 |
-} |
|
| 1105 |
- |
|
| 1106 |
-func readContainerFileWithExec(containerID, filename string) ([]byte, error) {
|
|
| 1107 |
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "exec", containerID, "cat", filename)) |
|
| 1108 |
- return []byte(out), err |
|
| 1109 |
-} |
|
| 1110 |
- |
|
| 1111 |
-// daemonTime provides the current time on the daemon host |
|
| 1112 |
-func daemonTime(c *check.C) time.Time {
|
|
| 1113 |
- if isLocalDaemon {
|
|
| 1114 |
- return time.Now() |
|
| 1115 |
- } |
|
| 1116 |
- |
|
| 1117 |
- status, body, err := sockRequest("GET", "/info", nil)
|
|
| 1118 |
- c.Assert(err, check.IsNil) |
|
| 1119 |
- c.Assert(status, check.Equals, http.StatusOK) |
|
| 1120 |
- |
|
| 1121 |
- type infoJSON struct {
|
|
| 1122 |
- SystemTime string |
|
| 1123 |
- } |
|
| 1124 |
- var info infoJSON |
|
| 1125 |
- err = json.Unmarshal(body, &info) |
|
| 1126 |
- c.Assert(err, check.IsNil, check.Commentf("unable to unmarshal GET /info response"))
|
|
| 1127 |
- |
|
| 1128 |
- dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) |
|
| 1129 |
- c.Assert(err, check.IsNil, check.Commentf("invalid time format in GET /info response"))
|
|
| 1130 |
- return dt |
|
| 1131 |
-} |
|
| 1132 |
- |
|
| 1133 |
-// daemonUnixTime returns the current time on the daemon host with nanoseconds precision. |
|
| 1134 |
-// It return the time formatted how the client sends timestamps to the server. |
|
| 1135 |
-func daemonUnixTime(c *check.C) string {
|
|
| 1136 |
- return parseEventTime(daemonTime(c)) |
|
| 1137 |
-} |
|
| 1138 |
- |
|
| 1139 |
-func parseEventTime(t time.Time) string {
|
|
| 1140 |
- return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond()))
|
|
| 1141 |
-} |
|
| 1142 |
- |
|
| 1143 |
-func setupRegistry(c *check.C, schema1 bool, auth, tokenURL string) *testRegistryV2 {
|
|
| 1144 |
- reg, err := newTestRegistryV2(c, schema1, auth, tokenURL) |
|
| 1145 |
- c.Assert(err, check.IsNil) |
|
| 1146 |
- |
|
| 1147 |
- // Wait for registry to be ready to serve requests. |
|
| 1148 |
- for i := 0; i != 50; i++ {
|
|
| 1149 |
- if err = reg.Ping(); err == nil {
|
|
| 1150 |
- break |
|
| 1151 |
- } |
|
| 1152 |
- time.Sleep(100 * time.Millisecond) |
|
| 1153 |
- } |
|
| 1154 |
- |
|
| 1155 |
- c.Assert(err, check.IsNil, check.Commentf("Timeout waiting for test registry to become available: %v", err))
|
|
| 1156 |
- return reg |
|
| 1157 |
-} |
|
| 1158 |
- |
|
| 1159 |
-func setupNotary(c *check.C) *testNotary {
|
|
| 1160 |
- ts, err := newTestNotary(c) |
|
| 1161 |
- c.Assert(err, check.IsNil) |
|
| 1162 |
- |
|
| 1163 |
- return ts |
|
| 1164 |
-} |
|
| 1165 |
- |
|
| 1166 |
-// appendBaseEnv appends the minimum set of environment variables to exec the |
|
| 1167 |
-// docker cli binary for testing with correct configuration to the given env |
|
| 1168 |
-// list. |
|
| 1169 |
-func appendBaseEnv(isTLS bool, env ...string) []string {
|
|
| 1170 |
- preserveList := []string{
|
|
| 1171 |
- // preserve remote test host |
|
| 1172 |
- "DOCKER_HOST", |
|
| 1173 |
- |
|
| 1174 |
- // windows: requires preserving SystemRoot, otherwise dial tcp fails |
|
| 1175 |
- // with "GetAddrInfoW: A non-recoverable error occurred during a database lookup." |
|
| 1176 |
- "SystemRoot", |
|
| 1177 |
- |
|
| 1178 |
- // testing help text requires the $PATH to dockerd is set |
|
| 1179 |
- "PATH", |
|
| 1180 |
- } |
|
| 1181 |
- if isTLS {
|
|
| 1182 |
- preserveList = append(preserveList, "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH") |
|
| 1183 |
- } |
|
| 1184 |
- |
|
| 1185 |
- for _, key := range preserveList {
|
|
| 1186 |
- if val := os.Getenv(key); val != "" {
|
|
| 1187 |
- env = append(env, fmt.Sprintf("%s=%s", key, val))
|
|
| 1188 |
- } |
|
| 1189 |
- } |
|
| 1190 |
- return env |
|
| 1191 |
-} |
|
| 1192 |
- |
|
| 1193 |
-func createTmpFile(c *check.C, content string) string {
|
|
| 1194 |
- f, err := ioutil.TempFile("", "testfile")
|
|
| 1195 |
- c.Assert(err, check.IsNil) |
|
| 1196 |
- |
|
| 1197 |
- filename := f.Name() |
|
| 1198 |
- |
|
| 1199 |
- err = ioutil.WriteFile(filename, []byte(content), 0644) |
|
| 1200 |
- c.Assert(err, check.IsNil) |
|
| 1201 |
- |
|
| 1202 |
- return filename |
|
| 1203 |
-} |
|
| 1204 |
- |
|
| 1205 |
-func waitForContainer(contID string, args ...string) error {
|
|
| 1206 |
- args = append([]string{dockerBinary, "run", "--name", contID}, args...)
|
|
| 1207 |
- result := icmd.RunCmd(icmd.Cmd{Command: args})
|
|
| 1208 |
- if result.Error != nil {
|
|
| 1209 |
- return result.Error |
|
| 1210 |
- } |
|
| 1211 |
- return waitRun(contID) |
|
| 1212 |
-} |
|
| 1213 |
- |
|
| 1214 |
-// waitRestart will wait for the specified container to restart once |
|
| 1215 |
-func waitRestart(contID string, duration time.Duration) error {
|
|
| 1216 |
- return waitInspect(contID, "{{.RestartCount}}", "1", duration)
|
|
| 1217 |
-} |
|
| 1218 |
- |
|
| 1219 |
-// waitRun will wait for the specified container to be running, maximum 5 seconds. |
|
| 1220 |
-func waitRun(contID string) error {
|
|
| 1221 |
- return waitInspect(contID, "{{.State.Running}}", "true", 5*time.Second)
|
|
| 1222 |
-} |
|
| 1223 |
- |
|
| 1224 |
-// waitExited will wait for the specified container to state exit, subject |
|
| 1225 |
-// to a maximum time limit in seconds supplied by the caller |
|
| 1226 |
-func waitExited(contID string, duration time.Duration) error {
|
|
| 1227 |
- return waitInspect(contID, "{{.State.Status}}", "exited", duration)
|
|
| 1228 |
-} |
|
| 1229 |
- |
|
| 1230 |
-// waitInspect will wait for the specified container to have the specified string |
|
| 1231 |
-// in the inspect output. It will wait until the specified timeout (in seconds) |
|
| 1232 |
-// is reached. |
|
| 1233 |
-func waitInspect(name, expr, expected string, timeout time.Duration) error {
|
|
| 1234 |
- return waitInspectWithArgs(name, expr, expected, timeout) |
|
| 1235 |
-} |
|
| 1236 |
- |
|
| 1237 |
-func waitInspectWithArgs(name, expr, expected string, timeout time.Duration, arg ...string) error {
|
|
| 1238 |
- return daemon.WaitInspectWithArgs(dockerBinary, name, expr, expected, timeout, arg...) |
|
| 1239 |
-} |
|
| 1240 |
- |
|
| 1241 |
-func getInspectBody(c *check.C, version, id string) []byte {
|
|
| 1242 |
- endpoint := fmt.Sprintf("/%s/containers/%s/json", version, id)
|
|
| 1243 |
- status, body, err := sockRequest("GET", endpoint, nil)
|
|
| 1244 |
- c.Assert(err, check.IsNil) |
|
| 1245 |
- c.Assert(status, check.Equals, http.StatusOK) |
|
| 1246 |
- return body |
|
| 1247 |
-} |
|
| 1248 |
- |
|
| 1249 |
-// Run a long running idle task in a background container using the |
|
| 1250 |
-// system-specific default image and command. |
|
| 1251 |
-func runSleepingContainer(c *check.C, extraArgs ...string) (string, int) {
|
|
| 1252 |
- return runSleepingContainerInImage(c, defaultSleepImage, extraArgs...) |
|
| 1253 |
-} |
|
| 1254 |
- |
|
| 1255 |
-// Run a long running idle task in a background container using the specified |
|
| 1256 |
-// image and the system-specific command. |
|
| 1257 |
-func runSleepingContainerInImage(c *check.C, image string, extraArgs ...string) (string, int) {
|
|
| 1258 |
- args := []string{"run", "-d"}
|
|
| 1259 |
- args = append(args, extraArgs...) |
|
| 1260 |
- args = append(args, image) |
|
| 1261 |
- args = append(args, sleepCommandForDaemonPlatform()...) |
|
| 1262 |
- return dockerCmd(c, args...) |
|
| 1263 |
-} |
|
| 1264 |
- |
|
| 1265 |
-// minimalBaseImage returns the name of the minimal base image for the current |
|
| 1266 |
-// daemon platform. |
|
| 1267 |
-func minimalBaseImage() string {
|
|
| 1268 |
- if daemonPlatform == "windows" {
|
|
| 1269 |
- return WindowsBaseImage |
|
| 1270 |
- } |
|
| 1271 |
- return "scratch" |
|
| 1272 |
-} |
|
| 1273 |
- |
|
| 1274 |
-func getGoroutineNumber() (int, error) {
|
|
| 1275 |
- i := struct {
|
|
| 1276 |
- NGoroutines int |
|
| 1277 |
- }{}
|
|
| 1278 |
- status, b, err := sockRequest("GET", "/info", nil)
|
|
| 1279 |
- if err != nil {
|
|
| 1280 |
- return 0, err |
|
| 1281 |
- } |
|
| 1282 |
- if status != http.StatusOK {
|
|
| 1283 |
- return 0, fmt.Errorf("http status code: %d", status)
|
|
| 1284 |
- } |
|
| 1285 |
- if err := json.Unmarshal(b, &i); err != nil {
|
|
| 1286 |
- return 0, err |
|
| 1287 |
- } |
|
| 1288 |
- return i.NGoroutines, nil |
|
| 1289 |
-} |
|
| 1290 |
- |
|
| 1291 |
-func waitForGoroutines(expected int) error {
|
|
| 1292 |
- t := time.After(30 * time.Second) |
|
| 1293 |
- for {
|
|
| 1294 |
- select {
|
|
| 1295 |
- case <-t: |
|
| 1296 |
- n, err := getGoroutineNumber() |
|
| 1297 |
- if err != nil {
|
|
| 1298 |
- return err |
|
| 1299 |
- } |
|
| 1300 |
- if n > expected {
|
|
| 1301 |
- return fmt.Errorf("leaked goroutines: expected less than or equal to %d, got: %d", expected, n)
|
|
| 1302 |
- } |
|
| 1303 |
- default: |
|
| 1304 |
- n, err := getGoroutineNumber() |
|
| 1305 |
- if err != nil {
|
|
| 1306 |
- return err |
|
| 1307 |
- } |
|
| 1308 |
- if n <= expected {
|
|
| 1309 |
- return nil |
|
| 1310 |
- } |
|
| 1311 |
- time.Sleep(200 * time.Millisecond) |
|
| 1312 |
- } |
|
| 1313 |
- } |
|
| 1314 |
-} |
|
| 1315 |
- |
|
| 1316 |
-// getErrorMessage returns the error message from an error API response |
|
| 1317 |
-func getErrorMessage(c *check.C, body []byte) string {
|
|
| 1318 |
- var resp types.ErrorResponse |
|
| 1319 |
- c.Assert(json.Unmarshal(body, &resp), check.IsNil) |
|
| 1320 |
- return strings.TrimSpace(resp.Message) |
|
| 1321 |
-} |
|
| 1322 |
- |
|
| 1323 |
-func waitAndAssert(c *check.C, timeout time.Duration, f checkF, checker check.Checker, args ...interface{}) {
|
|
| 1324 |
- after := time.After(timeout) |
|
| 1325 |
- for {
|
|
| 1326 |
- v, comment := f(c) |
|
| 1327 |
- assert, _ := checker.Check(append([]interface{}{v}, args...), checker.Info().Params)
|
|
| 1328 |
- select {
|
|
| 1329 |
- case <-after: |
|
| 1330 |
- assert = true |
|
| 1331 |
- default: |
|
| 1332 |
- } |
|
| 1333 |
- if assert {
|
|
| 1334 |
- if comment != nil {
|
|
| 1335 |
- args = append(args, comment) |
|
| 1336 |
- } |
|
| 1337 |
- c.Assert(v, checker, args...) |
|
| 1338 |
- return |
|
| 1339 |
- } |
|
| 1340 |
- time.Sleep(100 * time.Millisecond) |
|
| 1341 |
- } |
|
| 1342 |
-} |
|
| 1343 |
- |
|
| 1344 |
-type checkF func(*check.C) (interface{}, check.CommentInterface)
|
|
| 1345 |
-type reducer func(...interface{}) interface{}
|
|
| 1346 |
- |
|
| 1347 |
-func reducedCheck(r reducer, funcs ...checkF) checkF {
|
|
| 1348 |
- return func(c *check.C) (interface{}, check.CommentInterface) {
|
|
| 1349 |
- var values []interface{}
|
|
| 1350 |
- var comments []string |
|
| 1351 |
- for _, f := range funcs {
|
|
| 1352 |
- v, comment := f(c) |
|
| 1353 |
- values = append(values, v) |
|
| 1354 |
- if comment != nil {
|
|
| 1355 |
- comments = append(comments, comment.CheckCommentString()) |
|
| 1356 |
- } |
|
| 1357 |
- } |
|
| 1358 |
- return r(values...), check.Commentf("%v", strings.Join(comments, ", "))
|
|
| 1359 |
- } |
|
| 1360 |
-} |
|
| 1361 |
- |
|
| 1362 |
-func sumAsIntegers(vals ...interface{}) interface{} {
|
|
| 1363 |
- var s int |
|
| 1364 |
- for _, v := range vals {
|
|
| 1365 |
- s += v.(int) |
|
| 1366 |
- } |
|
| 1367 |
- return s |
|
| 1368 |
-} |
| 1369 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1308 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "bytes" |
|
| 5 |
+ "encoding/json" |
|
| 6 |
+ "errors" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "io" |
|
| 9 |
+ "io/ioutil" |
|
| 10 |
+ "net" |
|
| 11 |
+ "net/http" |
|
| 12 |
+ "net/http/httptest" |
|
| 13 |
+ "net/http/httputil" |
|
| 14 |
+ "net/url" |
|
| 15 |
+ "os" |
|
| 16 |
+ "os/exec" |
|
| 17 |
+ "path" |
|
| 18 |
+ "path/filepath" |
|
| 19 |
+ "strconv" |
|
| 20 |
+ "strings" |
|
| 21 |
+ "time" |
|
| 22 |
+ |
|
| 23 |
+ "github.com/docker/docker/api/types" |
|
| 24 |
+ volumetypes "github.com/docker/docker/api/types/volume" |
|
| 25 |
+ "github.com/docker/docker/integration-cli/daemon" |
|
| 26 |
+ "github.com/docker/docker/opts" |
|
| 27 |
+ "github.com/docker/docker/pkg/integration" |
|
| 28 |
+ "github.com/docker/docker/pkg/integration/checker" |
|
| 29 |
+ icmd "github.com/docker/docker/pkg/integration/cmd" |
|
| 30 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 31 |
+ "github.com/docker/docker/pkg/stringutils" |
|
| 32 |
+ "github.com/go-check/check" |
|
| 33 |
+) |
|
| 34 |
+ |
|
| 35 |
+func daemonHost() string {
|
|
| 36 |
+ daemonURLStr := "unix://" + opts.DefaultUnixSocket |
|
| 37 |
+ if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
|
|
| 38 |
+ daemonURLStr = daemonHostVar |
|
| 39 |
+ } |
|
| 40 |
+ return daemonURLStr |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// FIXME(vdemeester) should probably completely move to daemon struct/methods |
|
| 44 |
+func sockConn(timeout time.Duration, daemonStr string) (net.Conn, error) {
|
|
| 45 |
+ if daemonStr == "" {
|
|
| 46 |
+ daemonStr = daemonHost() |
|
| 47 |
+ } |
|
| 48 |
+ return daemon.SockConn(timeout, daemonStr) |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) {
|
|
| 52 |
+ jsonData := bytes.NewBuffer(nil) |
|
| 53 |
+ if err := json.NewEncoder(jsonData).Encode(data); err != nil {
|
|
| 54 |
+ return -1, nil, err |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") |
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ return -1, nil, err |
|
| 60 |
+ } |
|
| 61 |
+ b, err := integration.ReadBody(body) |
|
| 62 |
+ return res.StatusCode, b, err |
|
| 63 |
+} |
|
| 64 |
+ |
|
| 65 |
+func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) {
|
|
| 66 |
+ return sockRequestRawToDaemon(method, endpoint, data, ct, "") |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+func sockRequestRawToDaemon(method, endpoint string, data io.Reader, ct, daemon string) (*http.Response, io.ReadCloser, error) {
|
|
| 70 |
+ req, client, err := newRequestClient(method, endpoint, data, ct, daemon) |
|
| 71 |
+ if err != nil {
|
|
| 72 |
+ return nil, nil, err |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ resp, err := client.Do(req) |
|
| 76 |
+ if err != nil {
|
|
| 77 |
+ client.Close() |
|
| 78 |
+ return nil, nil, err |
|
| 79 |
+ } |
|
| 80 |
+ body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
|
|
| 81 |
+ defer resp.Body.Close() |
|
| 82 |
+ return client.Close() |
|
| 83 |
+ }) |
|
| 84 |
+ |
|
| 85 |
+ return resp, body, nil |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+func sockRequestHijack(method, endpoint string, data io.Reader, ct string) (net.Conn, *bufio.Reader, error) {
|
|
| 89 |
+ req, client, err := newRequestClient(method, endpoint, data, ct, "") |
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ return nil, nil, err |
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 94 |
+ client.Do(req) |
|
| 95 |
+ conn, br := client.Hijack() |
|
| 96 |
+ return conn, br, nil |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+func newRequestClient(method, endpoint string, data io.Reader, ct, daemon string) (*http.Request, *httputil.ClientConn, error) {
|
|
| 100 |
+ c, err := sockConn(time.Duration(10*time.Second), daemon) |
|
| 101 |
+ if err != nil {
|
|
| 102 |
+ return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err)
|
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ client := httputil.NewClientConn(c, nil) |
|
| 106 |
+ |
|
| 107 |
+ req, err := http.NewRequest(method, endpoint, data) |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ client.Close() |
|
| 110 |
+ return nil, nil, fmt.Errorf("could not create new request: %v", err)
|
|
| 111 |
+ } |
|
| 112 |
+ |
|
| 113 |
+ if ct != "" {
|
|
| 114 |
+ req.Header.Set("Content-Type", ct)
|
|
| 115 |
+ } |
|
| 116 |
+ return req, client, nil |
|
| 117 |
+} |
|
| 118 |
+ |
|
| 119 |
+func deleteContainer(container ...string) error {
|
|
| 120 |
+ result := icmd.RunCommand(dockerBinary, append([]string{"rm", "-fv"}, container...)...)
|
|
| 121 |
+ return result.Compare(icmd.Success) |
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+func getAllContainers() (string, error) {
|
|
| 125 |
+ getContainersCmd := exec.Command(dockerBinary, "ps", "-q", "-a") |
|
| 126 |
+ out, exitCode, err := runCommandWithOutput(getContainersCmd) |
|
| 127 |
+ if exitCode != 0 && err == nil {
|
|
| 128 |
+ err = fmt.Errorf("failed to get a list of containers: %v\n", out)
|
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ return out, err |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func deleteAllContainers(c *check.C) {
|
|
| 135 |
+ containers, err := getAllContainers() |
|
| 136 |
+ c.Assert(err, checker.IsNil, check.Commentf("containers: %v", containers))
|
|
| 137 |
+ |
|
| 138 |
+ if containers != "" {
|
|
| 139 |
+ err = deleteContainer(strings.Split(strings.TrimSpace(containers), "\n")...) |
|
| 140 |
+ c.Assert(err, checker.IsNil) |
|
| 141 |
+ } |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+func deleteAllNetworks(c *check.C) {
|
|
| 145 |
+ networks, err := getAllNetworks() |
|
| 146 |
+ c.Assert(err, check.IsNil) |
|
| 147 |
+ var errs []string |
|
| 148 |
+ for _, n := range networks {
|
|
| 149 |
+ if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
|
|
| 150 |
+ continue |
|
| 151 |
+ } |
|
| 152 |
+ if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
|
|
| 153 |
+ // nat is a pre-defined network on Windows and cannot be removed |
|
| 154 |
+ continue |
|
| 155 |
+ } |
|
| 156 |
+ status, b, err := sockRequest("DELETE", "/networks/"+n.Name, nil)
|
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ errs = append(errs, err.Error()) |
|
| 159 |
+ continue |
|
| 160 |
+ } |
|
| 161 |
+ if status != http.StatusNoContent {
|
|
| 162 |
+ errs = append(errs, fmt.Sprintf("error deleting network %s: %s", n.Name, string(b)))
|
|
| 163 |
+ } |
|
| 164 |
+ } |
|
| 165 |
+ c.Assert(errs, checker.HasLen, 0, check.Commentf(strings.Join(errs, "\n"))) |
|
| 166 |
+} |
|
| 167 |
+ |
|
| 168 |
+func getAllNetworks() ([]types.NetworkResource, error) {
|
|
| 169 |
+ var networks []types.NetworkResource |
|
| 170 |
+ _, b, err := sockRequest("GET", "/networks", nil)
|
|
| 171 |
+ if err != nil {
|
|
| 172 |
+ return nil, err |
|
| 173 |
+ } |
|
| 174 |
+ if err := json.Unmarshal(b, &networks); err != nil {
|
|
| 175 |
+ return nil, err |
|
| 176 |
+ } |
|
| 177 |
+ return networks, nil |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 180 |
+func deleteAllPlugins(c *check.C) {
|
|
| 181 |
+ plugins, err := getAllPlugins() |
|
| 182 |
+ c.Assert(err, checker.IsNil) |
|
| 183 |
+ var errs []string |
|
| 184 |
+ for _, p := range plugins {
|
|
| 185 |
+ pluginName := p.Name |
|
| 186 |
+ status, b, err := sockRequest("DELETE", "/plugins/"+pluginName+"?force=1", nil)
|
|
| 187 |
+ if err != nil {
|
|
| 188 |
+ errs = append(errs, err.Error()) |
|
| 189 |
+ continue |
|
| 190 |
+ } |
|
| 191 |
+ if status != http.StatusOK {
|
|
| 192 |
+ errs = append(errs, fmt.Sprintf("error deleting plugin %s: %s", p.Name, string(b)))
|
|
| 193 |
+ } |
|
| 194 |
+ } |
|
| 195 |
+ c.Assert(errs, checker.HasLen, 0, check.Commentf(strings.Join(errs, "\n"))) |
|
| 196 |
+} |
|
| 197 |
+ |
|
| 198 |
+func getAllPlugins() (types.PluginsListResponse, error) {
|
|
| 199 |
+ var plugins types.PluginsListResponse |
|
| 200 |
+ _, b, err := sockRequest("GET", "/plugins", nil)
|
|
| 201 |
+ if err != nil {
|
|
| 202 |
+ return nil, err |
|
| 203 |
+ } |
|
| 204 |
+ if err := json.Unmarshal(b, &plugins); err != nil {
|
|
| 205 |
+ return nil, err |
|
| 206 |
+ } |
|
| 207 |
+ return plugins, nil |
|
| 208 |
+} |
|
| 209 |
+ |
|
| 210 |
+func deleteAllVolumes(c *check.C) {
|
|
| 211 |
+ volumes, err := getAllVolumes() |
|
| 212 |
+ c.Assert(err, checker.IsNil) |
|
| 213 |
+ var errs []string |
|
| 214 |
+ for _, v := range volumes {
|
|
| 215 |
+ status, b, err := sockRequest("DELETE", "/volumes/"+v.Name, nil)
|
|
| 216 |
+ if err != nil {
|
|
| 217 |
+ errs = append(errs, err.Error()) |
|
| 218 |
+ continue |
|
| 219 |
+ } |
|
| 220 |
+ if status != http.StatusNoContent {
|
|
| 221 |
+ errs = append(errs, fmt.Sprintf("error deleting volume %s: %s", v.Name, string(b)))
|
|
| 222 |
+ } |
|
| 223 |
+ } |
|
| 224 |
+ c.Assert(errs, checker.HasLen, 0, check.Commentf(strings.Join(errs, "\n"))) |
|
| 225 |
+} |
|
| 226 |
+ |
|
| 227 |
+func getAllVolumes() ([]*types.Volume, error) {
|
|
| 228 |
+ var volumes volumetypes.VolumesListOKBody |
|
| 229 |
+ _, b, err := sockRequest("GET", "/volumes", nil)
|
|
| 230 |
+ if err != nil {
|
|
| 231 |
+ return nil, err |
|
| 232 |
+ } |
|
| 233 |
+ if err := json.Unmarshal(b, &volumes); err != nil {
|
|
| 234 |
+ return nil, err |
|
| 235 |
+ } |
|
| 236 |
+ return volumes.Volumes, nil |
|
| 237 |
+} |
|
| 238 |
+ |
|
| 239 |
+func deleteAllImages(c *check.C) {
|
|
| 240 |
+ cmd := exec.Command(dockerBinary, "images", "--digests") |
|
| 241 |
+ cmd.Env = appendBaseEnv(true) |
|
| 242 |
+ out, err := cmd.CombinedOutput() |
|
| 243 |
+ c.Assert(err, checker.IsNil) |
|
| 244 |
+ lines := strings.Split(string(out), "\n")[1:] |
|
| 245 |
+ imgMap := map[string]struct{}{}
|
|
| 246 |
+ for _, l := range lines {
|
|
| 247 |
+ if l == "" {
|
|
| 248 |
+ continue |
|
| 249 |
+ } |
|
| 250 |
+ fields := strings.Fields(l) |
|
| 251 |
+ imgTag := fields[0] + ":" + fields[1] |
|
| 252 |
+ if _, ok := protectedImages[imgTag]; !ok {
|
|
| 253 |
+ if fields[0] == "<none>" || fields[1] == "<none>" {
|
|
| 254 |
+ if fields[2] != "<none>" {
|
|
| 255 |
+ imgMap[fields[0]+"@"+fields[2]] = struct{}{}
|
|
| 256 |
+ } else {
|
|
| 257 |
+ imgMap[fields[3]] = struct{}{}
|
|
| 258 |
+ } |
|
| 259 |
+ // continue |
|
| 260 |
+ } else {
|
|
| 261 |
+ imgMap[imgTag] = struct{}{}
|
|
| 262 |
+ } |
|
| 263 |
+ } |
|
| 264 |
+ } |
|
| 265 |
+ if len(imgMap) != 0 {
|
|
| 266 |
+ imgs := make([]string, 0, len(imgMap)) |
|
| 267 |
+ for k := range imgMap {
|
|
| 268 |
+ imgs = append(imgs, k) |
|
| 269 |
+ } |
|
| 270 |
+ dockerCmd(c, append([]string{"rmi", "-f"}, imgs...)...)
|
|
| 271 |
+ } |
|
| 272 |
+} |
|
| 273 |
+ |
|
| 274 |
+func getPausedContainers() ([]string, error) {
|
|
| 275 |
+ getPausedContainersCmd := exec.Command(dockerBinary, "ps", "-f", "status=paused", "-q", "-a") |
|
| 276 |
+ out, exitCode, err := runCommandWithOutput(getPausedContainersCmd) |
|
| 277 |
+ if exitCode != 0 && err == nil {
|
|
| 278 |
+ err = fmt.Errorf("failed to get a list of paused containers: %v\n", out)
|
|
| 279 |
+ } |
|
| 280 |
+ if err != nil {
|
|
| 281 |
+ return nil, err |
|
| 282 |
+ } |
|
| 283 |
+ |
|
| 284 |
+ return strings.Fields(out), nil |
|
| 285 |
+} |
|
| 286 |
+ |
|
| 287 |
+func unpauseContainer(c *check.C, container string) {
|
|
| 288 |
+ dockerCmd(c, "unpause", container) |
|
| 289 |
+} |
|
| 290 |
+ |
|
| 291 |
+func unpauseAllContainers(c *check.C) {
|
|
| 292 |
+ containers, err := getPausedContainers() |
|
| 293 |
+ c.Assert(err, checker.IsNil, check.Commentf("containers: %v", containers))
|
|
| 294 |
+ for _, value := range containers {
|
|
| 295 |
+ unpauseContainer(c, value) |
|
| 296 |
+ } |
|
| 297 |
+} |
|
| 298 |
+ |
|
| 299 |
+func deleteImages(images ...string) error {
|
|
| 300 |
+ args := []string{dockerBinary, "rmi", "-f"}
|
|
| 301 |
+ return icmd.RunCmd(icmd.Cmd{Command: append(args, images...)}).Error
|
|
| 302 |
+} |
|
| 303 |
+ |
|
| 304 |
+func dockerCmdWithError(args ...string) (string, int, error) {
|
|
| 305 |
+ if err := validateArgs(args...); err != nil {
|
|
| 306 |
+ return "", 0, err |
|
| 307 |
+ } |
|
| 308 |
+ result := icmd.RunCommand(dockerBinary, args...) |
|
| 309 |
+ if result.Error != nil {
|
|
| 310 |
+ return result.Combined(), result.ExitCode, result.Compare(icmd.Success) |
|
| 311 |
+ } |
|
| 312 |
+ return result.Combined(), result.ExitCode, result.Error |
|
| 313 |
+} |
|
| 314 |
+ |
|
| 315 |
+func dockerCmdWithStdoutStderr(c *check.C, args ...string) (string, string, int) {
|
|
| 316 |
+ if err := validateArgs(args...); err != nil {
|
|
| 317 |
+ c.Fatalf(err.Error()) |
|
| 318 |
+ } |
|
| 319 |
+ |
|
| 320 |
+ result := icmd.RunCommand(dockerBinary, args...) |
|
| 321 |
+ c.Assert(result, icmd.Matches, icmd.Success) |
|
| 322 |
+ return result.Stdout(), result.Stderr(), result.ExitCode |
|
| 323 |
+} |
|
| 324 |
+ |
|
| 325 |
+func dockerCmd(c *check.C, args ...string) (string, int) {
|
|
| 326 |
+ if err := validateArgs(args...); err != nil {
|
|
| 327 |
+ c.Fatalf(err.Error()) |
|
| 328 |
+ } |
|
| 329 |
+ result := icmd.RunCommand(dockerBinary, args...) |
|
| 330 |
+ c.Assert(result, icmd.Matches, icmd.Success) |
|
| 331 |
+ return result.Combined(), result.ExitCode |
|
| 332 |
+} |
|
| 333 |
+ |
|
| 334 |
+func dockerCmdWithResult(args ...string) *icmd.Result {
|
|
| 335 |
+ return icmd.RunCommand(dockerBinary, args...) |
|
| 336 |
+} |
|
| 337 |
+ |
|
| 338 |
+func binaryWithArgs(args ...string) []string {
|
|
| 339 |
+ return append([]string{dockerBinary}, args...)
|
|
| 340 |
+} |
|
| 341 |
+ |
|
| 342 |
+// execute a docker command with a timeout |
|
| 343 |
+func dockerCmdWithTimeout(timeout time.Duration, args ...string) *icmd.Result {
|
|
| 344 |
+ if err := validateArgs(args...); err != nil {
|
|
| 345 |
+ return &icmd.Result{Error: err}
|
|
| 346 |
+ } |
|
| 347 |
+ return icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args...), Timeout: timeout})
|
|
| 348 |
+} |
|
| 349 |
+ |
|
| 350 |
+// execute a docker command in a directory |
|
| 351 |
+func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error) {
|
|
| 352 |
+ if err := validateArgs(args...); err != nil {
|
|
| 353 |
+ c.Fatalf(err.Error()) |
|
| 354 |
+ } |
|
| 355 |
+ result := icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args...), Dir: path})
|
|
| 356 |
+ return result.Combined(), result.ExitCode, result.Error |
|
| 357 |
+} |
|
| 358 |
+ |
|
| 359 |
+// validateArgs is a checker to ensure tests are not running commands which are |
|
| 360 |
+// not supported on platforms. Specifically on Windows this is 'busybox top'. |
|
| 361 |
+func validateArgs(args ...string) error {
|
|
| 362 |
+ if daemonPlatform != "windows" {
|
|
| 363 |
+ return nil |
|
| 364 |
+ } |
|
| 365 |
+ foundBusybox := -1 |
|
| 366 |
+ for key, value := range args {
|
|
| 367 |
+ if strings.ToLower(value) == "busybox" {
|
|
| 368 |
+ foundBusybox = key |
|
| 369 |
+ } |
|
| 370 |
+ if (foundBusybox != -1) && (key == foundBusybox+1) && (strings.ToLower(value) == "top") {
|
|
| 371 |
+ return errors.New("cannot use 'busybox top' in tests on Windows. Use runSleepingContainer()")
|
|
| 372 |
+ } |
|
| 373 |
+ } |
|
| 374 |
+ return nil |
|
| 375 |
+} |
|
| 376 |
+ |
|
| 377 |
+// find the State.ExitCode in container metadata |
|
| 378 |
+func findContainerExitCode(c *check.C, name string, vargs ...string) string {
|
|
| 379 |
+ args := append(vargs, "inspect", "--format='{{ .State.ExitCode }} {{ .State.Error }}'", name)
|
|
| 380 |
+ cmd := exec.Command(dockerBinary, args...) |
|
| 381 |
+ out, _, err := runCommandWithOutput(cmd) |
|
| 382 |
+ if err != nil {
|
|
| 383 |
+ c.Fatal(err, out) |
|
| 384 |
+ } |
|
| 385 |
+ return out |
|
| 386 |
+} |
|
| 387 |
+ |
|
| 388 |
+func findContainerIP(c *check.C, id string, network string) string {
|
|
| 389 |
+ out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", network), id)
|
|
| 390 |
+ return strings.Trim(out, " \r\n'") |
|
| 391 |
+} |
|
| 392 |
+ |
|
| 393 |
+func getContainerCount() (int, error) {
|
|
| 394 |
+ const containers = "Containers:" |
|
| 395 |
+ |
|
| 396 |
+ cmd := exec.Command(dockerBinary, "info") |
|
| 397 |
+ out, _, err := runCommandWithOutput(cmd) |
|
| 398 |
+ if err != nil {
|
|
| 399 |
+ return 0, err |
|
| 400 |
+ } |
|
| 401 |
+ |
|
| 402 |
+ lines := strings.Split(out, "\n") |
|
| 403 |
+ for _, line := range lines {
|
|
| 404 |
+ if strings.Contains(line, containers) {
|
|
| 405 |
+ output := strings.TrimSpace(line) |
|
| 406 |
+ output = strings.TrimLeft(output, containers) |
|
| 407 |
+ output = strings.Trim(output, " ") |
|
| 408 |
+ containerCount, err := strconv.Atoi(output) |
|
| 409 |
+ if err != nil {
|
|
| 410 |
+ return 0, err |
|
| 411 |
+ } |
|
| 412 |
+ return containerCount, nil |
|
| 413 |
+ } |
|
| 414 |
+ } |
|
| 415 |
+ return 0, fmt.Errorf("couldn't find the Container count in the output")
|
|
| 416 |
+} |
|
| 417 |
+ |
|
| 418 |
+// FakeContext creates directories that can be used as a build context |
|
| 419 |
+type FakeContext struct {
|
|
| 420 |
+ Dir string |
|
| 421 |
+} |
|
| 422 |
+ |
|
| 423 |
+// Add a file at a path, creating directories where necessary |
|
| 424 |
+func (f *FakeContext) Add(file, content string) error {
|
|
| 425 |
+ return f.addFile(file, []byte(content)) |
|
| 426 |
+} |
|
| 427 |
+ |
|
| 428 |
+func (f *FakeContext) addFile(file string, content []byte) error {
|
|
| 429 |
+ fp := filepath.Join(f.Dir, filepath.FromSlash(file)) |
|
| 430 |
+ dirpath := filepath.Dir(fp) |
|
| 431 |
+ if dirpath != "." {
|
|
| 432 |
+ if err := os.MkdirAll(dirpath, 0755); err != nil {
|
|
| 433 |
+ return err |
|
| 434 |
+ } |
|
| 435 |
+ } |
|
| 436 |
+ return ioutil.WriteFile(fp, content, 0644) |
|
| 437 |
+ |
|
| 438 |
+} |
|
| 439 |
+ |
|
| 440 |
+// Delete a file at a path |
|
| 441 |
+func (f *FakeContext) Delete(file string) error {
|
|
| 442 |
+ fp := filepath.Join(f.Dir, filepath.FromSlash(file)) |
|
| 443 |
+ return os.RemoveAll(fp) |
|
| 444 |
+} |
|
| 445 |
+ |
|
| 446 |
+// Close deletes the context |
|
| 447 |
+func (f *FakeContext) Close() error {
|
|
| 448 |
+ return os.RemoveAll(f.Dir) |
|
| 449 |
+} |
|
| 450 |
+ |
|
| 451 |
+func fakeContextFromNewTempDir() (*FakeContext, error) {
|
|
| 452 |
+ tmp, err := ioutil.TempDir("", "fake-context")
|
|
| 453 |
+ if err != nil {
|
|
| 454 |
+ return nil, err |
|
| 455 |
+ } |
|
| 456 |
+ if err := os.Chmod(tmp, 0755); err != nil {
|
|
| 457 |
+ return nil, err |
|
| 458 |
+ } |
|
| 459 |
+ return fakeContextFromDir(tmp), nil |
|
| 460 |
+} |
|
| 461 |
+ |
|
| 462 |
+func fakeContextFromDir(dir string) *FakeContext {
|
|
| 463 |
+ return &FakeContext{dir}
|
|
| 464 |
+} |
|
| 465 |
+ |
|
| 466 |
+func fakeContextWithFiles(files map[string]string) (*FakeContext, error) {
|
|
| 467 |
+ ctx, err := fakeContextFromNewTempDir() |
|
| 468 |
+ if err != nil {
|
|
| 469 |
+ return nil, err |
|
| 470 |
+ } |
|
| 471 |
+ for file, content := range files {
|
|
| 472 |
+ if err := ctx.Add(file, content); err != nil {
|
|
| 473 |
+ ctx.Close() |
|
| 474 |
+ return nil, err |
|
| 475 |
+ } |
|
| 476 |
+ } |
|
| 477 |
+ return ctx, nil |
|
| 478 |
+} |
|
| 479 |
+ |
|
| 480 |
+func fakeContextAddDockerfile(ctx *FakeContext, dockerfile string) error {
|
|
| 481 |
+ if err := ctx.Add("Dockerfile", dockerfile); err != nil {
|
|
| 482 |
+ ctx.Close() |
|
| 483 |
+ return err |
|
| 484 |
+ } |
|
| 485 |
+ return nil |
|
| 486 |
+} |
|
| 487 |
+ |
|
| 488 |
+func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) {
|
|
| 489 |
+ ctx, err := fakeContextWithFiles(files) |
|
| 490 |
+ if err != nil {
|
|
| 491 |
+ return nil, err |
|
| 492 |
+ } |
|
| 493 |
+ if err := fakeContextAddDockerfile(ctx, dockerfile); err != nil {
|
|
| 494 |
+ return nil, err |
|
| 495 |
+ } |
|
| 496 |
+ return ctx, nil |
|
| 497 |
+} |
|
| 498 |
+ |
|
| 499 |
+// FakeStorage is a static file server. It might be running locally or remotely |
|
| 500 |
+// on test host. |
|
| 501 |
+type FakeStorage interface {
|
|
| 502 |
+ Close() error |
|
| 503 |
+ URL() string |
|
| 504 |
+ CtxDir() string |
|
| 505 |
+} |
|
| 506 |
+ |
|
| 507 |
+func fakeBinaryStorage(archives map[string]*bytes.Buffer) (FakeStorage, error) {
|
|
| 508 |
+ ctx, err := fakeContextFromNewTempDir() |
|
| 509 |
+ if err != nil {
|
|
| 510 |
+ return nil, err |
|
| 511 |
+ } |
|
| 512 |
+ for name, content := range archives {
|
|
| 513 |
+ if err := ctx.addFile(name, content.Bytes()); err != nil {
|
|
| 514 |
+ return nil, err |
|
| 515 |
+ } |
|
| 516 |
+ } |
|
| 517 |
+ return fakeStorageWithContext(ctx) |
|
| 518 |
+} |
|
| 519 |
+ |
|
| 520 |
+// fakeStorage returns either a local or remote (at daemon machine) file server |
|
| 521 |
+func fakeStorage(files map[string]string) (FakeStorage, error) {
|
|
| 522 |
+ ctx, err := fakeContextWithFiles(files) |
|
| 523 |
+ if err != nil {
|
|
| 524 |
+ return nil, err |
|
| 525 |
+ } |
|
| 526 |
+ return fakeStorageWithContext(ctx) |
|
| 527 |
+} |
|
| 528 |
+ |
|
| 529 |
+// fakeStorageWithContext returns either a local or remote (at daemon machine) file server |
|
| 530 |
+func fakeStorageWithContext(ctx *FakeContext) (FakeStorage, error) {
|
|
| 531 |
+ if isLocalDaemon {
|
|
| 532 |
+ return newLocalFakeStorage(ctx) |
|
| 533 |
+ } |
|
| 534 |
+ return newRemoteFileServer(ctx) |
|
| 535 |
+} |
|
| 536 |
+ |
|
| 537 |
+// localFileStorage is a file storage on the running machine |
|
| 538 |
+type localFileStorage struct {
|
|
| 539 |
+ *FakeContext |
|
| 540 |
+ *httptest.Server |
|
| 541 |
+} |
|
| 542 |
+ |
|
| 543 |
+func (s *localFileStorage) URL() string {
|
|
| 544 |
+ return s.Server.URL |
|
| 545 |
+} |
|
| 546 |
+ |
|
| 547 |
+func (s *localFileStorage) CtxDir() string {
|
|
| 548 |
+ return s.FakeContext.Dir |
|
| 549 |
+} |
|
| 550 |
+ |
|
| 551 |
+func (s *localFileStorage) Close() error {
|
|
| 552 |
+ defer s.Server.Close() |
|
| 553 |
+ return s.FakeContext.Close() |
|
| 554 |
+} |
|
| 555 |
+ |
|
| 556 |
+func newLocalFakeStorage(ctx *FakeContext) (*localFileStorage, error) {
|
|
| 557 |
+ handler := http.FileServer(http.Dir(ctx.Dir)) |
|
| 558 |
+ server := httptest.NewServer(handler) |
|
| 559 |
+ return &localFileStorage{
|
|
| 560 |
+ FakeContext: ctx, |
|
| 561 |
+ Server: server, |
|
| 562 |
+ }, nil |
|
| 563 |
+} |
|
| 564 |
+ |
|
| 565 |
+// remoteFileServer is a containerized static file server started on the remote |
|
| 566 |
+// testing machine to be used in URL-accepting docker build functionality. |
|
| 567 |
+type remoteFileServer struct {
|
|
| 568 |
+ host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 |
|
| 569 |
+ container string |
|
| 570 |
+ image string |
|
| 571 |
+ ctx *FakeContext |
|
| 572 |
+} |
|
| 573 |
+ |
|
| 574 |
+func (f *remoteFileServer) URL() string {
|
|
| 575 |
+ u := url.URL{
|
|
| 576 |
+ Scheme: "http", |
|
| 577 |
+ Host: f.host} |
|
| 578 |
+ return u.String() |
|
| 579 |
+} |
|
| 580 |
+ |
|
| 581 |
+func (f *remoteFileServer) CtxDir() string {
|
|
| 582 |
+ return f.ctx.Dir |
|
| 583 |
+} |
|
| 584 |
+ |
|
| 585 |
+func (f *remoteFileServer) Close() error {
|
|
| 586 |
+ defer func() {
|
|
| 587 |
+ if f.ctx != nil {
|
|
| 588 |
+ f.ctx.Close() |
|
| 589 |
+ } |
|
| 590 |
+ if f.image != "" {
|
|
| 591 |
+ deleteImages(f.image) |
|
| 592 |
+ } |
|
| 593 |
+ }() |
|
| 594 |
+ if f.container == "" {
|
|
| 595 |
+ return nil |
|
| 596 |
+ } |
|
| 597 |
+ return deleteContainer(f.container) |
|
| 598 |
+} |
|
| 599 |
+ |
|
| 600 |
+func newRemoteFileServer(ctx *FakeContext) (*remoteFileServer, error) {
|
|
| 601 |
+ var ( |
|
| 602 |
+ image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10)))
|
|
| 603 |
+ container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10)))
|
|
| 604 |
+ ) |
|
| 605 |
+ |
|
| 606 |
+ if err := ensureHTTPServerImage(); err != nil {
|
|
| 607 |
+ return nil, err |
|
| 608 |
+ } |
|
| 609 |
+ |
|
| 610 |
+ // Build the image |
|
| 611 |
+ if err := fakeContextAddDockerfile(ctx, `FROM httpserver |
|
| 612 |
+COPY . /static`); err != nil {
|
|
| 613 |
+ return nil, fmt.Errorf("Cannot add Dockerfile to context: %v", err)
|
|
| 614 |
+ } |
|
| 615 |
+ if _, err := buildImageFromContext(image, ctx, false); err != nil {
|
|
| 616 |
+ return nil, fmt.Errorf("failed building file storage container image: %v", err)
|
|
| 617 |
+ } |
|
| 618 |
+ |
|
| 619 |
+ // Start the container |
|
| 620 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--name", container, image) |
|
| 621 |
+ if out, ec, err := runCommandWithOutput(runCmd); err != nil {
|
|
| 622 |
+ return nil, fmt.Errorf("failed to start file storage container. ec=%v\nout=%s\nerr=%v", ec, out, err)
|
|
| 623 |
+ } |
|
| 624 |
+ |
|
| 625 |
+ // Find out the system assigned port |
|
| 626 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "port", container, "80/tcp")) |
|
| 627 |
+ if err != nil {
|
|
| 628 |
+ return nil, fmt.Errorf("failed to find container port: err=%v\nout=%s", err, out)
|
|
| 629 |
+ } |
|
| 630 |
+ |
|
| 631 |
+ fileserverHostPort := strings.Trim(out, "\n") |
|
| 632 |
+ _, port, err := net.SplitHostPort(fileserverHostPort) |
|
| 633 |
+ if err != nil {
|
|
| 634 |
+ return nil, fmt.Errorf("unable to parse file server host:port: %v", err)
|
|
| 635 |
+ } |
|
| 636 |
+ |
|
| 637 |
+ dockerHostURL, err := url.Parse(daemonHost()) |
|
| 638 |
+ if err != nil {
|
|
| 639 |
+ return nil, fmt.Errorf("unable to parse daemon host URL: %v", err)
|
|
| 640 |
+ } |
|
| 641 |
+ |
|
| 642 |
+ host, _, err := net.SplitHostPort(dockerHostURL.Host) |
|
| 643 |
+ if err != nil {
|
|
| 644 |
+ return nil, fmt.Errorf("unable to parse docker daemon host:port: %v", err)
|
|
| 645 |
+ } |
|
| 646 |
+ |
|
| 647 |
+ return &remoteFileServer{
|
|
| 648 |
+ container: container, |
|
| 649 |
+ image: image, |
|
| 650 |
+ host: fmt.Sprintf("%s:%s", host, port),
|
|
| 651 |
+ ctx: ctx}, nil |
|
| 652 |
+} |
|
| 653 |
+ |
|
| 654 |
+func inspectFieldAndMarshall(c *check.C, name, field string, output interface{}) {
|
|
| 655 |
+ str := inspectFieldJSON(c, name, field) |
|
| 656 |
+ err := json.Unmarshal([]byte(str), output) |
|
| 657 |
+ if c != nil {
|
|
| 658 |
+ c.Assert(err, check.IsNil, check.Commentf("failed to unmarshal: %v", err))
|
|
| 659 |
+ } |
|
| 660 |
+} |
|
| 661 |
+ |
|
| 662 |
+func inspectFilter(name, filter string) (string, error) {
|
|
| 663 |
+ format := fmt.Sprintf("{{%s}}", filter)
|
|
| 664 |
+ inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) |
|
| 665 |
+ out, exitCode, err := runCommandWithOutput(inspectCmd) |
|
| 666 |
+ if err != nil || exitCode != 0 {
|
|
| 667 |
+ return "", fmt.Errorf("failed to inspect %s: %s", name, out)
|
|
| 668 |
+ } |
|
| 669 |
+ return strings.TrimSpace(out), nil |
|
| 670 |
+} |
|
| 671 |
+ |
|
| 672 |
+func inspectFieldWithError(name, field string) (string, error) {
|
|
| 673 |
+ return inspectFilter(name, fmt.Sprintf(".%s", field))
|
|
| 674 |
+} |
|
| 675 |
+ |
|
| 676 |
+func inspectField(c *check.C, name, field string) string {
|
|
| 677 |
+ out, err := inspectFilter(name, fmt.Sprintf(".%s", field))
|
|
| 678 |
+ if c != nil {
|
|
| 679 |
+ c.Assert(err, check.IsNil) |
|
| 680 |
+ } |
|
| 681 |
+ return out |
|
| 682 |
+} |
|
| 683 |
+ |
|
| 684 |
+func inspectFieldJSON(c *check.C, name, field string) string {
|
|
| 685 |
+ out, err := inspectFilter(name, fmt.Sprintf("json .%s", field))
|
|
| 686 |
+ if c != nil {
|
|
| 687 |
+ c.Assert(err, check.IsNil) |
|
| 688 |
+ } |
|
| 689 |
+ return out |
|
| 690 |
+} |
|
| 691 |
+ |
|
| 692 |
+func inspectFieldMap(c *check.C, name, path, field string) string {
|
|
| 693 |
+ out, err := inspectFilter(name, fmt.Sprintf("index .%s %q", path, field))
|
|
| 694 |
+ if c != nil {
|
|
| 695 |
+ c.Assert(err, check.IsNil) |
|
| 696 |
+ } |
|
| 697 |
+ return out |
|
| 698 |
+} |
|
| 699 |
+ |
|
| 700 |
+func inspectMountSourceField(name, destination string) (string, error) {
|
|
| 701 |
+ m, err := inspectMountPoint(name, destination) |
|
| 702 |
+ if err != nil {
|
|
| 703 |
+ return "", err |
|
| 704 |
+ } |
|
| 705 |
+ return m.Source, nil |
|
| 706 |
+} |
|
| 707 |
+ |
|
| 708 |
+func inspectMountPoint(name, destination string) (types.MountPoint, error) {
|
|
| 709 |
+ out, err := inspectFilter(name, "json .Mounts") |
|
| 710 |
+ if err != nil {
|
|
| 711 |
+ return types.MountPoint{}, err
|
|
| 712 |
+ } |
|
| 713 |
+ |
|
| 714 |
+ return inspectMountPointJSON(out, destination) |
|
| 715 |
+} |
|
| 716 |
+ |
|
| 717 |
+var errMountNotFound = errors.New("mount point not found")
|
|
| 718 |
+ |
|
| 719 |
+func inspectMountPointJSON(j, destination string) (types.MountPoint, error) {
|
|
| 720 |
+ var mp []types.MountPoint |
|
| 721 |
+ if err := json.Unmarshal([]byte(j), &mp); err != nil {
|
|
| 722 |
+ return types.MountPoint{}, err
|
|
| 723 |
+ } |
|
| 724 |
+ |
|
| 725 |
+ var m *types.MountPoint |
|
| 726 |
+ for _, c := range mp {
|
|
| 727 |
+ if c.Destination == destination {
|
|
| 728 |
+ m = &c |
|
| 729 |
+ break |
|
| 730 |
+ } |
|
| 731 |
+ } |
|
| 732 |
+ |
|
| 733 |
+ if m == nil {
|
|
| 734 |
+ return types.MountPoint{}, errMountNotFound
|
|
| 735 |
+ } |
|
| 736 |
+ |
|
| 737 |
+ return *m, nil |
|
| 738 |
+} |
|
| 739 |
+ |
|
| 740 |
+func inspectImage(name, filter string) (string, error) {
|
|
| 741 |
+ args := []string{"inspect", "--type", "image"}
|
|
| 742 |
+ if filter != "" {
|
|
| 743 |
+ format := fmt.Sprintf("{{%s}}", filter)
|
|
| 744 |
+ args = append(args, "-f", format) |
|
| 745 |
+ } |
|
| 746 |
+ args = append(args, name) |
|
| 747 |
+ inspectCmd := exec.Command(dockerBinary, args...) |
|
| 748 |
+ out, exitCode, err := runCommandWithOutput(inspectCmd) |
|
| 749 |
+ if err != nil || exitCode != 0 {
|
|
| 750 |
+ return "", fmt.Errorf("failed to inspect %s: %s", name, out)
|
|
| 751 |
+ } |
|
| 752 |
+ return strings.TrimSpace(out), nil |
|
| 753 |
+} |
|
| 754 |
+ |
|
| 755 |
+func getIDByName(name string) (string, error) {
|
|
| 756 |
+ return inspectFieldWithError(name, "Id") |
|
| 757 |
+} |
|
| 758 |
+ |
|
| 759 |
+func buildImageCmd(name, dockerfile string, useCache bool, buildFlags ...string) *exec.Cmd {
|
|
| 760 |
+ return daemon.BuildImageCmdWithHost(dockerBinary, name, dockerfile, "", useCache, buildFlags...) |
|
| 761 |
+} |
|
| 762 |
+ |
|
| 763 |
+func buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, error) {
|
|
| 764 |
+ buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...) |
|
| 765 |
+ out, exitCode, err := runCommandWithOutput(buildCmd) |
|
| 766 |
+ if err != nil || exitCode != 0 {
|
|
| 767 |
+ return "", out, fmt.Errorf("failed to build the image: %s", out)
|
|
| 768 |
+ } |
|
| 769 |
+ id, err := getIDByName(name) |
|
| 770 |
+ if err != nil {
|
|
| 771 |
+ return "", out, err |
|
| 772 |
+ } |
|
| 773 |
+ return id, out, nil |
|
| 774 |
+} |
|
| 775 |
+ |
|
| 776 |
+func buildImageWithStdoutStderr(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, string, error) {
|
|
| 777 |
+ buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...) |
|
| 778 |
+ result := icmd.RunCmd(transformCmd(buildCmd)) |
|
| 779 |
+ err := result.Error |
|
| 780 |
+ exitCode := result.ExitCode |
|
| 781 |
+ if err != nil || exitCode != 0 {
|
|
| 782 |
+ return "", result.Stdout(), result.Stderr(), fmt.Errorf("failed to build the image: %s", result.Combined())
|
|
| 783 |
+ } |
|
| 784 |
+ id, err := getIDByName(name) |
|
| 785 |
+ if err != nil {
|
|
| 786 |
+ return "", result.Stdout(), result.Stderr(), err |
|
| 787 |
+ } |
|
| 788 |
+ return id, result.Stdout(), result.Stderr(), nil |
|
| 789 |
+} |
|
| 790 |
+ |
|
| 791 |
+func buildImage(name, dockerfile string, useCache bool, buildFlags ...string) (string, error) {
|
|
| 792 |
+ id, _, err := buildImageWithOut(name, dockerfile, useCache, buildFlags...) |
|
| 793 |
+ return id, err |
|
| 794 |
+} |
|
| 795 |
+ |
|
| 796 |
+func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, error) {
|
|
| 797 |
+ id, _, err := buildImageFromContextWithOut(name, ctx, useCache, buildFlags...) |
|
| 798 |
+ if err != nil {
|
|
| 799 |
+ return "", err |
|
| 800 |
+ } |
|
| 801 |
+ return id, nil |
|
| 802 |
+} |
|
| 803 |
+ |
|
| 804 |
+func buildImageFromContextWithOut(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, string, error) {
|
|
| 805 |
+ args := []string{"build", "-t", name}
|
|
| 806 |
+ if !useCache {
|
|
| 807 |
+ args = append(args, "--no-cache") |
|
| 808 |
+ } |
|
| 809 |
+ args = append(args, buildFlags...) |
|
| 810 |
+ args = append(args, ".") |
|
| 811 |
+ buildCmd := exec.Command(dockerBinary, args...) |
|
| 812 |
+ buildCmd.Dir = ctx.Dir |
|
| 813 |
+ out, exitCode, err := runCommandWithOutput(buildCmd) |
|
| 814 |
+ if err != nil || exitCode != 0 {
|
|
| 815 |
+ return "", "", fmt.Errorf("failed to build the image: %s", out)
|
|
| 816 |
+ } |
|
| 817 |
+ id, err := getIDByName(name) |
|
| 818 |
+ if err != nil {
|
|
| 819 |
+ return "", "", err |
|
| 820 |
+ } |
|
| 821 |
+ return id, out, nil |
|
| 822 |
+} |
|
| 823 |
+ |
|
| 824 |
+func buildImageFromContextWithStdoutStderr(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, string, string, error) {
|
|
| 825 |
+ args := []string{"build", "-t", name}
|
|
| 826 |
+ if !useCache {
|
|
| 827 |
+ args = append(args, "--no-cache") |
|
| 828 |
+ } |
|
| 829 |
+ args = append(args, buildFlags...) |
|
| 830 |
+ args = append(args, ".") |
|
| 831 |
+ |
|
| 832 |
+ result := icmd.RunCmd(icmd.Cmd{
|
|
| 833 |
+ Command: append([]string{dockerBinary}, args...),
|
|
| 834 |
+ Dir: ctx.Dir, |
|
| 835 |
+ }) |
|
| 836 |
+ exitCode := result.ExitCode |
|
| 837 |
+ err := result.Error |
|
| 838 |
+ if err != nil || exitCode != 0 {
|
|
| 839 |
+ return "", result.Stdout(), result.Stderr(), fmt.Errorf("failed to build the image: %s", result.Combined())
|
|
| 840 |
+ } |
|
| 841 |
+ id, err := getIDByName(name) |
|
| 842 |
+ if err != nil {
|
|
| 843 |
+ return "", result.Stdout(), result.Stderr(), err |
|
| 844 |
+ } |
|
| 845 |
+ return id, result.Stdout(), result.Stderr(), nil |
|
| 846 |
+} |
|
| 847 |
+ |
|
| 848 |
+func buildImageFromGitWithStdoutStderr(name string, ctx *fakeGit, useCache bool, buildFlags ...string) (string, string, string, error) {
|
|
| 849 |
+ args := []string{"build", "-t", name}
|
|
| 850 |
+ if !useCache {
|
|
| 851 |
+ args = append(args, "--no-cache") |
|
| 852 |
+ } |
|
| 853 |
+ args = append(args, buildFlags...) |
|
| 854 |
+ args = append(args, ctx.RepoURL) |
|
| 855 |
+ result := icmd.RunCmd(icmd.Cmd{
|
|
| 856 |
+ Command: append([]string{dockerBinary}, args...),
|
|
| 857 |
+ }) |
|
| 858 |
+ exitCode := result.ExitCode |
|
| 859 |
+ err := result.Error |
|
| 860 |
+ if err != nil || exitCode != 0 {
|
|
| 861 |
+ return "", result.Stdout(), result.Stderr(), fmt.Errorf("failed to build the image: %s", result.Combined())
|
|
| 862 |
+ } |
|
| 863 |
+ id, err := getIDByName(name) |
|
| 864 |
+ if err != nil {
|
|
| 865 |
+ return "", result.Stdout(), result.Stderr(), err |
|
| 866 |
+ } |
|
| 867 |
+ return id, result.Stdout(), result.Stderr(), nil |
|
| 868 |
+} |
|
| 869 |
+ |
|
| 870 |
+func buildImageFromPath(name, path string, useCache bool, buildFlags ...string) (string, error) {
|
|
| 871 |
+ args := []string{"build", "-t", name}
|
|
| 872 |
+ if !useCache {
|
|
| 873 |
+ args = append(args, "--no-cache") |
|
| 874 |
+ } |
|
| 875 |
+ args = append(args, buildFlags...) |
|
| 876 |
+ args = append(args, path) |
|
| 877 |
+ buildCmd := exec.Command(dockerBinary, args...) |
|
| 878 |
+ out, exitCode, err := runCommandWithOutput(buildCmd) |
|
| 879 |
+ if err != nil || exitCode != 0 {
|
|
| 880 |
+ return "", fmt.Errorf("failed to build the image: %s", out)
|
|
| 881 |
+ } |
|
| 882 |
+ return getIDByName(name) |
|
| 883 |
+} |
|
| 884 |
+ |
|
| 885 |
+type gitServer interface {
|
|
| 886 |
+ URL() string |
|
| 887 |
+ Close() error |
|
| 888 |
+} |
|
| 889 |
+ |
|
| 890 |
+type localGitServer struct {
|
|
| 891 |
+ *httptest.Server |
|
| 892 |
+} |
|
| 893 |
+ |
|
| 894 |
+func (r *localGitServer) Close() error {
|
|
| 895 |
+ r.Server.Close() |
|
| 896 |
+ return nil |
|
| 897 |
+} |
|
| 898 |
+ |
|
| 899 |
+func (r *localGitServer) URL() string {
|
|
| 900 |
+ return r.Server.URL |
|
| 901 |
+} |
|
| 902 |
+ |
|
| 903 |
+type fakeGit struct {
|
|
| 904 |
+ root string |
|
| 905 |
+ server gitServer |
|
| 906 |
+ RepoURL string |
|
| 907 |
+} |
|
| 908 |
+ |
|
| 909 |
+func (g *fakeGit) Close() {
|
|
| 910 |
+ g.server.Close() |
|
| 911 |
+ os.RemoveAll(g.root) |
|
| 912 |
+} |
|
| 913 |
+ |
|
| 914 |
+func newFakeGit(name string, files map[string]string, enforceLocalServer bool) (*fakeGit, error) {
|
|
| 915 |
+ ctx, err := fakeContextWithFiles(files) |
|
| 916 |
+ if err != nil {
|
|
| 917 |
+ return nil, err |
|
| 918 |
+ } |
|
| 919 |
+ defer ctx.Close() |
|
| 920 |
+ curdir, err := os.Getwd() |
|
| 921 |
+ if err != nil {
|
|
| 922 |
+ return nil, err |
|
| 923 |
+ } |
|
| 924 |
+ defer os.Chdir(curdir) |
|
| 925 |
+ |
|
| 926 |
+ if output, err := exec.Command("git", "init", ctx.Dir).CombinedOutput(); err != nil {
|
|
| 927 |
+ return nil, fmt.Errorf("error trying to init repo: %s (%s)", err, output)
|
|
| 928 |
+ } |
|
| 929 |
+ err = os.Chdir(ctx.Dir) |
|
| 930 |
+ if err != nil {
|
|
| 931 |
+ return nil, err |
|
| 932 |
+ } |
|
| 933 |
+ if output, err := exec.Command("git", "config", "user.name", "Fake User").CombinedOutput(); err != nil {
|
|
| 934 |
+ return nil, fmt.Errorf("error trying to set 'user.name': %s (%s)", err, output)
|
|
| 935 |
+ } |
|
| 936 |
+ if output, err := exec.Command("git", "config", "user.email", "fake.user@example.com").CombinedOutput(); err != nil {
|
|
| 937 |
+ return nil, fmt.Errorf("error trying to set 'user.email': %s (%s)", err, output)
|
|
| 938 |
+ } |
|
| 939 |
+ if output, err := exec.Command("git", "add", "*").CombinedOutput(); err != nil {
|
|
| 940 |
+ return nil, fmt.Errorf("error trying to add files to repo: %s (%s)", err, output)
|
|
| 941 |
+ } |
|
| 942 |
+ if output, err := exec.Command("git", "commit", "-a", "-m", "Initial commit").CombinedOutput(); err != nil {
|
|
| 943 |
+ return nil, fmt.Errorf("error trying to commit to repo: %s (%s)", err, output)
|
|
| 944 |
+ } |
|
| 945 |
+ |
|
| 946 |
+ root, err := ioutil.TempDir("", "docker-test-git-repo")
|
|
| 947 |
+ if err != nil {
|
|
| 948 |
+ return nil, err |
|
| 949 |
+ } |
|
| 950 |
+ repoPath := filepath.Join(root, name+".git") |
|
| 951 |
+ if output, err := exec.Command("git", "clone", "--bare", ctx.Dir, repoPath).CombinedOutput(); err != nil {
|
|
| 952 |
+ os.RemoveAll(root) |
|
| 953 |
+ return nil, fmt.Errorf("error trying to clone --bare: %s (%s)", err, output)
|
|
| 954 |
+ } |
|
| 955 |
+ err = os.Chdir(repoPath) |
|
| 956 |
+ if err != nil {
|
|
| 957 |
+ os.RemoveAll(root) |
|
| 958 |
+ return nil, err |
|
| 959 |
+ } |
|
| 960 |
+ if output, err := exec.Command("git", "update-server-info").CombinedOutput(); err != nil {
|
|
| 961 |
+ os.RemoveAll(root) |
|
| 962 |
+ return nil, fmt.Errorf("error trying to git update-server-info: %s (%s)", err, output)
|
|
| 963 |
+ } |
|
| 964 |
+ err = os.Chdir(curdir) |
|
| 965 |
+ if err != nil {
|
|
| 966 |
+ os.RemoveAll(root) |
|
| 967 |
+ return nil, err |
|
| 968 |
+ } |
|
| 969 |
+ |
|
| 970 |
+ var server gitServer |
|
| 971 |
+ if !enforceLocalServer {
|
|
| 972 |
+ // use fakeStorage server, which might be local or remote (at test daemon) |
|
| 973 |
+ server, err = fakeStorageWithContext(fakeContextFromDir(root)) |
|
| 974 |
+ if err != nil {
|
|
| 975 |
+ return nil, fmt.Errorf("cannot start fake storage: %v", err)
|
|
| 976 |
+ } |
|
| 977 |
+ } else {
|
|
| 978 |
+ // always start a local http server on CLI test machine |
|
| 979 |
+ httpServer := httptest.NewServer(http.FileServer(http.Dir(root))) |
|
| 980 |
+ server = &localGitServer{httpServer}
|
|
| 981 |
+ } |
|
| 982 |
+ return &fakeGit{
|
|
| 983 |
+ root: root, |
|
| 984 |
+ server: server, |
|
| 985 |
+ RepoURL: fmt.Sprintf("%s/%s.git", server.URL(), name),
|
|
| 986 |
+ }, nil |
|
| 987 |
+} |
|
| 988 |
+ |
|
| 989 |
+// Write `content` to the file at path `dst`, creating it if necessary, |
|
| 990 |
+// as well as any missing directories. |
|
| 991 |
+// The file is truncated if it already exists. |
|
| 992 |
+// Fail the test when error occurs. |
|
| 993 |
+func writeFile(dst, content string, c *check.C) {
|
|
| 994 |
+ // Create subdirectories if necessary |
|
| 995 |
+ c.Assert(os.MkdirAll(path.Dir(dst), 0700), check.IsNil) |
|
| 996 |
+ f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) |
|
| 997 |
+ c.Assert(err, check.IsNil) |
|
| 998 |
+ defer f.Close() |
|
| 999 |
+ // Write content (truncate if it exists) |
|
| 1000 |
+ _, err = io.Copy(f, strings.NewReader(content)) |
|
| 1001 |
+ c.Assert(err, check.IsNil) |
|
| 1002 |
+} |
|
| 1003 |
+ |
|
| 1004 |
+// Return the contents of file at path `src`. |
|
| 1005 |
+// Fail the test when error occurs. |
|
| 1006 |
+func readFile(src string, c *check.C) (content string) {
|
|
| 1007 |
+ data, err := ioutil.ReadFile(src) |
|
| 1008 |
+ c.Assert(err, check.IsNil) |
|
| 1009 |
+ |
|
| 1010 |
+ return string(data) |
|
| 1011 |
+} |
|
| 1012 |
+ |
|
| 1013 |
+func containerStorageFile(containerID, basename string) string {
|
|
| 1014 |
+ return filepath.Join(containerStoragePath, containerID, basename) |
|
| 1015 |
+} |
|
| 1016 |
+ |
|
| 1017 |
+// docker commands that use this function must be run with the '-d' switch. |
|
| 1018 |
+func runCommandAndReadContainerFile(filename string, cmd *exec.Cmd) ([]byte, error) {
|
|
| 1019 |
+ out, _, err := runCommandWithOutput(cmd) |
|
| 1020 |
+ if err != nil {
|
|
| 1021 |
+ return nil, fmt.Errorf("%v: %q", err, out)
|
|
| 1022 |
+ } |
|
| 1023 |
+ |
|
| 1024 |
+ contID := strings.TrimSpace(out) |
|
| 1025 |
+ |
|
| 1026 |
+ if err := waitRun(contID); err != nil {
|
|
| 1027 |
+ return nil, fmt.Errorf("%v: %q", contID, err)
|
|
| 1028 |
+ } |
|
| 1029 |
+ |
|
| 1030 |
+ return readContainerFile(contID, filename) |
|
| 1031 |
+} |
|
| 1032 |
+ |
|
| 1033 |
+func readContainerFile(containerID, filename string) ([]byte, error) {
|
|
| 1034 |
+ f, err := os.Open(containerStorageFile(containerID, filename)) |
|
| 1035 |
+ if err != nil {
|
|
| 1036 |
+ return nil, err |
|
| 1037 |
+ } |
|
| 1038 |
+ defer f.Close() |
|
| 1039 |
+ |
|
| 1040 |
+ content, err := ioutil.ReadAll(f) |
|
| 1041 |
+ if err != nil {
|
|
| 1042 |
+ return nil, err |
|
| 1043 |
+ } |
|
| 1044 |
+ |
|
| 1045 |
+ return content, nil |
|
| 1046 |
+} |
|
| 1047 |
+ |
|
| 1048 |
+func readContainerFileWithExec(containerID, filename string) ([]byte, error) {
|
|
| 1049 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "exec", containerID, "cat", filename)) |
|
| 1050 |
+ return []byte(out), err |
|
| 1051 |
+} |
|
| 1052 |
+ |
|
| 1053 |
+// daemonTime provides the current time on the daemon host |
|
| 1054 |
+func daemonTime(c *check.C) time.Time {
|
|
| 1055 |
+ if isLocalDaemon {
|
|
| 1056 |
+ return time.Now() |
|
| 1057 |
+ } |
|
| 1058 |
+ |
|
| 1059 |
+ status, body, err := sockRequest("GET", "/info", nil)
|
|
| 1060 |
+ c.Assert(err, check.IsNil) |
|
| 1061 |
+ c.Assert(status, check.Equals, http.StatusOK) |
|
| 1062 |
+ |
|
| 1063 |
+ type infoJSON struct {
|
|
| 1064 |
+ SystemTime string |
|
| 1065 |
+ } |
|
| 1066 |
+ var info infoJSON |
|
| 1067 |
+ err = json.Unmarshal(body, &info) |
|
| 1068 |
+ c.Assert(err, check.IsNil, check.Commentf("unable to unmarshal GET /info response"))
|
|
| 1069 |
+ |
|
| 1070 |
+ dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) |
|
| 1071 |
+ c.Assert(err, check.IsNil, check.Commentf("invalid time format in GET /info response"))
|
|
| 1072 |
+ return dt |
|
| 1073 |
+} |
|
| 1074 |
+ |
|
| 1075 |
+// daemonUnixTime returns the current time on the daemon host with nanoseconds precision. |
|
| 1076 |
+// It return the time formatted how the client sends timestamps to the server. |
|
| 1077 |
+func daemonUnixTime(c *check.C) string {
|
|
| 1078 |
+ return parseEventTime(daemonTime(c)) |
|
| 1079 |
+} |
|
| 1080 |
+ |
|
| 1081 |
+func parseEventTime(t time.Time) string {
|
|
| 1082 |
+ return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond()))
|
|
| 1083 |
+} |
|
| 1084 |
+ |
|
| 1085 |
+func setupRegistry(c *check.C, schema1 bool, auth, tokenURL string) *testRegistryV2 {
|
|
| 1086 |
+ reg, err := newTestRegistryV2(c, schema1, auth, tokenURL) |
|
| 1087 |
+ c.Assert(err, check.IsNil) |
|
| 1088 |
+ |
|
| 1089 |
+ // Wait for registry to be ready to serve requests. |
|
| 1090 |
+ for i := 0; i != 50; i++ {
|
|
| 1091 |
+ if err = reg.Ping(); err == nil {
|
|
| 1092 |
+ break |
|
| 1093 |
+ } |
|
| 1094 |
+ time.Sleep(100 * time.Millisecond) |
|
| 1095 |
+ } |
|
| 1096 |
+ |
|
| 1097 |
+ c.Assert(err, check.IsNil, check.Commentf("Timeout waiting for test registry to become available: %v", err))
|
|
| 1098 |
+ return reg |
|
| 1099 |
+} |
|
| 1100 |
+ |
|
| 1101 |
+func setupNotary(c *check.C) *testNotary {
|
|
| 1102 |
+ ts, err := newTestNotary(c) |
|
| 1103 |
+ c.Assert(err, check.IsNil) |
|
| 1104 |
+ |
|
| 1105 |
+ return ts |
|
| 1106 |
+} |
|
| 1107 |
+ |
|
| 1108 |
+// appendBaseEnv appends the minimum set of environment variables to exec the |
|
| 1109 |
+// docker cli binary for testing with correct configuration to the given env |
|
| 1110 |
+// list. |
|
| 1111 |
+func appendBaseEnv(isTLS bool, env ...string) []string {
|
|
| 1112 |
+ preserveList := []string{
|
|
| 1113 |
+ // preserve remote test host |
|
| 1114 |
+ "DOCKER_HOST", |
|
| 1115 |
+ |
|
| 1116 |
+ // windows: requires preserving SystemRoot, otherwise dial tcp fails |
|
| 1117 |
+ // with "GetAddrInfoW: A non-recoverable error occurred during a database lookup." |
|
| 1118 |
+ "SystemRoot", |
|
| 1119 |
+ |
|
| 1120 |
+ // testing help text requires the $PATH to dockerd is set |
|
| 1121 |
+ "PATH", |
|
| 1122 |
+ } |
|
| 1123 |
+ if isTLS {
|
|
| 1124 |
+ preserveList = append(preserveList, "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH") |
|
| 1125 |
+ } |
|
| 1126 |
+ |
|
| 1127 |
+ for _, key := range preserveList {
|
|
| 1128 |
+ if val := os.Getenv(key); val != "" {
|
|
| 1129 |
+ env = append(env, fmt.Sprintf("%s=%s", key, val))
|
|
| 1130 |
+ } |
|
| 1131 |
+ } |
|
| 1132 |
+ return env |
|
| 1133 |
+} |
|
| 1134 |
+ |
|
| 1135 |
+func createTmpFile(c *check.C, content string) string {
|
|
| 1136 |
+ f, err := ioutil.TempFile("", "testfile")
|
|
| 1137 |
+ c.Assert(err, check.IsNil) |
|
| 1138 |
+ |
|
| 1139 |
+ filename := f.Name() |
|
| 1140 |
+ |
|
| 1141 |
+ err = ioutil.WriteFile(filename, []byte(content), 0644) |
|
| 1142 |
+ c.Assert(err, check.IsNil) |
|
| 1143 |
+ |
|
| 1144 |
+ return filename |
|
| 1145 |
+} |
|
| 1146 |
+ |
|
| 1147 |
+func waitForContainer(contID string, args ...string) error {
|
|
| 1148 |
+ args = append([]string{dockerBinary, "run", "--name", contID}, args...)
|
|
| 1149 |
+ result := icmd.RunCmd(icmd.Cmd{Command: args})
|
|
| 1150 |
+ if result.Error != nil {
|
|
| 1151 |
+ return result.Error |
|
| 1152 |
+ } |
|
| 1153 |
+ return waitRun(contID) |
|
| 1154 |
+} |
|
| 1155 |
+ |
|
| 1156 |
+// waitRestart will wait for the specified container to restart once |
|
| 1157 |
+func waitRestart(contID string, duration time.Duration) error {
|
|
| 1158 |
+ return waitInspect(contID, "{{.RestartCount}}", "1", duration)
|
|
| 1159 |
+} |
|
| 1160 |
+ |
|
| 1161 |
+// waitRun will wait for the specified container to be running, maximum 5 seconds. |
|
| 1162 |
+func waitRun(contID string) error {
|
|
| 1163 |
+ return waitInspect(contID, "{{.State.Running}}", "true", 5*time.Second)
|
|
| 1164 |
+} |
|
| 1165 |
+ |
|
| 1166 |
+// waitExited will wait for the specified container to state exit, subject |
|
| 1167 |
+// to a maximum time limit in seconds supplied by the caller |
|
| 1168 |
+func waitExited(contID string, duration time.Duration) error {
|
|
| 1169 |
+ return waitInspect(contID, "{{.State.Status}}", "exited", duration)
|
|
| 1170 |
+} |
|
| 1171 |
+ |
|
| 1172 |
+// waitInspect will wait for the specified container to have the specified string |
|
| 1173 |
+// in the inspect output. It will wait until the specified timeout (in seconds) |
|
| 1174 |
+// is reached. |
|
| 1175 |
+func waitInspect(name, expr, expected string, timeout time.Duration) error {
|
|
| 1176 |
+ return waitInspectWithArgs(name, expr, expected, timeout) |
|
| 1177 |
+} |
|
| 1178 |
+ |
|
| 1179 |
+func waitInspectWithArgs(name, expr, expected string, timeout time.Duration, arg ...string) error {
|
|
| 1180 |
+ return daemon.WaitInspectWithArgs(dockerBinary, name, expr, expected, timeout, arg...) |
|
| 1181 |
+} |
|
| 1182 |
+ |
|
| 1183 |
+func getInspectBody(c *check.C, version, id string) []byte {
|
|
| 1184 |
+ endpoint := fmt.Sprintf("/%s/containers/%s/json", version, id)
|
|
| 1185 |
+ status, body, err := sockRequest("GET", endpoint, nil)
|
|
| 1186 |
+ c.Assert(err, check.IsNil) |
|
| 1187 |
+ c.Assert(status, check.Equals, http.StatusOK) |
|
| 1188 |
+ return body |
|
| 1189 |
+} |
|
| 1190 |
+ |
|
| 1191 |
+// Run a long running idle task in a background container using the |
|
| 1192 |
+// system-specific default image and command. |
|
| 1193 |
+func runSleepingContainer(c *check.C, extraArgs ...string) (string, int) {
|
|
| 1194 |
+ return runSleepingContainerInImage(c, defaultSleepImage, extraArgs...) |
|
| 1195 |
+} |
|
| 1196 |
+ |
|
| 1197 |
+// Run a long running idle task in a background container using the specified |
|
| 1198 |
+// image and the system-specific command. |
|
| 1199 |
+func runSleepingContainerInImage(c *check.C, image string, extraArgs ...string) (string, int) {
|
|
| 1200 |
+ args := []string{"run", "-d"}
|
|
| 1201 |
+ args = append(args, extraArgs...) |
|
| 1202 |
+ args = append(args, image) |
|
| 1203 |
+ args = append(args, sleepCommandForDaemonPlatform()...) |
|
| 1204 |
+ return dockerCmd(c, args...) |
|
| 1205 |
+} |
|
| 1206 |
+ |
|
| 1207 |
+// minimalBaseImage returns the name of the minimal base image for the current |
|
| 1208 |
+// daemon platform. |
|
| 1209 |
+func minimalBaseImage() string {
|
|
| 1210 |
+ return testEnv.MinimalBaseImage() |
|
| 1211 |
+} |
|
| 1212 |
+ |
|
| 1213 |
+func getGoroutineNumber() (int, error) {
|
|
| 1214 |
+ i := struct {
|
|
| 1215 |
+ NGoroutines int |
|
| 1216 |
+ }{}
|
|
| 1217 |
+ status, b, err := sockRequest("GET", "/info", nil)
|
|
| 1218 |
+ if err != nil {
|
|
| 1219 |
+ return 0, err |
|
| 1220 |
+ } |
|
| 1221 |
+ if status != http.StatusOK {
|
|
| 1222 |
+ return 0, fmt.Errorf("http status code: %d", status)
|
|
| 1223 |
+ } |
|
| 1224 |
+ if err := json.Unmarshal(b, &i); err != nil {
|
|
| 1225 |
+ return 0, err |
|
| 1226 |
+ } |
|
| 1227 |
+ return i.NGoroutines, nil |
|
| 1228 |
+} |
|
| 1229 |
+ |
|
| 1230 |
+func waitForGoroutines(expected int) error {
|
|
| 1231 |
+ t := time.After(30 * time.Second) |
|
| 1232 |
+ for {
|
|
| 1233 |
+ select {
|
|
| 1234 |
+ case <-t: |
|
| 1235 |
+ n, err := getGoroutineNumber() |
|
| 1236 |
+ if err != nil {
|
|
| 1237 |
+ return err |
|
| 1238 |
+ } |
|
| 1239 |
+ if n > expected {
|
|
| 1240 |
+ return fmt.Errorf("leaked goroutines: expected less than or equal to %d, got: %d", expected, n)
|
|
| 1241 |
+ } |
|
| 1242 |
+ default: |
|
| 1243 |
+ n, err := getGoroutineNumber() |
|
| 1244 |
+ if err != nil {
|
|
| 1245 |
+ return err |
|
| 1246 |
+ } |
|
| 1247 |
+ if n <= expected {
|
|
| 1248 |
+ return nil |
|
| 1249 |
+ } |
|
| 1250 |
+ time.Sleep(200 * time.Millisecond) |
|
| 1251 |
+ } |
|
| 1252 |
+ } |
|
| 1253 |
+} |
|
| 1254 |
+ |
|
| 1255 |
+// getErrorMessage returns the error message from an error API response |
|
| 1256 |
+func getErrorMessage(c *check.C, body []byte) string {
|
|
| 1257 |
+ var resp types.ErrorResponse |
|
| 1258 |
+ c.Assert(json.Unmarshal(body, &resp), check.IsNil) |
|
| 1259 |
+ return strings.TrimSpace(resp.Message) |
|
| 1260 |
+} |
|
| 1261 |
+ |
|
| 1262 |
+func waitAndAssert(c *check.C, timeout time.Duration, f checkF, checker check.Checker, args ...interface{}) {
|
|
| 1263 |
+ after := time.After(timeout) |
|
| 1264 |
+ for {
|
|
| 1265 |
+ v, comment := f(c) |
|
| 1266 |
+ assert, _ := checker.Check(append([]interface{}{v}, args...), checker.Info().Params)
|
|
| 1267 |
+ select {
|
|
| 1268 |
+ case <-after: |
|
| 1269 |
+ assert = true |
|
| 1270 |
+ default: |
|
| 1271 |
+ } |
|
| 1272 |
+ if assert {
|
|
| 1273 |
+ if comment != nil {
|
|
| 1274 |
+ args = append(args, comment) |
|
| 1275 |
+ } |
|
| 1276 |
+ c.Assert(v, checker, args...) |
|
| 1277 |
+ return |
|
| 1278 |
+ } |
|
| 1279 |
+ time.Sleep(100 * time.Millisecond) |
|
| 1280 |
+ } |
|
| 1281 |
+} |
|
| 1282 |
+ |
|
| 1283 |
+type checkF func(*check.C) (interface{}, check.CommentInterface)
|
|
| 1284 |
+type reducer func(...interface{}) interface{}
|
|
| 1285 |
+ |
|
| 1286 |
+func reducedCheck(r reducer, funcs ...checkF) checkF {
|
|
| 1287 |
+ return func(c *check.C) (interface{}, check.CommentInterface) {
|
|
| 1288 |
+ var values []interface{}
|
|
| 1289 |
+ var comments []string |
|
| 1290 |
+ for _, f := range funcs {
|
|
| 1291 |
+ v, comment := f(c) |
|
| 1292 |
+ values = append(values, v) |
|
| 1293 |
+ if comment != nil {
|
|
| 1294 |
+ comments = append(comments, comment.CheckCommentString()) |
|
| 1295 |
+ } |
|
| 1296 |
+ } |
|
| 1297 |
+ return r(values...), check.Commentf("%v", strings.Join(comments, ", "))
|
|
| 1298 |
+ } |
|
| 1299 |
+} |
|
| 1300 |
+ |
|
| 1301 |
+func sumAsIntegers(vals ...interface{}) interface{} {
|
|
| 1302 |
+ var s int |
|
| 1303 |
+ for _, v := range vals {
|
|
| 1304 |
+ s += v.(int) |
|
| 1305 |
+ } |
|
| 1306 |
+ return s |
|
| 1307 |
+} |
| 0 | 1308 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,184 @@ |
| 0 |
+package environment |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "strconv" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 10 |
+ "golang.org/x/net/context" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/api/types" |
|
| 13 |
+ "github.com/docker/docker/api/types/container" |
|
| 14 |
+ "github.com/docker/docker/client" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+// Execution holds informations about the test execution environment. |
|
| 18 |
+type Execution struct {
|
|
| 19 |
+ daemonPlatform string |
|
| 20 |
+ localDaemon bool |
|
| 21 |
+ experimentalDaemon bool |
|
| 22 |
+ daemonStorageDriver string |
|
| 23 |
+ isolation container.Isolation |
|
| 24 |
+ daemonPid int |
|
| 25 |
+ daemonKernelVersion string |
|
| 26 |
+ // For a local daemon on Linux, these values will be used for testing |
|
| 27 |
+ // user namespace support as the standard graph path(s) will be |
|
| 28 |
+ // appended with the root remapped uid.gid prefix |
|
| 29 |
+ dockerBasePath string |
|
| 30 |
+ volumesConfigPath string |
|
| 31 |
+ containerStoragePath string |
|
| 32 |
+ // baseImage is the name of the base image for testing |
|
| 33 |
+ // Environment variable WINDOWS_BASE_IMAGE can override this |
|
| 34 |
+ baseImage string |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+// New creates a new Execution struct |
|
| 38 |
+func New() (*Execution, error) {
|
|
| 39 |
+ localDaemon := true |
|
| 40 |
+ // Deterministically working out the environment in which CI is running |
|
| 41 |
+ // to evaluate whether the daemon is local or remote is not possible through |
|
| 42 |
+ // a build tag. |
|
| 43 |
+ // |
|
| 44 |
+ // For example Windows to Linux CI under Jenkins tests the 64-bit |
|
| 45 |
+ // Windows binary build with the daemon build tag, but calls a remote |
|
| 46 |
+ // Linux daemon. |
|
| 47 |
+ // |
|
| 48 |
+ // We can't just say if Windows then assume the daemon is local as at |
|
| 49 |
+ // some point, we will be testing the Windows CLI against a Windows daemon. |
|
| 50 |
+ // |
|
| 51 |
+ // Similarly, it will be perfectly valid to also run CLI tests from |
|
| 52 |
+ // a Linux CLI (built with the daemon tag) against a Windows daemon. |
|
| 53 |
+ if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
|
|
| 54 |
+ localDaemon = false |
|
| 55 |
+ } |
|
| 56 |
+ info, err := getDaemonDockerInfo() |
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ return nil, err |
|
| 59 |
+ } |
|
| 60 |
+ daemonPlatform := info.OSType |
|
| 61 |
+ if daemonPlatform != "linux" && daemonPlatform != "windows" {
|
|
| 62 |
+ return nil, fmt.Errorf("Cannot run tests against platform: %s", daemonPlatform)
|
|
| 63 |
+ } |
|
| 64 |
+ baseImage := "scratch" |
|
| 65 |
+ volumesConfigPath := filepath.Join(info.DockerRootDir, "volumes") |
|
| 66 |
+ containerStoragePath := filepath.Join(info.DockerRootDir, "containers") |
|
| 67 |
+ // Make sure in context of daemon, not the local platform. Note we can't |
|
| 68 |
+ // use filepath.FromSlash or ToSlash here as they are a no-op on Unix. |
|
| 69 |
+ if daemonPlatform == "windows" {
|
|
| 70 |
+ volumesConfigPath = strings.Replace(volumesConfigPath, `/`, `\`, -1) |
|
| 71 |
+ containerStoragePath = strings.Replace(containerStoragePath, `/`, `\`, -1) |
|
| 72 |
+ |
|
| 73 |
+ baseImage = "microsoft/windowsservercore" |
|
| 74 |
+ if len(os.Getenv("WINDOWS_BASE_IMAGE")) > 0 {
|
|
| 75 |
+ baseImage = os.Getenv("WINDOWS_BASE_IMAGE")
|
|
| 76 |
+ fmt.Println("INFO: Windows Base image is ", baseImage)
|
|
| 77 |
+ } |
|
| 78 |
+ } else {
|
|
| 79 |
+ volumesConfigPath = strings.Replace(volumesConfigPath, `\`, `/`, -1) |
|
| 80 |
+ containerStoragePath = strings.Replace(containerStoragePath, `\`, `/`, -1) |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ var daemonPid int |
|
| 84 |
+ dest := os.Getenv("DEST")
|
|
| 85 |
+ b, err := ioutil.ReadFile(filepath.Join(dest, "docker.pid")) |
|
| 86 |
+ if err == nil {
|
|
| 87 |
+ if p, err := strconv.ParseInt(string(b), 10, 32); err == nil {
|
|
| 88 |
+ daemonPid = int(p) |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+ return &Execution{
|
|
| 92 |
+ localDaemon: localDaemon, |
|
| 93 |
+ daemonPlatform: daemonPlatform, |
|
| 94 |
+ daemonStorageDriver: info.Driver, |
|
| 95 |
+ daemonKernelVersion: info.KernelVersion, |
|
| 96 |
+ dockerBasePath: info.DockerRootDir, |
|
| 97 |
+ volumesConfigPath: volumesConfigPath, |
|
| 98 |
+ containerStoragePath: containerStoragePath, |
|
| 99 |
+ isolation: info.Isolation, |
|
| 100 |
+ daemonPid: daemonPid, |
|
| 101 |
+ experimentalDaemon: info.ExperimentalBuild, |
|
| 102 |
+ baseImage: baseImage, |
|
| 103 |
+ }, nil |
|
| 104 |
+} |
|
| 105 |
+func getDaemonDockerInfo() (types.Info, error) {
|
|
| 106 |
+ // FIXME(vdemeester) should be safe to use as is |
|
| 107 |
+ client, err := client.NewEnvClient() |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ return types.Info{}, err
|
|
| 110 |
+ } |
|
| 111 |
+ return client.Info(context.Background()) |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// LocalDaemon is true if the daemon under test is on the same |
|
| 115 |
+// host as the CLI. |
|
| 116 |
+func (e *Execution) LocalDaemon() bool {
|
|
| 117 |
+ return e.localDaemon |
|
| 118 |
+} |
|
| 119 |
+ |
|
| 120 |
+// DaemonPlatform is held globally so that tests can make intelligent |
|
| 121 |
+// decisions on how to configure themselves according to the platform |
|
| 122 |
+// of the daemon. This is initialized in docker_utils by sending |
|
| 123 |
+// a version call to the daemon and examining the response header. |
|
| 124 |
+func (e *Execution) DaemonPlatform() string {
|
|
| 125 |
+ return e.daemonPlatform |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+// DockerBasePath is the base path of the docker folder (by default it is -/var/run/docker) |
|
| 129 |
+func (e *Execution) DockerBasePath() string {
|
|
| 130 |
+ return e.dockerBasePath |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+// VolumesConfigPath is the path of the volume configuration for the testing daemon |
|
| 134 |
+func (e *Execution) VolumesConfigPath() string {
|
|
| 135 |
+ return e.volumesConfigPath |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+// ContainerStoragePath is the path where the container are stored for the testing daemon |
|
| 139 |
+func (e *Execution) ContainerStoragePath() string {
|
|
| 140 |
+ return e.containerStoragePath |
|
| 141 |
+} |
|
| 142 |
+ |
|
| 143 |
+// DaemonStorageDriver is held globally so that tests can know the storage |
|
| 144 |
+// driver of the daemon. This is initialized in docker_utils by sending |
|
| 145 |
+// a version call to the daemon and examining the response header. |
|
| 146 |
+func (e *Execution) DaemonStorageDriver() string {
|
|
| 147 |
+ return e.daemonStorageDriver |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+// Isolation is the isolation mode of the daemon under test |
|
| 151 |
+func (e *Execution) Isolation() container.Isolation {
|
|
| 152 |
+ return e.isolation |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+// DaemonPID is the pid of the main test daemon |
|
| 156 |
+func (e *Execution) DaemonPID() int {
|
|
| 157 |
+ return e.daemonPid |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+// ExperimentalDaemon tell whether the main daemon has |
|
| 161 |
+// experimental features enabled or not |
|
| 162 |
+func (e *Execution) ExperimentalDaemon() bool {
|
|
| 163 |
+ return e.experimentalDaemon |
|
| 164 |
+} |
|
| 165 |
+ |
|
| 166 |
+// MinimalBaseImage is the image used for minimal builds (it depends on the platform) |
|
| 167 |
+func (e *Execution) MinimalBaseImage() string {
|
|
| 168 |
+ return e.baseImage |
|
| 169 |
+} |
|
| 170 |
+ |
|
| 171 |
+// DaemonKernelVersion is the kernel version of the daemon |
|
| 172 |
+func (e *Execution) DaemonKernelVersion() string {
|
|
| 173 |
+ return e.daemonKernelVersion |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+// WindowsKernelVersion is used on Windows to distinguish between different |
|
| 177 |
+// versions. This is necessary to enable certain tests based on whether |
|
| 178 |
+// the platform supports it. For example, Windows Server 2016 TP3 did |
|
| 179 |
+// not support volumes, but TP4 did. |
|
| 180 |
+func WindowsKernelVersion(kernelVersion string) int {
|
|
| 181 |
+ winKV, _ := strconv.Atoi(strings.Split(kernelVersion, " ")[1]) |
|
| 182 |
+ return winKV |
|
| 183 |
+} |
| 0 | 184 |
deleted file mode 100644 |
| ... | ... |
@@ -1,206 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bufio" |
|
| 5 |
- "bytes" |
|
| 6 |
- "io" |
|
| 7 |
- "os/exec" |
|
| 8 |
- "regexp" |
|
| 9 |
- "strconv" |
|
| 10 |
- "strings" |
|
| 11 |
- |
|
| 12 |
- "github.com/Sirupsen/logrus" |
|
| 13 |
- eventstestutils "github.com/docker/docker/daemon/events/testutils" |
|
| 14 |
- "github.com/docker/docker/pkg/integration/checker" |
|
| 15 |
- "github.com/go-check/check" |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-// eventMatcher is a function that tries to match an event input. |
|
| 19 |
-// It returns true if the event matches and a map with |
|
| 20 |
-// a set of key/value to identify the match. |
|
| 21 |
-type eventMatcher func(text string) (map[string]string, bool) |
|
| 22 |
- |
|
| 23 |
-// eventMatchProcessor is a function to handle an event match. |
|
| 24 |
-// It receives a map of key/value with the information extracted in a match. |
|
| 25 |
-type eventMatchProcessor func(matches map[string]string) |
|
| 26 |
- |
|
| 27 |
-// eventObserver runs an events commands and observes its output. |
|
| 28 |
-type eventObserver struct {
|
|
| 29 |
- buffer *bytes.Buffer |
|
| 30 |
- command *exec.Cmd |
|
| 31 |
- scanner *bufio.Scanner |
|
| 32 |
- startTime string |
|
| 33 |
- disconnectionError error |
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-// newEventObserver creates the observer and initializes the command |
|
| 37 |
-// without running it. Users must call `eventObserver.Start` to start the command. |
|
| 38 |
-func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
|
|
| 39 |
- since := daemonTime(c).Unix() |
|
| 40 |
- return newEventObserverWithBacklog(c, since, args...) |
|
| 41 |
-} |
|
| 42 |
- |
|
| 43 |
-// newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return. |
|
| 44 |
-func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
|
|
| 45 |
- startTime := strconv.FormatInt(since, 10) |
|
| 46 |
- cmdArgs := []string{"events", "--since", startTime}
|
|
| 47 |
- if len(args) > 0 {
|
|
| 48 |
- cmdArgs = append(cmdArgs, args...) |
|
| 49 |
- } |
|
| 50 |
- eventsCmd := exec.Command(dockerBinary, cmdArgs...) |
|
| 51 |
- stdout, err := eventsCmd.StdoutPipe() |
|
| 52 |
- if err != nil {
|
|
| 53 |
- return nil, err |
|
| 54 |
- } |
|
| 55 |
- |
|
| 56 |
- return &eventObserver{
|
|
| 57 |
- buffer: new(bytes.Buffer), |
|
| 58 |
- command: eventsCmd, |
|
| 59 |
- scanner: bufio.NewScanner(stdout), |
|
| 60 |
- startTime: startTime, |
|
| 61 |
- }, nil |
|
| 62 |
-} |
|
| 63 |
- |
|
| 64 |
-// Start starts the events command. |
|
| 65 |
-func (e *eventObserver) Start() error {
|
|
| 66 |
- return e.command.Start() |
|
| 67 |
-} |
|
| 68 |
- |
|
| 69 |
-// Stop stops the events command. |
|
| 70 |
-func (e *eventObserver) Stop() {
|
|
| 71 |
- e.command.Process.Kill() |
|
| 72 |
- e.command.Process.Release() |
|
| 73 |
-} |
|
| 74 |
- |
|
| 75 |
-// Match tries to match the events output with a given matcher. |
|
| 76 |
-func (e *eventObserver) Match(match eventMatcher, process eventMatchProcessor) {
|
|
| 77 |
- for e.scanner.Scan() {
|
|
| 78 |
- text := e.scanner.Text() |
|
| 79 |
- e.buffer.WriteString(text) |
|
| 80 |
- e.buffer.WriteString("\n")
|
|
| 81 |
- |
|
| 82 |
- if matches, ok := match(text); ok {
|
|
| 83 |
- process(matches) |
|
| 84 |
- } |
|
| 85 |
- } |
|
| 86 |
- |
|
| 87 |
- err := e.scanner.Err() |
|
| 88 |
- if err == nil {
|
|
| 89 |
- err = io.EOF |
|
| 90 |
- } |
|
| 91 |
- |
|
| 92 |
- logrus.Debugf("EventObserver scanner loop finished: %v", err)
|
|
| 93 |
- e.disconnectionError = err |
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-func (e *eventObserver) CheckEventError(c *check.C, id, event string, match eventMatcher) {
|
|
| 97 |
- var foundEvent bool |
|
| 98 |
- scannerOut := e.buffer.String() |
|
| 99 |
- |
|
| 100 |
- if e.disconnectionError != nil {
|
|
| 101 |
- until := daemonUnixTime(c) |
|
| 102 |
- out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until) |
|
| 103 |
- events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 104 |
- for _, e := range events {
|
|
| 105 |
- if _, ok := match(e); ok {
|
|
| 106 |
- foundEvent = true |
|
| 107 |
- break |
|
| 108 |
- } |
|
| 109 |
- } |
|
| 110 |
- scannerOut = out |
|
| 111 |
- } |
|
| 112 |
- if !foundEvent {
|
|
| 113 |
- c.Fatalf("failed to observe event `%s` for %s. Disconnection error: %v\nout:\n%v", event, id, e.disconnectionError, scannerOut)
|
|
| 114 |
- } |
|
| 115 |
-} |
|
| 116 |
- |
|
| 117 |
-// matchEventLine matches a text with the event regular expression. |
|
| 118 |
-// It returns the matches and true if the regular expression matches with the given id and event type. |
|
| 119 |
-// It returns an empty map and false if there is no match. |
|
| 120 |
-func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
|
|
| 121 |
- return func(text string) (map[string]string, bool) {
|
|
| 122 |
- matches := eventstestutils.ScanMap(text) |
|
| 123 |
- if len(matches) == 0 {
|
|
| 124 |
- return matches, false |
|
| 125 |
- } |
|
| 126 |
- |
|
| 127 |
- if matchIDAndEventType(matches, id, eventType) {
|
|
| 128 |
- if _, ok := actions[matches["action"]]; ok {
|
|
| 129 |
- return matches, true |
|
| 130 |
- } |
|
| 131 |
- } |
|
| 132 |
- return matches, false |
|
| 133 |
- } |
|
| 134 |
-} |
|
| 135 |
- |
|
| 136 |
-// processEventMatch closes an action channel when an event line matches the expected action. |
|
| 137 |
-func processEventMatch(actions map[string]chan bool) eventMatchProcessor {
|
|
| 138 |
- return func(matches map[string]string) {
|
|
| 139 |
- if ch, ok := actions[matches["action"]]; ok {
|
|
| 140 |
- ch <- true |
|
| 141 |
- } |
|
| 142 |
- } |
|
| 143 |
-} |
|
| 144 |
- |
|
| 145 |
-// parseEventAction parses an event text and returns the action. |
|
| 146 |
-// It fails if the text is not in the event format. |
|
| 147 |
-func parseEventAction(c *check.C, text string) string {
|
|
| 148 |
- matches := eventstestutils.ScanMap(text) |
|
| 149 |
- return matches["action"] |
|
| 150 |
-} |
|
| 151 |
- |
|
| 152 |
-// eventActionsByIDAndType returns the actions for a given id and type. |
|
| 153 |
-// It fails if the text is not in the event format. |
|
| 154 |
-func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
|
|
| 155 |
- var filtered []string |
|
| 156 |
- for _, event := range events {
|
|
| 157 |
- matches := eventstestutils.ScanMap(event) |
|
| 158 |
- c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 159 |
- if matchIDAndEventType(matches, id, eventType) {
|
|
| 160 |
- filtered = append(filtered, matches["action"]) |
|
| 161 |
- } |
|
| 162 |
- } |
|
| 163 |
- return filtered |
|
| 164 |
-} |
|
| 165 |
- |
|
| 166 |
-// matchIDAndEventType returns true if an event matches a given id and type. |
|
| 167 |
-// It also resolves names in the event attributes if the id doesn't match. |
|
| 168 |
-func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
|
|
| 169 |
- return matchEventID(matches, id) && matches["eventType"] == eventType |
|
| 170 |
-} |
|
| 171 |
- |
|
| 172 |
-func matchEventID(matches map[string]string, id string) bool {
|
|
| 173 |
- matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id) |
|
| 174 |
- if !matchID && matches["attributes"] != "" {
|
|
| 175 |
- // try matching a name in the attributes |
|
| 176 |
- attributes := map[string]string{}
|
|
| 177 |
- for _, a := range strings.Split(matches["attributes"], ", ") {
|
|
| 178 |
- kv := strings.Split(a, "=") |
|
| 179 |
- attributes[kv[0]] = kv[1] |
|
| 180 |
- } |
|
| 181 |
- matchID = attributes["name"] == id |
|
| 182 |
- } |
|
| 183 |
- return matchID |
|
| 184 |
-} |
|
| 185 |
- |
|
| 186 |
-func parseEvents(c *check.C, out, match string) {
|
|
| 187 |
- events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 188 |
- for _, event := range events {
|
|
| 189 |
- matches := eventstestutils.ScanMap(event) |
|
| 190 |
- matched, err := regexp.MatchString(match, matches["action"]) |
|
| 191 |
- c.Assert(err, checker.IsNil) |
|
| 192 |
- c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
|
|
| 193 |
- } |
|
| 194 |
-} |
|
| 195 |
- |
|
| 196 |
-func parseEventsWithID(c *check.C, out, match, id string) {
|
|
| 197 |
- events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 198 |
- for _, event := range events {
|
|
| 199 |
- matches := eventstestutils.ScanMap(event) |
|
| 200 |
- c.Assert(matchEventID(matches, id), checker.True) |
|
| 201 |
- |
|
| 202 |
- matched, err := regexp.MatchString(match, matches["action"]) |
|
| 203 |
- c.Assert(err, checker.IsNil) |
|
| 204 |
- c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
|
|
| 205 |
- } |
|
| 206 |
-} |
| 207 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,206 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "bytes" |
|
| 5 |
+ "io" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+ "regexp" |
|
| 8 |
+ "strconv" |
|
| 9 |
+ "strings" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/Sirupsen/logrus" |
|
| 12 |
+ eventstestutils "github.com/docker/docker/daemon/events/testutils" |
|
| 13 |
+ "github.com/docker/docker/pkg/integration/checker" |
|
| 14 |
+ "github.com/go-check/check" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+// eventMatcher is a function that tries to match an event input. |
|
| 18 |
+// It returns true if the event matches and a map with |
|
| 19 |
+// a set of key/value to identify the match. |
|
| 20 |
+type eventMatcher func(text string) (map[string]string, bool) |
|
| 21 |
+ |
|
| 22 |
+// eventMatchProcessor is a function to handle an event match. |
|
| 23 |
+// It receives a map of key/value with the information extracted in a match. |
|
| 24 |
+type eventMatchProcessor func(matches map[string]string) |
|
| 25 |
+ |
|
| 26 |
+// eventObserver runs an events commands and observes its output. |
|
| 27 |
+type eventObserver struct {
|
|
| 28 |
+ buffer *bytes.Buffer |
|
| 29 |
+ command *exec.Cmd |
|
| 30 |
+ scanner *bufio.Scanner |
|
| 31 |
+ startTime string |
|
| 32 |
+ disconnectionError error |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// newEventObserver creates the observer and initializes the command |
|
| 36 |
+// without running it. Users must call `eventObserver.Start` to start the command. |
|
| 37 |
+func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
|
|
| 38 |
+ since := daemonTime(c).Unix() |
|
| 39 |
+ return newEventObserverWithBacklog(c, since, args...) |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return. |
|
| 43 |
+func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
|
|
| 44 |
+ startTime := strconv.FormatInt(since, 10) |
|
| 45 |
+ cmdArgs := []string{"events", "--since", startTime}
|
|
| 46 |
+ if len(args) > 0 {
|
|
| 47 |
+ cmdArgs = append(cmdArgs, args...) |
|
| 48 |
+ } |
|
| 49 |
+ eventsCmd := exec.Command(dockerBinary, cmdArgs...) |
|
| 50 |
+ stdout, err := eventsCmd.StdoutPipe() |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return nil, err |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ return &eventObserver{
|
|
| 56 |
+ buffer: new(bytes.Buffer), |
|
| 57 |
+ command: eventsCmd, |
|
| 58 |
+ scanner: bufio.NewScanner(stdout), |
|
| 59 |
+ startTime: startTime, |
|
| 60 |
+ }, nil |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+// Start starts the events command. |
|
| 64 |
+func (e *eventObserver) Start() error {
|
|
| 65 |
+ return e.command.Start() |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+// Stop stops the events command. |
|
| 69 |
+func (e *eventObserver) Stop() {
|
|
| 70 |
+ e.command.Process.Kill() |
|
| 71 |
+ e.command.Process.Release() |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+// Match tries to match the events output with a given matcher. |
|
| 75 |
+func (e *eventObserver) Match(match eventMatcher, process eventMatchProcessor) {
|
|
| 76 |
+ for e.scanner.Scan() {
|
|
| 77 |
+ text := e.scanner.Text() |
|
| 78 |
+ e.buffer.WriteString(text) |
|
| 79 |
+ e.buffer.WriteString("\n")
|
|
| 80 |
+ |
|
| 81 |
+ if matches, ok := match(text); ok {
|
|
| 82 |
+ process(matches) |
|
| 83 |
+ } |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 86 |
+ err := e.scanner.Err() |
|
| 87 |
+ if err == nil {
|
|
| 88 |
+ err = io.EOF |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ logrus.Debugf("EventObserver scanner loop finished: %v", err)
|
|
| 92 |
+ e.disconnectionError = err |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func (e *eventObserver) CheckEventError(c *check.C, id, event string, match eventMatcher) {
|
|
| 96 |
+ var foundEvent bool |
|
| 97 |
+ scannerOut := e.buffer.String() |
|
| 98 |
+ |
|
| 99 |
+ if e.disconnectionError != nil {
|
|
| 100 |
+ until := daemonUnixTime(c) |
|
| 101 |
+ out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until) |
|
| 102 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 103 |
+ for _, e := range events {
|
|
| 104 |
+ if _, ok := match(e); ok {
|
|
| 105 |
+ foundEvent = true |
|
| 106 |
+ break |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ scannerOut = out |
|
| 110 |
+ } |
|
| 111 |
+ if !foundEvent {
|
|
| 112 |
+ c.Fatalf("failed to observe event `%s` for %s. Disconnection error: %v\nout:\n%v", event, id, e.disconnectionError, scannerOut)
|
|
| 113 |
+ } |
|
| 114 |
+} |
|
| 115 |
+ |
|
| 116 |
+// matchEventLine matches a text with the event regular expression. |
|
| 117 |
+// It returns the matches and true if the regular expression matches with the given id and event type. |
|
| 118 |
+// It returns an empty map and false if there is no match. |
|
| 119 |
+func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
|
|
| 120 |
+ return func(text string) (map[string]string, bool) {
|
|
| 121 |
+ matches := eventstestutils.ScanMap(text) |
|
| 122 |
+ if len(matches) == 0 {
|
|
| 123 |
+ return matches, false |
|
| 124 |
+ } |
|
| 125 |
+ |
|
| 126 |
+ if matchIDAndEventType(matches, id, eventType) {
|
|
| 127 |
+ if _, ok := actions[matches["action"]]; ok {
|
|
| 128 |
+ return matches, true |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+ return matches, false |
|
| 132 |
+ } |
|
| 133 |
+} |
|
| 134 |
+ |
|
| 135 |
+// processEventMatch closes an action channel when an event line matches the expected action. |
|
| 136 |
+func processEventMatch(actions map[string]chan bool) eventMatchProcessor {
|
|
| 137 |
+ return func(matches map[string]string) {
|
|
| 138 |
+ if ch, ok := actions[matches["action"]]; ok {
|
|
| 139 |
+ ch <- true |
|
| 140 |
+ } |
|
| 141 |
+ } |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+// parseEventAction parses an event text and returns the action. |
|
| 145 |
+// It fails if the text is not in the event format. |
|
| 146 |
+func parseEventAction(c *check.C, text string) string {
|
|
| 147 |
+ matches := eventstestutils.ScanMap(text) |
|
| 148 |
+ return matches["action"] |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+// eventActionsByIDAndType returns the actions for a given id and type. |
|
| 152 |
+// It fails if the text is not in the event format. |
|
| 153 |
+func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
|
|
| 154 |
+ var filtered []string |
|
| 155 |
+ for _, event := range events {
|
|
| 156 |
+ matches := eventstestutils.ScanMap(event) |
|
| 157 |
+ c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 158 |
+ if matchIDAndEventType(matches, id, eventType) {
|
|
| 159 |
+ filtered = append(filtered, matches["action"]) |
|
| 160 |
+ } |
|
| 161 |
+ } |
|
| 162 |
+ return filtered |
|
| 163 |
+} |
|
| 164 |
+ |
|
| 165 |
+// matchIDAndEventType returns true if an event matches a given id and type. |
|
| 166 |
+// It also resolves names in the event attributes if the id doesn't match. |
|
| 167 |
+func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
|
|
| 168 |
+ return matchEventID(matches, id) && matches["eventType"] == eventType |
|
| 169 |
+} |
|
| 170 |
+ |
|
| 171 |
+func matchEventID(matches map[string]string, id string) bool {
|
|
| 172 |
+ matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id) |
|
| 173 |
+ if !matchID && matches["attributes"] != "" {
|
|
| 174 |
+ // try matching a name in the attributes |
|
| 175 |
+ attributes := map[string]string{}
|
|
| 176 |
+ for _, a := range strings.Split(matches["attributes"], ", ") {
|
|
| 177 |
+ kv := strings.Split(a, "=") |
|
| 178 |
+ attributes[kv[0]] = kv[1] |
|
| 179 |
+ } |
|
| 180 |
+ matchID = attributes["name"] == id |
|
| 181 |
+ } |
|
| 182 |
+ return matchID |
|
| 183 |
+} |
|
| 184 |
+ |
|
| 185 |
+func parseEvents(c *check.C, out, match string) {
|
|
| 186 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 187 |
+ for _, event := range events {
|
|
| 188 |
+ matches := eventstestutils.ScanMap(event) |
|
| 189 |
+ matched, err := regexp.MatchString(match, matches["action"]) |
|
| 190 |
+ c.Assert(err, checker.IsNil) |
|
| 191 |
+ c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
|
|
| 192 |
+ } |
|
| 193 |
+} |
|
| 194 |
+ |
|
| 195 |
+func parseEventsWithID(c *check.C, out, match, id string) {
|
|
| 196 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 197 |
+ for _, event := range events {
|
|
| 198 |
+ matches := eventstestutils.ScanMap(event) |
|
| 199 |
+ c.Assert(matchEventID(matches, id), checker.True) |
|
| 200 |
+ |
|
| 201 |
+ matched, err := regexp.MatchString(match, matches["action"]) |
|
| 202 |
+ c.Assert(err, checker.IsNil) |
|
| 203 |
+ c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
|
|
| 204 |
+ } |
|
| 205 |
+} |
| 0 | 206 |
deleted file mode 100644 |
| ... | ... |
@@ -1,69 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "os" |
|
| 7 |
- "os/exec" |
|
| 8 |
- "path/filepath" |
|
| 9 |
- "sync" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-var ensureHTTPServerOnce sync.Once |
|
| 13 |
- |
|
| 14 |
-func ensureHTTPServerImage() error {
|
|
| 15 |
- var doIt bool |
|
| 16 |
- ensureHTTPServerOnce.Do(func() {
|
|
| 17 |
- doIt = true |
|
| 18 |
- }) |
|
| 19 |
- |
|
| 20 |
- if !doIt {
|
|
| 21 |
- return nil |
|
| 22 |
- } |
|
| 23 |
- |
|
| 24 |
- protectedImages["httpserver:latest"] = struct{}{}
|
|
| 25 |
- |
|
| 26 |
- tmp, err := ioutil.TempDir("", "docker-http-server-test")
|
|
| 27 |
- if err != nil {
|
|
| 28 |
- return fmt.Errorf("could not build http server: %v", err)
|
|
| 29 |
- } |
|
| 30 |
- defer os.RemoveAll(tmp) |
|
| 31 |
- |
|
| 32 |
- goos := daemonPlatform |
|
| 33 |
- if goos == "" {
|
|
| 34 |
- goos = "linux" |
|
| 35 |
- } |
|
| 36 |
- goarch := os.Getenv("DOCKER_ENGINE_GOARCH")
|
|
| 37 |
- if goarch == "" {
|
|
| 38 |
- goarch = "amd64" |
|
| 39 |
- } |
|
| 40 |
- |
|
| 41 |
- goCmd, lookErr := exec.LookPath("go")
|
|
| 42 |
- if lookErr != nil {
|
|
| 43 |
- return fmt.Errorf("could not build http server: %v", lookErr)
|
|
| 44 |
- } |
|
| 45 |
- |
|
| 46 |
- cmd := exec.Command(goCmd, "build", "-o", filepath.Join(tmp, "httpserver"), "github.com/docker/docker/contrib/httpserver") |
|
| 47 |
- cmd.Env = append(os.Environ(), []string{
|
|
| 48 |
- "CGO_ENABLED=0", |
|
| 49 |
- "GOOS=" + goos, |
|
| 50 |
- "GOARCH=" + goarch, |
|
| 51 |
- }...) |
|
| 52 |
- var out []byte |
|
| 53 |
- if out, err = cmd.CombinedOutput(); err != nil {
|
|
| 54 |
- return fmt.Errorf("could not build http server: %s", string(out))
|
|
| 55 |
- } |
|
| 56 |
- |
|
| 57 |
- cpCmd, lookErr := exec.LookPath("cp")
|
|
| 58 |
- if lookErr != nil {
|
|
| 59 |
- return fmt.Errorf("could not build http server: %v", lookErr)
|
|
| 60 |
- } |
|
| 61 |
- if out, err = exec.Command(cpCmd, "../contrib/httpserver/Dockerfile", filepath.Join(tmp, "Dockerfile")).CombinedOutput(); err != nil {
|
|
| 62 |
- return fmt.Errorf("could not build http server: %v", string(out))
|
|
| 63 |
- } |
|
| 64 |
- |
|
| 65 |
- if out, err = exec.Command(dockerBinary, "build", "-q", "-t", "httpserver", tmp).CombinedOutput(); err != nil {
|
|
| 66 |
- return fmt.Errorf("could not build http server: %v", string(out))
|
|
| 67 |
- } |
|
| 68 |
- return nil |
|
| 69 |
-} |
| 70 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,143 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "os" |
|
| 7 |
- "os/exec" |
|
| 8 |
- "path/filepath" |
|
| 9 |
- "runtime" |
|
| 10 |
- "strings" |
|
| 11 |
- "sync" |
|
| 12 |
- "testing" |
|
| 13 |
- |
|
| 14 |
- "github.com/docker/docker/integration-cli/fixtures/load" |
|
| 15 |
- "github.com/docker/docker/pkg/integration/checker" |
|
| 16 |
- "github.com/go-check/check" |
|
| 17 |
-) |
|
| 18 |
- |
|
| 19 |
-func ensureFrozenImagesLinux(t *testing.T) {
|
|
| 20 |
- images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
|
|
| 21 |
- err := load.FrozenImagesLinux(dockerBinary, images...) |
|
| 22 |
- if err != nil {
|
|
| 23 |
- t.Log(dockerCmdWithError("images"))
|
|
| 24 |
- t.Fatalf("%+v", err)
|
|
| 25 |
- } |
|
| 26 |
- for _, img := range images {
|
|
| 27 |
- protectedImages[img] = struct{}{}
|
|
| 28 |
- } |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-var ensureSyscallTestOnce sync.Once |
|
| 32 |
- |
|
| 33 |
-func ensureSyscallTest(c *check.C) {
|
|
| 34 |
- var doIt bool |
|
| 35 |
- ensureSyscallTestOnce.Do(func() {
|
|
| 36 |
- doIt = true |
|
| 37 |
- }) |
|
| 38 |
- if !doIt {
|
|
| 39 |
- return |
|
| 40 |
- } |
|
| 41 |
- protectedImages["syscall-test:latest"] = struct{}{}
|
|
| 42 |
- |
|
| 43 |
- // if no match, must build in docker, which is significantly slower |
|
| 44 |
- // (slower mostly because of the vfs graphdriver) |
|
| 45 |
- if daemonPlatform != runtime.GOOS {
|
|
| 46 |
- ensureSyscallTestBuild(c) |
|
| 47 |
- return |
|
| 48 |
- } |
|
| 49 |
- |
|
| 50 |
- tmp, err := ioutil.TempDir("", "syscall-test-build")
|
|
| 51 |
- c.Assert(err, checker.IsNil, check.Commentf("couldn't create temp dir"))
|
|
| 52 |
- defer os.RemoveAll(tmp) |
|
| 53 |
- |
|
| 54 |
- gcc, err := exec.LookPath("gcc")
|
|
| 55 |
- c.Assert(err, checker.IsNil, check.Commentf("could not find gcc"))
|
|
| 56 |
- |
|
| 57 |
- tests := []string{"userns", "ns", "acct", "setuid", "setgid", "socket", "raw"}
|
|
| 58 |
- for _, test := range tests {
|
|
| 59 |
- out, err := exec.Command(gcc, "-g", "-Wall", "-static", fmt.Sprintf("../contrib/syscall-test/%s.c", test), "-o", fmt.Sprintf("%s/%s-test", tmp, test)).CombinedOutput()
|
|
| 60 |
- c.Assert(err, checker.IsNil, check.Commentf(string(out))) |
|
| 61 |
- } |
|
| 62 |
- |
|
| 63 |
- if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
|
|
| 64 |
- out, err := exec.Command(gcc, "-s", "-m32", "-nostdlib", "../contrib/syscall-test/exit32.s", "-o", tmp+"/"+"exit32-test").CombinedOutput() |
|
| 65 |
- c.Assert(err, checker.IsNil, check.Commentf(string(out))) |
|
| 66 |
- } |
|
| 67 |
- |
|
| 68 |
- dockerFile := filepath.Join(tmp, "Dockerfile") |
|
| 69 |
- content := []byte(` |
|
| 70 |
- FROM debian:jessie |
|
| 71 |
- COPY . /usr/bin/ |
|
| 72 |
- `) |
|
| 73 |
- err = ioutil.WriteFile(dockerFile, content, 600) |
|
| 74 |
- c.Assert(err, checker.IsNil) |
|
| 75 |
- |
|
| 76 |
- var buildArgs []string |
|
| 77 |
- if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 78 |
- buildArgs = strings.Split(arg, " ") |
|
| 79 |
- } |
|
| 80 |
- buildArgs = append(buildArgs, []string{"-q", "-t", "syscall-test", tmp}...)
|
|
| 81 |
- buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 82 |
- dockerCmd(c, buildArgs...) |
|
| 83 |
-} |
|
| 84 |
- |
|
| 85 |
-func ensureSyscallTestBuild(c *check.C) {
|
|
| 86 |
- err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie") |
|
| 87 |
- c.Assert(err, checker.IsNil) |
|
| 88 |
- |
|
| 89 |
- var buildArgs []string |
|
| 90 |
- if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 91 |
- buildArgs = strings.Split(arg, " ") |
|
| 92 |
- } |
|
| 93 |
- buildArgs = append(buildArgs, []string{"-q", "-t", "syscall-test", "../contrib/syscall-test"}...)
|
|
| 94 |
- buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 95 |
- dockerCmd(c, buildArgs...) |
|
| 96 |
-} |
|
| 97 |
- |
|
| 98 |
-func ensureNNPTest(c *check.C) {
|
|
| 99 |
- protectedImages["nnp-test:latest"] = struct{}{}
|
|
| 100 |
- if daemonPlatform != runtime.GOOS {
|
|
| 101 |
- ensureNNPTestBuild(c) |
|
| 102 |
- return |
|
| 103 |
- } |
|
| 104 |
- |
|
| 105 |
- tmp, err := ioutil.TempDir("", "docker-nnp-test")
|
|
| 106 |
- c.Assert(err, checker.IsNil) |
|
| 107 |
- |
|
| 108 |
- gcc, err := exec.LookPath("gcc")
|
|
| 109 |
- c.Assert(err, checker.IsNil, check.Commentf("could not find gcc"))
|
|
| 110 |
- |
|
| 111 |
- out, err := exec.Command(gcc, "-g", "-Wall", "-static", "../contrib/nnp-test/nnp-test.c", "-o", filepath.Join(tmp, "nnp-test")).CombinedOutput() |
|
| 112 |
- c.Assert(err, checker.IsNil, check.Commentf(string(out))) |
|
| 113 |
- |
|
| 114 |
- dockerfile := filepath.Join(tmp, "Dockerfile") |
|
| 115 |
- content := ` |
|
| 116 |
- FROM debian:jessie |
|
| 117 |
- COPY . /usr/bin |
|
| 118 |
- RUN chmod +s /usr/bin/nnp-test |
|
| 119 |
- ` |
|
| 120 |
- err = ioutil.WriteFile(dockerfile, []byte(content), 600) |
|
| 121 |
- c.Assert(err, checker.IsNil, check.Commentf("could not write Dockerfile for nnp-test image"))
|
|
| 122 |
- |
|
| 123 |
- var buildArgs []string |
|
| 124 |
- if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 125 |
- buildArgs = strings.Split(arg, " ") |
|
| 126 |
- } |
|
| 127 |
- buildArgs = append(buildArgs, []string{"-q", "-t", "nnp-test", tmp}...)
|
|
| 128 |
- buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 129 |
- dockerCmd(c, buildArgs...) |
|
| 130 |
-} |
|
| 131 |
- |
|
| 132 |
-func ensureNNPTestBuild(c *check.C) {
|
|
| 133 |
- err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie") |
|
| 134 |
- c.Assert(err, checker.IsNil) |
|
| 135 |
- |
|
| 136 |
- var buildArgs []string |
|
| 137 |
- if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 138 |
- buildArgs = strings.Split(arg, " ") |
|
| 139 |
- } |
|
| 140 |
- buildArgs = append(buildArgs, []string{"-q", "-t", "npp-test", "../contrib/nnp-test"}...)
|
|
| 141 |
- buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 142 |
- dockerCmd(c, buildArgs...) |
|
| 143 |
-} |
| 144 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,151 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ "runtime" |
|
| 9 |
+ "strings" |
|
| 10 |
+ "sync" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/integration-cli/fixtures/load" |
|
| 13 |
+ "github.com/docker/docker/pkg/integration/checker" |
|
| 14 |
+ "github.com/go-check/check" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+type testingT interface {
|
|
| 18 |
+ logT |
|
| 19 |
+ Fatalf(string, ...interface{})
|
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+type logT interface {
|
|
| 23 |
+ Logf(string, ...interface{})
|
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func ensureFrozenImagesLinux(t testingT) {
|
|
| 27 |
+ images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
|
|
| 28 |
+ err := load.FrozenImagesLinux(dockerBinary, images...) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ t.Logf(dockerCmdWithError("images"))
|
|
| 31 |
+ t.Fatalf("%+v", err)
|
|
| 32 |
+ } |
|
| 33 |
+ for _, img := range images {
|
|
| 34 |
+ protectedImages[img] = struct{}{}
|
|
| 35 |
+ } |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+var ensureSyscallTestOnce sync.Once |
|
| 39 |
+ |
|
| 40 |
+func ensureSyscallTest(c *check.C) {
|
|
| 41 |
+ var doIt bool |
|
| 42 |
+ ensureSyscallTestOnce.Do(func() {
|
|
| 43 |
+ doIt = true |
|
| 44 |
+ }) |
|
| 45 |
+ if !doIt {
|
|
| 46 |
+ return |
|
| 47 |
+ } |
|
| 48 |
+ protectedImages["syscall-test:latest"] = struct{}{}
|
|
| 49 |
+ |
|
| 50 |
+ // if no match, must build in docker, which is significantly slower |
|
| 51 |
+ // (slower mostly because of the vfs graphdriver) |
|
| 52 |
+ if daemonPlatform != runtime.GOOS {
|
|
| 53 |
+ ensureSyscallTestBuild(c) |
|
| 54 |
+ return |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ tmp, err := ioutil.TempDir("", "syscall-test-build")
|
|
| 58 |
+ c.Assert(err, checker.IsNil, check.Commentf("couldn't create temp dir"))
|
|
| 59 |
+ defer os.RemoveAll(tmp) |
|
| 60 |
+ |
|
| 61 |
+ gcc, err := exec.LookPath("gcc")
|
|
| 62 |
+ c.Assert(err, checker.IsNil, check.Commentf("could not find gcc"))
|
|
| 63 |
+ |
|
| 64 |
+ tests := []string{"userns", "ns", "acct", "setuid", "setgid", "socket", "raw"}
|
|
| 65 |
+ for _, test := range tests {
|
|
| 66 |
+ out, err := exec.Command(gcc, "-g", "-Wall", "-static", fmt.Sprintf("../contrib/syscall-test/%s.c", test), "-o", fmt.Sprintf("%s/%s-test", tmp, test)).CombinedOutput()
|
|
| 67 |
+ c.Assert(err, checker.IsNil, check.Commentf(string(out))) |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
|
|
| 71 |
+ out, err := exec.Command(gcc, "-s", "-m32", "-nostdlib", "../contrib/syscall-test/exit32.s", "-o", tmp+"/"+"exit32-test").CombinedOutput() |
|
| 72 |
+ c.Assert(err, checker.IsNil, check.Commentf(string(out))) |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ dockerFile := filepath.Join(tmp, "Dockerfile") |
|
| 76 |
+ content := []byte(` |
|
| 77 |
+ FROM debian:jessie |
|
| 78 |
+ COPY . /usr/bin/ |
|
| 79 |
+ `) |
|
| 80 |
+ err = ioutil.WriteFile(dockerFile, content, 600) |
|
| 81 |
+ c.Assert(err, checker.IsNil) |
|
| 82 |
+ |
|
| 83 |
+ var buildArgs []string |
|
| 84 |
+ if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 85 |
+ buildArgs = strings.Split(arg, " ") |
|
| 86 |
+ } |
|
| 87 |
+ buildArgs = append(buildArgs, []string{"-q", "-t", "syscall-test", tmp}...)
|
|
| 88 |
+ buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 89 |
+ dockerCmd(c, buildArgs...) |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func ensureSyscallTestBuild(c *check.C) {
|
|
| 93 |
+ err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie") |
|
| 94 |
+ c.Assert(err, checker.IsNil) |
|
| 95 |
+ |
|
| 96 |
+ var buildArgs []string |
|
| 97 |
+ if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 98 |
+ buildArgs = strings.Split(arg, " ") |
|
| 99 |
+ } |
|
| 100 |
+ buildArgs = append(buildArgs, []string{"-q", "-t", "syscall-test", "../contrib/syscall-test"}...)
|
|
| 101 |
+ buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 102 |
+ dockerCmd(c, buildArgs...) |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 105 |
+func ensureNNPTest(c *check.C) {
|
|
| 106 |
+ protectedImages["nnp-test:latest"] = struct{}{}
|
|
| 107 |
+ if daemonPlatform != runtime.GOOS {
|
|
| 108 |
+ ensureNNPTestBuild(c) |
|
| 109 |
+ return |
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 112 |
+ tmp, err := ioutil.TempDir("", "docker-nnp-test")
|
|
| 113 |
+ c.Assert(err, checker.IsNil) |
|
| 114 |
+ |
|
| 115 |
+ gcc, err := exec.LookPath("gcc")
|
|
| 116 |
+ c.Assert(err, checker.IsNil, check.Commentf("could not find gcc"))
|
|
| 117 |
+ |
|
| 118 |
+ out, err := exec.Command(gcc, "-g", "-Wall", "-static", "../contrib/nnp-test/nnp-test.c", "-o", filepath.Join(tmp, "nnp-test")).CombinedOutput() |
|
| 119 |
+ c.Assert(err, checker.IsNil, check.Commentf(string(out))) |
|
| 120 |
+ |
|
| 121 |
+ dockerfile := filepath.Join(tmp, "Dockerfile") |
|
| 122 |
+ content := ` |
|
| 123 |
+ FROM debian:jessie |
|
| 124 |
+ COPY . /usr/bin |
|
| 125 |
+ RUN chmod +s /usr/bin/nnp-test |
|
| 126 |
+ ` |
|
| 127 |
+ err = ioutil.WriteFile(dockerfile, []byte(content), 600) |
|
| 128 |
+ c.Assert(err, checker.IsNil, check.Commentf("could not write Dockerfile for nnp-test image"))
|
|
| 129 |
+ |
|
| 130 |
+ var buildArgs []string |
|
| 131 |
+ if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 132 |
+ buildArgs = strings.Split(arg, " ") |
|
| 133 |
+ } |
|
| 134 |
+ buildArgs = append(buildArgs, []string{"-q", "-t", "nnp-test", tmp}...)
|
|
| 135 |
+ buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 136 |
+ dockerCmd(c, buildArgs...) |
|
| 137 |
+} |
|
| 138 |
+ |
|
| 139 |
+func ensureNNPTestBuild(c *check.C) {
|
|
| 140 |
+ err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie") |
|
| 141 |
+ c.Assert(err, checker.IsNil) |
|
| 142 |
+ |
|
| 143 |
+ var buildArgs []string |
|
| 144 |
+ if arg := os.Getenv("DOCKER_BUILD_ARGS"); strings.TrimSpace(arg) != "" {
|
|
| 145 |
+ buildArgs = strings.Split(arg, " ") |
|
| 146 |
+ } |
|
| 147 |
+ buildArgs = append(buildArgs, []string{"-q", "-t", "npp-test", "../contrib/nnp-test"}...)
|
|
| 148 |
+ buildArgs = append([]string{"build"}, buildArgs...)
|
|
| 149 |
+ dockerCmd(c, buildArgs...) |
|
| 150 |
+} |
| 0 | 151 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,69 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ "sync" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+var ensureHTTPServerOnce sync.Once |
|
| 12 |
+ |
|
| 13 |
+func ensureHTTPServerImage() error {
|
|
| 14 |
+ var doIt bool |
|
| 15 |
+ ensureHTTPServerOnce.Do(func() {
|
|
| 16 |
+ doIt = true |
|
| 17 |
+ }) |
|
| 18 |
+ |
|
| 19 |
+ if !doIt {
|
|
| 20 |
+ return nil |
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ protectedImages["httpserver:latest"] = struct{}{}
|
|
| 24 |
+ |
|
| 25 |
+ tmp, err := ioutil.TempDir("", "docker-http-server-test")
|
|
| 26 |
+ if err != nil {
|
|
| 27 |
+ return fmt.Errorf("could not build http server: %v", err)
|
|
| 28 |
+ } |
|
| 29 |
+ defer os.RemoveAll(tmp) |
|
| 30 |
+ |
|
| 31 |
+ goos := daemonPlatform |
|
| 32 |
+ if goos == "" {
|
|
| 33 |
+ goos = "linux" |
|
| 34 |
+ } |
|
| 35 |
+ goarch := os.Getenv("DOCKER_ENGINE_GOARCH")
|
|
| 36 |
+ if goarch == "" {
|
|
| 37 |
+ goarch = "amd64" |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ goCmd, lookErr := exec.LookPath("go")
|
|
| 41 |
+ if lookErr != nil {
|
|
| 42 |
+ return fmt.Errorf("could not build http server: %v", lookErr)
|
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ cmd := exec.Command(goCmd, "build", "-o", filepath.Join(tmp, "httpserver"), "github.com/docker/docker/contrib/httpserver") |
|
| 46 |
+ cmd.Env = append(os.Environ(), []string{
|
|
| 47 |
+ "CGO_ENABLED=0", |
|
| 48 |
+ "GOOS=" + goos, |
|
| 49 |
+ "GOARCH=" + goarch, |
|
| 50 |
+ }...) |
|
| 51 |
+ var out []byte |
|
| 52 |
+ if out, err = cmd.CombinedOutput(); err != nil {
|
|
| 53 |
+ return fmt.Errorf("could not build http server: %s", string(out))
|
|
| 54 |
+ } |
|
| 55 |
+ |
|
| 56 |
+ cpCmd, lookErr := exec.LookPath("cp")
|
|
| 57 |
+ if lookErr != nil {
|
|
| 58 |
+ return fmt.Errorf("could not build http server: %v", lookErr)
|
|
| 59 |
+ } |
|
| 60 |
+ if out, err = exec.Command(cpCmd, "../contrib/httpserver/Dockerfile", filepath.Join(tmp, "Dockerfile")).CombinedOutput(); err != nil {
|
|
| 61 |
+ return fmt.Errorf("could not build http server: %v", string(out))
|
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 64 |
+ if out, err = exec.Command(dockerBinary, "build", "-q", "-t", "httpserver", tmp).CombinedOutput(); err != nil {
|
|
| 65 |
+ return fmt.Errorf("could not build http server: %v", string(out))
|
|
| 66 |
+ } |
|
| 67 |
+ return nil |
|
| 68 |
+} |
| 0 | 69 |
deleted file mode 100644 |
| ... | ... |
@@ -1,177 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "net/http" |
|
| 7 |
- "os" |
|
| 8 |
- "os/exec" |
|
| 9 |
- "path/filepath" |
|
| 10 |
- |
|
| 11 |
- "github.com/docker/distribution/digest" |
|
| 12 |
- "github.com/go-check/check" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-const ( |
|
| 16 |
- v2binary = "registry-v2" |
|
| 17 |
- v2binarySchema1 = "registry-v2-schema1" |
|
| 18 |
-) |
|
| 19 |
- |
|
| 20 |
-type testRegistryV2 struct {
|
|
| 21 |
- cmd *exec.Cmd |
|
| 22 |
- dir string |
|
| 23 |
- auth string |
|
| 24 |
- username string |
|
| 25 |
- password string |
|
| 26 |
- email string |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-func newTestRegistryV2(c *check.C, schema1 bool, auth, tokenURL string) (*testRegistryV2, error) {
|
|
| 30 |
- tmp, err := ioutil.TempDir("", "registry-test-")
|
|
| 31 |
- if err != nil {
|
|
| 32 |
- return nil, err |
|
| 33 |
- } |
|
| 34 |
- template := `version: 0.1 |
|
| 35 |
-loglevel: debug |
|
| 36 |
-storage: |
|
| 37 |
- filesystem: |
|
| 38 |
- rootdirectory: %s |
|
| 39 |
-http: |
|
| 40 |
- addr: %s |
|
| 41 |
-%s` |
|
| 42 |
- var ( |
|
| 43 |
- authTemplate string |
|
| 44 |
- username string |
|
| 45 |
- password string |
|
| 46 |
- email string |
|
| 47 |
- ) |
|
| 48 |
- switch auth {
|
|
| 49 |
- case "htpasswd": |
|
| 50 |
- htpasswdPath := filepath.Join(tmp, "htpasswd") |
|
| 51 |
- // generated with: htpasswd -Bbn testuser testpassword |
|
| 52 |
- userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m" |
|
| 53 |
- username = "testuser" |
|
| 54 |
- password = "testpassword" |
|
| 55 |
- email = "test@test.org" |
|
| 56 |
- if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
|
|
| 57 |
- return nil, err |
|
| 58 |
- } |
|
| 59 |
- authTemplate = fmt.Sprintf(`auth: |
|
| 60 |
- htpasswd: |
|
| 61 |
- realm: basic-realm |
|
| 62 |
- path: %s |
|
| 63 |
-`, htpasswdPath) |
|
| 64 |
- case "token": |
|
| 65 |
- authTemplate = fmt.Sprintf(`auth: |
|
| 66 |
- token: |
|
| 67 |
- realm: %s |
|
| 68 |
- service: "registry" |
|
| 69 |
- issuer: "auth-registry" |
|
| 70 |
- rootcertbundle: "fixtures/registry/cert.pem" |
|
| 71 |
-`, tokenURL) |
|
| 72 |
- } |
|
| 73 |
- |
|
| 74 |
- confPath := filepath.Join(tmp, "config.yaml") |
|
| 75 |
- config, err := os.Create(confPath) |
|
| 76 |
- if err != nil {
|
|
| 77 |
- return nil, err |
|
| 78 |
- } |
|
| 79 |
- defer config.Close() |
|
| 80 |
- |
|
| 81 |
- if _, err := fmt.Fprintf(config, template, tmp, privateRegistryURL, authTemplate); err != nil {
|
|
| 82 |
- os.RemoveAll(tmp) |
|
| 83 |
- return nil, err |
|
| 84 |
- } |
|
| 85 |
- |
|
| 86 |
- binary := v2binary |
|
| 87 |
- if schema1 {
|
|
| 88 |
- binary = v2binarySchema1 |
|
| 89 |
- } |
|
| 90 |
- cmd := exec.Command(binary, confPath) |
|
| 91 |
- if err := cmd.Start(); err != nil {
|
|
| 92 |
- os.RemoveAll(tmp) |
|
| 93 |
- if os.IsNotExist(err) {
|
|
| 94 |
- c.Skip(err.Error()) |
|
| 95 |
- } |
|
| 96 |
- return nil, err |
|
| 97 |
- } |
|
| 98 |
- return &testRegistryV2{
|
|
| 99 |
- cmd: cmd, |
|
| 100 |
- dir: tmp, |
|
| 101 |
- auth: auth, |
|
| 102 |
- username: username, |
|
| 103 |
- password: password, |
|
| 104 |
- email: email, |
|
| 105 |
- }, nil |
|
| 106 |
-} |
|
| 107 |
- |
|
| 108 |
-func (t *testRegistryV2) Ping() error {
|
|
| 109 |
- // We always ping through HTTP for our test registry. |
|
| 110 |
- resp, err := http.Get(fmt.Sprintf("http://%s/v2/", privateRegistryURL))
|
|
| 111 |
- if err != nil {
|
|
| 112 |
- return err |
|
| 113 |
- } |
|
| 114 |
- resp.Body.Close() |
|
| 115 |
- |
|
| 116 |
- fail := resp.StatusCode != http.StatusOK |
|
| 117 |
- if t.auth != "" {
|
|
| 118 |
- // unauthorized is a _good_ status when pinging v2/ and it needs auth |
|
| 119 |
- fail = fail && resp.StatusCode != http.StatusUnauthorized |
|
| 120 |
- } |
|
| 121 |
- if fail {
|
|
| 122 |
- return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
|
|
| 123 |
- } |
|
| 124 |
- return nil |
|
| 125 |
-} |
|
| 126 |
- |
|
| 127 |
-func (t *testRegistryV2) Close() {
|
|
| 128 |
- t.cmd.Process.Kill() |
|
| 129 |
- os.RemoveAll(t.dir) |
|
| 130 |
-} |
|
| 131 |
- |
|
| 132 |
-func (t *testRegistryV2) getBlobFilename(blobDigest digest.Digest) string {
|
|
| 133 |
- // Split the digest into its algorithm and hex components. |
|
| 134 |
- dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex() |
|
| 135 |
- |
|
| 136 |
- // The path to the target blob data looks something like: |
|
| 137 |
- // baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data" |
|
| 138 |
- return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", t.dir, dgstAlg, dgstHex[:2], dgstHex)
|
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-func (t *testRegistryV2) readBlobContents(c *check.C, blobDigest digest.Digest) []byte {
|
|
| 142 |
- // Load the target manifest blob. |
|
| 143 |
- manifestBlob, err := ioutil.ReadFile(t.getBlobFilename(blobDigest)) |
|
| 144 |
- if err != nil {
|
|
| 145 |
- c.Fatalf("unable to read blob: %s", err)
|
|
| 146 |
- } |
|
| 147 |
- |
|
| 148 |
- return manifestBlob |
|
| 149 |
-} |
|
| 150 |
- |
|
| 151 |
-func (t *testRegistryV2) writeBlobContents(c *check.C, blobDigest digest.Digest, data []byte) {
|
|
| 152 |
- if err := ioutil.WriteFile(t.getBlobFilename(blobDigest), data, os.FileMode(0644)); err != nil {
|
|
| 153 |
- c.Fatalf("unable to write malicious data blob: %s", err)
|
|
| 154 |
- } |
|
| 155 |
-} |
|
| 156 |
- |
|
| 157 |
-func (t *testRegistryV2) tempMoveBlobData(c *check.C, blobDigest digest.Digest) (undo func()) {
|
|
| 158 |
- tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
|
|
| 159 |
- if err != nil {
|
|
| 160 |
- c.Fatalf("unable to get temporary blob file: %s", err)
|
|
| 161 |
- } |
|
| 162 |
- tempFile.Close() |
|
| 163 |
- |
|
| 164 |
- blobFilename := t.getBlobFilename(blobDigest) |
|
| 165 |
- |
|
| 166 |
- // Move the existing data file aside, so that we can replace it with a |
|
| 167 |
- // another blob of data. |
|
| 168 |
- if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
|
|
| 169 |
- os.Remove(tempFile.Name()) |
|
| 170 |
- c.Fatalf("unable to move data blob: %s", err)
|
|
| 171 |
- } |
|
| 172 |
- |
|
| 173 |
- return func() {
|
|
| 174 |
- os.Rename(tempFile.Name(), blobFilename) |
|
| 175 |
- os.Remove(tempFile.Name()) |
|
| 176 |
- } |
|
| 177 |
-} |
| 178 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,55 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "net/http" |
|
| 5 |
- "net/http/httptest" |
|
| 6 |
- "regexp" |
|
| 7 |
- "strings" |
|
| 8 |
- "sync" |
|
| 9 |
- |
|
| 10 |
- "github.com/go-check/check" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-type handlerFunc func(w http.ResponseWriter, r *http.Request) |
|
| 14 |
- |
|
| 15 |
-type testRegistry struct {
|
|
| 16 |
- server *httptest.Server |
|
| 17 |
- hostport string |
|
| 18 |
- handlers map[string]handlerFunc |
|
| 19 |
- mu sync.Mutex |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-func (tr *testRegistry) registerHandler(path string, h handlerFunc) {
|
|
| 23 |
- tr.mu.Lock() |
|
| 24 |
- defer tr.mu.Unlock() |
|
| 25 |
- tr.handlers[path] = h |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-func newTestRegistry(c *check.C) (*testRegistry, error) {
|
|
| 29 |
- testReg := &testRegistry{handlers: make(map[string]handlerFunc)}
|
|
| 30 |
- |
|
| 31 |
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
| 32 |
- url := r.URL.String() |
|
| 33 |
- |
|
| 34 |
- var matched bool |
|
| 35 |
- var err error |
|
| 36 |
- for re, function := range testReg.handlers {
|
|
| 37 |
- matched, err = regexp.MatchString(re, url) |
|
| 38 |
- if err != nil {
|
|
| 39 |
- c.Fatal("Error with handler regexp")
|
|
| 40 |
- } |
|
| 41 |
- if matched {
|
|
| 42 |
- function(w, r) |
|
| 43 |
- break |
|
| 44 |
- } |
|
| 45 |
- } |
|
| 46 |
- |
|
| 47 |
- if !matched {
|
|
| 48 |
- c.Fatalf("Unable to match %s with regexp", url)
|
|
| 49 |
- } |
|
| 50 |
- })) |
|
| 51 |
- |
|
| 52 |
- testReg.server = ts |
|
| 53 |
- testReg.hostport = strings.Replace(ts.URL, "http://", "", 1) |
|
| 54 |
- return testReg, nil |
|
| 55 |
-} |
| 56 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,55 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "net/http" |
|
| 4 |
+ "net/http/httptest" |
|
| 5 |
+ "regexp" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "sync" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/go-check/check" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+type handlerFunc func(w http.ResponseWriter, r *http.Request) |
|
| 13 |
+ |
|
| 14 |
+type testRegistry struct {
|
|
| 15 |
+ server *httptest.Server |
|
| 16 |
+ hostport string |
|
| 17 |
+ handlers map[string]handlerFunc |
|
| 18 |
+ mu sync.Mutex |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+func (tr *testRegistry) registerHandler(path string, h handlerFunc) {
|
|
| 22 |
+ tr.mu.Lock() |
|
| 23 |
+ defer tr.mu.Unlock() |
|
| 24 |
+ tr.handlers[path] = h |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+func newTestRegistry(c *check.C) (*testRegistry, error) {
|
|
| 28 |
+ testReg := &testRegistry{handlers: make(map[string]handlerFunc)}
|
|
| 29 |
+ |
|
| 30 |
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
| 31 |
+ url := r.URL.String() |
|
| 32 |
+ |
|
| 33 |
+ var matched bool |
|
| 34 |
+ var err error |
|
| 35 |
+ for re, function := range testReg.handlers {
|
|
| 36 |
+ matched, err = regexp.MatchString(re, url) |
|
| 37 |
+ if err != nil {
|
|
| 38 |
+ c.Fatal("Error with handler regexp")
|
|
| 39 |
+ } |
|
| 40 |
+ if matched {
|
|
| 41 |
+ function(w, r) |
|
| 42 |
+ break |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ if !matched {
|
|
| 47 |
+ c.Fatalf("Unable to match %s with regexp", url)
|
|
| 48 |
+ } |
|
| 49 |
+ })) |
|
| 50 |
+ |
|
| 51 |
+ testReg.server = ts |
|
| 52 |
+ testReg.hostport = strings.Replace(ts.URL, "http://", "", 1) |
|
| 53 |
+ return testReg, nil |
|
| 54 |
+} |
| 0 | 55 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,177 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "net/http" |
|
| 6 |
+ "os" |
|
| 7 |
+ "os/exec" |
|
| 8 |
+ "path/filepath" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/distribution/digest" |
|
| 11 |
+ "github.com/go-check/check" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+const ( |
|
| 15 |
+ v2binary = "registry-v2" |
|
| 16 |
+ v2binarySchema1 = "registry-v2-schema1" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+type testRegistryV2 struct {
|
|
| 20 |
+ cmd *exec.Cmd |
|
| 21 |
+ dir string |
|
| 22 |
+ auth string |
|
| 23 |
+ username string |
|
| 24 |
+ password string |
|
| 25 |
+ email string |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+func newTestRegistryV2(c *check.C, schema1 bool, auth, tokenURL string) (*testRegistryV2, error) {
|
|
| 29 |
+ tmp, err := ioutil.TempDir("", "registry-test-")
|
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ return nil, err |
|
| 32 |
+ } |
|
| 33 |
+ template := `version: 0.1 |
|
| 34 |
+loglevel: debug |
|
| 35 |
+storage: |
|
| 36 |
+ filesystem: |
|
| 37 |
+ rootdirectory: %s |
|
| 38 |
+http: |
|
| 39 |
+ addr: %s |
|
| 40 |
+%s` |
|
| 41 |
+ var ( |
|
| 42 |
+ authTemplate string |
|
| 43 |
+ username string |
|
| 44 |
+ password string |
|
| 45 |
+ email string |
|
| 46 |
+ ) |
|
| 47 |
+ switch auth {
|
|
| 48 |
+ case "htpasswd": |
|
| 49 |
+ htpasswdPath := filepath.Join(tmp, "htpasswd") |
|
| 50 |
+ // generated with: htpasswd -Bbn testuser testpassword |
|
| 51 |
+ userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m" |
|
| 52 |
+ username = "testuser" |
|
| 53 |
+ password = "testpassword" |
|
| 54 |
+ email = "test@test.org" |
|
| 55 |
+ if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
|
|
| 56 |
+ return nil, err |
|
| 57 |
+ } |
|
| 58 |
+ authTemplate = fmt.Sprintf(`auth: |
|
| 59 |
+ htpasswd: |
|
| 60 |
+ realm: basic-realm |
|
| 61 |
+ path: %s |
|
| 62 |
+`, htpasswdPath) |
|
| 63 |
+ case "token": |
|
| 64 |
+ authTemplate = fmt.Sprintf(`auth: |
|
| 65 |
+ token: |
|
| 66 |
+ realm: %s |
|
| 67 |
+ service: "registry" |
|
| 68 |
+ issuer: "auth-registry" |
|
| 69 |
+ rootcertbundle: "fixtures/registry/cert.pem" |
|
| 70 |
+`, tokenURL) |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ confPath := filepath.Join(tmp, "config.yaml") |
|
| 74 |
+ config, err := os.Create(confPath) |
|
| 75 |
+ if err != nil {
|
|
| 76 |
+ return nil, err |
|
| 77 |
+ } |
|
| 78 |
+ defer config.Close() |
|
| 79 |
+ |
|
| 80 |
+ if _, err := fmt.Fprintf(config, template, tmp, privateRegistryURL, authTemplate); err != nil {
|
|
| 81 |
+ os.RemoveAll(tmp) |
|
| 82 |
+ return nil, err |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ binary := v2binary |
|
| 86 |
+ if schema1 {
|
|
| 87 |
+ binary = v2binarySchema1 |
|
| 88 |
+ } |
|
| 89 |
+ cmd := exec.Command(binary, confPath) |
|
| 90 |
+ if err := cmd.Start(); err != nil {
|
|
| 91 |
+ os.RemoveAll(tmp) |
|
| 92 |
+ if os.IsNotExist(err) {
|
|
| 93 |
+ c.Skip(err.Error()) |
|
| 94 |
+ } |
|
| 95 |
+ return nil, err |
|
| 96 |
+ } |
|
| 97 |
+ return &testRegistryV2{
|
|
| 98 |
+ cmd: cmd, |
|
| 99 |
+ dir: tmp, |
|
| 100 |
+ auth: auth, |
|
| 101 |
+ username: username, |
|
| 102 |
+ password: password, |
|
| 103 |
+ email: email, |
|
| 104 |
+ }, nil |
|
| 105 |
+} |
|
| 106 |
+ |
|
| 107 |
+func (t *testRegistryV2) Ping() error {
|
|
| 108 |
+ // We always ping through HTTP for our test registry. |
|
| 109 |
+ resp, err := http.Get(fmt.Sprintf("http://%s/v2/", privateRegistryURL))
|
|
| 110 |
+ if err != nil {
|
|
| 111 |
+ return err |
|
| 112 |
+ } |
|
| 113 |
+ resp.Body.Close() |
|
| 114 |
+ |
|
| 115 |
+ fail := resp.StatusCode != http.StatusOK |
|
| 116 |
+ if t.auth != "" {
|
|
| 117 |
+ // unauthorized is a _good_ status when pinging v2/ and it needs auth |
|
| 118 |
+ fail = fail && resp.StatusCode != http.StatusUnauthorized |
|
| 119 |
+ } |
|
| 120 |
+ if fail {
|
|
| 121 |
+ return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
|
|
| 122 |
+ } |
|
| 123 |
+ return nil |
|
| 124 |
+} |
|
| 125 |
+ |
|
| 126 |
+func (t *testRegistryV2) Close() {
|
|
| 127 |
+ t.cmd.Process.Kill() |
|
| 128 |
+ os.RemoveAll(t.dir) |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+func (t *testRegistryV2) getBlobFilename(blobDigest digest.Digest) string {
|
|
| 132 |
+ // Split the digest into its algorithm and hex components. |
|
| 133 |
+ dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex() |
|
| 134 |
+ |
|
| 135 |
+ // The path to the target blob data looks something like: |
|
| 136 |
+ // baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data" |
|
| 137 |
+ return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", t.dir, dgstAlg, dgstHex[:2], dgstHex)
|
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+func (t *testRegistryV2) readBlobContents(c *check.C, blobDigest digest.Digest) []byte {
|
|
| 141 |
+ // Load the target manifest blob. |
|
| 142 |
+ manifestBlob, err := ioutil.ReadFile(t.getBlobFilename(blobDigest)) |
|
| 143 |
+ if err != nil {
|
|
| 144 |
+ c.Fatalf("unable to read blob: %s", err)
|
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ return manifestBlob |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func (t *testRegistryV2) writeBlobContents(c *check.C, blobDigest digest.Digest, data []byte) {
|
|
| 151 |
+ if err := ioutil.WriteFile(t.getBlobFilename(blobDigest), data, os.FileMode(0644)); err != nil {
|
|
| 152 |
+ c.Fatalf("unable to write malicious data blob: %s", err)
|
|
| 153 |
+ } |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+func (t *testRegistryV2) tempMoveBlobData(c *check.C, blobDigest digest.Digest) (undo func()) {
|
|
| 157 |
+ tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
|
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ c.Fatalf("unable to get temporary blob file: %s", err)
|
|
| 160 |
+ } |
|
| 161 |
+ tempFile.Close() |
|
| 162 |
+ |
|
| 163 |
+ blobFilename := t.getBlobFilename(blobDigest) |
|
| 164 |
+ |
|
| 165 |
+ // Move the existing data file aside, so that we can replace it with a |
|
| 166 |
+ // another blob of data. |
|
| 167 |
+ if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
|
|
| 168 |
+ os.Remove(tempFile.Name()) |
|
| 169 |
+ c.Fatalf("unable to move data blob: %s", err)
|
|
| 170 |
+ } |
|
| 171 |
+ |
|
| 172 |
+ return func() {
|
|
| 173 |
+ os.Rename(tempFile.Name(), blobFilename) |
|
| 174 |
+ os.Remove(tempFile.Name()) |
|
| 175 |
+ } |
|
| 176 |
+} |
| 0 | 177 |
deleted file mode 100644 |
| ... | ... |
@@ -1,11 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-// sleepCommandForDaemonPlatform is a helper function that determines what |
|
| 4 |
-// the command is for a sleeping container based on the daemon platform. |
|
| 5 |
-// The Windows busybox image does not have a `top` command. |
|
| 6 |
-func sleepCommandForDaemonPlatform() []string {
|
|
| 7 |
- if daemonPlatform == "windows" {
|
|
| 8 |
- return []string{"sleep", "240"}
|
|
| 9 |
- } |
|
| 10 |
- return []string{"top"}
|
|
| 11 |
-} |
| 0 | 8 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+// sleepCommandForDaemonPlatform is a helper function that determines what |
|
| 3 |
+// the command is for a sleeping container based on the daemon platform. |
|
| 4 |
+// The Windows busybox image does not have a `top` command. |
|
| 5 |
+func sleepCommandForDaemonPlatform() []string {
|
|
| 6 |
+ if daemonPlatform == "windows" {
|
|
| 7 |
+ return []string{"sleep", "240"}
|
|
| 8 |
+ } |
|
| 9 |
+ return []string{"top"}
|
|
| 10 |
+} |
| 0 | 11 |
deleted file mode 100644 |
| ... | ... |
@@ -1,14 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package main |
|
| 4 |
- |
|
| 5 |
-const ( |
|
| 6 |
- // identifies if test suite is running on a unix platform |
|
| 7 |
- isUnixCli = true |
|
| 8 |
- |
|
| 9 |
- expectedFileChmod = "-rw-r--r--" |
|
| 10 |
- |
|
| 11 |
- // On Unix variants, the busybox image comes with the `top` command which |
|
| 12 |
- // runs indefinitely while still being interruptible by a signal. |
|
| 13 |
- defaultSleepImage = "busybox" |
|
| 14 |
-) |
| 15 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package main |
|
| 3 |
+ |
|
| 4 |
+const ( |
|
| 5 |
+ // identifies if test suite is running on a unix platform |
|
| 6 |
+ isUnixCli = true |
|
| 7 |
+ |
|
| 8 |
+ expectedFileChmod = "-rw-r--r--" |
|
| 9 |
+ |
|
| 10 |
+ // On Unix variants, the busybox image comes with the `top` command which |
|
| 11 |
+ // runs indefinitely while still being interruptible by a signal. |
|
| 12 |
+ defaultSleepImage = "busybox" |
|
| 13 |
+) |
| 0 | 14 |
deleted file mode 100644 |
| ... | ... |
@@ -1,15 +0,0 @@ |
| 1 |
-// +build windows |
|
| 2 |
- |
|
| 3 |
-package main |
|
| 4 |
- |
|
| 5 |
-const ( |
|
| 6 |
- // identifies if test suite is running on a unix platform |
|
| 7 |
- isUnixCli = false |
|
| 8 |
- |
|
| 9 |
- // this is the expected file permission set on windows: gh#11395 |
|
| 10 |
- expectedFileChmod = "-rwxr-xr-x" |
|
| 11 |
- |
|
| 12 |
- // On Windows, the busybox image doesn't have the `top` command, so we rely |
|
| 13 |
- // on `sleep` with a high duration. |
|
| 14 |
- defaultSleepImage = "busybox" |
|
| 15 |
-) |
| 16 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+package main |
|
| 3 |
+ |
|
| 4 |
+const ( |
|
| 5 |
+ // identifies if test suite is running on a unix platform |
|
| 6 |
+ isUnixCli = false |
|
| 7 |
+ |
|
| 8 |
+ // this is the expected file permission set on windows: gh#11395 |
|
| 9 |
+ expectedFileChmod = "-rwxr-xr-x" |
|
| 10 |
+ |
|
| 11 |
+ // On Windows, the busybox image doesn't have the `top` command, so we rely |
|
| 12 |
+ // on `sleep` with a high duration. |
|
| 13 |
+ defaultSleepImage = "busybox" |
|
| 14 |
+) |
| 0 | 15 |
deleted file mode 100644 |
| ... | ... |
@@ -1,344 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "net" |
|
| 7 |
- "net/http" |
|
| 8 |
- "os" |
|
| 9 |
- "os/exec" |
|
| 10 |
- "path/filepath" |
|
| 11 |
- "strings" |
|
| 12 |
- "time" |
|
| 13 |
- |
|
| 14 |
- cliconfig "github.com/docker/docker/cli/config" |
|
| 15 |
- "github.com/docker/docker/pkg/integration/checker" |
|
| 16 |
- "github.com/docker/go-connections/tlsconfig" |
|
| 17 |
- "github.com/go-check/check" |
|
| 18 |
-) |
|
| 19 |
- |
|
| 20 |
-var notaryBinary = "notary" |
|
| 21 |
-var notaryServerBinary = "notary-server" |
|
| 22 |
- |
|
| 23 |
-type keyPair struct {
|
|
| 24 |
- Public string |
|
| 25 |
- Private string |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-type testNotary struct {
|
|
| 29 |
- cmd *exec.Cmd |
|
| 30 |
- dir string |
|
| 31 |
- keys []keyPair |
|
| 32 |
-} |
|
| 33 |
- |
|
| 34 |
-const notaryHost = "localhost:4443" |
|
| 35 |
-const notaryURL = "https://" + notaryHost |
|
| 36 |
- |
|
| 37 |
-func newTestNotary(c *check.C) (*testNotary, error) {
|
|
| 38 |
- // generate server config |
|
| 39 |
- template := `{
|
|
| 40 |
- "server": {
|
|
| 41 |
- "http_addr": "%s", |
|
| 42 |
- "tls_key_file": "%s", |
|
| 43 |
- "tls_cert_file": "%s" |
|
| 44 |
- }, |
|
| 45 |
- "trust_service": {
|
|
| 46 |
- "type": "local", |
|
| 47 |
- "hostname": "", |
|
| 48 |
- "port": "", |
|
| 49 |
- "key_algorithm": "ed25519" |
|
| 50 |
- }, |
|
| 51 |
- "logging": {
|
|
| 52 |
- "level": "debug" |
|
| 53 |
- }, |
|
| 54 |
- "storage": {
|
|
| 55 |
- "backend": "memory" |
|
| 56 |
- } |
|
| 57 |
-}` |
|
| 58 |
- tmp, err := ioutil.TempDir("", "notary-test-")
|
|
| 59 |
- if err != nil {
|
|
| 60 |
- return nil, err |
|
| 61 |
- } |
|
| 62 |
- confPath := filepath.Join(tmp, "config.json") |
|
| 63 |
- config, err := os.Create(confPath) |
|
| 64 |
- if err != nil {
|
|
| 65 |
- return nil, err |
|
| 66 |
- } |
|
| 67 |
- defer config.Close() |
|
| 68 |
- |
|
| 69 |
- workingDir, err := os.Getwd() |
|
| 70 |
- if err != nil {
|
|
| 71 |
- return nil, err |
|
| 72 |
- } |
|
| 73 |
- if _, err := fmt.Fprintf(config, template, notaryHost, filepath.Join(workingDir, "fixtures/notary/localhost.key"), filepath.Join(workingDir, "fixtures/notary/localhost.cert")); err != nil {
|
|
| 74 |
- os.RemoveAll(tmp) |
|
| 75 |
- return nil, err |
|
| 76 |
- } |
|
| 77 |
- |
|
| 78 |
- // generate client config |
|
| 79 |
- clientConfPath := filepath.Join(tmp, "client-config.json") |
|
| 80 |
- clientConfig, err := os.Create(clientConfPath) |
|
| 81 |
- if err != nil {
|
|
| 82 |
- return nil, err |
|
| 83 |
- } |
|
| 84 |
- defer clientConfig.Close() |
|
| 85 |
- |
|
| 86 |
- template = `{
|
|
| 87 |
- "trust_dir" : "%s", |
|
| 88 |
- "remote_server": {
|
|
| 89 |
- "url": "%s", |
|
| 90 |
- "skipTLSVerify": true |
|
| 91 |
- } |
|
| 92 |
-}` |
|
| 93 |
- if _, err = fmt.Fprintf(clientConfig, template, filepath.Join(cliconfig.Dir(), "trust"), notaryURL); err != nil {
|
|
| 94 |
- os.RemoveAll(tmp) |
|
| 95 |
- return nil, err |
|
| 96 |
- } |
|
| 97 |
- |
|
| 98 |
- // load key fixture filenames |
|
| 99 |
- var keys []keyPair |
|
| 100 |
- for i := 1; i < 5; i++ {
|
|
| 101 |
- keys = append(keys, keyPair{
|
|
| 102 |
- Public: filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.crt", i)),
|
|
| 103 |
- Private: filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.key", i)),
|
|
| 104 |
- }) |
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- // run notary-server |
|
| 108 |
- cmd := exec.Command(notaryServerBinary, "-config", confPath) |
|
| 109 |
- if err := cmd.Start(); err != nil {
|
|
| 110 |
- os.RemoveAll(tmp) |
|
| 111 |
- if os.IsNotExist(err) {
|
|
| 112 |
- c.Skip(err.Error()) |
|
| 113 |
- } |
|
| 114 |
- return nil, err |
|
| 115 |
- } |
|
| 116 |
- |
|
| 117 |
- testNotary := &testNotary{
|
|
| 118 |
- cmd: cmd, |
|
| 119 |
- dir: tmp, |
|
| 120 |
- keys: keys, |
|
| 121 |
- } |
|
| 122 |
- |
|
| 123 |
- // Wait for notary to be ready to serve requests. |
|
| 124 |
- for i := 1; i <= 20; i++ {
|
|
| 125 |
- if err = testNotary.Ping(); err == nil {
|
|
| 126 |
- break |
|
| 127 |
- } |
|
| 128 |
- time.Sleep(10 * time.Millisecond * time.Duration(i*i)) |
|
| 129 |
- } |
|
| 130 |
- |
|
| 131 |
- if err != nil {
|
|
| 132 |
- c.Fatalf("Timeout waiting for test notary to become available: %s", err)
|
|
| 133 |
- } |
|
| 134 |
- |
|
| 135 |
- return testNotary, nil |
|
| 136 |
-} |
|
| 137 |
- |
|
| 138 |
-func (t *testNotary) Ping() error {
|
|
| 139 |
- tlsConfig := tlsconfig.ClientDefault() |
|
| 140 |
- tlsConfig.InsecureSkipVerify = true |
|
| 141 |
- client := http.Client{
|
|
| 142 |
- Transport: &http.Transport{
|
|
| 143 |
- Proxy: http.ProxyFromEnvironment, |
|
| 144 |
- Dial: (&net.Dialer{
|
|
| 145 |
- Timeout: 30 * time.Second, |
|
| 146 |
- KeepAlive: 30 * time.Second, |
|
| 147 |
- }).Dial, |
|
| 148 |
- TLSHandshakeTimeout: 10 * time.Second, |
|
| 149 |
- TLSClientConfig: tlsConfig, |
|
| 150 |
- }, |
|
| 151 |
- } |
|
| 152 |
- resp, err := client.Get(fmt.Sprintf("%s/v2/", notaryURL))
|
|
| 153 |
- if err != nil {
|
|
| 154 |
- return err |
|
| 155 |
- } |
|
| 156 |
- if resp.StatusCode != http.StatusOK {
|
|
| 157 |
- return fmt.Errorf("notary ping replied with an unexpected status code %d", resp.StatusCode)
|
|
| 158 |
- } |
|
| 159 |
- return nil |
|
| 160 |
-} |
|
| 161 |
- |
|
| 162 |
-func (t *testNotary) Close() {
|
|
| 163 |
- t.cmd.Process.Kill() |
|
| 164 |
- os.RemoveAll(t.dir) |
|
| 165 |
-} |
|
| 166 |
- |
|
| 167 |
-func (s *DockerTrustSuite) trustedCmd(cmd *exec.Cmd) {
|
|
| 168 |
- pwd := "12345678" |
|
| 169 |
- trustCmdEnv(cmd, notaryURL, pwd, pwd) |
|
| 170 |
-} |
|
| 171 |
- |
|
| 172 |
-func (s *DockerTrustSuite) trustedCmdWithServer(cmd *exec.Cmd, server string) {
|
|
| 173 |
- pwd := "12345678" |
|
| 174 |
- trustCmdEnv(cmd, server, pwd, pwd) |
|
| 175 |
-} |
|
| 176 |
- |
|
| 177 |
-func (s *DockerTrustSuite) trustedCmdWithPassphrases(cmd *exec.Cmd, rootPwd, repositoryPwd string) {
|
|
| 178 |
- trustCmdEnv(cmd, notaryURL, rootPwd, repositoryPwd) |
|
| 179 |
-} |
|
| 180 |
- |
|
| 181 |
-func trustCmdEnv(cmd *exec.Cmd, server, rootPwd, repositoryPwd string) {
|
|
| 182 |
- env := []string{
|
|
| 183 |
- "DOCKER_CONTENT_TRUST=1", |
|
| 184 |
- fmt.Sprintf("DOCKER_CONTENT_TRUST_SERVER=%s", server),
|
|
| 185 |
- fmt.Sprintf("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=%s", rootPwd),
|
|
| 186 |
- fmt.Sprintf("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=%s", repositoryPwd),
|
|
| 187 |
- } |
|
| 188 |
- cmd.Env = append(os.Environ(), env...) |
|
| 189 |
-} |
|
| 190 |
- |
|
| 191 |
-func (s *DockerTrustSuite) setupTrustedImage(c *check.C, name string) string {
|
|
| 192 |
- repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
|
|
| 193 |
- // tag the image and upload it to the private registry |
|
| 194 |
- dockerCmd(c, "tag", "busybox", repoName) |
|
| 195 |
- |
|
| 196 |
- pushCmd := exec.Command(dockerBinary, "push", repoName) |
|
| 197 |
- s.trustedCmd(pushCmd) |
|
| 198 |
- out, _, err := runCommandWithOutput(pushCmd) |
|
| 199 |
- |
|
| 200 |
- if err != nil {
|
|
| 201 |
- c.Fatalf("Error running trusted push: %s\n%s", err, out)
|
|
| 202 |
- } |
|
| 203 |
- if !strings.Contains(string(out), "Signing and pushing trust metadata") {
|
|
| 204 |
- c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
|
| 205 |
- } |
|
| 206 |
- |
|
| 207 |
- if out, status := dockerCmd(c, "rmi", repoName); status != 0 {
|
|
| 208 |
- c.Fatalf("Error removing image %q\n%s", repoName, out)
|
|
| 209 |
- } |
|
| 210 |
- |
|
| 211 |
- return repoName |
|
| 212 |
-} |
|
| 213 |
- |
|
| 214 |
-func (s *DockerTrustSuite) setupTrustedplugin(c *check.C, source, name string) string {
|
|
| 215 |
- repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
|
|
| 216 |
- // tag the image and upload it to the private registry |
|
| 217 |
- dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--alias", repoName, source) |
|
| 218 |
- |
|
| 219 |
- pushCmd := exec.Command(dockerBinary, "plugin", "push", repoName) |
|
| 220 |
- s.trustedCmd(pushCmd) |
|
| 221 |
- out, _, err := runCommandWithOutput(pushCmd) |
|
| 222 |
- |
|
| 223 |
- if err != nil {
|
|
| 224 |
- c.Fatalf("Error running trusted plugin push: %s\n%s", err, out)
|
|
| 225 |
- } |
|
| 226 |
- if !strings.Contains(string(out), "Signing and pushing trust metadata") {
|
|
| 227 |
- c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
|
| 228 |
- } |
|
| 229 |
- |
|
| 230 |
- if out, status := dockerCmd(c, "plugin", "rm", "-f", repoName); status != 0 {
|
|
| 231 |
- c.Fatalf("Error removing plugin %q\n%s", repoName, out)
|
|
| 232 |
- } |
|
| 233 |
- |
|
| 234 |
- return repoName |
|
| 235 |
-} |
|
| 236 |
- |
|
| 237 |
-func notaryClientEnv(cmd *exec.Cmd) {
|
|
| 238 |
- pwd := "12345678" |
|
| 239 |
- env := []string{
|
|
| 240 |
- fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
|
|
| 241 |
- fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
|
|
| 242 |
- fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
|
|
| 243 |
- fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
|
|
| 244 |
- } |
|
| 245 |
- cmd.Env = append(os.Environ(), env...) |
|
| 246 |
-} |
|
| 247 |
- |
|
| 248 |
-func (s *DockerTrustSuite) notaryInitRepo(c *check.C, repoName string) {
|
|
| 249 |
- initCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "init", repoName) |
|
| 250 |
- notaryClientEnv(initCmd) |
|
| 251 |
- out, _, err := runCommandWithOutput(initCmd) |
|
| 252 |
- if err != nil {
|
|
| 253 |
- c.Fatalf("Error initializing notary repository: %s\n", out)
|
|
| 254 |
- } |
|
| 255 |
-} |
|
| 256 |
- |
|
| 257 |
-func (s *DockerTrustSuite) notaryCreateDelegation(c *check.C, repoName, role string, pubKey string, paths ...string) {
|
|
| 258 |
- pathsArg := "--all-paths" |
|
| 259 |
- if len(paths) > 0 {
|
|
| 260 |
- pathsArg = "--paths=" + strings.Join(paths, ",") |
|
| 261 |
- } |
|
| 262 |
- |
|
| 263 |
- delgCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), |
|
| 264 |
- "delegation", "add", repoName, role, pubKey, pathsArg) |
|
| 265 |
- notaryClientEnv(delgCmd) |
|
| 266 |
- out, _, err := runCommandWithOutput(delgCmd) |
|
| 267 |
- if err != nil {
|
|
| 268 |
- c.Fatalf("Error adding %s role to notary repository: %s\n", role, out)
|
|
| 269 |
- } |
|
| 270 |
-} |
|
| 271 |
- |
|
| 272 |
-func (s *DockerTrustSuite) notaryPublish(c *check.C, repoName string) {
|
|
| 273 |
- pubCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "publish", repoName) |
|
| 274 |
- notaryClientEnv(pubCmd) |
|
| 275 |
- out, _, err := runCommandWithOutput(pubCmd) |
|
| 276 |
- if err != nil {
|
|
| 277 |
- c.Fatalf("Error publishing notary repository: %s\n", out)
|
|
| 278 |
- } |
|
| 279 |
-} |
|
| 280 |
- |
|
| 281 |
-func (s *DockerTrustSuite) notaryImportKey(c *check.C, repoName, role string, privKey string) {
|
|
| 282 |
- impCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "key", |
|
| 283 |
- "import", privKey, "-g", repoName, "-r", role) |
|
| 284 |
- notaryClientEnv(impCmd) |
|
| 285 |
- out, _, err := runCommandWithOutput(impCmd) |
|
| 286 |
- if err != nil {
|
|
| 287 |
- c.Fatalf("Error importing key to notary repository: %s\n", out)
|
|
| 288 |
- } |
|
| 289 |
-} |
|
| 290 |
- |
|
| 291 |
-func (s *DockerTrustSuite) notaryListTargetsInRole(c *check.C, repoName, role string) map[string]string {
|
|
| 292 |
- listCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "list", |
|
| 293 |
- repoName, "-r", role) |
|
| 294 |
- notaryClientEnv(listCmd) |
|
| 295 |
- out, _, err := runCommandWithOutput(listCmd) |
|
| 296 |
- if err != nil {
|
|
| 297 |
- c.Fatalf("Error listing targets in notary repository: %s\n", out)
|
|
| 298 |
- } |
|
| 299 |
- |
|
| 300 |
- // should look something like: |
|
| 301 |
- // NAME DIGEST SIZE (BYTES) ROLE |
|
| 302 |
- // ------------------------------------------------------------------------------------------------------ |
|
| 303 |
- // latest 24a36bbc059b1345b7e8be0df20f1b23caa3602e85d42fff7ecd9d0bd255de56 1377 targets |
|
| 304 |
- |
|
| 305 |
- targets := make(map[string]string) |
|
| 306 |
- |
|
| 307 |
- // no target |
|
| 308 |
- lines := strings.Split(strings.TrimSpace(out), "\n") |
|
| 309 |
- if len(lines) == 1 && strings.Contains(out, "No targets present in this repository.") {
|
|
| 310 |
- return targets |
|
| 311 |
- } |
|
| 312 |
- |
|
| 313 |
- // otherwise, there is at least one target |
|
| 314 |
- c.Assert(len(lines), checker.GreaterOrEqualThan, 3) |
|
| 315 |
- |
|
| 316 |
- for _, line := range lines[2:] {
|
|
| 317 |
- tokens := strings.Fields(line) |
|
| 318 |
- c.Assert(tokens, checker.HasLen, 4) |
|
| 319 |
- targets[tokens[0]] = tokens[3] |
|
| 320 |
- } |
|
| 321 |
- |
|
| 322 |
- return targets |
|
| 323 |
-} |
|
| 324 |
- |
|
| 325 |
-func (s *DockerTrustSuite) assertTargetInRoles(c *check.C, repoName, target string, roles ...string) {
|
|
| 326 |
- // check all the roles |
|
| 327 |
- for _, role := range roles {
|
|
| 328 |
- targets := s.notaryListTargetsInRole(c, repoName, role) |
|
| 329 |
- roleName, ok := targets[target] |
|
| 330 |
- c.Assert(ok, checker.True) |
|
| 331 |
- c.Assert(roleName, checker.Equals, role) |
|
| 332 |
- } |
|
| 333 |
-} |
|
| 334 |
- |
|
| 335 |
-func (s *DockerTrustSuite) assertTargetNotInRoles(c *check.C, repoName, target string, roles ...string) {
|
|
| 336 |
- targets := s.notaryListTargetsInRole(c, repoName, "targets") |
|
| 337 |
- |
|
| 338 |
- roleName, ok := targets[target] |
|
| 339 |
- if ok {
|
|
| 340 |
- for _, role := range roles {
|
|
| 341 |
- c.Assert(roleName, checker.Not(checker.Equals), role) |
|
| 342 |
- } |
|
| 343 |
- } |
|
| 344 |
-} |
| 345 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,344 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "net" |
|
| 6 |
+ "net/http" |
|
| 7 |
+ "os" |
|
| 8 |
+ "os/exec" |
|
| 9 |
+ "path/filepath" |
|
| 10 |
+ "strings" |
|
| 11 |
+ "time" |
|
| 12 |
+ |
|
| 13 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 14 |
+ "github.com/docker/docker/pkg/integration/checker" |
|
| 15 |
+ "github.com/docker/go-connections/tlsconfig" |
|
| 16 |
+ "github.com/go-check/check" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+var notaryBinary = "notary" |
|
| 20 |
+var notaryServerBinary = "notary-server" |
|
| 21 |
+ |
|
| 22 |
+type keyPair struct {
|
|
| 23 |
+ Public string |
|
| 24 |
+ Private string |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+type testNotary struct {
|
|
| 28 |
+ cmd *exec.Cmd |
|
| 29 |
+ dir string |
|
| 30 |
+ keys []keyPair |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+const notaryHost = "localhost:4443" |
|
| 34 |
+const notaryURL = "https://" + notaryHost |
|
| 35 |
+ |
|
| 36 |
+func newTestNotary(c *check.C) (*testNotary, error) {
|
|
| 37 |
+ // generate server config |
|
| 38 |
+ template := `{
|
|
| 39 |
+ "server": {
|
|
| 40 |
+ "http_addr": "%s", |
|
| 41 |
+ "tls_key_file": "%s", |
|
| 42 |
+ "tls_cert_file": "%s" |
|
| 43 |
+ }, |
|
| 44 |
+ "trust_service": {
|
|
| 45 |
+ "type": "local", |
|
| 46 |
+ "hostname": "", |
|
| 47 |
+ "port": "", |
|
| 48 |
+ "key_algorithm": "ed25519" |
|
| 49 |
+ }, |
|
| 50 |
+ "logging": {
|
|
| 51 |
+ "level": "debug" |
|
| 52 |
+ }, |
|
| 53 |
+ "storage": {
|
|
| 54 |
+ "backend": "memory" |
|
| 55 |
+ } |
|
| 56 |
+}` |
|
| 57 |
+ tmp, err := ioutil.TempDir("", "notary-test-")
|
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ return nil, err |
|
| 60 |
+ } |
|
| 61 |
+ confPath := filepath.Join(tmp, "config.json") |
|
| 62 |
+ config, err := os.Create(confPath) |
|
| 63 |
+ if err != nil {
|
|
| 64 |
+ return nil, err |
|
| 65 |
+ } |
|
| 66 |
+ defer config.Close() |
|
| 67 |
+ |
|
| 68 |
+ workingDir, err := os.Getwd() |
|
| 69 |
+ if err != nil {
|
|
| 70 |
+ return nil, err |
|
| 71 |
+ } |
|
| 72 |
+ if _, err := fmt.Fprintf(config, template, notaryHost, filepath.Join(workingDir, "fixtures/notary/localhost.key"), filepath.Join(workingDir, "fixtures/notary/localhost.cert")); err != nil {
|
|
| 73 |
+ os.RemoveAll(tmp) |
|
| 74 |
+ return nil, err |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ // generate client config |
|
| 78 |
+ clientConfPath := filepath.Join(tmp, "client-config.json") |
|
| 79 |
+ clientConfig, err := os.Create(clientConfPath) |
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ return nil, err |
|
| 82 |
+ } |
|
| 83 |
+ defer clientConfig.Close() |
|
| 84 |
+ |
|
| 85 |
+ template = `{
|
|
| 86 |
+ "trust_dir" : "%s", |
|
| 87 |
+ "remote_server": {
|
|
| 88 |
+ "url": "%s", |
|
| 89 |
+ "skipTLSVerify": true |
|
| 90 |
+ } |
|
| 91 |
+}` |
|
| 92 |
+ if _, err = fmt.Fprintf(clientConfig, template, filepath.Join(cliconfig.Dir(), "trust"), notaryURL); err != nil {
|
|
| 93 |
+ os.RemoveAll(tmp) |
|
| 94 |
+ return nil, err |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ // load key fixture filenames |
|
| 98 |
+ var keys []keyPair |
|
| 99 |
+ for i := 1; i < 5; i++ {
|
|
| 100 |
+ keys = append(keys, keyPair{
|
|
| 101 |
+ Public: filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.crt", i)),
|
|
| 102 |
+ Private: filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.key", i)),
|
|
| 103 |
+ }) |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ // run notary-server |
|
| 107 |
+ cmd := exec.Command(notaryServerBinary, "-config", confPath) |
|
| 108 |
+ if err := cmd.Start(); err != nil {
|
|
| 109 |
+ os.RemoveAll(tmp) |
|
| 110 |
+ if os.IsNotExist(err) {
|
|
| 111 |
+ c.Skip(err.Error()) |
|
| 112 |
+ } |
|
| 113 |
+ return nil, err |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ testNotary := &testNotary{
|
|
| 117 |
+ cmd: cmd, |
|
| 118 |
+ dir: tmp, |
|
| 119 |
+ keys: keys, |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ // Wait for notary to be ready to serve requests. |
|
| 123 |
+ for i := 1; i <= 20; i++ {
|
|
| 124 |
+ if err = testNotary.Ping(); err == nil {
|
|
| 125 |
+ break |
|
| 126 |
+ } |
|
| 127 |
+ time.Sleep(10 * time.Millisecond * time.Duration(i*i)) |
|
| 128 |
+ } |
|
| 129 |
+ |
|
| 130 |
+ if err != nil {
|
|
| 131 |
+ c.Fatalf("Timeout waiting for test notary to become available: %s", err)
|
|
| 132 |
+ } |
|
| 133 |
+ |
|
| 134 |
+ return testNotary, nil |
|
| 135 |
+} |
|
| 136 |
+ |
|
| 137 |
+func (t *testNotary) Ping() error {
|
|
| 138 |
+ tlsConfig := tlsconfig.ClientDefault() |
|
| 139 |
+ tlsConfig.InsecureSkipVerify = true |
|
| 140 |
+ client := http.Client{
|
|
| 141 |
+ Transport: &http.Transport{
|
|
| 142 |
+ Proxy: http.ProxyFromEnvironment, |
|
| 143 |
+ Dial: (&net.Dialer{
|
|
| 144 |
+ Timeout: 30 * time.Second, |
|
| 145 |
+ KeepAlive: 30 * time.Second, |
|
| 146 |
+ }).Dial, |
|
| 147 |
+ TLSHandshakeTimeout: 10 * time.Second, |
|
| 148 |
+ TLSClientConfig: tlsConfig, |
|
| 149 |
+ }, |
|
| 150 |
+ } |
|
| 151 |
+ resp, err := client.Get(fmt.Sprintf("%s/v2/", notaryURL))
|
|
| 152 |
+ if err != nil {
|
|
| 153 |
+ return err |
|
| 154 |
+ } |
|
| 155 |
+ if resp.StatusCode != http.StatusOK {
|
|
| 156 |
+ return fmt.Errorf("notary ping replied with an unexpected status code %d", resp.StatusCode)
|
|
| 157 |
+ } |
|
| 158 |
+ return nil |
|
| 159 |
+} |
|
| 160 |
+ |
|
| 161 |
+func (t *testNotary) Close() {
|
|
| 162 |
+ t.cmd.Process.Kill() |
|
| 163 |
+ os.RemoveAll(t.dir) |
|
| 164 |
+} |
|
| 165 |
+ |
|
| 166 |
+func (s *DockerTrustSuite) trustedCmd(cmd *exec.Cmd) {
|
|
| 167 |
+ pwd := "12345678" |
|
| 168 |
+ trustCmdEnv(cmd, notaryURL, pwd, pwd) |
|
| 169 |
+} |
|
| 170 |
+ |
|
| 171 |
+func (s *DockerTrustSuite) trustedCmdWithServer(cmd *exec.Cmd, server string) {
|
|
| 172 |
+ pwd := "12345678" |
|
| 173 |
+ trustCmdEnv(cmd, server, pwd, pwd) |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+func (s *DockerTrustSuite) trustedCmdWithPassphrases(cmd *exec.Cmd, rootPwd, repositoryPwd string) {
|
|
| 177 |
+ trustCmdEnv(cmd, notaryURL, rootPwd, repositoryPwd) |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 180 |
+func trustCmdEnv(cmd *exec.Cmd, server, rootPwd, repositoryPwd string) {
|
|
| 181 |
+ env := []string{
|
|
| 182 |
+ "DOCKER_CONTENT_TRUST=1", |
|
| 183 |
+ fmt.Sprintf("DOCKER_CONTENT_TRUST_SERVER=%s", server),
|
|
| 184 |
+ fmt.Sprintf("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=%s", rootPwd),
|
|
| 185 |
+ fmt.Sprintf("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=%s", repositoryPwd),
|
|
| 186 |
+ } |
|
| 187 |
+ cmd.Env = append(os.Environ(), env...) |
|
| 188 |
+} |
|
| 189 |
+ |
|
| 190 |
+func (s *DockerTrustSuite) setupTrustedImage(c *check.C, name string) string {
|
|
| 191 |
+ repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
|
|
| 192 |
+ // tag the image and upload it to the private registry |
|
| 193 |
+ dockerCmd(c, "tag", "busybox", repoName) |
|
| 194 |
+ |
|
| 195 |
+ pushCmd := exec.Command(dockerBinary, "push", repoName) |
|
| 196 |
+ s.trustedCmd(pushCmd) |
|
| 197 |
+ out, _, err := runCommandWithOutput(pushCmd) |
|
| 198 |
+ |
|
| 199 |
+ if err != nil {
|
|
| 200 |
+ c.Fatalf("Error running trusted push: %s\n%s", err, out)
|
|
| 201 |
+ } |
|
| 202 |
+ if !strings.Contains(string(out), "Signing and pushing trust metadata") {
|
|
| 203 |
+ c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+ if out, status := dockerCmd(c, "rmi", repoName); status != 0 {
|
|
| 207 |
+ c.Fatalf("Error removing image %q\n%s", repoName, out)
|
|
| 208 |
+ } |
|
| 209 |
+ |
|
| 210 |
+ return repoName |
|
| 211 |
+} |
|
| 212 |
+ |
|
| 213 |
+func (s *DockerTrustSuite) setupTrustedplugin(c *check.C, source, name string) string {
|
|
| 214 |
+ repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
|
|
| 215 |
+ // tag the image and upload it to the private registry |
|
| 216 |
+ dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--alias", repoName, source) |
|
| 217 |
+ |
|
| 218 |
+ pushCmd := exec.Command(dockerBinary, "plugin", "push", repoName) |
|
| 219 |
+ s.trustedCmd(pushCmd) |
|
| 220 |
+ out, _, err := runCommandWithOutput(pushCmd) |
|
| 221 |
+ |
|
| 222 |
+ if err != nil {
|
|
| 223 |
+ c.Fatalf("Error running trusted plugin push: %s\n%s", err, out)
|
|
| 224 |
+ } |
|
| 225 |
+ if !strings.Contains(string(out), "Signing and pushing trust metadata") {
|
|
| 226 |
+ c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
|
| 227 |
+ } |
|
| 228 |
+ |
|
| 229 |
+ if out, status := dockerCmd(c, "plugin", "rm", "-f", repoName); status != 0 {
|
|
| 230 |
+ c.Fatalf("Error removing plugin %q\n%s", repoName, out)
|
|
| 231 |
+ } |
|
| 232 |
+ |
|
| 233 |
+ return repoName |
|
| 234 |
+} |
|
| 235 |
+ |
|
| 236 |
+func notaryClientEnv(cmd *exec.Cmd) {
|
|
| 237 |
+ pwd := "12345678" |
|
| 238 |
+ env := []string{
|
|
| 239 |
+ fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
|
|
| 240 |
+ fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
|
|
| 241 |
+ fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
|
|
| 242 |
+ fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
|
|
| 243 |
+ } |
|
| 244 |
+ cmd.Env = append(os.Environ(), env...) |
|
| 245 |
+} |
|
| 246 |
+ |
|
| 247 |
+func (s *DockerTrustSuite) notaryInitRepo(c *check.C, repoName string) {
|
|
| 248 |
+ initCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "init", repoName) |
|
| 249 |
+ notaryClientEnv(initCmd) |
|
| 250 |
+ out, _, err := runCommandWithOutput(initCmd) |
|
| 251 |
+ if err != nil {
|
|
| 252 |
+ c.Fatalf("Error initializing notary repository: %s\n", out)
|
|
| 253 |
+ } |
|
| 254 |
+} |
|
| 255 |
+ |
|
| 256 |
+func (s *DockerTrustSuite) notaryCreateDelegation(c *check.C, repoName, role string, pubKey string, paths ...string) {
|
|
| 257 |
+ pathsArg := "--all-paths" |
|
| 258 |
+ if len(paths) > 0 {
|
|
| 259 |
+ pathsArg = "--paths=" + strings.Join(paths, ",") |
|
| 260 |
+ } |
|
| 261 |
+ |
|
| 262 |
+ delgCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), |
|
| 263 |
+ "delegation", "add", repoName, role, pubKey, pathsArg) |
|
| 264 |
+ notaryClientEnv(delgCmd) |
|
| 265 |
+ out, _, err := runCommandWithOutput(delgCmd) |
|
| 266 |
+ if err != nil {
|
|
| 267 |
+ c.Fatalf("Error adding %s role to notary repository: %s\n", role, out)
|
|
| 268 |
+ } |
|
| 269 |
+} |
|
| 270 |
+ |
|
| 271 |
+func (s *DockerTrustSuite) notaryPublish(c *check.C, repoName string) {
|
|
| 272 |
+ pubCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "publish", repoName) |
|
| 273 |
+ notaryClientEnv(pubCmd) |
|
| 274 |
+ out, _, err := runCommandWithOutput(pubCmd) |
|
| 275 |
+ if err != nil {
|
|
| 276 |
+ c.Fatalf("Error publishing notary repository: %s\n", out)
|
|
| 277 |
+ } |
|
| 278 |
+} |
|
| 279 |
+ |
|
| 280 |
+func (s *DockerTrustSuite) notaryImportKey(c *check.C, repoName, role string, privKey string) {
|
|
| 281 |
+ impCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "key", |
|
| 282 |
+ "import", privKey, "-g", repoName, "-r", role) |
|
| 283 |
+ notaryClientEnv(impCmd) |
|
| 284 |
+ out, _, err := runCommandWithOutput(impCmd) |
|
| 285 |
+ if err != nil {
|
|
| 286 |
+ c.Fatalf("Error importing key to notary repository: %s\n", out)
|
|
| 287 |
+ } |
|
| 288 |
+} |
|
| 289 |
+ |
|
| 290 |
+func (s *DockerTrustSuite) notaryListTargetsInRole(c *check.C, repoName, role string) map[string]string {
|
|
| 291 |
+ listCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "list", |
|
| 292 |
+ repoName, "-r", role) |
|
| 293 |
+ notaryClientEnv(listCmd) |
|
| 294 |
+ out, _, err := runCommandWithOutput(listCmd) |
|
| 295 |
+ if err != nil {
|
|
| 296 |
+ c.Fatalf("Error listing targets in notary repository: %s\n", out)
|
|
| 297 |
+ } |
|
| 298 |
+ |
|
| 299 |
+ // should look something like: |
|
| 300 |
+ // NAME DIGEST SIZE (BYTES) ROLE |
|
| 301 |
+ // ------------------------------------------------------------------------------------------------------ |
|
| 302 |
+ // latest 24a36bbc059b1345b7e8be0df20f1b23caa3602e85d42fff7ecd9d0bd255de56 1377 targets |
|
| 303 |
+ |
|
| 304 |
+ targets := make(map[string]string) |
|
| 305 |
+ |
|
| 306 |
+ // no target |
|
| 307 |
+ lines := strings.Split(strings.TrimSpace(out), "\n") |
|
| 308 |
+ if len(lines) == 1 && strings.Contains(out, "No targets present in this repository.") {
|
|
| 309 |
+ return targets |
|
| 310 |
+ } |
|
| 311 |
+ |
|
| 312 |
+ // otherwise, there is at least one target |
|
| 313 |
+ c.Assert(len(lines), checker.GreaterOrEqualThan, 3) |
|
| 314 |
+ |
|
| 315 |
+ for _, line := range lines[2:] {
|
|
| 316 |
+ tokens := strings.Fields(line) |
|
| 317 |
+ c.Assert(tokens, checker.HasLen, 4) |
|
| 318 |
+ targets[tokens[0]] = tokens[3] |
|
| 319 |
+ } |
|
| 320 |
+ |
|
| 321 |
+ return targets |
|
| 322 |
+} |
|
| 323 |
+ |
|
| 324 |
+func (s *DockerTrustSuite) assertTargetInRoles(c *check.C, repoName, target string, roles ...string) {
|
|
| 325 |
+ // check all the roles |
|
| 326 |
+ for _, role := range roles {
|
|
| 327 |
+ targets := s.notaryListTargetsInRole(c, repoName, role) |
|
| 328 |
+ roleName, ok := targets[target] |
|
| 329 |
+ c.Assert(ok, checker.True) |
|
| 330 |
+ c.Assert(roleName, checker.Equals, role) |
|
| 331 |
+ } |
|
| 332 |
+} |
|
| 333 |
+ |
|
| 334 |
+func (s *DockerTrustSuite) assertTargetNotInRoles(c *check.C, repoName, target string, roles ...string) {
|
|
| 335 |
+ targets := s.notaryListTargetsInRole(c, repoName, "targets") |
|
| 336 |
+ |
|
| 337 |
+ roleName, ok := targets[target] |
|
| 338 |
+ if ok {
|
|
| 339 |
+ for _, role := range roles {
|
|
| 340 |
+ c.Assert(roleName, checker.Not(checker.Equals), role) |
|
| 341 |
+ } |
|
| 342 |
+ } |
|
| 343 |
+} |
| 0 | 344 |
deleted file mode 100644 |
| ... | ... |
@@ -1,30 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/docker/docker/pkg/integration/cmd" |
|
| 5 |
- "os/exec" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
|
|
| 9 |
- if daemonPlatform == "windows" {
|
|
| 10 |
- return "c:", `\` |
|
| 11 |
- } |
|
| 12 |
- return "", "/" |
|
| 13 |
-} |
|
| 14 |
- |
|
| 15 |
-// TODO: update code to call cmd.RunCmd directly, and remove this function |
|
| 16 |
-func runCommandWithOutput(execCmd *exec.Cmd) (string, int, error) {
|
|
| 17 |
- result := cmd.RunCmd(transformCmd(execCmd)) |
|
| 18 |
- return result.Combined(), result.ExitCode, result.Error |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-// Temporary shim for migrating commands to the new function |
|
| 22 |
-func transformCmd(execCmd *exec.Cmd) cmd.Cmd {
|
|
| 23 |
- return cmd.Cmd{
|
|
| 24 |
- Command: execCmd.Args, |
|
| 25 |
- Env: execCmd.Env, |
|
| 26 |
- Dir: execCmd.Dir, |
|
| 27 |
- Stdin: execCmd.Stdin, |
|
| 28 |
- Stdout: execCmd.Stdout, |
|
| 29 |
- } |
|
| 30 |
-} |
| 31 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,30 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/pkg/integration/cmd" |
|
| 4 |
+ "os/exec" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
|
|
| 8 |
+ if daemonPlatform == "windows" {
|
|
| 9 |
+ return "c:", `\` |
|
| 10 |
+ } |
|
| 11 |
+ return "", "/" |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+// TODO: update code to call cmd.RunCmd directly, and remove this function |
|
| 15 |
+func runCommandWithOutput(execCmd *exec.Cmd) (string, int, error) {
|
|
| 16 |
+ result := cmd.RunCmd(transformCmd(execCmd)) |
|
| 17 |
+ return result.Combined(), result.ExitCode, result.Error |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// Temporary shim for migrating commands to the new function |
|
| 21 |
+func transformCmd(execCmd *exec.Cmd) cmd.Cmd {
|
|
| 22 |
+ return cmd.Cmd{
|
|
| 23 |
+ Command: execCmd.Args, |
|
| 24 |
+ Env: execCmd.Env, |
|
| 25 |
+ Dir: execCmd.Dir, |
|
| 26 |
+ Stdin: execCmd.Stdin, |
|
| 27 |
+ Stdout: execCmd.Stdout, |
|
| 28 |
+ } |
|
| 29 |
+} |