Skip to content

Commit c9778ab

Browse files
committed
exporter: add Docker compatible exporter
Signed-off-by: Tonis Tiigi <[email protected]>
1 parent b55bf20 commit c9778ab

File tree

7 files changed

+380
-50
lines changed

7 files changed

+380
-50
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,14 @@ If credentials are required, `buildctl` will attempt to read Docker configuratio
123123
buildctl build ... --exporter=local --exporter-opt output=path/to/output-dir
124124
```
125125
126-
#### Exporting OCI Image Format tarball to client
126+
##### Exporting build result to Docker
127+
128+
```
129+
# exported tarball is also compatible with OCI spec
130+
buildctl build ... --exporter=docker --exporter-opt name=myimage | docker load
131+
```
132+
133+
##### Exporting OCI Image Format tarball to client
127134
128135
```
129136
buildctl build ... --exporter=oci --exporter-opt output=path/to/output.tar

client/client_test.go

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -275,50 +275,80 @@ func testOCIExporter(t *testing.T, sb integration.Sandbox) {
275275
def, err := st.Marshal()
276276
require.NoError(t, err)
277277

278-
destDir, err := ioutil.TempDir("", "buildkit")
279-
require.NoError(t, err)
280-
defer os.RemoveAll(destDir)
281-
282-
out := filepath.Join(destDir, "out.tar")
283-
284-
err = c.Solve(context.TODO(), def, SolveOpt{
285-
Exporter: ExporterOCI,
286-
ExporterAttrs: map[string]string{
287-
"output": out,
288-
},
289-
}, nil)
290-
require.NoError(t, err)
291-
292-
dt, err := ioutil.ReadFile(out)
293-
require.NoError(t, err)
294-
295-
m, err := readTarToMap(dt, false)
296-
require.NoError(t, err)
297-
298-
_, ok := m["oci-layout"]
299-
require.True(t, ok)
300-
301-
var index ocispec.Index
302-
err = json.Unmarshal(m["index.json"].data, &index)
303-
require.NoError(t, err)
304-
require.Equal(t, 2, index.SchemaVersion)
305-
require.Equal(t, 1, len(index.Manifests))
306-
307-
var mfst ocispec.Manifest
308-
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].data, &mfst)
309-
require.NoError(t, err)
310-
require.Equal(t, 2, len(mfst.Layers))
278+
for _, exp := range []string{ExporterOCI, ExporterDocker} {
279+
280+
destDir, err := ioutil.TempDir("", "buildkit")
281+
require.NoError(t, err)
282+
defer os.RemoveAll(destDir)
283+
284+
out := filepath.Join(destDir, "out.tar")
285+
target := "example.com/buildkit/testoci:latest"
286+
287+
err = c.Solve(context.TODO(), def, SolveOpt{
288+
Exporter: exp,
289+
ExporterAttrs: map[string]string{
290+
"output": out,
291+
"name": target,
292+
},
293+
}, nil)
294+
require.NoError(t, err)
295+
296+
dt, err := ioutil.ReadFile(out)
297+
require.NoError(t, err)
298+
299+
m, err := readTarToMap(dt, false)
300+
require.NoError(t, err)
301+
302+
_, ok := m["oci-layout"]
303+
require.True(t, ok)
304+
305+
var index ocispec.Index
306+
err = json.Unmarshal(m["index.json"].data, &index)
307+
require.NoError(t, err)
308+
require.Equal(t, 2, index.SchemaVersion)
309+
require.Equal(t, 1, len(index.Manifests))
310+
311+
var mfst ocispec.Manifest
312+
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].data, &mfst)
313+
require.NoError(t, err)
314+
require.Equal(t, 2, len(mfst.Layers))
315+
316+
var ociimg ocispec.Image
317+
err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].data, &ociimg)
318+
require.NoError(t, err)
319+
require.Equal(t, "layers", ociimg.RootFS.Type)
320+
require.Equal(t, 2, len(ociimg.RootFS.DiffIDs))
321+
322+
_, ok = m["blobs/sha256/"+mfst.Layers[0].Digest.Hex()]
323+
require.True(t, ok)
324+
_, ok = m["blobs/sha256/"+mfst.Layers[1].Digest.Hex()]
325+
require.True(t, ok)
326+
327+
if exp != ExporterDocker {
328+
continue
329+
}
311330

312-
var ociimg ocispec.Image
313-
err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].data, &ociimg)
314-
require.NoError(t, err)
315-
require.Equal(t, "layers", ociimg.RootFS.Type)
316-
require.Equal(t, 2, len(ociimg.RootFS.DiffIDs))
331+
var dockerMfst []struct {
332+
Config string
333+
RepoTags []string
334+
Layers []string
335+
}
336+
err = json.Unmarshal(m["manifest.json"].data, &dockerMfst)
337+
require.NoError(t, err)
338+
require.Equal(t, 1, len(dockerMfst))
339+
340+
_, ok = m[dockerMfst[0].Config]
341+
require.True(t, ok)
342+
require.Equal(t, 2, len(dockerMfst[0].Layers))
343+
require.Equal(t, 1, len(dockerMfst[0].RepoTags))
344+
require.Equal(t, target, dockerMfst[0].RepoTags[0])
345+
346+
for _, l := range dockerMfst[0].Layers {
347+
_, ok := m[l]
348+
require.True(t, ok)
349+
}
317350

318-
_, ok = m["blobs/sha256/"+mfst.Layers[0].Digest.Hex()]
319-
require.True(t, ok)
320-
_, ok = m["blobs/sha256/"+mfst.Layers[1].Digest.Hex()]
321-
require.True(t, ok)
351+
}
322352
}
323353

324354
func testBuildPushAndValidate(t *testing.T, sb integration.Sandbox) {

client/exporters.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package client
22

33
const (
4-
ExporterImage = "image"
5-
ExporterLocal = "local"
6-
ExporterOCI = "oci"
4+
ExporterImage = "image"
5+
ExporterLocal = "local"
6+
ExporterOCI = "oci"
7+
ExporterDocker = "docker"
78

89
exporterLocalOutputDir = "output"
910
exporterOCIDestination = "output"

client/solve.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (c *Client) Solve(ctx context.Context, def *llb.Definition, opt SolveOpt, s
7979
return errors.Errorf("output directory is required for local exporter")
8080
}
8181
s.Allow(filesync.NewFSSyncTarget(outputDir))
82-
case ExporterOCI:
82+
case ExporterOCI, ExporterDocker:
8383
outputFile, ok := opt.ExporterAttrs[exporterOCIDestination]
8484
if ok {
8585
fi, err := os.Stat(outputFile)
@@ -91,7 +91,7 @@ func (c *Client) Solve(ctx context.Context, def *llb.Definition, opt SolveOpt, s
9191
}
9292
} else {
9393
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
94-
return errors.Errorf("output file is required for OCI exporter. refusing to write to console")
94+
return errors.Errorf("output file is required for %s exporter. refusing to write to console", opt.Exporter)
9595
}
9696
outputFile = ""
9797
}

0 commit comments

Comments
 (0)