github.com/YousefHaggyHeroku/pack@v1.5.5/internal/build/container_ops_test.go (about)

     1  package build_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"math/rand"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/mount"
    16  
    17  	dcontainer "github.com/docker/docker/api/types/container"
    18  	"github.com/docker/docker/client"
    19  	"github.com/heroku/color"
    20  	"github.com/sclevine/spec"
    21  	"github.com/sclevine/spec/report"
    22  
    23  	"github.com/YousefHaggyHeroku/pack/internal/build"
    24  	"github.com/YousefHaggyHeroku/pack/internal/builder"
    25  	"github.com/YousefHaggyHeroku/pack/internal/container"
    26  	h "github.com/YousefHaggyHeroku/pack/testhelpers"
    27  )
    28  
    29  // TestContainerOperations are integration tests for the container operations against a docker daemon
    30  func TestContainerOperations(t *testing.T) {
    31  	rand.Seed(time.Now().UTC().UnixNano())
    32  
    33  	color.Disable(true)
    34  	defer color.Disable(false)
    35  
    36  	h.RequireDocker(t)
    37  
    38  	var err error
    39  	ctrClient, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
    40  	h.AssertNil(t, err)
    41  
    42  	spec.Run(t, "container-ops", testContainerOps, spec.Report(report.Terminal{}), spec.Sequential())
    43  }
    44  
    45  func testContainerOps(t *testing.T, when spec.G, it spec.S) {
    46  	var (
    47  		imageName string
    48  		osType    string
    49  	)
    50  
    51  	it.Before(func() {
    52  		imageName = "container-ops.test-" + h.RandString(10)
    53  
    54  		info, err := ctrClient.Info(context.TODO())
    55  		h.AssertNil(t, err)
    56  		osType = info.OSType
    57  
    58  		dockerfileContent := `FROM busybox`
    59  		if osType == "windows" {
    60  			dockerfileContent = `FROM mcr.microsoft.com/windows/nanoserver:1809`
    61  		}
    62  
    63  		h.CreateImage(t, ctrClient, imageName, dockerfileContent)
    64  
    65  		h.AssertNil(t, err)
    66  	})
    67  
    68  	it.After(func() {
    69  		h.DockerRmi(ctrClient, imageName)
    70  	})
    71  
    72  	when("#CopyDir", func() {
    73  		it("writes contents with proper owner/permissions", func() {
    74  			containerDir := "/some-vol"
    75  			if osType == "windows" {
    76  				containerDir = `c:\some-vol`
    77  			}
    78  
    79  			ctrCmd := []string{"ls", "-al", "/some-vol"}
    80  			if osType == "windows" {
    81  				ctrCmd = []string{"cmd", "/c", `dir /q /s c:\some-vol`}
    82  			}
    83  
    84  			ctx := context.Background()
    85  			ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
    86  			h.AssertNil(t, err)
    87  			defer cleanupContainer(ctx, ctr.ID)
    88  
    89  			copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app"), containerDir, 123, 456, osType, nil)
    90  
    91  			var outBuf, errBuf bytes.Buffer
    92  			err = copyDirOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
    93  			h.AssertNil(t, err)
    94  
    95  			err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
    96  			h.AssertNil(t, err)
    97  
    98  			h.AssertEq(t, errBuf.String(), "")
    99  			if osType == "windows" {
   100  				h.AssertContainsMatch(t, strings.ReplaceAll(outBuf.String(), "\r", ""), `
   101  (.*)    <DIR>          ...                    .
   102  (.*)    <DIR>          ...                    ..
   103  (.*)                17 ...                    fake-app-file
   104  (.*)    <SYMLINK>      ...                    fake-app-symlink \[fake-app-file\]
   105  (.*)                 0 ...                    file-to-ignore
   106  `)
   107  			} else {
   108  				if runtime.GOOS == "windows" {
   109  					// LCOW does not currently support symlinks
   110  					h.AssertContainsMatch(t, outBuf.String(), `
   111  -rwxrwxrwx    1 123      456 (.*) fake-app-file
   112  -rwxrwxrwx    1 123      456 (.*) fake-app-symlink
   113  -rwxrwxrwx    1 123      456 (.*) file-to-ignore
   114  `)
   115  				} else {
   116  					h.AssertContainsMatch(t, outBuf.String(), `
   117  -rw-r--r--    1 123      456 (.*) fake-app-file
   118  lrwxrwxrwx    1 123      456 (.*) fake-app-symlink -> fake-app-file
   119  -rw-r--r--    1 123      456 (.*) file-to-ignore
   120  `)
   121  				}
   122  			}
   123  		})
   124  
   125  		it("writes contents ignoring from file filter", func() {
   126  			containerDir := "/some-vol"
   127  			if osType == "windows" {
   128  				containerDir = `c:\some-vol`
   129  			}
   130  
   131  			ctrCmd := []string{"ls", "-al", "/some-vol"}
   132  			if osType == "windows" {
   133  				ctrCmd = []string{"cmd", "/c", `dir /q /s /n c:\some-vol`}
   134  			}
   135  
   136  			ctx := context.Background()
   137  			ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
   138  			h.AssertNil(t, err)
   139  			defer cleanupContainer(ctx, ctr.ID)
   140  
   141  			copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app"), containerDir, 123, 456, osType, func(filename string) bool {
   142  				return filepath.Base(filename) != "file-to-ignore"
   143  			})
   144  
   145  			var outBuf, errBuf bytes.Buffer
   146  			err = copyDirOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
   147  			h.AssertNil(t, err)
   148  
   149  			err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
   150  			h.AssertNil(t, err)
   151  
   152  			h.AssertEq(t, errBuf.String(), "")
   153  			h.AssertContains(t, outBuf.String(), "fake-app-file")
   154  			h.AssertNotContains(t, outBuf.String(), "file-to-ignore")
   155  		})
   156  
   157  		it("writes contents from zip file", func() {
   158  			containerDir := "/some-vol"
   159  			if osType == "windows" {
   160  				containerDir = `c:\some-vol`
   161  			}
   162  
   163  			ctrCmd := []string{"ls", "-al", "/some-vol"}
   164  			if osType == "windows" {
   165  				ctrCmd = []string{"cmd", "/c", `dir /q /s /n c:\some-vol`}
   166  			}
   167  
   168  			ctx := context.Background()
   169  			ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
   170  			h.AssertNil(t, err)
   171  			defer cleanupContainer(ctx, ctr.ID)
   172  
   173  			copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app.zip"), containerDir, 123, 456, osType, nil)
   174  
   175  			var outBuf, errBuf bytes.Buffer
   176  			err = copyDirOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
   177  			h.AssertNil(t, err)
   178  
   179  			err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
   180  			h.AssertNil(t, err)
   181  
   182  			h.AssertEq(t, errBuf.String(), "")
   183  			if osType == "windows" {
   184  				h.AssertContainsMatch(t, strings.ReplaceAll(outBuf.String(), "\r", ""), `
   185  (.*)    <DIR>          ...                    .
   186  (.*)    <DIR>          ...                    ..
   187  (.*)                17 ...                    fake-app-file
   188  `)
   189  			} else {
   190  				h.AssertContainsMatch(t, outBuf.String(), `
   191  -rw-r--r--    1 123      456 (.*) fake-app-file
   192  `)
   193  			}
   194  		})
   195  	})
   196  
   197  	when("#WriteStackToml", func() {
   198  		it("writes file", func() {
   199  			containerDir := "/layers-vol"
   200  			containerPath := "/layers-vol/stack.toml"
   201  			if osType == "windows" {
   202  				containerDir = `c:\layers-vol`
   203  				containerPath = `c:\layers-vol\stack.toml`
   204  			}
   205  
   206  			ctrCmd := []string{"ls", "-al", "/layers-vol/stack.toml"}
   207  			if osType == "windows" {
   208  				ctrCmd = []string{"cmd", "/c", `dir /q /n c:\layers-vol\stack.toml`}
   209  			}
   210  			ctx := context.Background()
   211  			ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
   212  			h.AssertNil(t, err)
   213  			defer cleanupContainer(ctx, ctr.ID)
   214  
   215  			writeOp := build.WriteStackToml(containerPath, builder.StackMetadata{
   216  				RunImage: builder.RunImageMetadata{
   217  					Image: "image-1",
   218  					Mirrors: []string{
   219  						"mirror-1",
   220  						"mirror-2",
   221  					},
   222  				},
   223  			}, osType)
   224  
   225  			var outBuf, errBuf bytes.Buffer
   226  			err = writeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
   227  			h.AssertNil(t, err)
   228  
   229  			err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
   230  			h.AssertNil(t, err)
   231  
   232  			h.AssertEq(t, errBuf.String(), "")
   233  			if osType == "windows" {
   234  				h.AssertContains(t, outBuf.String(), `01/01/1980  12:00 AM                69 ...                    stack.toml`)
   235  			} else {
   236  				h.AssertContains(t, outBuf.String(), `-rwxr-xr-x    1 root     root            69 Jan  1  1980 /layers-vol/stack.toml`)
   237  			}
   238  		})
   239  
   240  		it("has expected contents", func() {
   241  			containerDir := "/layers-vol"
   242  			containerPath := "/layers-vol/stack.toml"
   243  			if osType == "windows" {
   244  				containerDir = `c:\layers-vol`
   245  				containerPath = `c:\layers-vol\stack.toml`
   246  			}
   247  
   248  			ctrCmd := []string{"cat", "/layers-vol/stack.toml"}
   249  			if osType == "windows" {
   250  				ctrCmd = []string{"cmd", "/c", `type c:\layers-vol\stack.toml`}
   251  			}
   252  
   253  			ctx := context.Background()
   254  			ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
   255  			h.AssertNil(t, err)
   256  			defer cleanupContainer(ctx, ctr.ID)
   257  
   258  			writeOp := build.WriteStackToml(containerPath, builder.StackMetadata{
   259  				RunImage: builder.RunImageMetadata{
   260  					Image: "image-1",
   261  					Mirrors: []string{
   262  						"mirror-1",
   263  						"mirror-2",
   264  					},
   265  				},
   266  			}, osType)
   267  
   268  			var outBuf, errBuf bytes.Buffer
   269  			err = writeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
   270  			h.AssertNil(t, err)
   271  
   272  			err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
   273  			h.AssertNil(t, err)
   274  
   275  			h.AssertEq(t, errBuf.String(), "")
   276  			h.AssertContains(t, outBuf.String(), `[run-image]
   277    image = "image-1"
   278    mirrors = ["mirror-1", "mirror-2"]
   279  `)
   280  		})
   281  	})
   282  
   283  	when("#EnsureVolumeAccess", func() {
   284  		it("changes owner of volume", func() {
   285  			h.SkipIf(t, osType != "windows", "no-op for linux")
   286  
   287  			ctx := context.Background()
   288  
   289  			ctrCmd := []string{"ls", "-al", "/my-volume"}
   290  			containerDir := "/my-volume"
   291  			if osType == "windows" {
   292  				ctrCmd = []string{"cmd", "/c", `icacls c:\my-volume`}
   293  				containerDir = `c:\my-volume`
   294  			}
   295  
   296  			ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
   297  			h.AssertNil(t, err)
   298  			defer cleanupContainer(ctx, ctr.ID)
   299  
   300  			inspect, err := ctrClient.ContainerInspect(ctx, ctr.ID)
   301  			if err != nil {
   302  				return
   303  			}
   304  
   305  			// use container's current volumes
   306  			var ctrVolumes []string
   307  			for _, m := range inspect.Mounts {
   308  				if m.Type == mount.TypeVolume {
   309  					ctrVolumes = append(ctrVolumes, m.Name)
   310  				}
   311  			}
   312  
   313  			var outBuf, errBuf bytes.Buffer
   314  
   315  			// reuse same volume twice to demonstrate multiple ops
   316  			initVolumeOp := build.EnsureVolumeAccess(123, 456, osType, ctrVolumes[0], ctrVolumes[0])
   317  			err = initVolumeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
   318  			h.AssertNil(t, err)
   319  			err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
   320  			h.AssertNil(t, err)
   321  
   322  			h.AssertEq(t, errBuf.String(), "")
   323  			h.AssertContains(t, outBuf.String(), `BUILTIN\Users:(OI)(CI)(F)`)
   324  		})
   325  	})
   326  }
   327  
   328  func createContainer(ctx context.Context, imageName, containerDir, osType string, cmd ...string) (dcontainer.ContainerCreateCreatedBody, error) {
   329  	isolationType := dcontainer.IsolationDefault
   330  	if osType == "windows" {
   331  		isolationType = dcontainer.IsolationProcess
   332  	}
   333  
   334  	return ctrClient.ContainerCreate(ctx,
   335  		&dcontainer.Config{
   336  			Image: imageName,
   337  			Cmd:   cmd,
   338  		},
   339  		&dcontainer.HostConfig{
   340  			Binds:     []string{fmt.Sprintf("%s:%s", fmt.Sprintf("tests-volume-%s", h.RandString(5)), filepath.ToSlash(containerDir))},
   341  			Isolation: isolationType,
   342  		}, nil, "",
   343  	)
   344  }
   345  
   346  func cleanupContainer(ctx context.Context, ctrID string) {
   347  	inspect, err := ctrClient.ContainerInspect(ctx, ctrID)
   348  	if err != nil {
   349  		return
   350  	}
   351  
   352  	// remove container
   353  	ctrClient.ContainerRemove(ctx, ctrID, types.ContainerRemoveOptions{})
   354  
   355  	// remove volumes
   356  	for _, m := range inspect.Mounts {
   357  		if m.Type == mount.TypeVolume {
   358  			ctrClient.VolumeRemove(ctx, m.Name, true)
   359  		}
   360  	}
   361  }