Implemented a FakeStorage alternative that supports spinning
up a remote container on DOCKER_TEST_HOST to serve files over
an offline-compiled Go static web server image so that tests which
use URLs in Dockerfile can build them over at the daemon side.
`fakeStorage` function now automatically chooses if it should
use a local httptest.Server or a remote container.
This fixes the following tests when running against a remote
daemon:
- `TestBuildCacheADD`
- `TestBuildCopyWildcardNoFind`
- `TestBuildCopyWildcardCache`
- `TestBuildADDRemoteFileWithCache`
- `TestBuildADDRemoteFileWithoutCache`
- `TestBuildADDRemoteFileMTime`
- `TestBuildADDLocalAndRemoteFilesWithCache`
- `TestBuildADDLocalAndRemoteFilesWithoutCache`
- `TestBuildFromURLWithF`
- `TestBuildApiDockerFileRemote`
Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
... | ... |
@@ -364,7 +364,7 @@ RUN find /tmp/`, |
364 | 364 |
} |
365 | 365 |
defer server.Close() |
366 | 366 |
|
367 |
- buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL+"/testD", nil, "application/json") |
|
367 |
+ buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") |
|
368 | 368 |
if err != nil { |
369 | 369 |
t.Fatalf("Build failed: %s", err) |
370 | 370 |
} |
... | ... |
@@ -8,14 +8,12 @@ import ( |
8 | 8 |
"io/ioutil" |
9 | 9 |
"os" |
10 | 10 |
"os/exec" |
11 |
- "path" |
|
12 | 11 |
"path/filepath" |
13 | 12 |
"reflect" |
14 | 13 |
"regexp" |
15 | 14 |
"runtime" |
16 | 15 |
"strconv" |
17 | 16 |
"strings" |
18 |
- "syscall" |
|
19 | 17 |
"testing" |
20 | 18 |
"text/template" |
21 | 19 |
"time" |
... | ... |
@@ -645,9 +643,10 @@ func TestBuildCacheADD(t *testing.T) { |
645 | 645 |
t.Fatal(err) |
646 | 646 |
} |
647 | 647 |
defer server.Close() |
648 |
+ |
|
648 | 649 |
if _, err := buildImage(name, |
649 | 650 |
fmt.Sprintf(`FROM scratch |
650 |
- ADD %s/robots.txt /`, server.URL), |
|
651 |
+ ADD %s/robots.txt /`, server.URL()), |
|
651 | 652 |
true); err != nil { |
652 | 653 |
t.Fatal(err) |
653 | 654 |
} |
... | ... |
@@ -657,7 +656,7 @@ func TestBuildCacheADD(t *testing.T) { |
657 | 657 |
deleteImages(name) |
658 | 658 |
_, out, err := buildImageWithOut(name, |
659 | 659 |
fmt.Sprintf(`FROM scratch |
660 |
- ADD %s/index.html /`, server.URL), |
|
660 |
+ ADD %s/index.html /`, server.URL()), |
|
661 | 661 |
true) |
662 | 662 |
if err != nil { |
663 | 663 |
t.Fatal(err) |
... | ... |
@@ -797,7 +796,7 @@ RUN [ $(ls -l /exists/test_file4 | awk '{print $3":"$4}') = 'root:root' ] |
797 | 797 |
RUN [ $(ls -l /exists/robots.txt | awk '{print $3":"$4}') = 'root:root' ] |
798 | 798 |
|
799 | 799 |
RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] |
800 |
-`, server.URL), |
|
800 |
+`, server.URL()), |
|
801 | 801 |
map[string]string{ |
802 | 802 |
"test_file1": "test1", |
803 | 803 |
"test_file2": "test2", |
... | ... |
@@ -1084,6 +1083,7 @@ func TestBuildCopyWildcard(t *testing.T) { |
1084 | 1084 |
t.Fatal(err) |
1085 | 1085 |
} |
1086 | 1086 |
defer server.Close() |
1087 |
+ |
|
1087 | 1088 |
ctx, err := fakeContext(fmt.Sprintf(`FROM busybox |
1088 | 1089 |
COPY file*.txt /tmp/ |
1089 | 1090 |
RUN ls /tmp/file1.txt /tmp/file2.txt |
... | ... |
@@ -1093,7 +1093,7 @@ func TestBuildCopyWildcard(t *testing.T) { |
1093 | 1093 |
RUN mkdir /tmp2 |
1094 | 1094 |
ADD dir/*dir %s/robots.txt /tmp2/ |
1095 | 1095 |
RUN ls /tmp2/nest_nest_file /tmp2/robots.txt |
1096 |
- `, server.URL), |
|
1096 |
+ `, server.URL()), |
|
1097 | 1097 |
map[string]string{ |
1098 | 1098 |
"file1.txt": "test1", |
1099 | 1099 |
"file2.txt": "test2", |
... | ... |
@@ -2831,10 +2831,11 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) { |
2831 | 2831 |
t.Fatal(err) |
2832 | 2832 |
} |
2833 | 2833 |
defer server.Close() |
2834 |
+ |
|
2834 | 2835 |
id1, err := buildImage(name, |
2835 | 2836 |
fmt.Sprintf(`FROM scratch |
2836 | 2837 |
MAINTAINER dockerio |
2837 |
- ADD %s/baz /usr/lib/baz/quux`, server.URL), |
|
2838 |
+ ADD %s/baz /usr/lib/baz/quux`, server.URL()), |
|
2838 | 2839 |
true) |
2839 | 2840 |
if err != nil { |
2840 | 2841 |
t.Fatal(err) |
... | ... |
@@ -2842,7 +2843,7 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) { |
2842 | 2842 |
id2, err := buildImage(name, |
2843 | 2843 |
fmt.Sprintf(`FROM scratch |
2844 | 2844 |
MAINTAINER dockerio |
2845 |
- ADD %s/baz /usr/lib/baz/quux`, server.URL), |
|
2845 |
+ ADD %s/baz /usr/lib/baz/quux`, server.URL()), |
|
2846 | 2846 |
true) |
2847 | 2847 |
if err != nil { |
2848 | 2848 |
t.Fatal(err) |
... | ... |
@@ -2864,10 +2865,11 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { |
2864 | 2864 |
t.Fatal(err) |
2865 | 2865 |
} |
2866 | 2866 |
defer server.Close() |
2867 |
+ |
|
2867 | 2868 |
id1, err := buildImage(name, |
2868 | 2869 |
fmt.Sprintf(`FROM scratch |
2869 | 2870 |
MAINTAINER dockerio |
2870 |
- ADD %s/baz /usr/lib/baz/quux`, server.URL), |
|
2871 |
+ ADD %s/baz /usr/lib/baz/quux`, server.URL()), |
|
2871 | 2872 |
true) |
2872 | 2873 |
if err != nil { |
2873 | 2874 |
t.Fatal(err) |
... | ... |
@@ -2875,7 +2877,7 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { |
2875 | 2875 |
id2, err := buildImage(name2, |
2876 | 2876 |
fmt.Sprintf(`FROM scratch |
2877 | 2877 |
MAINTAINER dockerio |
2878 |
- ADD %s/baz /usr/lib/baz/quux`, server.URL), |
|
2878 |
+ ADD %s/baz /usr/lib/baz/quux`, server.URL()), |
|
2879 | 2879 |
false) |
2880 | 2880 |
if err != nil { |
2881 | 2881 |
t.Fatal(err) |
... | ... |
@@ -2894,7 +2896,8 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { |
2894 | 2894 |
|
2895 | 2895 |
defer deleteImages(name, name2, name3, name4) |
2896 | 2896 |
|
2897 |
- server, err := fakeStorage(map[string]string{"baz": "hello"}) |
|
2897 |
+ files := map[string]string{"baz": "hello"} |
|
2898 |
+ server, err := fakeStorage(files) |
|
2898 | 2899 |
if err != nil { |
2899 | 2900 |
t.Fatal(err) |
2900 | 2901 |
} |
... | ... |
@@ -2902,7 +2905,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { |
2902 | 2902 |
|
2903 | 2903 |
ctx, err := fakeContext(fmt.Sprintf(`FROM scratch |
2904 | 2904 |
MAINTAINER dockerio |
2905 |
- ADD %s/baz /usr/lib/baz/quux`, server.URL), nil) |
|
2905 |
+ ADD %s/baz /usr/lib/baz/quux`, server.URL()), nil) |
|
2906 | 2906 |
if err != nil { |
2907 | 2907 |
t.Fatal(err) |
2908 | 2908 |
} |
... | ... |
@@ -2921,15 +2924,26 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { |
2921 | 2921 |
t.Fatal("The cache should have been used but wasn't - #1") |
2922 | 2922 |
} |
2923 | 2923 |
|
2924 |
- // Now set baz's times to anything else and redo the build |
|
2924 |
+ // Now create a different server withsame contents (causes different mtim) |
|
2925 | 2925 |
// This time the cache should not be used |
2926 |
- bazPath := path.Join(server.FakeContext.Dir, "baz") |
|
2927 |
- err = syscall.UtimesNano(bazPath, make([]syscall.Timespec, 2)) |
|
2926 |
+ |
|
2927 |
+ // allow some time for clock to pass as mtime precision is only 1s |
|
2928 |
+ time.Sleep(2 * time.Second) |
|
2929 |
+ |
|
2930 |
+ server2, err := fakeStorage(files) |
|
2928 | 2931 |
if err != nil { |
2929 |
- t.Fatalf("Error setting mtime on %q: %v", bazPath, err) |
|
2932 |
+ t.Fatal(err) |
|
2930 | 2933 |
} |
2934 |
+ defer server2.Close() |
|
2931 | 2935 |
|
2932 |
- id3, err := buildImageFromContext(name3, ctx, true) |
|
2936 |
+ ctx2, err := fakeContext(fmt.Sprintf(`FROM scratch |
|
2937 |
+ MAINTAINER dockerio |
|
2938 |
+ ADD %s/baz /usr/lib/baz/quux`, server2.URL()), nil) |
|
2939 |
+ if err != nil { |
|
2940 |
+ t.Fatal(err) |
|
2941 |
+ } |
|
2942 |
+ defer ctx2.Close() |
|
2943 |
+ id3, err := buildImageFromContext(name3, ctx2, true) |
|
2933 | 2944 |
if err != nil { |
2934 | 2945 |
t.Fatal(err) |
2935 | 2946 |
} |
... | ... |
@@ -2938,7 +2952,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { |
2938 | 2938 |
} |
2939 | 2939 |
|
2940 | 2940 |
// And for good measure do it again and make sure cache is used this time |
2941 |
- id4, err := buildImageFromContext(name4, ctx, true) |
|
2941 |
+ id4, err := buildImageFromContext(name4, ctx2, true) |
|
2942 | 2942 |
if err != nil { |
2943 | 2943 |
t.Fatal(err) |
2944 | 2944 |
} |
... | ... |
@@ -2958,10 +2972,11 @@ func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { |
2958 | 2958 |
t.Fatal(err) |
2959 | 2959 |
} |
2960 | 2960 |
defer server.Close() |
2961 |
+ |
|
2961 | 2962 |
ctx, err := fakeContext(fmt.Sprintf(`FROM scratch |
2962 | 2963 |
MAINTAINER dockerio |
2963 | 2964 |
ADD foo /usr/lib/bla/bar |
2964 |
- ADD %s/baz /usr/lib/baz/quux`, server.URL), |
|
2965 |
+ ADD %s/baz /usr/lib/baz/quux`, server.URL()), |
|
2965 | 2966 |
map[string]string{ |
2966 | 2967 |
"foo": "hello world", |
2967 | 2968 |
}) |
... | ... |
@@ -3047,10 +3062,11 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { |
3047 | 3047 |
t.Fatal(err) |
3048 | 3048 |
} |
3049 | 3049 |
defer server.Close() |
3050 |
+ |
|
3050 | 3051 |
ctx, err := fakeContext(fmt.Sprintf(`FROM scratch |
3051 | 3052 |
MAINTAINER dockerio |
3052 | 3053 |
ADD foo /usr/lib/bla/bar |
3053 |
- ADD %s/baz /usr/lib/baz/quux`, server.URL), |
|
3054 |
+ ADD %s/baz /usr/lib/baz/quux`, server.URL()), |
|
3054 | 3055 |
map[string]string{ |
3055 | 3056 |
"foo": "hello world", |
3056 | 3057 |
}) |
... | ... |
@@ -4773,7 +4789,7 @@ RUN echo from Dockerfile`, |
4773 | 4773 |
|
4774 | 4774 |
// Make sure that -f is ignored and that we don't use the Dockerfile |
4775 | 4775 |
// that's in the current dir |
4776 |
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL+"/baz") |
|
4776 |
+ out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz") |
|
4777 | 4777 |
if err != nil { |
4778 | 4778 |
t.Fatalf("Failed to build: %s\n%s", out, err) |
4779 | 4779 |
} |
... | ... |
@@ -581,17 +581,42 @@ func fakeContext(dockerfile string, files map[string]string) (*FakeContext, erro |
581 | 581 |
return ctx, nil |
582 | 582 |
} |
583 | 583 |
|
584 |
-type FakeStorage struct { |
|
584 |
+// FakeStorage is a static file server. It might be running locally or remotely |
|
585 |
+// on test host. |
|
586 |
+type FakeStorage interface { |
|
587 |
+ Close() error |
|
588 |
+ URL() string |
|
589 |
+ CtxDir() string |
|
590 |
+} |
|
591 |
+ |
|
592 |
+// fakeStorage returns either a local or remote (at daemon machine) file server |
|
593 |
+func fakeStorage(files map[string]string) (FakeStorage, error) { |
|
594 |
+ if isLocalDaemon { |
|
595 |
+ return newLocalFakeStorage(files) |
|
596 |
+ } |
|
597 |
+ return newRemoteFileServer(files) |
|
598 |
+} |
|
599 |
+ |
|
600 |
+// localFileStorage is a file storage on the running machine |
|
601 |
+type localFileStorage struct { |
|
585 | 602 |
*FakeContext |
586 | 603 |
*httptest.Server |
587 | 604 |
} |
588 | 605 |
|
589 |
-func (f *FakeStorage) Close() error { |
|
590 |
- f.Server.Close() |
|
591 |
- return f.FakeContext.Close() |
|
606 |
+func (s *localFileStorage) URL() string { |
|
607 |
+ return s.Server.URL |
|
608 |
+} |
|
609 |
+ |
|
610 |
+func (s *localFileStorage) CtxDir() string { |
|
611 |
+ return s.FakeContext.Dir |
|
612 |
+} |
|
613 |
+ |
|
614 |
+func (s *localFileStorage) Close() error { |
|
615 |
+ defer s.Server.Close() |
|
616 |
+ return s.FakeContext.Close() |
|
592 | 617 |
} |
593 | 618 |
|
594 |
-func fakeStorage(files map[string]string) (*FakeStorage, error) { |
|
619 |
+func newLocalFakeStorage(files map[string]string) (*localFileStorage, error) { |
|
595 | 620 |
tmp, err := ioutil.TempDir("", "fake-storage") |
596 | 621 |
if err != nil { |
597 | 622 |
return nil, err |
... | ... |
@@ -605,42 +630,101 @@ func fakeStorage(files map[string]string) (*FakeStorage, error) { |
605 | 605 |
} |
606 | 606 |
handler := http.FileServer(http.Dir(ctx.Dir)) |
607 | 607 |
server := httptest.NewServer(handler) |
608 |
- return &FakeStorage{ |
|
608 |
+ return &localFileStorage{ |
|
609 | 609 |
FakeContext: ctx, |
610 | 610 |
Server: server, |
611 | 611 |
}, nil |
612 | 612 |
} |
613 | 613 |
|
614 |
-func inspectField(name, field string) (string, error) { |
|
615 |
- format := fmt.Sprintf("{{.%s}}", field) |
|
616 |
- inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) |
|
617 |
- out, exitCode, err := runCommandWithOutput(inspectCmd) |
|
618 |
- if err != nil || exitCode != 0 { |
|
619 |
- return "", fmt.Errorf("failed to inspect %s: %s", name, out) |
|
614 |
+// remoteFileServer is a containerized static file server started on the remote |
|
615 |
+// testing machine to be used in URL-accepting docker build functionality. |
|
616 |
+type remoteFileServer struct { |
|
617 |
+ host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 |
|
618 |
+ container string |
|
619 |
+ image string |
|
620 |
+ ctx *FakeContext |
|
621 |
+} |
|
622 |
+ |
|
623 |
+func (f *remoteFileServer) URL() string { |
|
624 |
+ u := url.URL{ |
|
625 |
+ Scheme: "http", |
|
626 |
+ Host: f.host} |
|
627 |
+ return u.String() |
|
628 |
+} |
|
629 |
+ |
|
630 |
+func (f *remoteFileServer) CtxDir() string { |
|
631 |
+ return f.ctx.Dir |
|
632 |
+} |
|
633 |
+ |
|
634 |
+func (f *remoteFileServer) Close() error { |
|
635 |
+ defer func() { |
|
636 |
+ if f.ctx != nil { |
|
637 |
+ f.ctx.Close() |
|
638 |
+ } |
|
639 |
+ if f.image != "" { |
|
640 |
+ deleteImages(f.image) |
|
641 |
+ } |
|
642 |
+ }() |
|
643 |
+ if f.container == "" { |
|
644 |
+ return nil |
|
620 | 645 |
} |
621 |
- return strings.TrimSpace(out), nil |
|
646 |
+ return deleteContainer(f.container) |
|
622 | 647 |
} |
623 | 648 |
|
624 |
-func inspectFieldJSON(name, field string) (string, error) { |
|
625 |
- format := fmt.Sprintf("{{json .%s}}", field) |
|
626 |
- inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) |
|
627 |
- out, exitCode, err := runCommandWithOutput(inspectCmd) |
|
628 |
- if err != nil || exitCode != 0 { |
|
629 |
- return "", fmt.Errorf("failed to inspect %s: %s", name, out) |
|
649 |
+func newRemoteFileServer(files map[string]string) (*remoteFileServer, error) { |
|
650 |
+ var ( |
|
651 |
+ image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(makeRandomString(10))) |
|
652 |
+ container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(makeRandomString(10))) |
|
653 |
+ ) |
|
654 |
+ |
|
655 |
+ // Build the image |
|
656 |
+ ctx, err := fakeContext(`FROM httpserver |
|
657 |
+COPY . /static`, files) |
|
658 |
+ if _, err := buildImageFromContext(image, ctx, false); err != nil { |
|
659 |
+ return nil, fmt.Errorf("failed building file storage container image: %v", err) |
|
630 | 660 |
} |
631 |
- return strings.TrimSpace(out), nil |
|
661 |
+ |
|
662 |
+ // Start the container |
|
663 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--name", container, image) |
|
664 |
+ if out, ec, err := runCommandWithOutput(runCmd); err != nil { |
|
665 |
+ return nil, fmt.Errorf("failed to start file storage container. ec=%v\nout=%s\nerr=%v", ec, out, err) |
|
666 |
+ } |
|
667 |
+ |
|
668 |
+ // Find out the system assigned port |
|
669 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "port", container, "80/tcp")) |
|
670 |
+ if err != nil { |
|
671 |
+ return nil, fmt.Errorf("failed to find container port: err=%v\nout=%s", err, out) |
|
672 |
+ } |
|
673 |
+ |
|
674 |
+ return &remoteFileServer{ |
|
675 |
+ container: container, |
|
676 |
+ image: image, |
|
677 |
+ host: strings.Trim(out, "\n"), |
|
678 |
+ ctx: ctx}, nil |
|
632 | 679 |
} |
633 | 680 |
|
634 |
-func inspectFieldMap(name, path, field string) (string, error) { |
|
635 |
- format := fmt.Sprintf("{{index .%s %q}}", path, field) |
|
681 |
+func inspectFilter(name, filter string) (string, error) { |
|
682 |
+ format := fmt.Sprintf("{{%s}}", filter) |
|
636 | 683 |
inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) |
637 | 684 |
out, exitCode, err := runCommandWithOutput(inspectCmd) |
638 | 685 |
if err != nil || exitCode != 0 { |
639 |
- return "", fmt.Errorf("failed to inspect %s: %s", name, out) |
|
686 |
+ return "", fmt.Errorf("failed to inspect container %s: %s", name, out) |
|
640 | 687 |
} |
641 | 688 |
return strings.TrimSpace(out), nil |
642 | 689 |
} |
643 | 690 |
|
691 |
+func inspectField(name, field string) (string, error) { |
|
692 |
+ return inspectFilter(name, fmt.Sprintf(".%s", field)) |
|
693 |
+} |
|
694 |
+ |
|
695 |
+func inspectFieldJSON(name, field string) (string, error) { |
|
696 |
+ return inspectFilter(name, fmt.Sprintf("json .%s", field)) |
|
697 |
+} |
|
698 |
+ |
|
699 |
+func inspectFieldMap(name, path, field string) (string, error) { |
|
700 |
+ return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field)) |
|
701 |
+} |
|
702 |
+ |
|
644 | 703 |
func getIDByName(name string) (string, error) { |
645 | 704 |
return inspectField(name, "Id") |
646 | 705 |
} |
647 | 706 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,15 @@ |
0 |
+#!/bin/bash |
|
1 |
+set -e |
|
2 |
+ |
|
3 |
+# Build a Go static web server on top of busybox image |
|
4 |
+# and compile it for target daemon |
|
5 |
+ |
|
6 |
+dir="$DEST/httpserver" |
|
7 |
+mkdir -p "$dir" |
|
8 |
+( |
|
9 |
+ cd "$dir" |
|
10 |
+ GOOS=linux GOARCH=amd64 go build -o httpserver github.com/docker/docker/contrib/httpserver |
|
11 |
+ cp ../../../../contrib/httpserver/Dockerfile . |
|
12 |
+ docker build -qt httpserver . > /dev/null |
|
13 |
+) |
|
14 |
+rm -rf "$dir" |