Skip to content

Commit 90384ad

Browse files
committed
dockerfile: promote --parents flag from labs
Signed-off-by: Jonathan A. Sternberg <[email protected]>
1 parent 202e28f commit 90384ad

File tree

7 files changed

+204
-35
lines changed

7 files changed

+204
-35
lines changed

docker-bake.hcl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ target "lint" {
233233
matrix = {
234234
buildtags = [
235235
{ name = "default", tags = "", target = "golangci-lint" },
236-
{ name = "labs", tags = "dfrunsecurity dfparents", target = "golangci-lint" },
236+
{ name = "labs", tags = "dfrunsecurity dfrundevice", target = "golangci-lint" },
237237
{ name = "nydus", tags = "nydus", target = "golangci-lint" },
238238
{ name = "yaml", tags = "", target = "yamllint" },
239239
{ name = "golangci-verify", tags = "", target = "golangci-verify" },

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,14 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
16451645
return errors.Wrap(err, "removing drive letter")
16461646
}
16471647

1648+
for i, requiredPath := range requiredPaths {
1649+
p, err := system.NormalizePath("/", requiredPath, d.platform.OS, false)
1650+
if err != nil {
1651+
return errors.Wrap(err, "removing drive letter")
1652+
}
1653+
requiredPaths[i] = p
1654+
}
1655+
16481656
unpack := cfg.isAddCommand
16491657
if cfg.unpack != nil {
16501658
unpack = *cfg.unpack

frontend/dockerfile/dockerfile_parents_test.go

Lines changed: 187 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build dfparents
2-
31
package dockerfile
42

53
import (
@@ -18,6 +16,10 @@ import (
1816
var parentsTests = integration.TestFuncs(
1917
testCopyParents,
2018
testCopyRelativeParents,
19+
// Duplicate of testCopyRelativeParents but more simplified
20+
// so it will run on Windows. Remove when testCopyRelativeParents
21+
// works on Windows.
22+
testCopyRelativeParentsSimplified,
2123
testCopyParentsMissingDirectory,
2224
)
2325

@@ -78,6 +80,7 @@ COPY --parents foo1/foo2/ba* .
7880
}
7981

8082
func testCopyRelativeParents(t *testing.T, sb integration.Sandbox) {
83+
integration.SkipOnPlatform(t, "windows")
8184
f := getFrontend(t, sb)
8285

8386
dockerfile := []byte(`
@@ -182,7 +185,189 @@ eot
182185
}
183186
}
184187

188+
func testCopyRelativeParentsSimplified(t *testing.T, sb integration.Sandbox) {
189+
f := getFrontend(t, sb)
190+
191+
dockerfile := []byte(integration.UnixOrWindows(`
192+
FROM alpine AS base
193+
WORKDIR /test
194+
RUN <<eot
195+
set -ex
196+
mkdir -p a/b/c/d/e
197+
mkdir -p a/b2/c/d/e
198+
mkdir -p a/b/c2/d/e
199+
mkdir -p a/b/c2/d/e2
200+
touch a/b/c/d/foo
201+
touch a/b/c/d/e/bay
202+
touch a/b2/c/d/e/bar
203+
touch a/b/c2/d/e/baz
204+
touch a/b/c2/d/e2/baz
205+
eot
206+
207+
FROM alpine AS middle
208+
COPY --from=base --parents /test/a/b/./c/d /out/
209+
RUN <<eot
210+
set -ex
211+
[ -d /out/c/d/e ]
212+
[ -f /out/c/d/foo ]
213+
[ ! -d /out/a ]
214+
[ ! -d /out/e ]
215+
eot
216+
217+
FROM alpine AS end
218+
COPY --from=base --parents /test/a/b/c/d/. /out/
219+
RUN <<eot
220+
set -ex
221+
[ -d /out/test/a/b/c/d/e ]
222+
[ -f /out/test/a/b/c/d/foo ]
223+
eot
224+
225+
FROM alpine AS start
226+
COPY --from=base --parents ./test/a/b/c/d /out/
227+
RUN <<eot
228+
set -ex
229+
[ -d /out/test/a/b/c/d/e ]
230+
[ -f /out/test/a/b/c/d/foo ]
231+
eot
232+
233+
FROM alpine AS double
234+
COPY --from=base --parents /test/a/./b/./c /out/
235+
RUN <<eot
236+
set -ex
237+
[ -d /out/b/c/d/e ]
238+
[ -f /out/b/c/d/foo ]
239+
eot
240+
241+
FROM alpine AS wildcard
242+
COPY --from=base --parents /test/a/./*/c /out/
243+
RUN <<eot
244+
set -ex
245+
[ -d /out/b/c/d/e ]
246+
[ -f /out/b2/c/d/e/bar ]
247+
eot
248+
249+
FROM alpine AS doublewildcard
250+
COPY --from=base --parents /test/a/b*/./c/**/e /out/
251+
RUN <<eot
252+
set -ex
253+
[ -d /out/c/d/e ]
254+
[ -f /out/c/d/e/bay ] # via b
255+
[ -f /out/c/d/e/bar ] # via b2
256+
eot
257+
258+
FROM alpine AS doubleinputs
259+
COPY --from=base --parents /test/a/b/c*/./d/**/baz /test/a/b*/./c/**/bar /out/
260+
RUN <<eot
261+
set -ex
262+
[ -f /out/d/e/baz ]
263+
[ ! -f /out/d/e/bay ]
264+
[ -f /out/d/e2/baz ]
265+
[ -f /out/c/d/e/bar ] # via b2
266+
eot
267+
`, `
268+
FROM nanoserver:plus AS base
269+
USER ContainerAdministrator
270+
WORKDIR /test
271+
RUN <<eot
272+
mkdir a\b\c\d\e
273+
mkdir a\b2\c\d\e
274+
mkdir a\b\c2\d\e
275+
mkdir a\b\c2\d\e2
276+
type nul > a\b\c\d\foo
277+
type nul > a\b\c\d\e\bay
278+
type nul > a\b2\c\d\e\bar
279+
type nul > a\b\c2\d\e\baz
280+
type nul > a\b\c2\d\e2\baz
281+
eot
282+
283+
FROM nanoserver:plus AS middle
284+
USER ContainerAdministrator
285+
COPY --from=base --parents /test/a/b/./c/d /out/
286+
RUN <<eot
287+
if not exist \out\c\d\e exit 1
288+
if not exist \out\c\d\foo exit 1
289+
if exist \out\a exit 1
290+
if exist \out\e exit 1
291+
eot
292+
293+
FROM nanoserver:plus AS end
294+
USER ContainerAdministrator
295+
COPY --from=base --parents /test/a/b/c/d/. /out/
296+
RUN <<eot
297+
if not exist \out\test\a\b\c\d\e exit 1
298+
if not exist \out\test\a\b\c\d\foo exit 1
299+
eot
300+
301+
FROM nanoserver:plus AS start
302+
USER ContainerAdministrator
303+
COPY --from=base --parents ./test/a/b/c/d /out/
304+
RUN <<eot
305+
if not exist \out\test\a\b\c\d\e exit 1
306+
if not exist \out\test\a\b\c\d\foo exit 1
307+
eot
308+
309+
FROM nanoserver:plus AS double
310+
USER ContainerAdministrator
311+
COPY --from=base --parents /test/a/./b/./c /out/
312+
RUN <<eot
313+
if not exist \out\b\c\d\e exit 1
314+
if not exist \out\b\c\d\foo exit 1
315+
eot
316+
317+
FROM alpine AS wildcard
318+
USER ContainerAdministrator
319+
COPY --from=base --parents /test/a/./*/c /out/
320+
RUN <<eot
321+
if not exist \out\b\c\d\e exit 1
322+
if not exist \out\b2\c\d\e\bar exit 1
323+
eot
324+
325+
FROM alpine AS doublewildcard
326+
USER ContainerAdministrator
327+
COPY --from=base --parents /test/a/b*/./c/**/e /out/
328+
RUN <<eot
329+
if not exist \out\c\d\e exit 1
330+
if not exist \out\c\d\e\bay exit 1
331+
if not exist \out\c\d\e\bar exit 1
332+
eot
333+
334+
FROM alpine AS doubleinputs
335+
USER ContainerAdministrator
336+
COPY --from=base --parents /test/a/b/c*/./d/**/baz /test/a/b*/./c/**/bar /out/
337+
RUN <<eot
338+
if not exist \out\d\e\baz exit 1
339+
if exist \out\d\e\bay exit 1
340+
if not exist \out\d\e2\baz exit 1
341+
if not exist \out\c\d\e\bar exit 1
342+
eot
343+
`))
344+
345+
dir := integration.Tmpdir(
346+
t,
347+
fstest.CreateFile("Dockerfile", dockerfile, 0600),
348+
)
349+
350+
c, err := client.New(sb.Context(), sb.Address())
351+
require.NoError(t, err)
352+
defer c.Close()
353+
354+
for _, target := range []string{"middle", "end", "start", "double", "wildcard", "doublewildcard", "doubleinputs"} {
355+
t.Logf("target: %s", target)
356+
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
357+
FrontendAttrs: map[string]string{
358+
"target": target,
359+
},
360+
LocalMounts: map[string]fsutil.FS{
361+
dockerui.DefaultLocalNameDockerfile: dir,
362+
dockerui.DefaultLocalNameContext: dir,
363+
},
364+
}, nil)
365+
require.NoError(t, err)
366+
}
367+
}
368+
185369
func testCopyParentsMissingDirectory(t *testing.T, sb integration.Sandbox) {
370+
integration.SkipOnPlatform(t, "windows")
186371
f := getFrontend(t, sb)
187372

188373
dockerfile := []byte(`

frontend/dockerfile/docs/reference.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,7 @@ The available `[OPTIONS]` are:
16351635
| [`--chown`](#copy---chown---chmod) | |
16361636
| [`--chmod`](#copy---chown---chmod) | 1.2 |
16371637
| [`--link`](#copy---link) | 1.4 |
1638-
| [`--parents`](#copy---parents) | 1.7-labs |
1638+
| [`--parents`](#copy---parents) | 1.20 |
16391639
| [`--exclude`](#copy---exclude) | 1.19 |
16401640

16411641
The `COPY` instruction copies new files or directories from `<src>` and adds
@@ -1926,17 +1926,14 @@ conditions for cache reuse.
19261926

19271927
### COPY --parents
19281928

1929-
> [!NOTE]
1930-
> Not yet available in stable syntax, use [`docker/dockerfile:1-labs`](#syntax) version.
1931-
19321929
```dockerfile
19331930
COPY [--parents[=<boolean>]] <src> ... <dest>
19341931
```
19351932

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

19381935
```dockerfile
1939-
# syntax=docker/dockerfile:1-labs
1936+
# syntax=docker/dockerfile:1
19401937
FROM scratch
19411938

19421939
COPY ./x/a.txt ./y/a.txt /no_parents/
@@ -1956,7 +1953,7 @@ directories after it will be preserved. This may be especially useful copies bet
19561953
with `--from` where the source paths need to be absolute.
19571954

19581955
```dockerfile
1959-
# syntax=docker/dockerfile:1-labs
1956+
# syntax=docker/dockerfile:1
19601957
FROM scratch
19611958

19621959
COPY --parents ./x/./y/*.txt /parents/

frontend/dockerfile/instructions/parse.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ var (
3636
parseRunPostHooks []func(*RunCommand, parseRequest) error
3737
)
3838

39-
var parentsEnabled = false
40-
4139
func nodeArgs(node *parser.Node) []string {
4240
result := []string{}
4341
for ; node.Next != nil; node = node.Next {
@@ -313,14 +311,6 @@ func parseSourcesAndDest(req parseRequest, command string) (*SourcesAndDest, err
313311
}, nil
314312
}
315313

316-
func stringValuesFromFlagIfPossible(f *Flag) []string {
317-
if f == nil {
318-
return nil
319-
}
320-
321-
return f.StringValues
322-
}
323-
324314
func parseAdd(req parseRequest) (*AddCommand, error) {
325315
if len(req.args) < 2 {
326316
return nil, errNoDestinationArgument("ADD")
@@ -362,7 +352,7 @@ func parseAdd(req parseRequest) (*AddCommand, error) {
362352
Link: flLink.Value == "true",
363353
KeepGitDir: keepGit,
364354
Checksum: flChecksum.Value,
365-
ExcludePatterns: stringValuesFromFlagIfPossible(flExcludes),
355+
ExcludePatterns: flExcludes.StringValues,
366356
Unpack: unpack,
367357
}, nil
368358
}
@@ -372,16 +362,12 @@ func parseCopy(req parseRequest) (*CopyCommand, error) {
372362
return nil, errNoDestinationArgument("COPY")
373363
}
374364

375-
var flParents *Flag
376-
if parentsEnabled {
377-
flParents = req.flags.AddBool("parents", false)
378-
}
379-
380365
flChown := req.flags.AddString("chown", "")
381366
flFrom := req.flags.AddString("from", "")
382367
flChmod := req.flags.AddString("chmod", "")
383368
flLink := req.flags.AddBool("link", false)
384369
flExcludes := req.flags.AddStrings("exclude")
370+
flParents := req.flags.AddBool("parents", false)
385371

386372
if err := req.flags.Parse(); err != nil {
387373
return nil, err
@@ -399,8 +385,8 @@ func parseCopy(req parseRequest) (*CopyCommand, error) {
399385
Chown: flChown.Value,
400386
Chmod: flChmod.Value,
401387
Link: flLink.Value == "true",
402-
Parents: flParents != nil && flParents.Value == "true",
403-
ExcludePatterns: stringValuesFromFlagIfPossible(flExcludes),
388+
Parents: flParents.Value == "true",
389+
ExcludePatterns: flExcludes.StringValues,
404390
}, nil
405391
}
406392

frontend/dockerfile/instructions/parse_parents.go

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
dfrunsecurity dfparents dfrundevice
1+
dfrunsecurity dfrundevice

0 commit comments

Comments
 (0)