build: fix oci-layout named context serialization
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
+10
-6
@@ -917,29 +917,33 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle OCI layout
|
// handle OCI layout
|
||||||
if localPath, ok := strings.CutPrefix(v.Path, "oci-layout://"); ok {
|
if ref, ok, err := ocilayout.Parse(v.Path); ok {
|
||||||
ref, _, err := ocilayout.Parse("oci-layout://" + localPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
localPath, dig, tag := ref.Path, ref.Digest.String(), ref.Tag
|
localPath := ref.Path
|
||||||
if dig == "" {
|
|
||||||
dig, err = resolveDigest(localPath, tag)
|
if ref.Digest == "" {
|
||||||
|
dig, err := resolveDigest(localPath, ref.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "oci-layout reference %q could not be resolved", v.Path)
|
return nil, errors.Wrapf(err, "oci-layout reference %q could not be resolved", v.Path)
|
||||||
}
|
}
|
||||||
|
ref.Digest = digest.Digest(dig)
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := local.NewStore(localPath)
|
store, err := local.NewStore(localPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "invalid store at %s", localPath)
|
return nil, errors.Wrapf(err, "invalid store at %s", localPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
storeName := identity.NewID()
|
storeName := identity.NewID()
|
||||||
if target.OCIStores == nil {
|
if target.OCIStores == nil {
|
||||||
target.OCIStores = map[string]content.Store{}
|
target.OCIStores = map[string]content.Store{}
|
||||||
}
|
}
|
||||||
target.OCIStores[storeName] = store
|
target.OCIStores[storeName] = store
|
||||||
|
|
||||||
target.FrontendAttrs["context:"+k] = "oci-layout://" + storeName + ":" + tag + "@" + dig
|
ref.Path = storeName
|
||||||
|
target.FrontendAttrs["context:"+k] = ref.String()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
|
"github.com/docker/buildx/util/ocilayout"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/client/ociindex"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -154,3 +160,91 @@ func TestProxyArgKeyExists(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadInputsOCILayoutNamedContext(t *testing.T) {
|
||||||
|
layoutPath := t.TempDir()
|
||||||
|
|
||||||
|
idx := ociindex.NewStoreIndex(layoutPath)
|
||||||
|
manifestDigest := digest.FromString("manifest")
|
||||||
|
err := idx.Put(ocispecs.Descriptor{
|
||||||
|
MediaType: ocispecs.MediaTypeImageManifest,
|
||||||
|
Digest: manifestDigest,
|
||||||
|
Size: 1,
|
||||||
|
}, ociindex.Tag("latest"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ref string
|
||||||
|
wantRef ocilayout.Ref
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "digest only",
|
||||||
|
ref: "oci-layout://" + layoutPath + "@" + manifestDigest.String(),
|
||||||
|
wantRef: ocilayout.Ref{
|
||||||
|
Digest: manifestDigest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag only",
|
||||||
|
ref: "oci-layout://" + layoutPath + ":latest",
|
||||||
|
wantRef: ocilayout.Ref{
|
||||||
|
Tag: "latest",
|
||||||
|
Digest: manifestDigest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag and digest",
|
||||||
|
ref: "oci-layout://" + layoutPath + ":latest@" + manifestDigest.String(),
|
||||||
|
wantRef: ocilayout.Ref{
|
||||||
|
Tag: "latest",
|
||||||
|
Digest: manifestDigest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
target := &client.SolveOpt{
|
||||||
|
FrontendAttrs: map[string]string{},
|
||||||
|
}
|
||||||
|
inp := &Inputs{
|
||||||
|
ContextPath: "https://example.com/context.tar.gz",
|
||||||
|
NamedContexts: map[string]NamedContext{
|
||||||
|
"proxy": {
|
||||||
|
Path: tt.ref,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := loadInputs(context.Background(), nil, inp, testProgressWriter{}, target)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, release)
|
||||||
|
t.Cleanup(release)
|
||||||
|
|
||||||
|
attr, ok := target.FrontendAttrs["context:proxy"]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, target.OCIStores, 1)
|
||||||
|
|
||||||
|
parsed, ok, err := ocilayout.Parse(attr)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, parsed.Path)
|
||||||
|
assert.Equal(t, tt.wantRef.Tag, parsed.Tag)
|
||||||
|
assert.Equal(t, tt.wantRef.Digest, parsed.Digest)
|
||||||
|
target.OCIStores = nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testProgressWriter struct{}
|
||||||
|
|
||||||
|
func (testProgressWriter) Write(*client.SolveStatus) {}
|
||||||
|
|
||||||
|
func (testProgressWriter) WriteBuildRef(string, string) {}
|
||||||
|
|
||||||
|
func (testProgressWriter) ValidateLogSource(digest.Digest, any) bool { return true }
|
||||||
|
|
||||||
|
func (testProgressWriter) ClearLogSource(any) {}
|
||||||
|
|
||||||
|
var _ progress.Writer = testProgressWriter{}
|
||||||
|
|||||||
+110
@@ -1,7 +1,9 @@
|
|||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -13,6 +15,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/v2/core/content"
|
||||||
|
"github.com/containerd/containerd/v2/plugins/content/local"
|
||||||
"github.com/containerd/continuity/fs/fstest"
|
"github.com/containerd/continuity/fs/fstest"
|
||||||
"github.com/containerd/platforms"
|
"github.com/containerd/platforms"
|
||||||
"github.com/creack/pty"
|
"github.com/creack/pty"
|
||||||
@@ -21,6 +25,7 @@ import (
|
|||||||
"github.com/docker/buildx/util/gitutil"
|
"github.com/docker/buildx/util/gitutil"
|
||||||
"github.com/docker/buildx/util/gitutil/gittestutil"
|
"github.com/docker/buildx/util/gitutil/gittestutil"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/client/ociindex"
|
||||||
"github.com/moby/buildkit/frontend/subrequests/lint"
|
"github.com/moby/buildkit/frontend/subrequests/lint"
|
||||||
"github.com/moby/buildkit/frontend/subrequests/outline"
|
"github.com/moby/buildkit/frontend/subrequests/outline"
|
||||||
"github.com/moby/buildkit/frontend/subrequests/targets"
|
"github.com/moby/buildkit/frontend/subrequests/targets"
|
||||||
@@ -32,6 +37,8 @@ import (
|
|||||||
"github.com/moby/buildkit/util/testutil"
|
"github.com/moby/buildkit/util/testutil"
|
||||||
"github.com/moby/buildkit/util/testutil/integration"
|
"github.com/moby/buildkit/util/testutil/integration"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
"github.com/opencontainers/image-spec/specs-go"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -50,6 +57,7 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){
|
|||||||
testBuildStdin,
|
testBuildStdin,
|
||||||
testBuildRemote,
|
testBuildRemote,
|
||||||
testBuildRemoteAuth,
|
testBuildRemoteAuth,
|
||||||
|
testBuildNamedContextOCILayoutDigestOnly,
|
||||||
testBuildLocalState,
|
testBuildLocalState,
|
||||||
testBuildLocalStateStdin,
|
testBuildLocalStateStdin,
|
||||||
testBuildLocalStateRemote,
|
testBuildLocalStateRemote,
|
||||||
@@ -305,6 +313,35 @@ COPY foo /foo
|
|||||||
require.FileExists(t, filepath.Join(dirDest, "foo"))
|
require.FileExists(t, filepath.Join(dirDest, "foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBuildNamedContextOCILayoutDigestOnly(t *testing.T, sb integration.Sandbox) {
|
||||||
|
if isMobyWorker(sb) {
|
||||||
|
t.Skip("oci-layout named contexts are not supported by the docker worker")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := tmpdir(t, fstest.CreateFile("Dockerfile", []byte(`
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=proxy /foo /foo
|
||||||
|
`), 0o600))
|
||||||
|
layoutPath := filepath.Join(dir, "layout")
|
||||||
|
expected := "from-oci-layout"
|
||||||
|
manifestDigest := createOCILayoutImage(t, layoutPath, "foo", []byte(expected), "latest")
|
||||||
|
dirDest := t.TempDir()
|
||||||
|
|
||||||
|
out, err := buildCmd(sb,
|
||||||
|
withDir(dir),
|
||||||
|
withArgs(
|
||||||
|
"--build-context", "proxy=oci-layout://layout@"+manifestDigest.String(),
|
||||||
|
"--output=type=local,dest="+dirDest,
|
||||||
|
dir,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
require.NoError(t, err, out)
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(filepath.Join(dirDest, "foo"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(dt))
|
||||||
|
}
|
||||||
|
|
||||||
func testBuildLocalState(t *testing.T, sb integration.Sandbox) {
|
func testBuildLocalState(t *testing.T, sb integration.Sandbox) {
|
||||||
dockerfile := []byte(`
|
dockerfile := []byte(`
|
||||||
FROM busybox:latest AS base
|
FROM busybox:latest AS base
|
||||||
@@ -1665,3 +1702,76 @@ COPY --from=base /etc/bar /bar
|
|||||||
)
|
)
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createOCILayoutImage(t *testing.T, layoutPath, fileName string, fileContents []byte, tag string) digest.Digest {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
store, err := local.NewStore(layoutPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
layerBytes := bytes.NewBuffer(nil)
|
||||||
|
tw := tar.NewWriter(layerBytes)
|
||||||
|
err = tw.WriteHeader(&tar.Header{
|
||||||
|
Name: fileName,
|
||||||
|
Mode: 0o644,
|
||||||
|
Size: int64(len(fileContents)),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = tw.Write(fileContents)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, tw.Close())
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
layerDesc := ocispecs.Descriptor{
|
||||||
|
MediaType: ocispecs.MediaTypeImageLayer,
|
||||||
|
Digest: digest.FromBytes(layerBytes.Bytes()),
|
||||||
|
Size: int64(layerBytes.Len()),
|
||||||
|
}
|
||||||
|
err = content.WriteBlob(ctx, store, "layer-"+layerDesc.Digest.String(), bytes.NewReader(layerBytes.Bytes()), layerDesc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cfgBytes, err := json.Marshal(ocispecs.Image{
|
||||||
|
Platform: ocispecs.Platform{
|
||||||
|
Architecture: "amd64",
|
||||||
|
OS: "linux",
|
||||||
|
},
|
||||||
|
Config: ocispecs.ImageConfig{
|
||||||
|
WorkingDir: "/",
|
||||||
|
},
|
||||||
|
RootFS: ocispecs.RootFS{
|
||||||
|
Type: "layers",
|
||||||
|
DiffIDs: []digest.Digest{layerDesc.Digest},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
cfgDesc := ocispecs.Descriptor{
|
||||||
|
MediaType: ocispecs.MediaTypeImageConfig,
|
||||||
|
Digest: digest.FromBytes(cfgBytes),
|
||||||
|
Size: int64(len(cfgBytes)),
|
||||||
|
}
|
||||||
|
err = content.WriteBlob(ctx, store, "config-"+cfgDesc.Digest.String(), bytes.NewReader(cfgBytes), cfgDesc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
manifestBytes, err := json.Marshal(ocispecs.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
MediaType: ocispecs.MediaTypeImageManifest,
|
||||||
|
Config: cfgDesc,
|
||||||
|
Layers: []ocispecs.Descriptor{layerDesc},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
manifestDesc := ocispecs.Descriptor{
|
||||||
|
MediaType: ocispecs.MediaTypeImageManifest,
|
||||||
|
Digest: digest.FromBytes(manifestBytes),
|
||||||
|
Size: int64(len(manifestBytes)),
|
||||||
|
}
|
||||||
|
err = content.WriteBlob(ctx, store, "manifest-"+manifestDesc.Digest.String(), bytes.NewReader(manifestBytes), manifestDesc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
idx := ociindex.NewStoreIndex(layoutPath)
|
||||||
|
err = idx.Put(manifestDesc, ociindex.Tag(tag))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return manifestDesc.Digest
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package imagetools
|
package imagetools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/buildx/util/ocilayout"
|
"github.com/docker/buildx/util/ocilayout"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
@@ -181,7 +179,3 @@ func (l *Location) ValidateTargetDigest(desc digest.Digest) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsOCILayout(s string) bool {
|
|
||||||
return strings.HasPrefix(s, "oci-layout://")
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user