Skip to content

Commit

Permalink
dockerfile: add --parents flag for COPY
Browse files Browse the repository at this point in the history
Co-Authored-By: Dmitrii Efimov <[email protected]>
Signed-off-by: Justin Chadwell <[email protected]>

Signed-off-by: Justin Chadwell <[email protected]>
  • Loading branch information
jedevc committed Jul 26, 2023
1 parent 8656037 commit 2beb32a
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 5 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ run:

build-tags:
- dfrunsecurity
- dfparents

linters:
enable:
Expand Down
10 changes: 10 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
chown: c.Chown,
chmod: c.Chmod,
link: c.Link,
parents: c.Parents,
location: c.Location(),
opt: opt,
})
Expand Down Expand Up @@ -1159,6 +1160,14 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
AllowEmptyWildcard: true,
}}, copyOpt...)

if cfg.parents {
path := strings.TrimPrefix(src, "/")
opts = append(opts, &llb.CopyInfo{
IncludePatterns: []string{path},
})
src = "/"
}

if a == nil {
a = llb.Copy(cfg.source, src, dest, opts...)
} else {
Expand Down Expand Up @@ -1249,6 +1258,7 @@ type copyConfig struct {
link bool
keepGitDir bool
checksum digest.Digest
parents bool
location []parser.Range
opt dispatchOpt
}
Expand Down
77 changes: 77 additions & 0 deletions frontend/dockerfile/dockerfile_parents_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//go:build dfparents
// +build dfparents

package dockerfile

import (
"os"
"path/filepath"
"testing"

"github.com/containerd/continuity/fs/fstest"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/frontend/dockerui"
"github.com/moby/buildkit/util/testutil/integration"
"github.com/stretchr/testify/require"
)

var parentsTests = integration.TestFuncs(
testCopyParents,
)

func init() {
allTests = append(allTests, parentsTests...)
}

func testCopyParents(t *testing.T, sb integration.Sandbox) {
f := getFrontend(t, sb)

dockerfile := []byte(`
FROM scratch
COPY --parents foo1/foo2/bar /
WORKDIR /test
COPY --parents foo1/foo2/ba* .
`)

dir, err := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateDir("foo1", 0700),
fstest.CreateDir("foo1/foo2", 0700),
fstest.CreateFile("foo1/foo2/bar", []byte(`testing`), 0600),
fstest.CreateFile("foo1/foo2/baz", []byte(`testing2`), 0600),
)
require.NoError(t, err)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

destDir := t.TempDir()

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir,
},
},
LocalDirs: map[string]string{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
}, nil)
require.NoError(t, err)

dt, err := os.ReadFile(filepath.Join(destDir, "foo1/foo2/bar"))
require.NoError(t, err)
require.Equal(t, "testing", string(dt))

dt, err = os.ReadFile(filepath.Join(destDir, "test/foo1/foo2/bar"))
require.NoError(t, err)
require.Equal(t, "testing", string(dt))
dt, err = os.ReadFile(filepath.Join(destDir, "test/foo1/foo2/baz"))
require.NoError(t, err)
require.Equal(t, "testing2", string(dt))
}
40 changes: 40 additions & 0 deletions frontend/dockerfile/docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,46 @@ path, using `--link` is always recommended. The performance of `--link` is
equivalent or better than the default behavior and, it creates much better
conditions for cache reuse.


## COPY --parents

> **Note**
>
> Available in [`docker/dockerfile-upstream:master-labs`](#syntax).
> Will be included in `docker/dockerfile:1.6-labs`.
```dockerfile
COPY [--parents[=<boolean>]] <src>... <dest>
```

The `--parents` flag preserves parent directories for `src` entries. This flag defaults to `false`.

```dockerfile
# syntax=docker/dockerfile-upstream:master-labs
FROM scratch

COPY ./x/a.txt ./y/a.txt /no_parents/
COPY --parents ./x/a.txt ./y/a.txt /parents/

# /no_parents/a.txt
# /parents/x/a.txt
# /parents/y/a.txt
```

This behavior is analogous to the [Linux `cp` utility's](https://www.man7.org/linux/man-pages/man1/cp.1.html)
`--parents` flag.

Note that, without the `--parents` flag specified, any filename collision will
fail the Linux `cp` operation with an explicit error message
(`cp: will not overwrite just-created './x/a.txt' with './y/a.txt'`), where the
Buildkit will silently overwrite the target file at the destination.

While it is possible to preserve the directory structure for `COPY`
instructions consisting of only one `src` entry, usually it is more beneficial
to keep the layer count in the resulting image as low as possible. Therefore,
with the `--parents` flag, the Buildkit is capable of packing multiple
`COPY` instructions together, keeping the directory structure intact.

## ENTRYPOINT

ENTRYPOINT has two forms:
Expand Down
9 changes: 5 additions & 4 deletions frontend/dockerfile/instructions/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,11 @@ func (c *AddCommand) Expand(expander SingleWordExpander) error {
type CopyCommand struct {
withNameAndCode
SourcesAndDest
From string
Chown string
Chmod string
Link bool
From string
Chown string
Chmod string
Link bool
Parents bool // parents preserves directory structure
}

func (c *CopyCommand) Expand(expander SingleWordExpander) error {
Expand Down
4 changes: 4 additions & 0 deletions frontend/dockerfile/instructions/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type parseRequest struct {
var parseRunPreHooks []func(*RunCommand, parseRequest) error
var parseRunPostHooks []func(*RunCommand, parseRequest) error

var parentsEnabled = false

func nodeArgs(node *parser.Node) []string {
result := []string{}
for ; node.Next != nil; node = node.Next {
Expand Down Expand Up @@ -315,6 +317,7 @@ func parseCopy(req parseRequest) (*CopyCommand, error) {
flFrom := req.flags.AddString("from", "")
flChmod := req.flags.AddString("chmod", "")
flLink := req.flags.AddBool("link", false)
flParents := req.flags.AddBool("parents", false)
if err := req.flags.Parse(); err != nil {
return nil, err
}
Expand All @@ -331,6 +334,7 @@ func parseCopy(req parseRequest) (*CopyCommand, error) {
Chown: flChown.Value,
Chmod: flChmod.Value,
Link: flLink.Value == "true",
Parents: (flParents.Value == "true") && parentsEnabled, // silently ignore if not -labs
}, nil
}

Expand Down
8 changes: 8 additions & 0 deletions frontend/dockerfile/instructions/parse_parents.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//go:build dfparents
// +build dfparents

package instructions

func init() {
parentsEnabled = true
}
2 changes: 1 addition & 1 deletion frontend/dockerfile/release/labs/tags
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dfrunsecurity
dfrunsecurity dfparents

0 comments on commit 2beb32a

Please sign in to comment.