| package fakestorage // import "github.com/docker/docker/internal/test/fakestorage" |
| |
| import ( |
| "context" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "net/http" |
| "net/http/httptest" |
| "net/url" |
| "os" |
| "strings" |
| |
| "github.com/docker/docker/api/types" |
| containertypes "github.com/docker/docker/api/types/container" |
| "github.com/docker/docker/client" |
| "github.com/docker/docker/internal/test" |
| "github.com/docker/docker/internal/test/environment" |
| "github.com/docker/docker/internal/test/fakecontext" |
| "github.com/docker/docker/internal/test/request" |
| "github.com/docker/docker/internal/testutil" |
| "github.com/docker/go-connections/nat" |
| "gotest.tools/assert" |
| ) |
| |
| var testEnv *environment.Execution |
| |
| type testingT interface { |
| assert.TestingT |
| logT |
| skipT |
| Fatal(args ...interface{}) |
| Fatalf(string, ...interface{}) |
| } |
| |
| type logT interface { |
| Logf(string, ...interface{}) |
| } |
| |
| type skipT interface { |
| Skip(reason string) |
| } |
| |
| // Fake is a static file server. It might be running locally or remotely |
| // on test host. |
| type Fake interface { |
| Close() error |
| URL() string |
| CtxDir() string |
| } |
| |
| // SetTestEnvironment sets a static test environment |
| // TODO: decouple this package from environment |
| func SetTestEnvironment(env *environment.Execution) { |
| testEnv = env |
| } |
| |
| // New returns a static file server that will be use as build context. |
| func New(t testingT, dir string, modifiers ...func(*fakecontext.Fake) error) Fake { |
| if ht, ok := t.(test.HelperT); ok { |
| ht.Helper() |
| } |
| if testEnv == nil { |
| t.Fatal("fakstorage package requires SetTestEnvironment() to be called before use.") |
| } |
| ctx := fakecontext.New(t, dir, modifiers...) |
| switch { |
| case testEnv.IsRemoteDaemon() && strings.HasPrefix(request.DaemonHost(), "unix:///"): |
| t.Skip("e2e run : daemon is remote but docker host points to a unix socket") |
| case testEnv.IsLocalDaemon(): |
| return newLocalFakeStorage(ctx) |
| default: |
| return newRemoteFileServer(t, ctx, testEnv.APIClient()) |
| } |
| return nil |
| } |
| |
| // localFileStorage is a file storage on the running machine |
| type localFileStorage struct { |
| *fakecontext.Fake |
| *httptest.Server |
| } |
| |
| func (s *localFileStorage) URL() string { |
| return s.Server.URL |
| } |
| |
| func (s *localFileStorage) CtxDir() string { |
| return s.Fake.Dir |
| } |
| |
| func (s *localFileStorage) Close() error { |
| defer s.Server.Close() |
| return s.Fake.Close() |
| } |
| |
| func newLocalFakeStorage(ctx *fakecontext.Fake) *localFileStorage { |
| handler := http.FileServer(http.Dir(ctx.Dir)) |
| server := httptest.NewServer(handler) |
| return &localFileStorage{ |
| Fake: ctx, |
| Server: server, |
| } |
| } |
| |
| // remoteFileServer is a containerized static file server started on the remote |
| // testing machine to be used in URL-accepting docker build functionality. |
| type remoteFileServer struct { |
| host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 |
| container string |
| image string |
| client client.APIClient |
| ctx *fakecontext.Fake |
| } |
| |
| func (f *remoteFileServer) URL() string { |
| u := url.URL{ |
| Scheme: "http", |
| Host: f.host} |
| return u.String() |
| } |
| |
| func (f *remoteFileServer) CtxDir() string { |
| return f.ctx.Dir |
| } |
| |
| func (f *remoteFileServer) Close() error { |
| defer func() { |
| if f.ctx != nil { |
| f.ctx.Close() |
| } |
| if f.image != "" { |
| if _, err := f.client.ImageRemove(context.Background(), f.image, types.ImageRemoveOptions{ |
| Force: true, |
| }); err != nil { |
| fmt.Fprintf(os.Stderr, "Error closing remote file server : %v\n", err) |
| } |
| } |
| if err := f.client.Close(); err != nil { |
| fmt.Fprintf(os.Stderr, "Error closing remote file server : %v\n", err) |
| } |
| }() |
| if f.container == "" { |
| return nil |
| } |
| return f.client.ContainerRemove(context.Background(), f.container, types.ContainerRemoveOptions{ |
| Force: true, |
| RemoveVolumes: true, |
| }) |
| } |
| |
| func newRemoteFileServer(t testingT, ctx *fakecontext.Fake, c client.APIClient) *remoteFileServer { |
| var ( |
| image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(testutil.GenerateRandomAlphaOnlyString(10))) |
| container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(testutil.GenerateRandomAlphaOnlyString(10))) |
| ) |
| |
| ensureHTTPServerImage(t) |
| |
| // Build the image |
| if err := ctx.Add("Dockerfile", `FROM httpserver |
| COPY . /static`); err != nil { |
| t.Fatal(err) |
| } |
| resp, err := c.ImageBuild(context.Background(), ctx.AsTarReader(t), types.ImageBuildOptions{ |
| NoCache: true, |
| Tags: []string{image}, |
| }) |
| assert.NilError(t, err) |
| _, err = io.Copy(ioutil.Discard, resp.Body) |
| assert.NilError(t, err) |
| |
| // Start the container |
| b, err := c.ContainerCreate(context.Background(), &containertypes.Config{ |
| Image: image, |
| }, &containertypes.HostConfig{}, nil, container) |
| assert.NilError(t, err) |
| err = c.ContainerStart(context.Background(), b.ID, types.ContainerStartOptions{}) |
| assert.NilError(t, err) |
| |
| // Find out the system assigned port |
| i, err := c.ContainerInspect(context.Background(), b.ID) |
| assert.NilError(t, err) |
| newP, err := nat.NewPort("tcp", "80") |
| assert.NilError(t, err) |
| ports, exists := i.NetworkSettings.Ports[newP] |
| if !exists || len(ports) != 1 { |
| t.Fatalf("unable to find port 80/tcp for %s", container) |
| } |
| host := ports[0].HostIP |
| port := ports[0].HostPort |
| |
| return &remoteFileServer{ |
| container: container, |
| image: image, |
| host: fmt.Sprintf("%s:%s", host, port), |
| ctx: ctx, |
| client: c, |
| } |
| } |