Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 2 additions & 149 deletions cli/command/image/build.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package image

import (
"archive/tar"
"bufio"
"bytes"
"context"
"encoding/json"
Expand All @@ -20,9 +18,7 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/image/build"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/cli/opts"
buildtypes "github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/container"
Expand Down Expand Up @@ -65,7 +61,6 @@ type buildOptions struct {
target string
imageIDFile string
platform string
untrusted bool
}

// dockerfileFromStdin returns true when the user specified that the Dockerfile
Expand Down Expand Up @@ -144,7 +139,8 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/buildx/build/#target"})
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")

command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
flags.Bool("disable-content-trust", dockerCli.ContentTrustEnabled(), "Skip image verification (deprecated)")
_ = flags.MarkHidden("disable-content-trust")

flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
flags.SetAnnotation("platform", "version", []string{"1.38"})
Expand Down Expand Up @@ -286,26 +282,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
ctx, cancel := context.WithCancel(ctx)
defer cancel()

var resolvedTags []*resolvedTag
if !options.untrusted {
translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
return TrustedReference(ctx, dockerCli, ref)
}
// if there is a tar wrapper, the dockerfile needs to be replaced inside it
if buildCtx != nil {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileForContentTrust(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
} else if dockerfileCtx != nil {
// if there was not archive context still do the possible replacements in Dockerfile
newDockerfile, _, err := rewriteDockerfileFromForContentTrust(ctx, dockerfileCtx, translator)
if err != nil {
return err
}
dockerfileCtx = io.NopCloser(bytes.NewBuffer(newDockerfile))
}
}

if options.compress {
buildCtx, err = build.Compress(buildCtx)
if err != nil {
Expand Down Expand Up @@ -402,21 +378,10 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
return err
}
}
if !options.untrusted {
// Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite.
for _, resolved := range resolvedTags {
if err := trust.TagTrusted(ctx, dockerCli.Client(), dockerCli.Err(), resolved.digestRef, resolved.tagRef); err != nil {
return err
}
}
}

return nil
}

type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)

// validateTag checks if the given image name can be resolved.
func validateTag(rawRepo string) (string, error) {
_, err := reference.ParseNormalizedNamed(rawRepo)
Expand All @@ -427,118 +392,6 @@ func validateTag(rawRepo string) (string, error) {
return rawRepo, nil
}

var dockerfileFromLinePattern = lazyregexp.New(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)

// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
type resolvedTag struct {
digestRef reference.Canonical
tagRef reference.NamedTagged
}

// noBaseImageSpecifier is the symbol used by the FROM
// command to specify that no base image is to be used.
const noBaseImageSpecifier = "scratch"

// rewriteDockerfileFromForContentTrust rewrites the given Dockerfile by resolving images in
// "FROM <image>" instructions to a digest reference. `translator` is a
// function that takes a repository name and tag reference and returns a
// trusted digest reference.
// This should be called *only* when content trust is enabled
func rewriteDockerfileFromForContentTrust(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
scanner := bufio.NewScanner(dockerfile)
buf := bytes.NewBuffer(nil)

// Scan the lines of the Dockerfile, looking for a "FROM" line.
for scanner.Scan() {
line := scanner.Text()

matches := dockerfileFromLinePattern.FindStringSubmatch(line)
if matches != nil && matches[1] != noBaseImageSpecifier {
// Replace the line with a resolved "FROM repo@digest"
var ref reference.Named
ref, err = reference.ParseNormalizedNamed(matches[1])
if err != nil {
return nil, nil, err
}
ref = reference.TagNameOnly(ref)
if ref, ok := ref.(reference.NamedTagged); ok {
trustedRef, err := translator(ctx, ref)
if err != nil {
return nil, nil, err
}

line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, "FROM "+reference.FamiliarString(trustedRef))
resolvedTags = append(resolvedTags, &resolvedTag{
digestRef: trustedRef,
tagRef: ref,
})
}
}

_, err := fmt.Fprintln(buf, line)
if err != nil {
return nil, nil, err
}
}

return buf.Bytes(), resolvedTags, scanner.Err()
}

