From ca6eb5049b06af6b5a658c08a7e75cba41c66f7e Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 18 Mar 2019 10:33:59 -0700 Subject: [PATCH] build: allow setting buildkit outputs Signed-off-by: Tonis Tiigi --- cli/command/image/build.go | 53 +++++++++++++++++++++++++++++ cli/command/image/build_buildkit.go | 18 ++++++++++ 2 files changed, 71 insertions(+) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 3c67946b2956..5966cada2301 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -5,6 +5,7 @@ import ( "bufio" "bytes" "context" + "encoding/csv" "encoding/json" "fmt" "io" @@ -73,6 +74,7 @@ type buildOptions struct { untrusted bool secrets []string ssh []string + outputs []string } // dockerfileFromStdin returns true when the user specified that the Dockerfile @@ -176,6 +178,11 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { flags.StringArrayVar(&options.ssh, "ssh", []string{}, "SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format: default|[=|[,]])") flags.SetAnnotation("ssh", "version", []string{"1.39"}) flags.SetAnnotation("ssh", "buildkit", nil) + + flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)") + flags.SetAnnotation("output", "version", []string{"1.40"}) + flags.SetAnnotation("output", "buildkit", nil) + return cmd } @@ -643,3 +650,49 @@ func imageBuildOptions(dockerCli command.Cli, options buildOptions) types.ImageB Platform: options.platform, } } + +func parseOutputs(inp []string) ([]types.ImageBuildOutput, error) { + var outs []types.ImageBuildOutput + if len(inp) == 0 { + return nil, nil + } + for _, s := range inp { + csvReader := csv.NewReader(strings.NewReader(s)) + fields, err := csvReader.Read() + if err != nil { + return nil, err + } + if len(fields) == 1 && fields[0] == s { + outs = append(outs, types.ImageBuildOutput{ + Type: "local", + Attrs: map[string]string{ + "dest": s, + }, + }) + continue + } + + out := types.ImageBuildOutput{ + Attrs: map[string]string{}, + } + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return nil, errors.Errorf("invalid value %s", field) + } + key := strings.ToLower(parts[0]) + value := parts[1] + switch key { + case "type": + out.Type = value + default: + out.Attrs[key] = value + } + } + if out.Type == "" { + return nil, errors.Errorf("type is required for output") + } + outs = append(outs, out) + } + return outs, nil +} diff --git a/cli/command/image/build_buildkit.go b/cli/command/image/build_buildkit.go index cd5591e1aa02..24367ce2779c 100644 --- a/cli/command/image/build_buildkit.go +++ b/cli/command/image/build_buildkit.go @@ -117,6 +117,23 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error { defer os.RemoveAll(dockerfileDir) } + outputs, err := parseOutputs(options.outputs) + if err != nil { + return errors.Wrapf(err, "failed to parse outputs") + } + + for _, out := range outputs { + if out.Type == "local" { + // dest is handled on client side for local exporter + outDir, ok := out.Attrs["dest"] + if !ok { + return errors.Errorf("dest is required for local output") + } + delete(out.Attrs, "dest") + s.Allow(filesync.NewFSSyncTargetDir(outDir)) + } + } + if dockerfileDir != "" { s.Allow(filesync.NewFSSyncProvider([]filesync.SyncedDir{ { @@ -182,6 +199,7 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error { buildOptions.RemoteContext = remote buildOptions.SessionID = s.ID() buildOptions.BuildID = buildID + buildOptions.Outputs = outputs return doBuild(ctx, eg, dockerCli, options, buildOptions) })