2017-12-01 19:44:27 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-07-27 17:24:31 +00:00
|
|
|
"runtime"
|
2017-12-01 19:44:27 +00:00
|
|
|
"testing"
|
|
|
|
|
2019-09-20 04:23:52 +00:00
|
|
|
"github.com/containerd/containerd/content/local"
|
2021-08-03 01:57:39 +00:00
|
|
|
"github.com/containerd/containerd/diff/apply"
|
|
|
|
"github.com/containerd/containerd/diff/walking"
|
2019-09-20 04:23:52 +00:00
|
|
|
ctdmetadata "github.com/containerd/containerd/metadata"
|
|
|
|
"github.com/containerd/containerd/snapshots"
|
2018-04-03 09:37:16 +00:00
|
|
|
"github.com/containerd/containerd/snapshots/native"
|
2017-12-01 19:44:27 +00:00
|
|
|
"github.com/moby/buildkit/cache"
|
|
|
|
"github.com/moby/buildkit/cache/metadata"
|
|
|
|
"github.com/moby/buildkit/identity"
|
|
|
|
"github.com/moby/buildkit/snapshot"
|
2019-09-20 04:23:52 +00:00
|
|
|
containerdsnapshot "github.com/moby/buildkit/snapshot/containerd"
|
2017-12-01 19:44:27 +00:00
|
|
|
"github.com/moby/buildkit/source"
|
2019-09-20 04:23:52 +00:00
|
|
|
"github.com/moby/buildkit/util/leaseutil"
|
2017-12-01 19:44:27 +00:00
|
|
|
"github.com/moby/buildkit/util/testutil/httpserver"
|
2021-08-03 01:57:39 +00:00
|
|
|
"github.com/moby/buildkit/util/winlayers"
|
2017-12-01 19:44:27 +00:00
|
|
|
digest "github.com/opencontainers/go-digest"
|
|
|
|
"github.com/stretchr/testify/require"
|
2019-09-20 04:23:52 +00:00
|
|
|
bolt "go.etcd.io/bbolt"
|
2017-12-01 19:44:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestHTTPSource(t *testing.T) {
|
2020-07-27 17:24:31 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Depends on unimplemented containerd bind-mount support on Windows")
|
|
|
|
}
|
|
|
|
|
2017-12-09 02:19:08 +00:00
|
|
|
t.Parallel()
|
2017-12-01 19:44:27 +00:00
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
tmpdir, err := ioutil.TempDir("", "buildkit-state")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
|
|
|
|
hs, err := newHTTPSource(tmpdir)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
resp := httpserver.Response{
|
|
|
|
Etag: identity.NewID(),
|
|
|
|
Content: []byte("content1"),
|
|
|
|
}
|
|
|
|
server := httpserver.NewTestServer(map[string]httpserver.Response{
|
|
|
|
"/foo": resp,
|
|
|
|
})
|
|
|
|
defer server.Close()
|
|
|
|
|
2020-07-18 16:11:39 +00:00
|
|
|
id := &source.HTTPIdentifier{URL: server.URL + "/foo"}
|
2017-12-01 19:44:27 +00:00
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
h, err := hs.Resolve(ctx, id, nil, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-09-17 23:39:19 +00:00
|
|
|
k, p, _, _, err := h.CacheKey(ctx, nil, 0)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
expectedContent1 := "sha256:0b1a154faa3003c1fbe7fda9c8a42d55fde2df2a2c405c32038f8ac7ed6b044a"
|
2021-09-17 23:39:19 +00:00
|
|
|
expectedPin1 := "sha256:d0b425e00e15a0d36b9b361f02bab63563aed6cb4665083905386c55d5b679fa"
|
2017-12-04 03:38:37 +00:00
|
|
|
|
|
|
|
require.Equal(t, expectedContent1, k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, expectedPin1, p)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").AllRequests, 1)
|
|
|
|
require.Equal(t, server.Stats("/foo").CachedRequests, 0)
|
|
|
|
|
2020-06-30 01:06:02 +00:00
|
|
|
ref, err := h.Snapshot(ctx, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
if ref != nil {
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
dt, err := readFile(ctx, ref, "foo")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, dt, []byte("content1"))
|
|
|
|
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
|
|
|
|
// repeat, should use the etag
|
2020-05-28 20:46:33 +00:00
|
|
|
h, err = hs.Resolve(ctx, id, nil, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-09-17 23:39:19 +00:00
|
|
|
k, p, _, _, err = h.CacheKey(ctx, nil, 0)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
require.Equal(t, expectedContent1, k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, expectedPin1, p)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").AllRequests, 2)
|
|
|
|
require.Equal(t, server.Stats("/foo").CachedRequests, 1)
|
|
|
|
|
2020-06-30 01:06:02 +00:00
|
|
|
ref, err = h.Snapshot(ctx, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
if ref != nil {
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
dt, err = readFile(ctx, ref, "foo")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, dt, []byte("content1"))
|
|
|
|
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
|
|
|
|
resp2 := httpserver.Response{
|
|
|
|
Etag: identity.NewID(),
|
|
|
|
Content: []byte("content2"),
|
|
|
|
}
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
expectedContent2 := "sha256:888722f299c02bfae173a747a0345bb2291cf6a076c36d8eb6fab442a8adddfa"
|
2021-09-17 23:39:19 +00:00
|
|
|
expectedPin2 := "sha256:dab741b6289e7dccc1ed42330cae1accc2b755ce8079c2cd5d4b5366c9f769a6"
|
2017-12-04 03:38:37 +00:00
|
|
|
|
2017-12-01 19:44:27 +00:00
|
|
|
// update etag, downloads again
|
|
|
|
server.SetRoute("/foo", resp2)
|
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
h, err = hs.Resolve(ctx, id, nil, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-09-17 23:39:19 +00:00
|
|
|
k, p, _, _, err = h.CacheKey(ctx, nil, 0)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
require.Equal(t, expectedContent2, k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, expectedPin2, p)
|
2019-02-25 07:05:01 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").AllRequests, 4)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").CachedRequests, 1)
|
|
|
|
|
2020-06-30 01:06:02 +00:00
|
|
|
ref, err = h.Snapshot(ctx, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
if ref != nil {
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
dt, err = readFile(ctx, ref, "foo")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, dt, []byte("content2"))
|
|
|
|
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestHTTPDefaultName(t *testing.T) {
|
2020-07-27 17:24:31 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Depends on unimplemented containerd bind-mount support on Windows")
|
|
|
|
}
|
|
|
|
|
2017-12-09 02:19:08 +00:00
|
|
|
t.Parallel()
|
2017-12-01 19:44:27 +00:00
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
tmpdir, err := ioutil.TempDir("", "buildkit-state")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
|
|
|
|
hs, err := newHTTPSource(tmpdir)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
resp := httpserver.Response{
|
|
|
|
Etag: identity.NewID(),
|
|
|
|
Content: []byte("content1"),
|
|
|
|
}
|
|
|
|
server := httpserver.NewTestServer(map[string]httpserver.Response{
|
|
|
|
"/": resp,
|
|
|
|
})
|
|
|
|
defer server.Close()
|
|
|
|
|
2020-07-18 16:11:39 +00:00
|
|
|
id := &source.HTTPIdentifier{URL: server.URL}
|
2017-12-01 19:44:27 +00:00
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
h, err := hs.Resolve(ctx, id, nil, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-09-17 23:39:19 +00:00
|
|
|
k, p, _, _, err := h.CacheKey(ctx, nil, 0)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
require.Equal(t, "sha256:146f16ec8810a62a57ce314aba391f95f7eaaf41b8b1ebaf2ab65fd63b1ad437", k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, "sha256:d0b425e00e15a0d36b9b361f02bab63563aed6cb4665083905386c55d5b679fa", p)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/").AllRequests, 1)
|
|
|
|
require.Equal(t, server.Stats("/").CachedRequests, 0)
|
|
|
|
|
2020-06-30 01:06:02 +00:00
|
|
|
ref, err := h.Snapshot(ctx, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
if ref != nil {
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
dt, err := readFile(ctx, ref, "download")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, dt, []byte("content1"))
|
|
|
|
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestHTTPInvalidURL(t *testing.T) {
|
2017-12-09 02:19:08 +00:00
|
|
|
t.Parallel()
|
2017-12-01 19:44:27 +00:00
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
tmpdir, err := ioutil.TempDir("", "buildkit-state")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
|
|
|
|
hs, err := newHTTPSource(tmpdir)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
server := httpserver.NewTestServer(map[string]httpserver.Response{})
|
|
|
|
defer server.Close()
|
|
|
|
|
2020-07-18 16:11:39 +00:00
|
|
|
id := &source.HTTPIdentifier{URL: server.URL + "/foo"}
|
2017-12-01 19:44:27 +00:00
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
h, err := hs.Resolve(ctx, id, nil, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-09-17 23:39:19 +00:00
|
|
|
_, _, _, _, err = h.CacheKey(ctx, nil, 0)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Contains(t, err.Error(), "invalid response")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestHTTPChecksum(t *testing.T) {
|
2020-07-27 17:24:31 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Depends on unimplemented containerd bind-mount support on Windows")
|
|
|
|
}
|
|
|
|
|
2017-12-09 02:19:08 +00:00
|
|
|
t.Parallel()
|
2017-12-01 19:44:27 +00:00
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
tmpdir, err := ioutil.TempDir("", "buildkit-state")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
|
|
|
|
hs, err := newHTTPSource(tmpdir)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
resp := httpserver.Response{
|
|
|
|
Etag: identity.NewID(),
|
|
|
|
Content: []byte("content-correct"),
|
|
|
|
}
|
|
|
|
server := httpserver.NewTestServer(map[string]httpserver.Response{
|
|
|
|
"/foo": resp,
|
|
|
|
})
|
|
|
|
defer server.Close()
|
|
|
|
|
2020-07-18 16:11:39 +00:00
|
|
|
id := &source.HTTPIdentifier{URL: server.URL + "/foo", Checksum: digest.FromBytes([]byte("content-different"))}
|
2017-12-01 19:44:27 +00:00
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
h, err := hs.Resolve(ctx, id, nil, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-09-17 23:39:19 +00:00
|
|
|
k, p, _, _, err := h.CacheKey(ctx, nil, 0)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
expectedContentDifferent := "sha256:f25996f463dca69cffb580f8273ffacdda43332b5f0a8bea2ead33900616d44b"
|
|
|
|
expectedContentCorrect := "sha256:c6a440110a7757b9e1e47b52e413cba96c62377c37a474714b6b3c4f8b74e536"
|
2021-09-17 23:39:19 +00:00
|
|
|
expectedPinDifferent := "sha256:ab0d5a7aa55c1c95d59c302eb12c55368940e6f0a257646afd455cabe248edc4"
|
|
|
|
expectedPinCorrect := "sha256:f5fa14774044d2ec428ffe7efbfaa0a439db7bc8127d6b71aea21e1cd558d0f0"
|
2017-12-04 03:38:37 +00:00
|
|
|
|
|
|
|
require.Equal(t, expectedContentDifferent, k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, expectedPinDifferent, p)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").AllRequests, 0)
|
|
|
|
require.Equal(t, server.Stats("/foo").CachedRequests, 0)
|
|
|
|
|
2020-06-30 01:06:02 +00:00
|
|
|
_, err = h.Snapshot(ctx, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
require.Equal(t, expectedContentDifferent, k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, expectedPinDifferent, p)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").AllRequests, 1)
|
|
|
|
require.Equal(t, server.Stats("/foo").CachedRequests, 0)
|
|
|
|
|
2020-07-18 16:11:39 +00:00
|
|
|
id = &source.HTTPIdentifier{URL: server.URL + "/foo", Checksum: digest.FromBytes([]byte("content-correct"))}
|
2017-12-01 19:44:27 +00:00
|
|
|
|
2020-05-28 20:46:33 +00:00
|
|
|
h, err = hs.Resolve(ctx, id, nil, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-09-17 23:39:19 +00:00
|
|
|
k, p, _, _, err = h.CacheKey(ctx, nil, 0)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
require.Equal(t, expectedContentCorrect, k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, expectedPinCorrect, p)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").AllRequests, 1)
|
|
|
|
require.Equal(t, server.Stats("/foo").CachedRequests, 0)
|
|
|
|
|
2020-06-30 01:06:02 +00:00
|
|
|
ref, err := h.Snapshot(ctx, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
if ref != nil {
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
dt, err := readFile(ctx, ref, "foo")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, dt, []byte("content-correct"))
|
|
|
|
|
2017-12-04 03:38:37 +00:00
|
|
|
require.Equal(t, expectedContentCorrect, k)
|
2021-09-17 23:39:19 +00:00
|
|
|
require.Equal(t, expectedPinCorrect, p)
|
2017-12-01 19:44:27 +00:00
|
|
|
require.Equal(t, server.Stats("/foo").AllRequests, 2)
|
|
|
|
require.Equal(t, server.Stats("/foo").CachedRequests, 0)
|
|
|
|
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
ref = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func readFile(ctx context.Context, ref cache.ImmutableRef, fp string) ([]byte, error) {
|
2021-10-28 19:59:26 +00:00
|
|
|
mount, err := ref.Mount(ctx, true, nil)
|
2017-12-01 19:44:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
lm := snapshot.LocalMounter(mount)
|
|
|
|
dir, err := lm.Mount()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer lm.Unmount()
|
|
|
|
|
|
|
|
dt, err := ioutil.ReadFile(filepath.Join(dir, fp))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newHTTPSource(tmpdir string) (source.Source, error) {
|
2018-04-03 09:37:16 +00:00
|
|
|
snapshotter, err := native.NewSnapshotter(filepath.Join(tmpdir, "snapshots"))
|
2017-12-01 19:44:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-09-20 04:23:52 +00:00
|
|
|
store, err := local.NewStore(tmpdir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
db, err := bolt.Open(filepath.Join(tmpdir, "containerdmeta.db"), 0644, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mdb := ctdmetadata.NewDB(db, store, map[string]snapshots.Snapshotter{
|
|
|
|
"native": snapshotter,
|
|
|
|
})
|
|
|
|
|
2021-07-09 00:09:35 +00:00
|
|
|
md, err := metadata.NewStore(filepath.Join(tmpdir, "metadata.db"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-10-28 19:59:26 +00:00
|
|
|
lm := leaseutil.WithNamespace(ctdmetadata.NewLeaseManager(mdb), "buildkit")
|
2021-08-03 01:57:39 +00:00
|
|
|
c := mdb.ContentStore()
|
|
|
|
applier := winlayers.NewFileSystemApplierWithWindows(c, apply.NewFileSystemApplier(c))
|
|
|
|
differ := winlayers.NewWalkingDiffWithWindows(c, walking.NewWalkingDiff(c))
|
2021-07-09 00:09:35 +00:00
|
|
|
|
2017-12-01 19:44:27 +00:00
|
|
|
cm, err := cache.NewManager(cache.ManagerOpt{
|
2019-09-20 04:23:52 +00:00
|
|
|
Snapshotter: snapshot.FromContainerdSnapshotter("native", containerdsnapshot.NSSnapshotter("buildkit", mdb.Snapshotter("native")), nil),
|
|
|
|
MetadataStore: md,
|
2021-10-28 19:59:26 +00:00
|
|
|
LeaseManager: lm,
|
2021-08-03 01:57:39 +00:00
|
|
|
ContentStore: c,
|
|
|
|
Applier: applier,
|
|
|
|
Differ: differ,
|
2019-09-20 04:23:52 +00:00
|
|
|
GarbageCollect: mdb.GarbageCollect,
|
2017-12-01 19:44:27 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return NewSource(Opt{
|
|
|
|
CacheAccessor: cm,
|
|
|
|
})
|
|
|
|
}
|