// replaceDockerfileForContentTrust wraps the given input tar archive stream and
// uses the translator to replace the Dockerfile which uses a trusted reference.
// Returns a new tar archive stream with the replaced Dockerfile.
func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser {
pipeReader, pipeWriter := io.Pipe()
go func() {
tarReader := tar.NewReader(inputTarStream)
tarWriter := tar.NewWriter(pipeWriter)

defer inputTarStream.Close()

for {
hdr, err := tarReader.Next()
if err == io.EOF {
// Signals end of archive.
_ = tarWriter.Close()
_ = pipeWriter.Close()
return
}
if err != nil {
_ = pipeWriter.CloseWithError(err)
return
}

content := io.Reader(tarReader)
if hdr.Name == dockerfileName {
// This entry is the Dockerfile. Since the tar archive was
// generated from a directory on the local filesystem, the
// Dockerfile will only appear once in the archive.
var newDockerfile []byte
newDockerfile, *resolvedTags, err = rewriteDockerfileFromForContentTrust(ctx, content, translator)
if err != nil {
_ = pipeWriter.CloseWithError(err)
return
}
hdr.Size = int64(len(newDockerfile))
content = bytes.NewBuffer(newDockerfile)
}

if err := tarWriter.WriteHeader(hdr); err != nil {
_ = pipeWriter.CloseWithError(err)
return
}

if _, err := io.Copy(tarWriter, content); err != nil {
_ = pipeWriter.CloseWithError(err)
return
}
}
}()

return pipeReader
}

