Signed-off-by: Vincent Demeester <vincent@sbr.pm>
Vincent Demeester authored on 2016/12/26 04:28:38... | ... |
@@ -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 |
+} |