Skip to content

Commit c0ca3b9

Browse files
committed
add support for local output
1 parent aceb174 commit c0ca3b9

File tree

2 files changed

+438
-4
lines changed

2 files changed

+438
-4
lines changed

pkg/build/build.go

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package build
1818

1919
import (
2020
"context"
21+
"errors"
22+
"fmt"
2123
"io"
2224
"os"
2325
"path/filepath"
@@ -29,6 +31,7 @@ import (
2931
"github.com/moby/buildkit/cmd/buildctl/build"
3032
"github.com/moby/buildkit/session"
3133
"github.com/sirupsen/logrus"
34+
"github.com/tonistiigi/go-csvvalue"
3235
"google.golang.org/grpc"
3336
)
3437

@@ -53,7 +56,7 @@ func Build(ctx context.Context, opts *BOpts) error {
5356
}
5457
defer buildkit.Close()
5558

56-
exports, err := build.ParseOutput(opts.Outputs)
59+
exports, err := parseOutput(opts.Outputs)
5760
if err != nil {
5861
return err
5962
}
@@ -85,10 +88,21 @@ func Build(ctx context.Context, opts *BOpts) error {
8588

8689
exportsWithOutput := []client.ExportEntry{}
8790
for _, export := range exports {
88-
export.Output = func(map[string]string) (io.WriteCloser, error) {
89-
return wf, nil
91+
switch export.Type {
92+
case client.ExporterLocal:
93+
localDest := filepath.Join(GlobalExportPath, opts.BuildID, "local")
94+
os.MkdirAll(localDest, 0o755)
95+
if export.OutputDir == "" {
96+
export.OutputDir = localDest
97+
}
98+
export.Attrs["dest"] = localDest
99+
default: // oci, tar
100+
export.Output = func(map[string]string) (io.WriteCloser, error) {
101+
return wf, nil
102+
}
103+
export.Attrs["output"] = filepath.Join(GlobalExportPath, opts.BuildID, "out.tar")
90104
}
91-
export.Attrs["output"] = filepath.Join(GlobalExportPath, opts.BuildID, "out.tar")
105+
92106
if _, ok := export.Attrs["name"]; !ok {
93107
export.Attrs["name"] = opts.Tag
94108
}
@@ -183,3 +197,68 @@ func (w *wrappedWriteCloser) Close() error {
183197
}
184198
return nil
185199
}
200+
201+
// parseOutput parses CSV output strings and returns ExportEntry slice.
202+
// It validates the output types and allows type=local without dest field.
203+
// Supported types: oci, tar, local
204+
func parseOutput(outputs []string) ([]client.ExportEntry, error) {
205+
var entries []client.ExportEntry
206+
207+
for _, output := range outputs {
208+
entry, err := parseOutputCSV(output)
209+
if err != nil {
210+
return nil, err
211+
}
212+
entries = append(entries, entry)
213+
}
214+
215+
return entries, nil
216+
}
217+
218+
// parseOutputCSV parses a single CSV output string into an ExportEntry
219+
func parseOutputCSV(output string) (client.ExportEntry, error) {
220+
entry := client.ExportEntry{
221+
Attrs: make(map[string]string),
222+
}
223+
224+
// Parse CSV fields
225+
fields, err := csvvalue.Fields(output, nil)
226+
if err != nil {
227+
return entry, fmt.Errorf("failed to parse CSV: %w", err)
228+
}
229+
230+
// Process each field
231+
for _, field := range fields {
232+
key, value, ok := strings.Cut(field, "=")
233+
if !ok {
234+
return entry, fmt.Errorf("invalid field format: %s (expected key=value)", field)
235+
}
236+
237+
key = strings.ToLower(strings.TrimSpace(key))
238+
value = strings.TrimSpace(value)
239+
240+
switch key {
241+
case "type":
242+
entry.Type = value
243+
default:
244+
entry.Attrs[key] = value
245+
}
246+
}
247+
248+
// Validate type is provided
249+
if entry.Type == "" {
250+
return entry, errors.New("output type is required (type=<type>)")
251+
}
252+
253+
// Validate supported types
254+
switch entry.Type {
255+
case "oci", "tar", "local":
256+
// These are the supported types
257+
default:
258+
return entry, fmt.Errorf("unsupported output type: %s (supported: oci, tar, local)", entry.Type)
259+
}
260+
261+
// No path validation - just return the parsed entry
262+
263+
return entry, nil
264+
}

0 commit comments

Comments
 (0)