func imageBuildOptions(dockerCli command.Cli, options buildOptions) buildtypes.ImageBuildOptions {
configFile := dockerCli.ConfigFile()
return buildtypes.ImageBuildOptions{
Expand Down
4 changes: 0 additions & 4 deletions cli/command/image/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
options.compress = true
options.dockerfileName = "-"
options.context = dir.Path()
options.untrusted = true
assert.NilError(t, runBuild(context.TODO(), cli, options))

expected := []string{fakeBuild.options.Dockerfile, ".dockerignore", "foo"}
Expand All @@ -74,7 +73,6 @@ func TestRunBuildResetsUidAndGidInContext(t *testing.T) {

options := newBuildOptions()
options.context = dir.Path()
options.untrusted = true
assert.NilError(t, runBuild(context.TODO(), cli, options))

headers := fakeBuild.headers(t)
Expand Down Expand Up @@ -109,7 +107,6 @@ COPY data /data
options := newBuildOptions()
options.context = dir.Path()
options.dockerfileName = df.Path()
options.untrusted = true
assert.NilError(t, runBuild(context.TODO(), cli, options))

expected := []string{fakeBuild.options.Dockerfile, ".dockerignore", "data"}
Expand Down Expand Up @@ -170,7 +167,6 @@ RUN echo hello world
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeBuild.build})
options := newBuildOptions()
options.context = tmpDir.Join("context-link")
options.untrusted = true
assert.NilError(t, runBuild(context.TODO(), cli, options))

assert.DeepEqual(t, fakeBuild.filenames(t), []string{"Dockerfile"})
Expand Down
1 change: 0 additions & 1 deletion contrib/completion/bash/docker
Original file line number Diff line number Diff line change
Expand Up @@ -2803,7 +2803,6 @@ _docker_image_build() {
"

local boolean_options="
--disable-content-trust=false
--force-rm
--help
--no-cache
Expand Down
1 change: 0 additions & 1 deletion contrib/completion/fish/docker.fish
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l cpu-quota -d
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s c -l cpu-shares -d 'CPU shares (relative weight)'
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l cpuset-cpus -d 'CPUs in which to allow execution (0-3, 0,1)'
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l cpuset-mems -d 'MEMs in which to allow execution (0-3, 0,1)'
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l disable-content-trust -d 'Skip image verification'
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s f -l file -d "Name of the Dockerfile (Default is ‘PATH/Dockerfile’)"
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l force-rm -d 'Always remove intermediate containers'
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l help -d 'Print usage'
Expand Down
1 change: 0 additions & 1 deletion contrib/completion/zsh/_docker
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,6 @@ __docker_image_subcommand() {
"($help)--cpu-rt-runtime=[Limit the CPU real-time runtime]:CPU real-time runtime in microseconds: " \
"($help)--cpuset-cpus=[CPUs in which to allow execution]:CPUs: " \
"($help)--cpuset-mems=[MEMs in which to allow execution]:MEMs: " \
"($help)--disable-content-trust[Skip image verification]" \
"($help -f --file)"{-f=,--file=}"[Name of the Dockerfile]:Dockerfile:_files" \
"($help)--force-rm[Always remove intermediate containers]" \
"($help)--isolation=[Container isolation technology]:isolation:(default hyperv process)" \
Expand Down
1 change: 0 additions & 1 deletion docs/reference/commandline/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ Build an image from a Dockerfile
| `-c`, `--cpu-shares` | `int64` | `0` | CPU shares (relative weight) |
| `--cpuset-cpus` | `string` | | CPUs in which to allow execution (0-3, 0,1) |
| `--cpuset-mems` | `string` | | MEMs in which to allow execution (0-3, 0,1) |
| `--disable-content-trust` | `bool` | `true` | Skip image verification |
| [`-f`](https://docs.docker.com/reference/cli/docker/buildx/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/buildx/build/#file) | `string` | | Name of the Dockerfile (Default is `PATH/Dockerfile`) |
| `--force-rm` | `bool` | | Always remove intermediate containers |
| `--iidfile` | `string` | | Write the image ID to the file |
Expand Down
1 change: 0 additions & 1 deletion docs/reference/commandline/builder_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ Build an image from a Dockerfile
| `-c`, `--cpu-shares` | `int64` | `0` | CPU shares (relative weight) |
| `--cpuset-cpus` | `string` | | CPUs in which to allow execution (0-3, 0,1) |
| `--cpuset-mems` | `string` | | MEMs in which to allow execution (0-3, 0,1) |
| `--disable-content-trust` | `bool` | `true` | Skip image verification |
| [`-f`](https://docs.docker.com/reference/cli/docker/buildx/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/buildx/build/#file) | `string` | | Name of the Dockerfile (Default is `PATH/Dockerfile`) |
| `--force-rm` | `bool` | | Always remove intermediate containers |
| `--iidfile` | `string` | | Write the image ID to the file |
Expand Down
1 change: 0 additions & 1 deletion docs/reference/commandline/image_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ Build an image from a Dockerfile
| `-c`, `--cpu-shares` | `int64` | `0` | CPU shares (relative weight) |
| `--cpuset-cpus` | `string` | | CPUs in which to allow execution (0-3, 0,1) |
| `--cpuset-mems` | `string` | | MEMs in which to allow execution (0-3, 0,1) |
| `--disable-content-trust` | `bool` | `true` | Skip image verification |
| [`-f`](https://docs.docker.com/reference/cli/docker/buildx/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/buildx/build/#file) | `string` | | Name of the Dockerfile (Default is `PATH/Dockerfile`) |
| `--force-rm` | `bool` | | Always remove intermediate containers |
| `--iidfile` | `string` | | Write the image ID to the file |
Expand Down
63 changes: 0 additions & 63 deletions e2e/image/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
"gotest.tools/v3/icmd"
"gotest.tools/v3/skip"
)

func TestBuildFromContextDirectoryWithTag(t *testing.T) {
Expand Down Expand Up @@ -62,68 +61,6 @@ func TestBuildFromContextDirectoryWithTag(t *testing.T) {
})
}

func TestTrustedBuild(t *testing.T) {
skip.If(t, environment.RemoteDaemon())
t.Setenv("DOCKER_BUILDKIT", "0")

dir := fixtures.SetupConfigFile(t)
defer dir.Remove()
image1 := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-build1", "latest")
image2 := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-build2", "latest")

buildDir := fs.NewDir(t, "test-trusted-build-context-dir",
fs.WithFile("Dockerfile", fmt.Sprintf(`
FROM %s as build-base
RUN echo ok > /foo
FROM %s
COPY --from=build-base foo bar
`, image1, image2)))
defer buildDir.Remove()

result := icmd.RunCmd(
icmd.Command("docker", "build", "-t", "myimage", "."),
withWorkingDir(buildDir),
fixtures.WithConfig(dir.Path()),
fixtures.WithTrust,
fixtures.WithNotary,
)

result.Assert(t, icmd.Expected{
Out: fmt.Sprintf("FROM %s@sha", image1[:len(image1)-7]),
Err: fmt.Sprintf("Tagging %s@sha", image1[:len(image1)-7]),
})
result.Assert(t, icmd.Expected{
Out: fmt.Sprintf("FROM %s@sha", image2[:len(image2)-7]),
})
}

func TestTrustedBuildUntrustedImage(t *testing.T) {
skip.If(t, environment.RemoteDaemon())
t.Setenv("DOCKER_BUILDKIT", "0")

dir := fixtures.SetupConfigFile(t)
defer dir.Remove()
buildDir := fs.NewDir(t, "test-trusted-build-context-dir",
fs.WithFile("Dockerfile", fmt.Sprintf(`
FROM %s
RUN []
`, fixtures.AlpineImage)))
defer buildDir.Remove()

result := icmd.RunCmd(
icmd.Command("docker", "build", "-t", "myimage", "."),
withWorkingDir(buildDir),
fixtures.WithConfig(dir.Path()),
fixtures.WithTrust,
fixtures.WithNotary,
)

result.Assert(t, icmd.Expected{
ExitCode: 1,
Err: "does not have trust data for",
})
}

func TestBuildIidFileSquash(t *testing.T) {
environment.SkipIfNotExperimentalDaemon(t)
t.Setenv("DOCKER_BUILDKIT", "0")
Expand Down
Loading