Skip to content

Commit

Permalink
export(local): wrap opt
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
  • Loading branch information
crazy-max committed Oct 14, 2022
1 parent a3e10ee commit 8e1272d
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 11 deletions.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,54 @@ buildctl build ... --output type=tar,dest=out.tar
buildctl build ... --output type=tar > out.tar
```

With a [multi-platform build](docs/multi-platform.md), a subfolder matching
each target platform will be created in the destination directory:

```dockerfile
FROM busybox AS build
ARG TARGETOS
ARG TARGETARCH
RUN mkdir /out && echo foo > /out/hello-$TARGETOS-$TARGETARCH

FROM scratch
COPY --from=build /out /
```

```bash
$ buildctl build \
--frontend dockerfile.v0 \
--opt platform=linux/amd64,linux/arm64 \
--output type=local,dest=./bin/release

$ tree ./bin
./bin/
└── release
├── linux_amd64
│ └── hello-linux-amd64
└── linux_arm64
└── hello-linux-arm64
```

You can use the `wrap` option to wrap content in destination directory:

```bash
$ buildctl build \
--frontend dockerfile.v0 \
--opt platform=linux/amd64,linux/arm64 \
--output type=local,dest=./bin/release,wrap=true

$ tree ./bin
./bin/
└── release
├── hello-linux-amd64
└── hello-linux-arm64
```

> **Warning**
>
> Wrapping content may overwrite existing files from build result of other
> platforms.
#### Docker tarball

```bash
Expand Down
43 changes: 32 additions & 11 deletions exporter/local/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"os"
"strconv"
"strings"
"time"

Expand All @@ -23,6 +24,12 @@ import (
"golang.org/x/time/rate"
)

const (
// wrap is an exporter option which can be used to merge result in the
// output directory when multiple references are exported.
attrWrap = "wrap"
)

type Opt struct {
SessionManager *session.Manager
}
Expand All @@ -38,16 +45,28 @@ func New(opt Opt) (exporter.Exporter, error) {
}

func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
ei := &localExporterInstance{localExporter: e}

if v, ok := opt[attrWrap]; ok {
b, err := strconv.ParseBool(v)
if err != nil {
return nil, errors.Wrapf(err, "non-bool value for %s: %s", attrWrap, v)
}
ei.wrap = b
}

tm, _, err := epoch.ParseAttr(opt)
if err != nil {
return nil, err
}
ei.epoch = tm

return &localExporterInstance{localExporter: e, epoch: tm}, nil
return ei, nil
}

type localExporterInstance struct {
*localExporter
wrap bool
epoch *time.Time
}

Expand Down Expand Up @@ -152,16 +171,18 @@ func (e *localExporterInstance) Export(ctx context.Context, inp *exporter.Source
lbl := "copying files"
if isMap {
lbl += " " + k
st := fstypes.Stat{
Mode: uint32(os.ModeDir | 0755),
Path: strings.Replace(k, "/", "_", -1),
}
if e.epoch != nil {
st.ModTime = e.epoch.UnixNano()
}
fs, err = fsutil.SubDirFS([]fsutil.Dir{{FS: fs, Stat: st}})
if err != nil {
return err
if !e.wrap {
st := fstypes.Stat{
Mode: uint32(os.ModeDir | 0755),
Path: strings.Replace(k, "/", "_", -1),
}
if e.epoch != nil {
st.ModTime = e.epoch.UnixNano()
}
fs, err = fsutil.SubDirFS([]fsutil.Dir{{FS: fs, Stat: st}})
if err != nil {
return err
}
}
}

Expand Down
62 changes: 62 additions & 0 deletions frontend/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ var allTests = integration.TestFuncs(
testPlatformArgsImplicit,
testPlatformArgsExplicit,
testExportMultiPlatform,
testExportLocalWrapMultiPlatform,
testQuotedMetaArgs,
testIgnoreEntrypoint,
testSymlinkedDockerfile,
Expand Down Expand Up @@ -1475,6 +1476,67 @@ COPY arch-$TARGETARCH whoami
}
}

func testExportLocalWrapMultiPlatform(t *testing.T, sb integration.Sandbox) {
integration.SkipIfDockerd(t, sb, "oci exporter", "multi-platform")
f := getFrontend(t, sb)

dockerfile := []byte(`
FROM scratch
ARG TARGETOS
ARG TARGETARCH
COPY hello-$TARGETOS-$TARGETARCH .
`)

dir, err := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("hello-linux-arm", []byte(`hello linux/arm`), 0600),
fstest.CreateFile("hello-linux-amd64", []byte(`hello linux/amd64`), 0600),
fstest.CreateFile("hello-linux-s390x", []byte(`hello linux/s390x`), 0600),
fstest.CreateFile("hello-linux-ppc64le", []byte(`hello linux/ppc64le`), 0600),
fstest.CreateFile("hello-windows-amd64", []byte(`hello windows/amd64`), 0600),
)
require.NoError(t, err)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

destDir := t.TempDir()

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
LocalDirs: map[string]string{
builder.DefaultLocalNameDockerfile: dir,
builder.DefaultLocalNameContext: dir,
},
FrontendAttrs: map[string]string{
"platform": "windows/amd64,linux/arm,linux/s390x",
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir,
Attrs: map[string]string{
"wrap": "true",
},
},
},
}, nil)
require.NoError(t, err)

dt, err := os.ReadFile(filepath.Join(destDir, "hello-windows-amd64"))
require.NoError(t, err)
require.Equal(t, "hello windows/amd64", string(dt))

dt, err = os.ReadFile(filepath.Join(destDir, "hello-linux-arm"))
require.NoError(t, err)
require.Equal(t, "hello linux/arm", string(dt))

dt, err = os.ReadFile(filepath.Join(destDir, "hello-linux-s390x"))
require.NoError(t, err)
require.Equal(t, "hello linux/s390x", string(dt))
}

// tonistiigi/fsutil#46
func testContextChangeDirToFile(t *testing.T, sb integration.Sandbox) {
f := getFrontend(t, sb)
Expand Down

0 comments on commit 8e1272d

Please sign in to comment.