New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable usage of lifecycle image for windows #823
Changes from all commits
5b2130c
6ef83d2
b2cdd90
21ba14b
1c7bc2c
e8a6b83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We ended up changing this to match the required behavior for Windows, that you can't create file symlinks as the lowest-privileged user |
||
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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On Windows, |
||
) | ||
|
||
|
@@ -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 | ||
) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change test stacks to use a UID/GID that more closely match the This is also closer to the current samples nanoserver stack |
||
USER ContainerAdministrator | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there another test in the section that tries to build Windows containers? If not, can we add one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests wouldn't exercise any new code paths due to mocking. Now that we've removed guard that returned an error for windows containers, there are no more windows-specific code paths. |
||
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{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,43 +8,39 @@ import ( | |
"io/ioutil" | ||
"os" | ||
"runtime" | ||
"strings" | ||
|
||
"github.com/BurntSushi/toml" | ||
"github.com/docker/docker/api/types" | ||
dcontainer "github.com/docker/docker/api/types/container" | ||
"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), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added better documentation for xcopy command |
||
}, | ||
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" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no-op for Linux, but I wonder if Linux and Windows should eventually be setting appropriate permissions for arbitrary volumes passed in with |
||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uses the |
||
} | ||
|
||
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, | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥳