diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go index f85875cce97..f0939160dfb 100644 --- a/test/e2e/build_test.go +++ b/test/e2e/build_test.go @@ -5,6 +5,7 @@ package integration import ( "bytes" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -1378,4 +1379,87 @@ COPY --from=img2 /etc/alpine-release /prefix-test/container-prefix.txt` session.WaitWithDefaultTimeout() Expect(session).Should(ExitCleanly()) }) + + It("podman build --output ./folder", func() { + session := podmanTest.Podman([]string{"build", "-f", "build/basicalpine/Containerfile", "--output", podmanTest.TempDir}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + + files, err := os.ReadDir(podmanTest.TempDir) + Expect(err).ToNot(HaveOccurred()) + Expect(len(files)).To(BeNumerically(">", 1)) + }) + + It("podman build --output type=local,dest=./folder", func() { + session := podmanTest.Podman([]string{"build", "-f", "build/basicalpine/Containerfile", "--output", fmt.Sprintf("type=local,dest=%v", podmanTest.TempDir)}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + + files, err := os.ReadDir(podmanTest.TempDir) + Expect(err).ToNot(HaveOccurred()) + Expect(len(files)).To(BeNumerically(">", 1)) + }) + + It("podman build --output type=tar,dest=./folder/file.tar", func() { + session := podmanTest.Podman([]string{"build", "-f", "build/basicalpine/Containerfile", "--output", fmt.Sprintf("type=tar,dest=%v/file.tar", podmanTest.TempDir)}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + + tarFile := filepath.Join(podmanTest.TempDir, "file.tar") + _, err := os.Stat(tarFile) + Expect(err).ToNot(HaveOccurred()) + }) + + It("podman build --output -", func() { + // Capture output to buffer manually, to avoid binary output leaking into test logs + session := podmanTest.PodmanWithOptions(PodmanExecOptions{ + FullOutputWriter: io.Discard, + }, "build", "-f", "build/basicalpine/Containerfile", "--output", "-") + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + + // Check for tar header magic number + Expect(session.OutputToString()).To(ContainSubstring("ustar")) + }) + + It("podman build --output type=tar,dest=-", func() { + // Capture output to buffer manually, to avoid binary output leaking into test logs + session := podmanTest.PodmanWithOptions(PodmanExecOptions{ + FullOutputWriter: io.Discard, + }, "build", "-f", "build/basicalpine/Containerfile", "--output", "type=tar,dest=-") + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + + // Check for tar header magic number + Expect(session.OutputToString()).To(ContainSubstring("ustar")) + }) + + // Should error because no type + It("podman build --output dest=./folder", func() { + session := podmanTest.Podman([]string{"build", "-f", "build/basicalpine/Containerfile", "--output", fmt.Sprintf("dest=%v", podmanTest.TempDir)}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitWithError(125, `missing required key "type"`)) + }) + + // Should error because invalid type + It("podman build --output type=INVALID", func() { + session := podmanTest.Podman([]string{"build", "-f", "build/basicalpine/Containerfile", "--output", "type=INVALID"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitWithError(125, `invalid type "INVALID"`)) + }) + + // Should error because no dest specified + It("podman build --output type=local", func() { + session := podmanTest.Podman([]string{"build", "-f", "build/basicalpine/Containerfile", "--output", "type=local"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitWithError(125, `missing required key "dest"`)) + }) + + // Should error because invalid dest for local type + It("podman build --output type=local,dest=-", func() { + session := podmanTest.Podman([]string{"build", "-f", "build/basicalpine/Containerfile", "--output", "type=local,dest=-"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitWithError(125, `only "type=tar" can be used with "dest=-"`)) + }) + }) diff --git a/vendor/github.com/containers/buildah/define/types.go b/vendor/github.com/containers/buildah/define/types.go index 022634ce551..0c254c9491e 100644 --- a/vendor/github.com/containers/buildah/define/types.go +++ b/vendor/github.com/containers/buildah/define/types.go @@ -109,11 +109,18 @@ type Secret struct { SourceType string } +type BuildOutputType int + +const ( + BuildOutputStdout BuildOutputType = 0 // stream tar to stdout + BuildOutputLocalDir BuildOutputType = 1 + BuildOutputTar BuildOutputType = 2 +) + // BuildOutputOptions contains the the outcome of parsing the value of a build --output flag type BuildOutputOption struct { - Path string // Only valid if !IsStdout - IsDir bool - IsStdout bool + Type BuildOutputType + Path string // Only valid if Type is local dir or tar } // ConfidentialWorkloadOptions encapsulates options which control whether or not diff --git a/vendor/github.com/containers/buildah/internal/util/util.go b/vendor/github.com/containers/buildah/internal/util/util.go index b230d0e3055..ac10ee0af24 100644 --- a/vendor/github.com/containers/buildah/internal/util/util.go +++ b/vendor/github.com/containers/buildah/internal/util/util.go @@ -58,7 +58,7 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error { return err } } - if opts.IsDir { + if opts.Type == define.BuildOutputLocalDir { // In order to keep this feature as close as possible to // buildkit it was decided to preserve ownership when // invoked as root since caller already has access to artifacts @@ -80,7 +80,7 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error { } } else { outFile := os.Stdout - if !opts.IsStdout { + if opts.Type != define.BuildOutputStdout { if outFile, err = os.Create(opts.Path); err != nil { return fmt.Errorf("failed while creating destination tar at %q: %w", opts.Path, err) } diff --git a/vendor/github.com/containers/buildah/pkg/cli/build.go b/vendor/github.com/containers/buildah/pkg/cli/build.go index 9449ac883a7..1ae57b3546f 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/build.go +++ b/vendor/github.com/containers/buildah/pkg/cli/build.go @@ -279,7 +279,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) ( if err != nil { return options, nil, nil, err } - if buildOption.IsStdout { + if buildOption.Type == define.BuildOutputStdout { iopts.Quiet = true } } diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index 911d5dedac1..ddb58cee4b4 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -712,26 +712,25 @@ func AuthConfig(creds string) (*types.DockerAuthConfig, error) { // GetBuildOutput is responsible for parsing custom build output argument i.e `build --output` flag. // Takes `buildOutput` as string and returns BuildOutputOption func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) { - if buildOutput == "-" { - // Feature parity with buildkit, output tar to stdout - // Read more here: https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs - return define.BuildOutputOption{ - Path: "", - IsDir: false, - IsStdout: true, - }, nil - } - if !strings.Contains(buildOutput, ",") { - // expect default --output + // Support simple values, in the form --output ./mydir + if !strings.Contains(buildOutput, ",") && !strings.Contains(buildOutput, "=") { + if buildOutput == "-" { + // Feature parity with buildkit, output tar to stdout + // Read more here: https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs + return define.BuildOutputOption{ + Type: define.BuildOutputStdout, + Path: "", + }, nil + } + return define.BuildOutputOption{ - Path: buildOutput, - IsDir: true, - IsStdout: false, + Type: define.BuildOutputLocalDir, + Path: buildOutput, }, nil } - isDir := true - isStdout := false - typeSelected := "" + + // Support complex values, in the form --output type=local,dest=./mydir + var typeSelected define.BuildOutputType = -1 pathSelected := "" for option := range strings.SplitSeq(buildOutput, ",") { key, value, found := strings.Cut(option, "=") @@ -740,15 +739,14 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) { } switch key { case "type": - if typeSelected != "" { + if typeSelected != -1 { return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", key) } - typeSelected = value - switch typeSelected { + switch value { case "local": - isDir = true + typeSelected = define.BuildOutputLocalDir case "tar": - isDir = false + typeSelected = define.BuildOutputTar default: return define.BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", value, buildOutput) } @@ -762,17 +760,35 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) { } } - if typeSelected == "" || pathSelected == "" { - return define.BuildOutputOption{}, fmt.Errorf(`invalid build output option %q, accepted keys are "type" and "dest" must be present`, buildOutput) + // Validate there is a type + if typeSelected == -1 { + return define.BuildOutputOption{}, fmt.Errorf("missing required key %q in build output option: %q", "type", buildOutput) } + // Validate path is only set when needed + if typeSelected == define.BuildOutputLocalDir || typeSelected == define.BuildOutputTar { + if pathSelected == "" { + return define.BuildOutputOption{}, fmt.Errorf("missing required key %q in build output option: %q", "dest", buildOutput) + } + } else { + // Clear path when not needed by type + pathSelected = "" + } + + // Handle redirecting stdout for tar output if pathSelected == "-" { - if isDir { - return define.BuildOutputOption{}, fmt.Errorf(`invalid build output option %q, "type=local" can not be used with "dest=-"`, buildOutput) + if typeSelected == define.BuildOutputTar { + typeSelected = define.BuildOutputStdout + pathSelected = "" + } else { + return define.BuildOutputOption{}, fmt.Errorf(`invalid build output option %q, only "type=tar" can be used with "dest=-"`, buildOutput) } } - return define.BuildOutputOption{Path: pathSelected, IsDir: isDir, IsStdout: isStdout}, nil + return define.BuildOutputOption{ + Type: typeSelected, + Path: pathSelected, + }, nil } // TeeType parses a string value and returns a TeeType