Merge pull request #513 from shouze/reset-id-pair-during-build-to-avoid-cache-busting

Reset uid/gid to 0 in build context to fix cache busting issues on ADD/COPY
master
Daniel Nephin 2017-09-13 15:19:26 -04:00 committed by GitHub
commit 7b77ab5c60
7 changed files with 124 additions and 17 deletions

View File

@ -21,6 +21,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
@ -243,6 +244,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
ExcludePatterns: excludes,
ChownOpts: &idtools.IDPair{UID: 0, GID: 0},
})
if err != nil {
return err

View File

@ -6,18 +6,57 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"syscall"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/gotestyourself/gotestyourself/fs"
"github.com/gotestyourself/gotestyourself/skip"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestRunBuildResetsUidAndGidInContext(t *testing.T) {
skip.IfCondition(t, runtime.GOOS == "windows", "uid and gid not relevant on windows")
dest := fs.NewDir(t, "test-build-context-dest")
defer dest.Remove()
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
assert.NoError(t, archive.Untar(context, dest.Path(), nil))
body := new(bytes.Buffer)
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
}
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
dir := fs.NewDir(t, "test-build-context",
fs.WithFile("foo", "some content", fs.AsUser(65534, 65534)),
fs.WithFile("Dockerfile", `
FROM alpine:3.6
COPY foo bar /
`),
)
defer dir.Remove()
options := newBuildOptions()
options.context = dir.Path()
err := runBuild(cli, options)
require.NoError(t, err)
files, err := ioutil.ReadDir(dest.Path())
require.NoError(t, err)
for _, fileInfo := range files {
assert.Equal(t, uint32(0), fileInfo.Sys().(*syscall.Stat_t).Uid)
assert.Equal(t, uint32(0), fileInfo.Sys().(*syscall.Stat_t).Gid)
}
}
func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
dest, err := ioutil.TempDir("", "test-build-compress-dest")
require.NoError(t, err)

View File

@ -20,7 +20,7 @@ github.com/gogo/protobuf v0.4
github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4
github.com/gorilla/context v1.1
github.com/gorilla/mux v1.1
github.com/gotestyourself/gotestyourself v1.1.0
github.com/gotestyourself/gotestyourself v1.2.0
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
github.com/mattn/go-shellwords v1.0.3
github.com/Microsoft/go-winio v0.4.4
@ -28,7 +28,7 @@ github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
github.com/moby/buildkit da2b9dc7dab99e824b2b1067ad7d0523e32dd2d9 https://github.com/dmcgowan/buildkit.git
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448
github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448
github.com/opencontainers/image-spec v1.0.0
github.com/opencontainers/runc d40db12e72a40109dfcf28539f5ee0930d2f0277
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9

View File

@ -31,14 +31,17 @@ func AsUser(uid, gid int) PathOp {
}
// WithFile creates a file in the directory at path with content
func WithFile(filename, content string) PathOp {
func WithFile(filename, content string, ops ...PathOp) PathOp {
return func(path Path) error {
return createFile(path.Path(), filename, content)
fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
if err := createFile(fullpath, content); err != nil {
return err
}
return applyPathOps(&File{path: fullpath}, ops)
}
}
func createFile(dir, filename, content string) error {
fullpath := filepath.Join(dir, filepath.FromSlash(filename))
func createFile(fullpath string, content string) error {
return ioutil.WriteFile(fullpath, []byte(content), 0644)
}
@ -46,7 +49,8 @@ func createFile(dir, filename, content string) error {
func WithFiles(files map[string]string) PathOp {
return func(path Path) error {
for filename, content := range files {
if err := createFile(path.Path(), filename, content); err != nil {
fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
if err := createFile(fullpath, content); err != nil {
return err
}
}
@ -61,6 +65,35 @@ func FromDir(source string) PathOp {
}
}
// WithDir creates a subdirectory in the directory at path. Additional PathOp
// can be used to modify the subdirectory
func WithDir(name string, ops ...PathOp) PathOp {
return func(path Path) error {
fullpath := filepath.Join(path.Path(), filepath.FromSlash(name))
err := os.MkdirAll(fullpath, 0755)
if err != nil {
return err
}
return applyPathOps(&Dir{path: fullpath}, ops)
}
}
func applyPathOps(path Path, ops []PathOp) error {
for _, op := range ops {
if err := op(path); err != nil {
return err
}
}
return nil
}
// WithMode sets the file mode on the directory or file at path
func WithMode(mode os.FileMode) PathOp {
return func(path Path) error {
return os.Chmod(path.Path(), mode)
}
}
func copyDirectory(source, dest string) error {
entries, err := ioutil.ReadDir(source)
if err != nil {

View File

@ -54,16 +54,17 @@ func IfCondition(t skipT, condition bool, msgAndArgs ...interface{}) {
t.Skip(formatWithCustomMessage(source, formatMessage(msgAndArgs...)))
}
// getConditionSource returns the condition string by reading it from the file
// identified in the callstack. In golang 1.9 the line number changed from
// being the line where the statement ended to the line where the statement began.
func getConditionSource() (string, error) {
const callstackIndex = 3
lines, err := getSourceLine(callstackIndex)
lines, err := getSourceLine()
if err != nil {
return "", err
}
for i := range lines {
source := strings.Join(lines[len(lines)-i-1:], "\n")
node, err := parser.ParseExpr(source)
node, err := parser.ParseExpr(getSource(lines, i))
if err == nil {
return getConditionArgFromAST(node)
}
@ -79,7 +80,8 @@ const maxContextLines = 10
// few preceding lines. To properly parse the AST a complete statement is
// required, and that statement may be split across multiple lines, so include
// up to maxContextLines.
func getSourceLine(stackIndex int) ([]string, error) {
func getSourceLine() ([]string, error) {
const stackIndex = 3
_, filename, line, ok := runtime.Caller(stackIndex)
if !ok {
return nil, errors.New("failed to get caller info")
@ -94,11 +96,8 @@ func getSourceLine(stackIndex int) ([]string, error) {
if len(lines) < line {
return nil, errors.Errorf("file %s does not have line %d", filename, line)
}
firstLine := line - maxContextLines
if firstLine < 0 {
firstLine = 0
}
return lines[firstLine:line], nil
firstLine, lastLine := getSourceLinesRange(line, len(lines))
return lines[firstLine:lastLine], nil
}
func getConditionArgFromAST(node ast.Expr) (string, error) {

View File

@ -0,0 +1,17 @@
// +build !go1.9,!go.10,!go.11,!go1.12
package skip
import "strings"
func getSourceLinesRange(line int, _ int) (int, int) {
firstLine := line - maxContextLines
if firstLine < 0 {
firstLine = 0
}
return firstLine, line
}
func getSource(lines []string, i int) string {
return strings.Join(lines[len(lines)-i-1:], "\n")
}

View File

@ -0,0 +1,17 @@
// +build go1.9
package skip
import "strings"
func getSourceLinesRange(line int, lines int) (int, int) {
lastLine := line + maxContextLines
if lastLine > lines {
lastLine = lines
}
return line - 1, lastLine
}
func getSource(lines []string, i int) string {
return strings.Join(lines[:i], "\n")
}