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  }