Merge pull request #618 from tonistiigi/test-local
integration: use local registry mirrorsdocker-18.09
commit
95e6139521
|
@ -3,10 +3,13 @@ package contentutil
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Copy(ctx context.Context, ingester content.Ingester, provider content.Provider, desc ocispec.Descriptor) error {
|
||||
|
@ -41,3 +44,38 @@ func (r *rc) Read(b []byte) (int, error) {
|
|||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func CopyChain(ctx context.Context, ingester content.Ingester, provider content.Provider, desc ocispec.Descriptor) error {
|
||||
var m sync.Mutex
|
||||
manifestStack := []ocispec.Descriptor{}
|
||||
|
||||
filterHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
switch desc.MediaType {
|
||||
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
|
||||
images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
||||
m.Lock()
|
||||
manifestStack = append(manifestStack, desc)
|
||||
m.Unlock()
|
||||
return nil, images.ErrStopHandler
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
})
|
||||
handlers := []images.Handler{
|
||||
images.ChildrenHandler(provider),
|
||||
filterHandler,
|
||||
remotes.FetchHandler(ingester, &localFetcher{provider}),
|
||||
}
|
||||
|
||||
if err := images.Dispatch(ctx, images.Handlers(handlers...), desc); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for i := len(manifestStack) - 1; i >= 0; i-- {
|
||||
if err := Copy(ctx, ingester, provider, manifestStack[i]); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -55,6 +55,9 @@ func (r *readerAt) ReadAt(b []byte, off int64) (int, error) {
|
|||
var totalN int
|
||||
for len(b) > 0 {
|
||||
n, err := r.Reader.Read(b)
|
||||
if err == io.EOF && n == len(b) {
|
||||
err = nil
|
||||
}
|
||||
r.offset += int64(n)
|
||||
totalN += n
|
||||
b = b[n:]
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package contentutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func ProviderFromRef(ref string) (ocispec.Descriptor, content.Provider, error) {
|
||||
remote := docker.NewResolver(docker.ResolverOptions{
|
||||
Client: http.DefaultClient,
|
||||
})
|
||||
|
||||
name, desc, err := remote.Resolve(context.TODO(), ref)
|
||||
if err != nil {
|
||||
return ocispec.Descriptor{}, nil, err
|
||||
}
|
||||
|
||||
fetcher, err := remote.Fetcher(context.TODO(), name)
|
||||
if err != nil {
|
||||
return ocispec.Descriptor{}, nil, err
|
||||
}
|
||||
return desc, FromFetcher(fetcher), nil
|
||||
}
|
||||
|
||||
func IngesterFromRef(ref string) (content.Ingester, error) {
|
||||
remote := docker.NewResolver(docker.ResolverOptions{
|
||||
Client: http.DefaultClient,
|
||||
})
|
||||
|
||||
pusher, err := remote.Pusher(context.TODO(), ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ingester{
|
||||
pusher: pusher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ingester struct {
|
||||
pusher remotes.Pusher
|
||||
}
|
||||
|
||||
func (w *ingester) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
|
||||
var wo content.WriterOpts
|
||||
for _, o := range opts {
|
||||
if err := o(&wo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return w.pusher.Push(ctx, wo.Desc)
|
||||
}
|
|
@ -48,7 +48,12 @@ func (c *containerd) Name() string {
|
|||
return c.name
|
||||
}
|
||||
|
||||
func (c *containerd) New() (sb Sandbox, cl func() error, err error) {
|
||||
func (c *containerd) New(opt ...SandboxOpt) (sb Sandbox, cl func() error, err error) {
|
||||
var conf SandboxConf
|
||||
for _, o := range opt {
|
||||
o(&conf)
|
||||
}
|
||||
|
||||
if err := lookupBinary(c.containerd); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -115,12 +120,25 @@ disabled_plugins = ["cri"]
|
|||
}
|
||||
deferF.append(ctdStop)
|
||||
|
||||
buildkitdSock, stop, err := runBuildkitd([]string{"buildkitd",
|
||||
buildkitdArgs := []string{"buildkitd",
|
||||
"--oci-worker=false",
|
||||
"--containerd-worker=true",
|
||||
"--containerd-worker-addr", address,
|
||||
"--containerd-worker-labels=org.mobyproject.buildkit.worker.sandbox=true", // Include use of --containerd-worker-labels to trigger https://github.com/moby/buildkit/pull/603
|
||||
}, logs, 0, 0)
|
||||
}
|
||||
|
||||
if conf.mirror != "" {
|
||||
dir, err := configWithMirror(conf.mirror)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
deferF.append(func() error {
|
||||
return os.RemoveAll(dir)
|
||||
})
|
||||
buildkitdArgs = append(buildkitdArgs, "--config="+filepath.Join(dir, "buildkitd.toml"))
|
||||
}
|
||||
|
||||
buildkitdSock, stop, err := runBuildkitd(buildkitdArgs, logs, 0, 0)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ func init() {
|
|||
register(&oci{uid: uid, gid: gid})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type oci struct {
|
||||
|
@ -44,7 +45,12 @@ func (s *oci) Name() string {
|
|||
return "oci"
|
||||
}
|
||||
|
||||
func (s *oci) New() (Sandbox, func() error, error) {
|
||||
func (s *oci) New(opt ...SandboxOpt) (Sandbox, func() error, error) {
|
||||
var c SandboxConf
|
||||
for _, o := range opt {
|
||||
o(&c)
|
||||
}
|
||||
|
||||
if err := lookupBinary("buildkitd"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -54,8 +60,23 @@ func (s *oci) New() (Sandbox, func() error, error) {
|
|||
logs := map[string]*bytes.Buffer{}
|
||||
// Include use of --oci-worker-labels to trigger https://github.com/moby/buildkit/pull/603
|
||||
buildkitdArgs := []string{"buildkitd", "--oci-worker=true", "--containerd-worker=false", "--oci-worker-labels=org.mobyproject.buildkit.worker.sandbox=true"}
|
||||
|
||||
deferF := &multiCloser{}
|
||||
|
||||
if c.mirror != "" {
|
||||
dir, err := configWithMirror(c.mirror)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
deferF.append(func() error {
|
||||
return os.RemoveAll(dir)
|
||||
})
|
||||
buildkitdArgs = append(buildkitdArgs, "--config="+filepath.Join(dir, "buildkitd.toml"))
|
||||
}
|
||||
|
||||
if s.uid != 0 {
|
||||
if s.gid == 0 {
|
||||
deferF.F()()
|
||||
return nil, nil, errors.Errorf("unsupported id pair: uid=%d, gid=%d", s.uid, s.gid)
|
||||
}
|
||||
// TODO: make sure the user exists and subuid/subgid are configured.
|
||||
|
@ -63,10 +84,10 @@ func (s *oci) New() (Sandbox, func() error, error) {
|
|||
}
|
||||
buildkitdSock, stop, err := runBuildkitd(buildkitdArgs, logs, s.uid, s.gid)
|
||||
if err != nil {
|
||||
deferF.F()()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
deferF := &multiCloser{}
|
||||
deferF.append(stop)
|
||||
|
||||
return &sandbox{address: buildkitdSock, logs: logs, cleanup: deferF, rootless: s.uid != 0}, deferF.F(), nil
|
||||
|
@ -94,7 +115,7 @@ func (sb *sandbox) PrintLogs(t *testing.T) {
|
|||
}
|
||||
|
||||
func (sb *sandbox) NewRegistry() (string, error) {
|
||||
url, cl, err := newRegistry()
|
||||
url, cl, err := newRegistry("")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func newRegistry() (url string, cl func() error, err error) {
|
||||
func newRegistry(dir string) (url string, cl func() error, err error) {
|
||||
if err := lookupBinary("registry"); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
@ -30,26 +30,34 @@ func newRegistry() (url string, cl func() error, err error) {
|
|||
}
|
||||
}()
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "test-registry")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
if dir == "" {
|
||||
tmpdir, err := ioutil.TempDir("", "test-registry")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
deferF.append(func() error { return os.RemoveAll(tmpdir) })
|
||||
dir = tmpdir
|
||||
}
|
||||
deferF.append(func() error { return os.RemoveAll(tmpdir) })
|
||||
|
||||
template := fmt.Sprintf(`version: 0.1
|
||||
if _, err := os.Stat(filepath.Join(dir, "config.yaml")); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return "", nil, err
|
||||
}
|
||||
template := fmt.Sprintf(`version: 0.1
|
||||
loglevel: debug
|
||||
storage:
|
||||
filesystem:
|
||||
rootdirectory: %s
|
||||
http:
|
||||
addr: 127.0.0.1:0
|
||||
`, filepath.Join(tmpdir, "data"))
|
||||
`, filepath.Join(dir, "data"))
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpdir, "config.yaml"), []byte(template), 0600); err != nil {
|
||||
return "", nil, err
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, "config.yaml"), []byte(template), 0600); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("registry", "serve", filepath.Join(tmpdir, "config.yaml"))
|
||||
cmd := exec.Command("registry", "serve", filepath.Join(dir, "config.yaml"))
|
||||
rc, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||
"github.com/moby/buildkit/util/contentutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -21,10 +30,22 @@ type Sandbox interface {
|
|||
}
|
||||
|
||||
type Worker interface {
|
||||
New() (Sandbox, func() error, error)
|
||||
New(...SandboxOpt) (Sandbox, func() error, error)
|
||||
Name() string
|
||||
}
|
||||
|
||||
type SandboxConf struct {
|
||||
mirror string
|
||||
}
|
||||
|
||||
type SandboxOpt func(*SandboxConf)
|
||||
|
||||
func WithMirror(h string) SandboxOpt {
|
||||
return func(c *SandboxConf) {
|
||||
c.mirror = h
|
||||
}
|
||||
}
|
||||
|
||||
type Test func(*testing.T, Sandbox)
|
||||
|
||||
var defaultWorkers []Worker
|
||||
|
@ -41,10 +62,30 @@ func Run(t *testing.T, testCases []Test) {
|
|||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
mirror, cleanup, err := runMirror(t)
|
||||
require.NoError(t, err)
|
||||
|
||||
var mu sync.Mutex
|
||||
var count int
|
||||
cleanOnComplete := func() func() {
|
||||
count++
|
||||
return func() {
|
||||
mu.Lock()
|
||||
count--
|
||||
if count == 0 {
|
||||
cleanup()
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
defer cleanOnComplete()()
|
||||
|
||||
for _, br := range List() {
|
||||
for _, tc := range testCases {
|
||||
ok := t.Run(getFunctionName(tc)+"/worker="+br.Name(), func(t *testing.T) {
|
||||
sb, close, err := br.New()
|
||||
defer cleanOnComplete()()
|
||||
sb, close, err := br.New(WithMirror(mirror))
|
||||
if err != nil {
|
||||
if errors.Cause(err) == ErrorRequirements {
|
||||
t.Skip(err.Error())
|
||||
|
@ -69,3 +110,92 @@ func getFunctionName(i interface{}) string {
|
|||
dot := strings.LastIndex(fullname, ".") + 1
|
||||
return strings.Title(fullname[dot:])
|
||||
}
|
||||
|
||||
func copyImagesLocal(t *testing.T, host string) error {
|
||||
for to, from := range offlineImages() {
|
||||
desc, provider, err := contentutil.ProviderFromRef(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ingester, err := contentutil.IngesterFromRef(host + "/" + to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := contentutil.CopyChain(context.TODO(), ingester, provider, desc); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Logf("copied %s to local mirror %s", from, host+"/"+to)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func offlineImages() map[string]string {
|
||||
arch := runtime.GOARCH
|
||||
if arch == "arm64" {
|
||||
arch = "arm64v8"
|
||||
}
|
||||
return map[string]string{
|
||||
"library/busybox:latest": "docker.io/" + arch + "/busybox:latest",
|
||||
"library/alpine:latest": "docker.io/" + arch + "/alpine:latest",
|
||||
"tonistiigi/copy:v0.1.4": "docker.io/" + dockerfile2llb.DefaultCopyImage,
|
||||
}
|
||||
}
|
||||
|
||||
func configWithMirror(mirror string) (string, error) {
|
||||
tmpdir, err := ioutil.TempDir("", "bktest_config")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.Chmod(tmpdir, 0711); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpdir, "buildkitd.toml"), []byte(fmt.Sprintf(`
|
||||
[registry."docker.io"]
|
||||
mirrors=["%s"]
|
||||
`, mirror)), 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tmpdir, nil
|
||||
}
|
||||
|
||||
func runMirror(t *testing.T) (host string, cleanup func() error, err error) {
|
||||
mirrorDir := os.Getenv("BUILDKIT_REGISTRY_MIRROR_DIR")
|
||||
|
||||
var f *os.File
|
||||
if mirrorDir != "" {
|
||||
f, err = os.Create(filepath.Join(mirrorDir, "lock"))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
f.Close()
|
||||
}
|
||||
}()
|
||||
if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mirror, cleanup, err := newRegistry(mirrorDir)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := copyImagesLocal(t, mirror); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if mirrorDir != "" {
|
||||
if err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return mirror, cleanup, err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue