1package fakestorage // import "github.com/docker/docker/internal/test/fakestorage" 2 3import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "net/http/httptest" 10 "net/url" 11 "os" 12 "strings" 13 14 "github.com/docker/docker/api/types" 15 containertypes "github.com/docker/docker/api/types/container" 16 "github.com/docker/docker/client" 17 "github.com/docker/docker/internal/test" 18 "github.com/docker/docker/internal/test/environment" 19 "github.com/docker/docker/internal/test/fakecontext" 20 "github.com/docker/docker/internal/test/request" 21 "github.com/docker/docker/internal/testutil" 22 "github.com/docker/go-connections/nat" 23 "gotest.tools/assert" 24) 25 26var testEnv *environment.Execution 27 28type testingT interface { 29 assert.TestingT 30 logT 31 skipT 32 Fatal(args ...interface{}) 33 Fatalf(string, ...interface{}) 34} 35 36type logT interface { 37 Logf(string, ...interface{}) 38} 39 40type skipT interface { 41 Skip(reason string) 42} 43 44// Fake is a static file server. It might be running locally or remotely 45// on test host. 46type Fake interface { 47 Close() error 48 URL() string 49 CtxDir() string 50} 51 52// SetTestEnvironment sets a static test environment 53// TODO: decouple this package from environment 54func SetTestEnvironment(env *environment.Execution) { 55 testEnv = env 56} 57 58// New returns a static file server that will be use as build context. 59func New(t testingT, dir string, modifiers ...func(*fakecontext.Fake) error) Fake { 60 if ht, ok := t.(test.HelperT); ok { 61 ht.Helper() 62 } 63 if testEnv == nil { 64 t.Fatal("fakstorage package requires SetTestEnvironment() to be called before use.") 65 } 66 ctx := fakecontext.New(t, dir, modifiers...) 67 switch { 68 case testEnv.IsRemoteDaemon() && strings.HasPrefix(request.DaemonHost(), "unix:///"): 69 t.Skip("e2e run : daemon is remote but docker host points to a unix socket") 70 case testEnv.IsLocalDaemon(): 71 return newLocalFakeStorage(ctx) 72 default: 73 return newRemoteFileServer(t, ctx, testEnv.APIClient()) 74 } 75 return nil 76} 77 78// localFileStorage is a file storage on the running machine 79type localFileStorage struct { 80 *fakecontext.Fake 81 *httptest.Server 82} 83 84func (s *localFileStorage) URL() string { 85 return s.Server.URL 86} 87 88func (s *localFileStorage) CtxDir() string { 89 return s.Fake.Dir 90} 91 92func (s *localFileStorage) Close() error { 93 defer s.Server.Close() 94 return s.Fake.Close() 95} 96 97func newLocalFakeStorage(ctx *fakecontext.Fake) *localFileStorage { 98 handler := http.FileServer(http.Dir(ctx.Dir)) 99 server := httptest.NewServer(handler) 100 return &localFileStorage{ 101 Fake: ctx, 102 Server: server, 103 } 104} 105 106// remoteFileServer is a containerized static file server started on the remote 107// testing machine to be used in URL-accepting docker build functionality. 108type remoteFileServer struct { 109 host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 110 container string 111 image string 112 client client.APIClient 113 ctx *fakecontext.Fake 114} 115 116func (f *remoteFileServer) URL() string { 117 u := url.URL{ 118 Scheme: "http", 119 Host: f.host} 120 return u.String() 121} 122 123func (f *remoteFileServer) CtxDir() string { 124 return f.ctx.Dir 125} 126 127func (f *remoteFileServer) Close() error { 128 defer func() { 129 if f.ctx != nil { 130 f.ctx.Close() 131 } 132 if f.image != "" { 133 if _, err := f.client.ImageRemove(context.Background(), f.image, types.ImageRemoveOptions{ 134 Force: true, 135 }); err != nil { 136 fmt.Fprintf(os.Stderr, "Error closing remote file server : %v\n", err) 137 } 138 } 139 if err := f.client.Close(); err != nil { 140 fmt.Fprintf(os.Stderr, "Error closing remote file server : %v\n", err) 141 } 142 }() 143 if f.container == "" { 144 return nil 145 } 146 return f.client.ContainerRemove(context.Background(), f.container, types.ContainerRemoveOptions{ 147 Force: true, 148 RemoveVolumes: true, 149 }) 150} 151 152func newRemoteFileServer(t testingT, ctx *fakecontext.Fake, c client.APIClient) *remoteFileServer { 153 var ( 154 image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(testutil.GenerateRandomAlphaOnlyString(10))) 155 container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(testutil.GenerateRandomAlphaOnlyString(10))) 156 ) 157 158 ensureHTTPServerImage(t) 159 160 // Build the image 161 if err := ctx.Add("Dockerfile", `FROM httpserver 162COPY . /static`); err != nil { 163 t.Fatal(err) 164 } 165 resp, err := c.ImageBuild(context.Background(), ctx.AsTarReader(t), types.ImageBuildOptions{ 166 NoCache: true, 167 Tags: []string{image}, 168 }) 169 assert.NilError(t, err) 170 _, err = io.Copy(ioutil.Discard, resp.Body) 171 assert.NilError(t, err) 172 173 // Start the container 174 b, err := c.ContainerCreate(context.Background(), &containertypes.Config{ 175 Image: image, 176 }, &containertypes.HostConfig{}, nil, container) 177 assert.NilError(t, err) 178 err = c.ContainerStart(context.Background(), b.ID, types.ContainerStartOptions{}) 179 assert.NilError(t, err) 180 181 // Find out the system assigned port 182 i, err := c.ContainerInspect(context.Background(), b.ID) 183 assert.NilError(t, err) 184 newP, err := nat.NewPort("tcp", "80") 185 assert.NilError(t, err) 186 ports, exists := i.NetworkSettings.Ports[newP] 187 if !exists || len(ports) != 1 { 188 t.Fatalf("unable to find port 80/tcp for %s", container) 189 } 190 host := ports[0].HostIP 191 port := ports[0].HostPort 192 193 return &remoteFileServer{ 194 container: container, 195 image: image, 196 host: fmt.Sprintf("%s:%s", host, port), 197 ctx: ctx, 198 client: c, 199 } 200} 201