github.com/moby/docker@v26.1.3+incompatible/testutil/fakestorage/storage.go (about) 1 package fakestorage // import "github.com/docker/docker/testutil/fakestorage" 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "net/http/httptest" 9 "net/url" 10 "os" 11 "strings" 12 "testing" 13 14 "github.com/docker/docker/api/types" 15 containertypes "github.com/docker/docker/api/types/container" 16 "github.com/docker/docker/api/types/image" 17 "github.com/docker/docker/client" 18 "github.com/docker/docker/testutil" 19 "github.com/docker/docker/testutil/environment" 20 "github.com/docker/docker/testutil/fakecontext" 21 "github.com/docker/docker/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 } 103 return u.String() 104 } 105 106 func (f *remoteFileServer) CtxDir() string { 107 return f.ctx.Dir 108 } 109 110 func (f *remoteFileServer) Close() error { 111 defer func() { 112 if f.ctx != nil { 113 f.ctx.Close() 114 } 115 if f.image != "" { 116 if _, err := f.client.ImageRemove(context.Background(), f.image, image.RemoveOptions{ 117 Force: true, 118 }); err != nil { 119 fmt.Fprintf(os.Stderr, "Error closing remote file server : %v\n", err) 120 } 121 } 122 if err := f.client.Close(); err != nil { 123 fmt.Fprintf(os.Stderr, "Error closing remote file server : %v\n", err) 124 } 125 }() 126 if f.container == "" { 127 return nil 128 } 129 return f.client.ContainerRemove(context.Background(), f.container, containertypes.RemoveOptions{ 130 Force: true, 131 RemoveVolumes: true, 132 }) 133 } 134 135 func newRemoteFileServer(t testing.TB, ctx *fakecontext.Fake, c client.APIClient) *remoteFileServer { 136 var ( 137 image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(testutil.GenerateRandomAlphaOnlyString(10))) 138 container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(testutil.GenerateRandomAlphaOnlyString(10))) 139 ) 140 141 ensureHTTPServerImage(t) 142 143 // Build the image 144 if err := ctx.Add("Dockerfile", `FROM httpserver 145 COPY . /static`); err != nil { 146 t.Fatal(err) 147 } 148 resp, err := c.ImageBuild(context.Background(), ctx.AsTarReader(t), types.ImageBuildOptions{ 149 NoCache: true, 150 Tags: []string{image}, 151 }) 152 assert.NilError(t, err) 153 _, err = io.Copy(io.Discard, resp.Body) 154 assert.NilError(t, err) 155 156 // Start the container 157 b, err := c.ContainerCreate(context.Background(), &containertypes.Config{ 158 Image: image, 159 }, &containertypes.HostConfig{}, nil, nil, container) 160 assert.NilError(t, err) 161 err = c.ContainerStart(context.Background(), b.ID, containertypes.StartOptions{}) 162 assert.NilError(t, err) 163 164 // Find out the system assigned port 165 i, err := c.ContainerInspect(context.Background(), b.ID) 166 assert.NilError(t, err) 167 newP, err := nat.NewPort("tcp", "80") 168 assert.NilError(t, err) 169 ports, exists := i.NetworkSettings.Ports[newP] 170 if !exists || len(ports) != 1 { 171 t.Fatalf("unable to find port 80/tcp for %s", container) 172 } 173 host := ports[0].HostIP 174 port := ports[0].HostPort 175 176 return &remoteFileServer{ 177 container: container, 178 image: image, 179 host: fmt.Sprintf("%s:%s", host, port), 180 ctx: ctx, 181 client: c, 182 } 183 }