Create a global variable to hold output file
With a function scoped `os.File`, next time the GC passes
the instance is collected, calling the finalizer and triggering
the invalidation of the FD, that cannot be used anymore.
@@ -23,6 +23,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
compose "github.com/docker/cli/cli/compose/types"
+ "github.com/docker/cli/cli/streams"
"github.com/docker/cnab-to-oci/remotes"
"github.com/docker/distribution/reference"
"github.com/moby/buildkit/client"
@@ -75,6 +76,30 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
return cmd
}
+// FIXME: DO NOT SET THIS VARIABLE DIRECTLY! Use `getOutputFile`
+// This global var prevents the file to be garbage collected and by that invalidated
+// A an alternative fix for this would be writing the output to a bytes buffer and flushing to stdout.
+// The impossibility here is that os.File is not an interface that a buffer can implement.
+// Maybe `progress.NewPrinter` should implement an "os.File-like" interface just for its needs.
+// See https://github.com/golang/go/issues/14106
+var _outputFile *os.File
+
+func getOutputFile(realOut *streams.Out, quiet bool) (*os.File, error) {
+ if _outputFile != nil {
+ return _outputFile, nil
+ }
+ if quiet {
+ var err error
+ _outputFile, err = os.Create(os.DevNull)
+ if err != nil {
+ return nil, err
+ }
+ return _outputFile, nil
+ }
+ _outputFile = os.NewFile(realOut.FD(), os.Stdout.Name())
+ return _outputFile, nil
+}
+
func runBuild(dockerCli command.Cli, contextPath string, opt buildOptions) error {
err := checkMinimalEngineVersion(dockerCli)
if err != nil {
@@ -160,14 +185,10 @@ func buildImageUsingBuildx(app *types.App, contextPath string, opt buildOptions,
},
}
- var out *os.File
- if opt.quiet {
- if out, err = os.Create(os.DevNull); err != nil {
+ out, err := getOutputFile(dockerCli.Out(), opt.quiet)
+ if err != nil {
return nil, err
}
- } else {
- out = os.NewFile(dockerCli.Out().FD(), "/dev/stdout")
- }
pw := progress.NewPrinter(ctx, out, opt.progress)
