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