diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go
index aa941894d..53eac1938 100644
--- a/acceptance/acceptance_test.go
+++ b/acceptance/acceptance_test.go
@@ -616,8 +616,6 @@ func testAcceptance(
var untrustedBuilderName string
it.Before(func() {
- h.SkipIf(t, dockerHostOS() == "windows", "untrusted builders are not yet supported for windows builds")
-
var err error
untrustedBuilderName, err = createBuilder(
t,
@@ -630,9 +628,6 @@ func testAcceptance(
})
it.After(func() {
- if dockerHostOS() == "windows" {
- return
- }
h.DockerRmi(dockerCli, untrustedBuilderName)
})
@@ -2360,6 +2355,7 @@ func assertMockAppRunsWithOutput(t *testing.T, assert h.AssertionManager, repoNa
ctrID := runDockerImageExposePort(t, assert, containerName, repoName)
defer dockerCli.ContainerKill(context.TODO(), containerName, "SIGKILL")
defer dockerCli.ContainerRemove(context.TODO(), containerName, dockertypes.ContainerRemoveOptions{Force: true})
+
logs, err := dockerCli.ContainerLogs(context.TODO(), ctrID, dockertypes.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
diff --git a/acceptance/testdata/mock_app/run b/acceptance/testdata/mock_app/run
index a1653205f..93630c7ef 100755
--- a/acceptance/testdata/mock_app/run
+++ b/acceptance/testdata/mock_app/run
@@ -6,7 +6,7 @@ port="${1-8080}"
echo "listening on port $port"
-resp=$(echo "HTTP/1.1 200 OK\n" && cat "$PWD"/*-dep /contents*.txt)
+resp=$(echo "HTTP/1.1 200 OK\n" && cat "$PWD"/*-deps/*-dep /contents*.txt)
while true; do
nc -l -p "$port" -c "echo \"$resp\""
done
diff --git a/acceptance/testdata/mock_app/run.bat b/acceptance/testdata/mock_app/run.bat
index 1183af17e..4fb4646cc 100644
--- a/acceptance/testdata/mock_app/run.bat
+++ b/acceptance/testdata/mock_app/run.bat
@@ -3,6 +3,6 @@
set port=8080
if [%1] neq [] set port=%1
-C:\util\server.exe -p %port% -g "%cd%\*-dep, c:\contents*.txt"
+C:\util\server.exe -p %port% -g "%cd%\*-deps\*-dep, c:\contents*.txt"
diff --git a/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build
index 24cb49967..fc20479ce 100755
--- a/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build
+++ b/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build
@@ -15,7 +15,7 @@ if [[ -f "$platform_dir/env/ENV1_CONTENTS" ]]; then
mkdir "$launch_dir/env1-launch-layer"
contents=$(cat "$platform_dir/env/ENV1_CONTENTS")
echo "$contents" > "$launch_dir/env1-launch-layer/env1-launch-dep"
- ln -snf "$launch_dir/env1-launch-layer/env1-launch-dep" env1-launch-dep
+ ln -snf "$launch_dir/env1-launch-layer" env1-launch-deps
echo "launch = true" > "$launch_dir/env1-launch-layer.toml"
fi
@@ -25,7 +25,7 @@ if [[ -f "$platform_dir/env/ENV2_CONTENTS" ]]; then
mkdir "$launch_dir/env2-launch-layer"
contents=$(cat "$platform_dir/env/ENV2_CONTENTS")
echo "$contents" > "$launch_dir/env2-launch-layer/env2-launch-dep"
- ln -snf "$launch_dir/env2-launch-layer/env2-launch-dep" env2-launch-dep
+ ln -snf "$launch_dir/env2-launch-layer" env2-launch-deps
echo "launch = true" > "$launch_dir/env2-launch-layer.toml"
fi
diff --git a/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build.bat b/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build.bat
index bc0b4f071..dd5e96d5c 100644
--- a/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build.bat
+++ b/acceptance/testdata/mock_buildpacks/0.2/read-env-buildpack/bin/build.bat
@@ -10,7 +10,7 @@ if exist %platform_dir%\env\ENV1_CONTENTS (
mkdir %launch_dir%\env1-launch-layer
set /p contents=<%platform_dir%\env\ENV1_CONTENTS
echo !contents!> %launch_dir%\env1-launch-layer\env1-launch-dep
- mklink env1-launch-dep %launch_dir%\env1-launch-layer\env1-launch-dep
+ mklink /j env1-launch-deps %launch_dir%\env1-launch-layer
echo launch = true> %launch_dir%\env1-launch-layer.toml
)
@@ -20,7 +20,7 @@ if exist %platform_dir%\env\ENV2_CONTENTS (
mkdir %launch_dir%\env2-launch-layer
set /p contents=<%platform_dir%\env\ENV2_CONTENTS
echo !contents!> %launch_dir%\env2-launch-layer\env2-launch-dep
- mklink env2-launch-dep %launch_dir%\env2-launch-layer\env2-launch-dep
+ mklink /j env2-launch-deps %launch_dir%\env2-launch-layer
echo launch = true> %launch_dir%\env2-launch-layer.toml
)
diff --git a/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build
index e2a7c1bb2..01568be0f 100755
--- a/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build
+++ b/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build
@@ -16,7 +16,7 @@ echo "Color: [0mStyled"
mkdir "$launch_dir/launch-layer"
echo "Launch Dep Contents" > "$launch_dir/launch-layer/launch-dep"
-ln -snf "$launch_dir/launch-layer/launch-dep" launch-dep
+ln -snf "$launch_dir/launch-layer" launch-deps
echo "launch = true" > "$launch_dir/launch-layer.toml"
## makes a cached launch layer
@@ -24,12 +24,12 @@ if [[ ! -f "$launch_dir/cached-launch-layer.toml" ]]; then
echo "making cached launch layer"
mkdir "$launch_dir/cached-launch-layer"
echo "Cached Dep Contents" > "$launch_dir/cached-launch-layer/cached-dep"
- ln -snf "$launch_dir/cached-launch-layer/cached-dep" cached-dep
+ ln -snf "$launch_dir/cached-launch-layer" cached-deps
echo "launch = true" > "$launch_dir/cached-launch-layer.toml"
echo "cache = true" >> "$launch_dir/cached-launch-layer.toml"
else
echo "reusing cached launch layer"
- ln -snf "$launch_dir/cached-launch-layer/cached-dep" cached-dep
+ ln -snf "$launch_dir/cached-launch-layer" cached-deps
fi
## adds a process
diff --git a/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build.bat b/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build.bat
index 8da9e3ec6..2047dc714 100644
--- a/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build.bat
+++ b/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/bin/build.bat
@@ -4,23 +4,23 @@ echo --- Build: Simple Layers Buildpack
set launch_dir=%1
:: makes a launch layer
-echo making launch layer
+echo making launch layer %launch_dir%\launch-layer
mkdir %launch_dir%\launch-layer
echo Launch Dep Contents > "%launch_dir%\launch-layer\launch-dep
-mklink launch-dep %launch_dir%\launch-layer\launch-dep
+mklink /j launch-deps %launch_dir%\launch-layer
echo launch = true > %launch_dir%\launch-layer.toml
:: makes a cached launch layer
if not exist %launch_dir%\cached-launch-layer.toml (
- echo making cached launch layer
+ echo making cached launch layer %launch_dir%\cached-launch-layer
mkdir %launch_dir%\cached-launch-layer
echo Cached Dep Contents > %launch_dir%\cached-launch-layer\cached-dep
- mklink cached-dep %launch_dir%\cached-launch-layer\cached-dep
+ mklink /j cached-deps %launch_dir%\cached-launch-layer
echo launch = true > %launch_dir%\cached-launch-layer.toml
echo cache = true >> %launch_dir%\cached-launch-layer.toml
) else (
- echo reusing cached launch layer
- mklink cached-dep %launch_dir%\cached-launch-layer\cached-dep
+ echo reusing cached launch layer %launch_dir%\cached-launch-layer
+ mklink /j cached-deps %launch_dir%\cached-launch-layer
)
:: adds a process
diff --git a/acceptance/testdata/mock_stack/windows/build/Dockerfile b/acceptance/testdata/mock_stack/windows/build/Dockerfile
index 0c0e063e2..37029d129 100644
--- a/acceptance/testdata/mock_stack/windows/build/Dockerfile
+++ b/acceptance/testdata/mock_stack/windows/build/Dockerfile
@@ -1,8 +1,8 @@
FROM mcr.microsoft.com/windows/nanoserver:1809
-# placeholder values until correct values are deteremined
-ENV CNB_USER_ID=0
-ENV CNB_GROUP_ID=0
+# non-zero sets all user-owned directories to BUILTIN\Users
+ENV CNB_USER_ID=1
+ENV CNB_GROUP_ID=1
USER ContainerAdministrator
diff --git a/acceptance/testdata/mock_stack/windows/run/Dockerfile b/acceptance/testdata/mock_stack/windows/run/Dockerfile
index 1e8bd9316..76bcfbe6b 100644
--- a/acceptance/testdata/mock_stack/windows/run/Dockerfile
+++ b/acceptance/testdata/mock_stack/windows/run/Dockerfile
@@ -9,9 +9,9 @@ FROM mcr.microsoft.com/windows/nanoserver:1809
COPY --from=gobuild /util/server.exe /util/server.exe
-# placeholder values until correct values are deteremined
-ENV CNB_USER_ID=0
-ENV CNB_GROUP_ID=0
+# non-zero sets all user-owned directories to BUILTIN\Users
+ENV CNB_USER_ID=1
+ENV CNB_GROUP_ID=1
USER ContainerAdministrator
diff --git a/build.go b/build.go
index 81e3c4c88..cc84c76a2 100644
--- a/build.go
+++ b/build.go
@@ -311,10 +311,6 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
}
func lifecycleImageSupported(builderOS string, lifecycleVersion *builder.Version) bool {
- if builderOS == "windows" {
- return false
- }
-
return lifecycleVersion.Equal(builder.VersionMustParse(prevLifecycleVersionSupportingImage)) ||
!lifecycleVersion.LessThan(semver.MustParse(minLifecycleVersionSupportingImage))
}
diff --git a/build_test.go b/build_test.go
index 991b3565b..02c176bc6 100644
--- a/build_test.go
+++ b/build_test.go
@@ -1447,18 +1447,6 @@ func testBuild(t *testing.T, when spec.G, it spec.S) {
})
when("builder is untrusted", func() {
- when("building Windows containers", func() {
- it("errors and mentions that builder must be trusted", func() {
- defaultBuilderImage.SetPlatform("windows", "", "")
- h.AssertError(t, subject.Build(context.TODO(), BuildOptions{
- Image: "some/app",
- Builder: defaultBuilderName,
- Publish: true,
- TrustBuilder: false,
- }), "does not have an associated lifecycle image. Builder must be trusted.")
- })
- })
-
when("lifecycle image is available", func() {
it("uses the 5 phases with the lifecycle image", func() {
h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{
diff --git a/internal/build/container_ops.go b/internal/build/container_ops.go
index aa659f499..8d1b43002 100644
--- a/internal/build/container_ops.go
+++ b/internal/build/container_ops.go
@@ -8,7 +8,6 @@ import (
"io/ioutil"
"os"
"runtime"
- "strings"
"github.com/BurntSushi/toml"
"github.com/docker/docker/api/types"
@@ -16,35 +15,32 @@ import (
"github.com/docker/docker/client"
"github.com/pkg/errors"
+ "github.com/buildpacks/pack/internal/paths"
+
"github.com/buildpacks/pack/internal/archive"
"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/internal/container"
- "github.com/buildpacks/pack/internal/style"
)
type ContainerOperation func(ctrClient client.CommonAPIClient, ctx context.Context, containerID string, stdout, stderr io.Writer) error
// CopyDir copies a local directory (src) to the destination on the container while filtering files and changing it's UID/GID.
-func CopyDir(src, dst string, uid, gid int, fileFilter func(string) bool) ContainerOperation {
+func CopyDir(src, dst string, uid, gid int, os string, fileFilter func(string) bool) ContainerOperation {
return func(ctrClient client.CommonAPIClient, ctx context.Context, containerID string, stdout, stderr io.Writer) error {
- info, err := ctrClient.Info(ctx)
- if err != nil {
- return err
+ tarPath := dst
+ if os == "windows" {
+ tarPath = paths.WindowsToSlash(dst)
}
- if info.OSType == "windows" {
- readerDst := strings.ReplaceAll(dst, `\`, "/")[2:] // Strip volume, convert slashes to conform to TAR format
- reader, err := createReader(src, readerDst, uid, gid, fileFilter)
- if err != nil {
- return errors.Wrapf(err, "create tar archive from '%s'", src)
- }
- defer reader.Close()
- return copyDirWindows(ctx, ctrClient, containerID, reader, dst, stdout, stderr)
- }
- reader, err := createReader(src, dst, uid, gid, fileFilter)
+
+ reader, err := createReader(src, tarPath, uid, gid, fileFilter)
if err != nil {
return errors.Wrapf(err, "create tar archive from '%s'", src)
}
defer reader.Close()
+
+ if os == "windows" {
+ return copyDirWindows(ctx, ctrClient, containerID, reader, dst, stdout, stderr)
+ }
return copyDir(ctx, ctrClient, containerID, reader)
}
}
@@ -76,17 +72,13 @@ func copyDir(ctx context.Context, ctrClient client.CommonAPIClient, containerID
// for Windows containers and does not work. Instead, we perform the copy from inside a container
// using xcopy.
// See: https://github.com/moby/moby/issues/40771
-func copyDirWindows(ctx context.Context, ctrClient client.CommonAPIClient, containerID string, appReader io.Reader, dst string, stdout, stderr io.Writer) error {
+func copyDirWindows(ctx context.Context, ctrClient client.CommonAPIClient, containerID string, reader io.Reader, dst string, stdout, stderr io.Writer) error {
info, err := ctrClient.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
- pathElements := strings.Split(dst, `\`)
- if len(pathElements) < 1 {
- return fmt.Errorf("cannot determine base name for destination path: %s", style.Symbol(dst))
- }
- baseName := pathElements[len(pathElements)-1]
+ baseName := paths.WindowsBasename(dst)
mnt, err := findMount(info, dst)
if err != nil {
@@ -97,10 +89,16 @@ func copyDirWindows(ctx context.Context, ctrClient client.CommonAPIClient, conta
&dcontainer.Config{
Image: info.Image,
Cmd: []string{
- "xcopy",
- fmt.Sprintf(`c:\windows\%s`, baseName),
- dst,
- "/e", "/h", "/y", "/c", "/b",
+ "cmd",
+ "/c",
+
+ //xcopy args
+ // e - recursively create subdirectories
+ // h - copy hidden and system files
+ // b - copy symlinks, do not dereference
+ // x - copy attributes
+ // y - suppress prompting
+ fmt.Sprintf(`xcopy c:\windows\%s %s /e /h /b /x /y`, baseName, dst),
},
WorkingDir: "/",
User: windowsContainerAdmin,
@@ -116,7 +114,7 @@ func copyDirWindows(ctx context.Context, ctrClient client.CommonAPIClient, conta
}
defer ctrClient.ContainerRemove(context.Background(), ctr.ID, types.ContainerRemoveOptions{Force: true})
- err = ctrClient.CopyToContainer(ctx, ctr.ID, "/windows", appReader, types.CopyToContainerOptions{})
+ err = ctrClient.CopyToContainer(ctx, ctr.ID, "/windows", reader, types.CopyToContainerOptions{})
if err != nil {
return errors.Wrap(err, "copy app to container")
}
@@ -136,11 +134,11 @@ func findMount(info types.ContainerJSON, dst string) (types.MountPoint, error) {
return m, nil
}
}
- return types.MountPoint{}, errors.New("no matching mount found")
+ return types.MountPoint{}, fmt.Errorf("no matching mount found for %s", dst)
}
// WriteStackToml writes a `stack.toml` based on the StackMetadata provided to the destination path.
-func WriteStackToml(dstPath string, stack builder.StackMetadata) ContainerOperation {
+func WriteStackToml(dstPath string, stack builder.StackMetadata, os string) ContainerOperation {
return func(ctrClient client.CommonAPIClient, ctx context.Context, containerID string, stdout, stderr io.Writer) error {
buf := &bytes.Buffer{}
err := toml.NewEncoder(buf).Encode(stack)
@@ -149,10 +147,21 @@ func WriteStackToml(dstPath string, stack builder.StackMetadata) ContainerOperat
}
tarBuilder := archive.TarBuilder{}
- tarBuilder.AddFile(dstPath, 0755, archive.NormalizedDateTime, buf.Bytes())
+
+ tarPath := dstPath
+ if os == "windows" {
+ tarPath = paths.WindowsToSlash(dstPath)
+ }
+
+ tarBuilder.AddFile(tarPath, 0755, archive.NormalizedDateTime, buf.Bytes())
reader := tarBuilder.Reader(archive.DefaultTarWriterFactory())
defer reader.Close()
+ if os == "windows" {
+ dirName := paths.WindowsDir(dstPath)
+ return copyDirWindows(ctx, ctrClient, containerID, reader, dirName, stdout, stderr)
+ }
+
return ctrClient.CopyToContainer(ctx, containerID, "/", reader, types.CopyToContainerOptions{})
}
}
@@ -174,3 +183,67 @@ func createReader(src, dst string, uid, gid int, fileFilter func(string) bool) (
return archive.ReadZipAsTar(src, dst, uid, gid, -1, false, fileFilter), nil
}
+
+//EnsureVolumeAccess grants full access permissions to volumes for UID/GID-based user
+//When UID/GID are 0 it grants explicit full access to BUILTIN\Administrators and any other UID/GID grants full access to BUILTIN\Users
+//Changing permissions on volumes through stopped containers does not work on Docker for Windows so we start the container and make change using icacls
+//See: https://github.com/moby/moby/issues/40771
+func EnsureVolumeAccess(uid, gid int, os string, volumeNames ...string) ContainerOperation {
+ return func(ctrClient client.CommonAPIClient, ctx context.Context, containerID string, stdout, stderr io.Writer) error {
+ if os != "windows" {
+ return nil
+ }
+
+ containerInfo, err := ctrClient.ContainerInspect(ctx, containerID)
+ if err != nil {
+ return err
+ }
+
+ cmd := ""
+ binds := []string{}
+ for i, volumeName := range volumeNames {
+ containerPath := fmt.Sprintf("c:/volume-mnt-%d", i)
+ binds = append(binds, fmt.Sprintf("%s:%s", volumeName, containerPath))
+
+ if cmd != "" {
+ cmd += "&&"
+ }
+
+ //icacls args
+ // /grant - add new permissions instead of replacing
+ // (OI) - object inherit
+ // (CI) - container inherit
+ // F - full access
+ // /t - recursively apply
+ // /l - perform on a symbolic link itself versus its target
+ // /q - suppress success messages
+ cmd += fmt.Sprintf(`icacls %s /grant *%s:(OI)(CI)F /t /l /q`, containerPath, paths.WindowsPathSID(uid, gid))
+ }
+
+ ctr, err := ctrClient.ContainerCreate(ctx,
+ &dcontainer.Config{
+ Image: containerInfo.Image,
+ Cmd: []string{"cmd", "/c", cmd},
+ WorkingDir: "/",
+ User: windowsContainerAdmin,
+ },
+ &dcontainer.HostConfig{
+ Binds: binds,
+ Isolation: dcontainer.IsolationProcess,
+ },
+ nil, "",
+ )
+ if err != nil {
+ return err
+ }
+ defer ctrClient.ContainerRemove(context.Background(), ctr.ID, types.ContainerRemoveOptions{Force: true})
+
+ return container.Run(
+ ctx,
+ ctrClient,
+ ctr.ID,
+ ioutil.Discard, // Suppress icacls output
+ stderr,
+ )
+ }
+}
diff --git a/internal/build/container_ops_test.go b/internal/build/container_ops_test.go
index 61abcfe0e..b0fb38e28 100644
--- a/internal/build/container_ops_test.go
+++ b/internal/build/container_ops_test.go
@@ -7,9 +7,13 @@ import (
"math/rand"
"path/filepath"
"runtime"
+ "strings"
"testing"
"time"
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/mount"
+
dcontainer "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/heroku/color"
@@ -35,22 +39,30 @@ func TestContainerOperations(t *testing.T) {
ctrClient, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
h.AssertNil(t, err)
- info, err := ctrClient.Info(context.TODO())
- h.AssertNil(t, err)
- h.SkipIf(t, info.OSType == "windows", "These tests are not yet compatible with Windows-based containers")
-
spec.Run(t, "container-ops", testContainerOps, spec.Report(report.Terminal{}), spec.Sequential())
}
func testContainerOps(t *testing.T, when spec.G, it spec.S) {
var (
- imageName string
- outBuf, errBuf bytes.Buffer
+ imageName string
+ osType string
)
it.Before(func() {
imageName = "container-ops.test-" + h.RandString(10)
- h.CreateImage(t, ctrClient, imageName, `FROM busybox`)
+
+ info, err := ctrClient.Info(context.TODO())
+ h.AssertNil(t, err)
+ osType = info.OSType
+
+ dockerfileContent := `FROM busybox`
+ if osType == "windows" {
+ dockerfileContent = `FROM mcr.microsoft.com/windows/nanoserver:1809`
+ }
+
+ h.CreateImage(t, ctrClient, imageName, dockerfileContent)
+
+ h.AssertNil(t, err)
})
it.After(func() {
@@ -59,72 +71,148 @@ func testContainerOps(t *testing.T, when spec.G, it spec.S) {
when("#CopyDir", func() {
it("writes contents with proper owner/permissions", func() {
- copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app"), "/some-location", 123, 456, nil)
- ctx := context.Background()
+ containerDir := "/some-vol"
+ if osType == "windows" {
+ containerDir = `c:\some-vol`
+ }
+
+ ctrCmd := []string{"ls", "-al", "/some-vol"}
+ if osType == "windows" {
+ ctrCmd = []string{"cmd", "/c", `dir /q /s c:\some-vol`}
+ }
- ctr, err := createContainer(ctx, imageName, "ls", "-al", "/some-location")
+ ctx := context.Background()
+ ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
h.AssertNil(t, err)
+ defer cleanupContainer(ctx, ctr.ID)
+
+ copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app"), containerDir, 123, 456, osType, nil)
+ var outBuf, errBuf bytes.Buffer
err = copyDirOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
- perms := "-rw-r--r--"
- if runtime.GOOS == "windows" {
- perms = "-rwxrwxrwx"
+ h.AssertEq(t, errBuf.String(), "")
+ if osType == "windows" {
+ h.AssertContainsMatch(t, strings.ReplaceAll(outBuf.String(), "\r", ""), `
+(.*)
... .
+(.*) ... ..
+(.*) 17 ... fake-app-file
+(.*) ... fake-app-symlink \[fake-app-file\]
+(.*) 0 ... file-to-ignore
+`)
+ } else {
+ if runtime.GOOS == "windows" {
+ // LCOW does not currently support symlinks
+ h.AssertContainsMatch(t, outBuf.String(), `
+-rwxrwxrwx 1 123 456 (.*) fake-app-file
+-rwxrwxrwx 1 123 456 (.*) fake-app-symlink
+-rwxrwxrwx 1 123 456 (.*) file-to-ignore
+`)
+ } else {
+ h.AssertContainsMatch(t, outBuf.String(), `
+-rw-r--r-- 1 123 456 (.*) fake-app-file
+lrwxrwxrwx 1 123 456 (.*) fake-app-symlink -> fake-app-file
+-rw-r--r-- 1 123 456 (.*) file-to-ignore
+`)
+ }
}
-
- output := outBuf.String()
- h.AssertContainsMatch(t, output, fmt.Sprintf(`
-%s 1 123 456 (.*) fake-app-file
-%s 1 123 456 (.*) file-to-ignore
-`, perms, perms))
})
it("writes contents ignoring from file filter", func() {
- copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app"), "/some-location", 123, 456, func(filename string) bool {
- return filepath.Base(filename) != "file-to-ignore"
- })
- ctx := context.Background()
+ containerDir := "/some-vol"
+ if osType == "windows" {
+ containerDir = `c:\some-vol`
+ }
- ctr, err := createContainer(ctx, imageName, "ls", "-al", "/some-location")
+ ctrCmd := []string{"ls", "-al", "/some-vol"}
+ if osType == "windows" {
+ ctrCmd = []string{"cmd", "/c", `dir /q /s /n c:\some-vol`}
+ }
+
+ ctx := context.Background()
+ ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
h.AssertNil(t, err)
+ defer cleanupContainer(ctx, ctr.ID)
+ copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app"), containerDir, 123, 456, osType, func(filename string) bool {
+ return filepath.Base(filename) != "file-to-ignore"
+ })
+
+ var outBuf, errBuf bytes.Buffer
err = copyDirOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
- output := outBuf.String()
- h.AssertNotContains(t, output, "file-to-ignore")
+ h.AssertEq(t, errBuf.String(), "")
+ h.AssertContains(t, outBuf.String(), "fake-app-file")
+ h.AssertNotContains(t, outBuf.String(), "file-to-ignore")
})
it("writes contents from zip file", func() {
- copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app.zip"), "/some-location", 123, 456, nil)
- ctx := context.Background()
+ containerDir := "/some-vol"
+ if osType == "windows" {
+ containerDir = `c:\some-vol`
+ }
- ctr, err := createContainer(ctx, imageName, "ls", "-al", "/some-location")
+ ctrCmd := []string{"ls", "-al", "/some-vol"}
+ if osType == "windows" {
+ ctrCmd = []string{"cmd", "/c", `dir /q /s /n c:\some-vol`}
+ }
+
+ ctx := context.Background()
+ ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
h.AssertNil(t, err)
+ defer cleanupContainer(ctx, ctr.ID)
+ copyDirOp := build.CopyDir(filepath.Join("testdata", "fake-app.zip"), containerDir, 123, 456, osType, nil)
+
+ var outBuf, errBuf bytes.Buffer
err = copyDirOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
- output := outBuf.String()
- h.AssertContainsMatch(t, output, `
+ h.AssertEq(t, errBuf.String(), "")
+ if osType == "windows" {
+ h.AssertContainsMatch(t, strings.ReplaceAll(outBuf.String(), "\r", ""), `
+(.*) ... .
+(.*) ... ..
+(.*) 17 ... fake-app-file
+`)
+ } else {
+ h.AssertContainsMatch(t, outBuf.String(), `
-rw-r--r-- 1 123 456 (.*) fake-app-file
`)
+ }
})
})
when("#WriteStackToml", func() {
it("writes file", func() {
- writeOp := build.WriteStackToml("/some/stack.toml", builder.StackMetadata{
+ containerDir := "/layers-vol"
+ containerPath := "/layers-vol/stack.toml"
+ if osType == "windows" {
+ containerDir = `c:\layers-vol`
+ containerPath = `c:\layers-vol\stack.toml`
+ }
+
+ ctrCmd := []string{"ls", "-al", "/layers-vol/stack.toml"}
+ if osType == "windows" {
+ ctrCmd = []string{"cmd", "/c", `dir /q /n c:\layers-vol\stack.toml`}
+ }
+ ctx := context.Background()
+ ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
+ h.AssertNil(t, err)
+ defer cleanupContainer(ctx, ctr.ID)
+
+ writeOp := build.WriteStackToml(containerPath, builder.StackMetadata{
RunImage: builder.RunImageMetadata{
Image: "image-1",
Mirrors: []string{
@@ -132,23 +220,42 @@ func testContainerOps(t *testing.T, when spec.G, it spec.S) {
"mirror-2",
},
},
- })
- ctx := context.Background()
- ctr, err := createContainer(ctx, imageName, "ls", "-al", "/some/stack.toml")
- h.AssertNil(t, err)
+ }, osType)
+ var outBuf, errBuf bytes.Buffer
err = writeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
- output := outBuf.String()
- h.AssertContains(t, output, `-rwxr-xr-x 1 root root 69 Jan 1 1980 /some/stack.toml`)
+ h.AssertEq(t, errBuf.String(), "")
+ if osType == "windows" {
+ h.AssertContains(t, outBuf.String(), `01/01/1980 12:00 AM 69 ... stack.toml`)
+ } else {
+ h.AssertContains(t, outBuf.String(), `-rwxr-xr-x 1 root root 69 Jan 1 1980 /layers-vol/stack.toml`)
+ }
})
it("has expected contents", func() {
- writeOp := build.WriteStackToml("/some/stack.toml", builder.StackMetadata{
+ containerDir := "/layers-vol"
+ containerPath := "/layers-vol/stack.toml"
+ if osType == "windows" {
+ containerDir = `c:\layers-vol`
+ containerPath = `c:\layers-vol\stack.toml`
+ }
+
+ ctrCmd := []string{"cat", "/layers-vol/stack.toml"}
+ if osType == "windows" {
+ ctrCmd = []string{"cmd", "/c", `type c:\layers-vol\stack.toml`}
+ }
+
+ ctx := context.Background()
+ ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
+ h.AssertNil(t, err)
+ defer cleanupContainer(ctx, ctr.ID)
+
+ writeOp := build.WriteStackToml(containerPath, builder.StackMetadata{
RunImage: builder.RunImageMetadata{
Image: "image-1",
Mirrors: []string{
@@ -156,29 +263,99 @@ func testContainerOps(t *testing.T, when spec.G, it spec.S) {
"mirror-2",
},
},
- })
- ctx := context.Background()
- ctr, err := createContainer(ctx, imageName, "cat", "/some/stack.toml")
- h.AssertNil(t, err)
+ }, osType)
+ var outBuf, errBuf bytes.Buffer
err = writeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)
- output := outBuf.String()
- h.AssertContains(t, output, `[run-image]
+ h.AssertEq(t, errBuf.String(), "")
+ h.AssertContains(t, outBuf.String(), `[run-image]
image = "image-1"
mirrors = ["mirror-1", "mirror-2"]
`)
})
})
+
+ when("#EnsureVolumeAccess", func() {
+ it("changes owner of volume", func() {
+ h.SkipIf(t, osType != "windows", "no-op for linux")
+
+ ctx := context.Background()
+
+ ctrCmd := []string{"ls", "-al", "/my-volume"}
+ containerDir := "/my-volume"
+ if osType == "windows" {
+ ctrCmd = []string{"cmd", "/c", `icacls c:\my-volume`}
+ containerDir = `c:\my-volume`
+ }
+
+ ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
+ h.AssertNil(t, err)
+ defer cleanupContainer(ctx, ctr.ID)
+
+ inspect, err := ctrClient.ContainerInspect(ctx, ctr.ID)
+ if err != nil {
+ return
+ }
+
+ // use container's current volumes
+ var ctrVolumes []string
+ for _, m := range inspect.Mounts {
+ if m.Type == mount.TypeVolume {
+ ctrVolumes = append(ctrVolumes, m.Name)
+ }
+ }
+
+ var outBuf, errBuf bytes.Buffer
+
+ // reuse same volume twice to demonstrate multiple ops
+ initVolumeOp := build.EnsureVolumeAccess(123, 456, osType, ctrVolumes[0], ctrVolumes[0])
+ err = initVolumeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
+ h.AssertNil(t, err)
+ err = container.Run(ctx, ctrClient, ctr.ID, &outBuf, &errBuf)
+ h.AssertNil(t, err)
+
+ h.AssertEq(t, errBuf.String(), "")
+ h.AssertContains(t, outBuf.String(), `BUILTIN\Users:(OI)(CI)(F)`)
+ })
+ })
}
-func createContainer(ctx context.Context, imageName string, cmd ...string) (dcontainer.ContainerCreateCreatedBody, error) {
+func createContainer(ctx context.Context, imageName, containerDir, osType string, cmd ...string) (dcontainer.ContainerCreateCreatedBody, error) {
+ isolationType := dcontainer.IsolationDefault
+ if osType == "windows" {
+ isolationType = dcontainer.IsolationProcess
+ }
+
return ctrClient.ContainerCreate(ctx,
- &dcontainer.Config{Image: imageName, Cmd: cmd},
- &dcontainer.HostConfig{}, nil, "",
+ &dcontainer.Config{
+ Image: imageName,
+ Cmd: cmd,
+ },
+ &dcontainer.HostConfig{
+ Binds: []string{fmt.Sprintf("%s:%s", fmt.Sprintf("tests-volume-%s", h.RandString(5)), filepath.ToSlash(containerDir))},
+ Isolation: isolationType,
+ }, nil, "",
)
}
+
+func cleanupContainer(ctx context.Context, ctrID string) {
+ inspect, err := ctrClient.ContainerInspect(ctx, ctrID)
+ if err != nil {
+ return
+ }
+
+ // remove container
+ ctrClient.ContainerRemove(ctx, ctrID, types.ContainerRemoveOptions{})
+
+ // remove volumes
+ for _, m := range inspect.Mounts {
+ if m.Type == mount.TypeVolume {
+ ctrClient.VolumeRemove(ctx, m.Name, true)
+ }
+ }
+}
diff --git a/internal/build/lifecycle_execution.go b/internal/build/lifecycle_execution.go
index 0814d2d09..03fd87c9b 100644
--- a/internal/build/lifecycle_execution.go
+++ b/internal/build/lifecycle_execution.go
@@ -42,6 +42,11 @@ func NewLifecycleExecution(logger logging.Logger, docker client.CommonAPIClient,
return nil, err
}
+ osType, err := opts.Builder.Image().OS()
+ if err != nil {
+ return nil, err
+ }
+
exec := &LifecycleExecution{
logger: logger,
docker: docker,
@@ -49,16 +54,10 @@ func NewLifecycleExecution(logger logging.Logger, docker client.CommonAPIClient,
appVolume: paths.FilterReservedNames("pack-app-" + randString(10)),
platformAPI: latestSupportedPlatformAPI,
opts: opts,
+ os: osType,
+ mountPaths: mountPathsForOS(osType),
}
- os, err := opts.Builder.Image().OS()
- if err != nil {
- return nil, err
- }
-
- exec.os = os
- exec.mountPaths = mountPathsForOS(os)
-
return exec, nil
}
@@ -196,7 +195,7 @@ func (l *LifecycleExecution) Create(
WithArgs(repoName),
WithNetwork(networkMode),
WithBinds(append(volumes, fmt.Sprintf("%s:%s", cacheName, l.mountPaths.cacheDir()))...),
- WithContainerOperations(CopyDir(l.opts.AppPath, l.mountPaths.appDir(), l.opts.Builder.UID(), l.opts.Builder.GID(), l.opts.FileFilter)),
+ WithContainerOperations(CopyDir(l.opts.AppPath, l.mountPaths.appDir(), l.opts.Builder.UID(), l.opts.Builder.GID(), l.os, l.opts.FileFilter)),
}
if publish {
@@ -232,7 +231,10 @@ func (l *LifecycleExecution) Detect(ctx context.Context, networkMode string, vol
),
WithNetwork(networkMode),
WithBinds(volumes...),
- WithContainerOperations(CopyDir(l.opts.AppPath, l.mountPaths.appDir(), l.opts.Builder.UID(), l.opts.Builder.GID(), l.opts.FileFilter)),
+ WithContainerOperations(
+ EnsureVolumeAccess(l.opts.Builder.UID(), l.opts.Builder.GID(), l.os, l.layersVolume, l.appVolume),
+ CopyDir(l.opts.AppPath, l.mountPaths.appDir(), l.opts.Builder.UID(), l.opts.Builder.GID(), l.os, l.opts.FileFilter),
+ ),
)
detect := phaseFactory.New(configProvider)
@@ -389,7 +391,7 @@ func (l *LifecycleExecution) newExport(repoName, runImage string, publish bool,
WithRoot(),
WithNetwork(networkMode),
WithBinds(fmt.Sprintf("%s:%s", cacheName, l.mountPaths.cacheDir())),
- WithContainerOperations(WriteStackToml(l.mountPaths.stackPath(), l.opts.Builder.Stack())),
+ WithContainerOperations(WriteStackToml(l.mountPaths.stackPath(), l.opts.Builder.Stack(), l.os)),
}
if publish {
diff --git a/internal/build/lifecycle_execution_test.go b/internal/build/lifecycle_execution_test.go
index e8849c3b2..6a233cf83 100644
--- a/internal/build/lifecycle_execution_test.go
+++ b/internal/build/lifecycle_execution_test.go
@@ -626,8 +626,9 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertSliceContains(t, configProvider.HostConfig().Binds, expectedBind)
- h.AssertEq(t, len(configProvider.ContainerOps()), 1)
- h.AssertFunctionName(t, configProvider.ContainerOps()[0], "CopyDir")
+ h.AssertEq(t, len(configProvider.ContainerOps()), 2)
+ h.AssertFunctionName(t, configProvider.ContainerOps()[0], "EnsureVolumeAccess")
+ h.AssertFunctionName(t, configProvider.ContainerOps()[1], "CopyDir")
})
})
diff --git a/internal/build/phase_test.go b/internal/build/phase_test.go
index 03f613b40..fbf5d2411 100644
--- a/internal/build/phase_test.go
+++ b/internal/build/phase_test.go
@@ -71,6 +71,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) {
outBuf, errBuf bytes.Buffer
docker client.CommonAPIClient
logger logging.Logger
+ osType string
)
it.Before(func() {
@@ -79,6 +80,11 @@ func testPhase(t *testing.T, when spec.G, it spec.S) {
var err error
docker, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
h.AssertNil(t, err)
+
+ info, err := ctrClient.Info(context.Background())
+ h.AssertNil(t, err)
+ osType = info.OSType
+
lifecycleExec, err = CreateFakeLifecycleExecution(logger, docker, filepath.Join("testdata", "fake-app"), repoName)
h.AssertNil(t, err)
phaseFactory = build.NewDefaultPhaseFactory(lifecycleExec)
@@ -140,6 +146,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) {
"/workspace",
lifecycleExec.Builder().UID(),
lifecycleExec.Builder().GID(),
+ osType,
nil,
),
),
@@ -164,7 +171,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) {
when("app is a dir", func() {
it("preserves original mod times", func() {
- assertAppModTimePreserved(t, lifecycleExec, phaseFactory, &outBuf, &errBuf)
+ assertAppModTimePreserved(t, lifecycleExec, phaseFactory, &outBuf, &errBuf, osType)
})
})
@@ -175,7 +182,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, err)
phaseFactory = build.NewDefaultPhaseFactory(lifecycleExec)
- assertAppModTimePreserved(t, lifecycleExec, phaseFactory, &outBuf, &errBuf)
+ assertAppModTimePreserved(t, lifecycleExec, phaseFactory, &outBuf, &errBuf, osType)
})
})
@@ -215,7 +222,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) {
lifecycleExec,
build.WithArgs("read", "/workspace/fake-app-file"),
build.WithContainerOperations(
- build.CopyDir(lifecycleExec.AppPath(), "/workspace", 0, 0, nil),
+ build.CopyDir(lifecycleExec.AppPath(), "/workspace", 0, 0, osType, nil),
),
))
h.AssertNil(t, err)
@@ -367,14 +374,14 @@ func testPhase(t *testing.T, when spec.G, it spec.S) {
})
}
-func assertAppModTimePreserved(t *testing.T, lifecycle *build.LifecycleExecution, phaseFactory build.PhaseFactory, outBuf *bytes.Buffer, errBuf *bytes.Buffer) {
+func assertAppModTimePreserved(t *testing.T, lifecycle *build.LifecycleExecution, phaseFactory build.PhaseFactory, outBuf *bytes.Buffer, errBuf *bytes.Buffer, osType string) {
t.Helper()
readPhase := phaseFactory.New(build.NewPhaseConfigProvider(
phaseName,
lifecycle,
build.WithArgs("read", "/workspace/fake-app-file"),
build.WithContainerOperations(
- build.CopyDir(lifecycle.AppPath(), "/workspace", 0, 0, nil),
+ build.CopyDir(lifecycle.AppPath(), "/workspace", 0, 0, osType, nil),
),
))
assertRunSucceeds(t, readPhase, outBuf, errBuf)
diff --git a/internal/build/testdata/fake-app/fake-app-symlink b/internal/build/testdata/fake-app/fake-app-symlink
new file mode 120000
index 000000000..89264cea2
--- /dev/null
+++ b/internal/build/testdata/fake-app/fake-app-symlink
@@ -0,0 +1 @@
+fake-app-file
\ No newline at end of file
diff --git a/internal/container/run.go b/internal/container/run.go
index 218776a4c..04bedc065 100644
--- a/internal/container/run.go
+++ b/internal/container/run.go
@@ -15,21 +15,24 @@ import (
func Run(ctx context.Context, docker client.CommonAPIClient, ctrID string, out, errOut io.Writer) error {
bodyChan, errChan := docker.ContainerWait(ctx, ctrID, dcontainer.WaitConditionNextExit)
- if err := docker.ContainerStart(ctx, ctrID, types.ContainerStartOptions{}); err != nil {
- return errors.Wrap(err, "container start")
- }
- logs, err := docker.ContainerLogs(ctx, ctrID, types.ContainerLogsOptions{
- ShowStdout: true,
- ShowStderr: true,
- Follow: true,
+ resp, err := docker.ContainerAttach(ctx, ctrID, types.ContainerAttachOptions{
+ Stream: true,
+ Stdout: true,
+ Stderr: true,
})
if err != nil {
- return errors.Wrap(err, "container logs stdout")
+ return err
+ }
+ defer resp.Close()
+
+ if err := docker.ContainerStart(ctx, ctrID, types.ContainerStartOptions{}); err != nil {
+ return errors.Wrap(err, "container start")
}
copyErr := make(chan error)
go func() {
- _, err := stdcopy.StdCopy(out, errOut, logs)
+ _, err := stdcopy.StdCopy(out, errOut, resp.Reader)
+
copyErr <- err
}()
@@ -41,5 +44,6 @@ func Run(ctx context.Context, docker client.CommonAPIClient, ctrID string, out,
case err := <-errChan:
return err
}
+
return <-copyErr
}
diff --git a/internal/paths/paths.go b/internal/paths/paths.go
index 813e22b21..083de50a8 100644
--- a/internal/paths/paths.go
+++ b/internal/paths/paths.go
@@ -15,8 +15,8 @@ func IsURI(ref string) bool {
return schemeRegexp.MatchString(ref)
}
-func IsDir(path string) (bool, error) {
- fileInfo, err := os.Stat(path)
+func IsDir(p string) (bool, error) {
+ fileInfo, err := os.Stat(p)
if err != nil {
return false, err
}
@@ -24,22 +24,22 @@ func IsDir(path string) (bool, error) {
return fileInfo.IsDir(), nil
}
-func FilePathToURI(path string) (string, error) {
+func FilePathToURI(p string) (string, error) {
var err error
- if !filepath.IsAbs(path) {
- path, err = filepath.Abs(path)
+ if !filepath.IsAbs(p) {
+ p, err = filepath.Abs(p)
if err != nil {
return "", err
}
}
if runtime.GOOS == "windows" {
- if strings.HasPrefix(path, `\\`) {
- return "file://" + filepath.ToSlash(strings.TrimPrefix(path, `\\`)), nil
+ if strings.HasPrefix(p, `\\`) {
+ return "file://" + filepath.ToSlash(strings.TrimPrefix(p, `\\`)), nil
}
- return "file:///" + filepath.ToSlash(path), nil
+ return "file:///" + filepath.ToSlash(p), nil
}
- return "file://" + path, nil
+ return "file://" + p, nil
}
// examples:
@@ -87,7 +87,7 @@ func ToAbsolute(uri, relativeTo string) (string, error) {
return uri, nil
}
-func FilterReservedNames(path string) string {
+func FilterReservedNames(p string) string {
// The following keys are reserved on Windows
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces
reservedNameConversions := map[string]string{
@@ -99,8 +99,46 @@ func FilterReservedNames(path string) string {
"prn": "p_r_n",
}
for k, v := range reservedNameConversions {
- path = strings.Replace(path, k, v, -1)
+ p = strings.Replace(p, k, v, -1)
}
- return path
+ return p
+}
+
+//WindowsDir is equivalent to path.Dir or filepath.Dir but always for Windows paths
+//reproduced because Windows implementation is not exported
+func WindowsDir(p string) string {
+ pathElements := strings.Split(p, `\`)
+
+ dirName := strings.Join(pathElements[:len(pathElements)-1], `\`)
+
+ return dirName
+}
+
+//WindowsBasename is equivalent to path.Basename or filepath.Basename but always for Windows paths
+//reproduced because Windows implementation is not exported
+func WindowsBasename(p string) string {
+ pathElements := strings.Split(p, `\`)
+
+ return pathElements[len(pathElements)-1]
+}
+
+//WindowsToSlash is equivalent to path.ToSlash or filepath.ToSlash but always for Windows paths
+//reproduced because Windows implementation is not exported
+func WindowsToSlash(p string) string {
+ slashPath := strings.ReplaceAll(p, `\`, "/") // convert slashes
+ if len(slashPath) < 2 {
+ return ""
+ }
+
+ return slashPath[2:] // strip volume
+}
+
+//WindowsPathSID returns the appropriate SID for a given UID and GID
+//This the basic logic for path permissions in Pack and Lifecycle
+func WindowsPathSID(uid, gid int) string {
+ if uid == 0 && gid == 0 {
+ return "S-1-5-32-544" // BUILTIN\Administrators
+ }
+ return "S-1-5-32-545" // BUILTIN\Users
}
diff --git a/internal/paths/paths_test.go b/internal/paths/paths_test.go
index e9e240f59..a96c97057 100644
--- a/internal/paths/paths_test.go
+++ b/internal/paths/paths_test.go
@@ -154,4 +154,61 @@ func testPaths(t *testing.T, when spec.G, it spec.S) {
})
})
})
+
+ when("#WindowsDir", func() {
+ it("returns the path directory", func() {
+ path := WindowsDir(`C:\layers\file.txt`)
+ h.AssertEq(t, path, `C:\layers`)
+ })
+
+ it("returns empty for empty", func() {
+ path := WindowsBasename("")
+ h.AssertEq(t, path, "")
+ })
+ })
+
+ when("#WindowsBasename", func() {
+ it("returns the path basename", func() {
+ path := WindowsBasename(`C:\layers\file.txt`)
+ h.AssertEq(t, path, `file.txt`)
+ })
+
+ it("returns empty for empty", func() {
+ path := WindowsBasename("")
+ h.AssertEq(t, path, "")
+ })
+ })
+
+ when("#WindowsToSlash", func() {
+ it("returns the path; backward slashes converted to forward with volume stripped ", func() {
+ path := WindowsToSlash(`C:\layers\file.txt`)
+ h.AssertEq(t, path, `/layers/file.txt`)
+ })
+
+ it("returns / for volume", func() {
+ path := WindowsToSlash(`c:\`)
+ h.AssertEq(t, path, `/`)
+ })
+
+ it("returns empty for empty", func() {
+ path := WindowsToSlash("")
+ h.AssertEq(t, path, "")
+ })
+ })
+
+ when("#WindowsPathSID", func() {
+ when("UID and GID are both 0", func() {
+ it(`returns the built-in BUILTIN\Administrators SID`, func() {
+ sid := WindowsPathSID(0, 0)
+ h.AssertEq(t, sid, "S-1-5-32-544")
+ })
+ })
+
+ when("UID and GID are both non-zero", func() {
+ it(`returns the built-in BUILTIN\Users SID`, func() {
+ sid := WindowsPathSID(99, 99)
+ h.AssertEq(t, sid, "S-1-5-32-545")
+ })
+ })
+ })
}
diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go
index 67c35ddde..1ae8847f9 100644
--- a/testhelpers/testhelpers.go
+++ b/testhelpers/testhelpers.go
@@ -580,31 +580,22 @@ func SkipUnless(t *testing.T, expression bool, reason string) {
func RunContainer(ctx context.Context, dockerCli client.CommonAPIClient, id string, stdout io.Writer, stderr io.Writer) error {
bodyChan, errChan := dockerCli.ContainerWait(ctx, id, container.WaitConditionNextExit)
- if err := dockerCli.ContainerStart(ctx, id, dockertypes.ContainerStartOptions{}); err != nil {
- return errors.Wrap(err, "container start")
- }
-
- info, err := dockerCli.Info(ctx)
+ logs, err := dockerCli.ContainerAttach(ctx, id, dockertypes.ContainerAttachOptions{
+ Stream: true,
+ Stdout: true,
+ Stderr: true,
+ })
if err != nil {
- return errors.Wrap(err, "getting docker info")
- }
- if info.OSType == "windows" {
- // wait for logs to show
- time.Sleep(time.Second)
+ return err
}
- logs, err := dockerCli.ContainerLogs(ctx, id, dockertypes.ContainerLogsOptions{
- ShowStdout: true,
- ShowStderr: true,
- Follow: true,
- })
- if err != nil {
- return errors.Wrap(err, "container logs stdout")
+ if err := dockerCli.ContainerStart(ctx, id, dockertypes.ContainerStartOptions{}); err != nil {
+ return errors.Wrap(err, "container start")
}
copyErr := make(chan error)
go func() {
- _, err := stdcopy.StdCopy(stdout, stderr, logs)
+ _, err := stdcopy.StdCopy(stdout, stderr, logs.Reader)
copyErr <- err
}()