Skip to content

Commit

Permalink
exporter: add Docker compatible exporter
Browse files Browse the repository at this point in the history
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
  • Loading branch information
tonistiigi committed Dec 18, 2017
1 parent 1c0b8ac commit 85c0f99
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 50 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,14 @@ If credentials are required, `buildctl` will attempt to read Docker configuratio
buildctl build ... --exporter=local --exporter-opt output=path/to/output-dir
```
#### Exporting OCI Image Format tarball to client
##### Exporting build result to Docker
```
# exported tarball is also compatible with OCI spec
buildctl build ... --exporter=docker --exporter-opt name=myimage | docker load
```
##### Exporting OCI Image Format tarball to client
```
buildctl build ... --exporter=oci --exporter-opt output=path/to/output.tar
Expand Down
114 changes: 72 additions & 42 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,50 +275,80 @@ func testOCIExporter(t *testing.T, sb integration.Sandbox) {
def, err := st.Marshal()
require.NoError(t, err)

destDir, err := ioutil.TempDir("", "buildkit")
require.NoError(t, err)
defer os.RemoveAll(destDir)

out := filepath.Join(destDir, "out.tar")

err = c.Solve(context.TODO(), def, SolveOpt{
Exporter: ExporterOCI,
ExporterAttrs: map[string]string{
"output": out,
},
}, nil)
require.NoError(t, err)

dt, err := ioutil.ReadFile(out)
require.NoError(t, err)

m, err := readTarToMap(dt, false)
require.NoError(t, err)

_, ok := m["oci-layout"]
require.True(t, ok)

var index ocispec.Index
err = json.Unmarshal(m["index.json"].data, &index)
require.NoError(t, err)
require.Equal(t, 2, index.SchemaVersion)
require.Equal(t, 1, len(index.Manifests))

var mfst ocispec.Manifest
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].data, &mfst)
require.NoError(t, err)
require.Equal(t, 2, len(mfst.Layers))
for _, exp := range []string{ExporterOCI, ExporterDocker} {

destDir, err := ioutil.TempDir("", "buildkit")
require.NoError(t, err)
defer os.RemoveAll(destDir)

out := filepath.Join(destDir, "out.tar")
target := "example.com/buildkit/testoci:latest"

err = c.Solve(context.TODO(), def, SolveOpt{
Exporter: exp,
ExporterAttrs: map[string]string{
"output": out,
"name": target,
},
}, nil)
require.NoError(t, err)

dt, err := ioutil.ReadFile(out)
require.NoError(t, err)

m, err := readTarToMap(dt, false)
require.NoError(t, err)

_, ok := m["oci-layout"]
require.True(t, ok)

var index ocispec.Index
err = json.Unmarshal(m["index.json"].data, &index)
require.NoError(t, err)
require.Equal(t, 2, index.SchemaVersion)
require.Equal(t, 1, len(index.Manifests))

var mfst ocispec.Manifest
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].data, &mfst)
require.NoError(t, err)
require.Equal(t, 2, len(mfst.Layers))

var ociimg ocispec.Image
err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].data, &ociimg)
require.NoError(t, err)
require.Equal(t, "layers", ociimg.RootFS.Type)
require.Equal(t, 2, len(ociimg.RootFS.DiffIDs))

_, ok = m["blobs/sha256/"+mfst.Layers[0].Digest.Hex()]
require.True(t, ok)
_, ok = m["blobs/sha256/"+mfst.Layers[1].Digest.Hex()]
require.True(t, ok)

if exp != ExporterDocker {
continue
}

var ociimg ocispec.Image
err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].data, &ociimg)
require.NoError(t, err)
require.Equal(t, "layers", ociimg.RootFS.Type)
require.Equal(t, 2, len(ociimg.RootFS.DiffIDs))
var dockerMfst []struct {
Config string
RepoTags []string
Layers []string
}
err = json.Unmarshal(m["manifest.json"].data, &dockerMfst)
require.NoError(t, err)
require.Equal(t, 1, len(dockerMfst))

_, ok = m[dockerMfst[0].Config]
require.True(t, ok)
require.Equal(t, 2, len(dockerMfst[0].Layers))
require.Equal(t, 1, len(dockerMfst[0].RepoTags))
require.Equal(t, target, dockerMfst[0].RepoTags[0])

for _, l := range dockerMfst[0].Layers {
_, ok := m[l]
require.True(t, ok)
}

_, ok = m["blobs/sha256/"+mfst.Layers[0].Digest.Hex()]
require.True(t, ok)
_, ok = m["blobs/sha256/"+mfst.Layers[1].Digest.Hex()]
require.True(t, ok)
}
}

func testBuildPushAndValidate(t *testing.T, sb integration.Sandbox) {
Expand Down
7 changes: 4 additions & 3 deletions client/exporters.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package client

const (
ExporterImage = "image"
ExporterLocal = "local"
ExporterOCI = "oci"
ExporterImage = "image"
ExporterLocal = "local"
ExporterOCI = "oci"
ExporterDocker = "docker"

exporterLocalOutputDir = "output"
exporterOCIDestination = "output"
Expand Down
4 changes: 2 additions & 2 deletions client/solve.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (c *Client) Solve(ctx context.Context, def *llb.Definition, opt SolveOpt, s
return errors.Errorf("output directory is required for local exporter")
}
s.Allow(filesync.NewFSSyncTarget(outputDir))
case ExporterOCI:
case ExporterOCI, ExporterDocker:
outputFile, ok := opt.ExporterAttrs[exporterOCIDestination]
if ok {
fi, err := os.Stat(outputFile)
Expand All @@ -91,7 +91,7 @@ func (c *Client) Solve(ctx context.Context, def *llb.Definition, opt SolveOpt, s
}
} else {
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
return errors.Errorf("output file is required for OCI exporter. refusing to write to console")
return errors.Errorf("output file is required for %s exporter. refusing to write to console", opt.Exporter)
}
outputFile = ""
}
Expand Down
Loading

0 comments on commit 85c0f99

Please sign in to comment.