parent
							
								
									3d630c6f7f
								
							
						
					
					
						commit
						3ff9abca3a
					
				@ -0,0 +1,9 @@
 | 
			
		||||
module github.com/Microsoft/go-winio
 | 
			
		||||
 | 
			
		||||
go 1.12
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/pkg/errors v0.8.1
 | 
			
		||||
	github.com/sirupsen/logrus v1.4.1
 | 
			
		||||
	golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b
 | 
			
		||||
)
 | 
			
		||||
@ -0,0 +1,16 @@
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
 | 
			
		||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 | 
			
		||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 | 
			
		||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
@ -0,0 +1,305 @@
 | 
			
		||||
package winio
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unsafe"
 | 
			
		||||
 | 
			
		||||
	"github.com/Microsoft/go-winio/pkg/guid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	afHvSock = 34 // AF_HYPERV
 | 
			
		||||
 | 
			
		||||
	socketError = ^uintptr(0)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// An HvsockAddr is an address for a AF_HYPERV socket.
 | 
			
		||||
type HvsockAddr struct {
 | 
			
		||||
	VMID      guid.GUID
 | 
			
		||||
	ServiceID guid.GUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rawHvsockAddr struct {
 | 
			
		||||
	Family    uint16
 | 
			
		||||
	_         uint16
 | 
			
		||||
	VMID      guid.GUID
 | 
			
		||||
	ServiceID guid.GUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Network returns the address's network name, "hvsock".
 | 
			
		||||
func (addr *HvsockAddr) Network() string {
 | 
			
		||||
	return "hvsock"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (addr *HvsockAddr) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
 | 
			
		||||
func VsockServiceID(port uint32) guid.GUID {
 | 
			
		||||
	g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
 | 
			
		||||
	g.Data1 = port
 | 
			
		||||
	return g
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (addr *HvsockAddr) raw() rawHvsockAddr {
 | 
			
		||||
	return rawHvsockAddr{
 | 
			
		||||
		Family:    afHvSock,
 | 
			
		||||
		VMID:      addr.VMID,
 | 
			
		||||
		ServiceID: addr.ServiceID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
 | 
			
		||||
	addr.VMID = raw.VMID
 | 
			
		||||
	addr.ServiceID = raw.ServiceID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HvsockListener is a socket listener for the AF_HYPERV address family.
 | 
			
		||||
type HvsockListener struct {
 | 
			
		||||
	sock *win32File
 | 
			
		||||
	addr HvsockAddr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HvsockConn is a connected socket of the AF_HYPERV address family.
 | 
			
		||||
type HvsockConn struct {
 | 
			
		||||
	sock          *win32File
 | 
			
		||||
	local, remote HvsockAddr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newHvSocket() (*win32File, error) {
 | 
			
		||||
	fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, os.NewSyscallError("socket", err)
 | 
			
		||||
	}
 | 
			
		||||
	f, err := makeWin32File(fd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		syscall.Close(fd)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	f.socket = true
 | 
			
		||||
	return f, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListenHvsock listens for connections on the specified hvsock address.
 | 
			
		||||
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
 | 
			
		||||
	l := &HvsockListener{addr: *addr}
 | 
			
		||||
	sock, err := newHvSocket()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, l.opErr("listen", err)
 | 
			
		||||
	}
 | 
			
		||||
	sa := addr.raw()
 | 
			
		||||
	err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, l.opErr("listen", os.NewSyscallError("socket", err))
 | 
			
		||||
	}
 | 
			
		||||
	err = syscall.Listen(sock.handle, 16)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, l.opErr("listen", os.NewSyscallError("listen", err))
 | 
			
		||||
	}
 | 
			
		||||
	return &HvsockListener{sock: sock, addr: *addr}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *HvsockListener) opErr(op string, err error) error {
 | 
			
		||||
	return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Addr returns the listener's network address.
 | 
			
		||||
func (l *HvsockListener) Addr() net.Addr {
 | 
			
		||||
	return &l.addr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Accept waits for the next connection and returns it.
 | 
			
		||||
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
 | 
			
		||||
	sock, err := newHvSocket()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, l.opErr("accept", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if sock != nil {
 | 
			
		||||
			sock.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	c, err := l.sock.prepareIo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, l.opErr("accept", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer l.sock.wg.Done()
 | 
			
		||||
 | 
			
		||||
	// AcceptEx, per documentation, requires an extra 16 bytes per address.
 | 
			
		||||
	const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
 | 
			
		||||
	var addrbuf [addrlen * 2]byte
 | 
			
		||||
 | 
			
		||||
	var bytes uint32
 | 
			
		||||
	err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
 | 
			
		||||
	_, err = l.sock.asyncIo(c, nil, bytes, err)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
 | 
			
		||||
	}
 | 
			
		||||
	conn := &HvsockConn{
 | 
			
		||||
		sock: sock,
 | 
			
		||||
	}
 | 
			
		||||
	conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
 | 
			
		||||
	conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
 | 
			
		||||
	sock = nil
 | 
			
		||||
	return conn, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the listener, causing any pending Accept calls to fail.
 | 
			
		||||
func (l *HvsockListener) Close() error {
 | 
			
		||||
	return l.sock.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Need to finish ConnectEx handling
 | 
			
		||||
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
 | 
			
		||||
	sock, err := newHvSocket()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if sock != nil {
 | 
			
		||||
			sock.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	c, err := sock.prepareIo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer sock.wg.Done()
 | 
			
		||||
	var bytes uint32
 | 
			
		||||
	err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
 | 
			
		||||
	_, err = sock.asyncIo(ctx, c, nil, bytes, err)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	conn := &HvsockConn{
 | 
			
		||||
		sock:   sock,
 | 
			
		||||
		remote: *addr,
 | 
			
		||||
	}
 | 
			
		||||
	sock = nil
 | 
			
		||||
	return conn, nil
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
func (conn *HvsockConn) opErr(op string, err error) error {
 | 
			
		||||
	return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (conn *HvsockConn) Read(b []byte) (int, error) {
 | 
			
		||||
	c, err := conn.sock.prepareIo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, conn.opErr("read", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.sock.wg.Done()
 | 
			
		||||
	buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
 | 
			
		||||
	var flags, bytes uint32
 | 
			
		||||
	err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
 | 
			
		||||
	n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if _, ok := err.(syscall.Errno); ok {
 | 
			
		||||
			err = os.NewSyscallError("wsarecv", err)
 | 
			
		||||
		}
 | 
			
		||||
		return 0, conn.opErr("read", err)
 | 
			
		||||
	} else if n == 0 {
 | 
			
		||||
		err = io.EOF
 | 
			
		||||
	}
 | 
			
		||||
	return n, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (conn *HvsockConn) Write(b []byte) (int, error) {
 | 
			
		||||
	t := 0
 | 
			
		||||
	for len(b) != 0 {
 | 
			
		||||
		n, err := conn.write(b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return t + n, err
 | 
			
		||||
		}
 | 
			
		||||
		t += n
 | 
			
		||||
		b = b[n:]
 | 
			
		||||
	}
 | 
			
		||||
	return t, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (conn *HvsockConn) write(b []byte) (int, error) {
 | 
			
		||||
	c, err := conn.sock.prepareIo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, conn.opErr("write", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.sock.wg.Done()
 | 
			
		||||
	buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
 | 
			
		||||
	var bytes uint32
 | 
			
		||||
	err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
 | 
			
		||||
	n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if _, ok := err.(syscall.Errno); ok {
 | 
			
		||||
			err = os.NewSyscallError("wsasend", err)
 | 
			
		||||
		}
 | 
			
		||||
		return 0, conn.opErr("write", err)
 | 
			
		||||
	}
 | 
			
		||||
	return n, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the socket connection, failing any pending read or write calls.
 | 
			
		||||
func (conn *HvsockConn) Close() error {
 | 
			
		||||
	return conn.sock.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (conn *HvsockConn) shutdown(how int) error {
 | 
			
		||||
	err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return os.NewSyscallError("shutdown", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CloseRead shuts down the read end of the socket.
 | 
			
		||||
func (conn *HvsockConn) CloseRead() error {
 | 
			
		||||
	err := conn.shutdown(syscall.SHUT_RD)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return conn.opErr("close", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
 | 
			
		||||
// no more data will be written.
 | 
			
		||||
func (conn *HvsockConn) CloseWrite() error {
 | 
			
		||||
	err := conn.shutdown(syscall.SHUT_WR)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return conn.opErr("close", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LocalAddr returns the local address of the connection.
 | 
			
		||||
func (conn *HvsockConn) LocalAddr() net.Addr {
 | 
			
		||||
	return &conn.local
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoteAddr returns the remote address of the connection.
 | 
			
		||||
func (conn *HvsockConn) RemoteAddr() net.Addr {
 | 
			
		||||
	return &conn.remote
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDeadline implements the net.Conn SetDeadline method.
 | 
			
		||||
func (conn *HvsockConn) SetDeadline(t time.Time) error {
 | 
			
		||||
	conn.SetReadDeadline(t)
 | 
			
		||||
	conn.SetWriteDeadline(t)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetReadDeadline implements the net.Conn SetReadDeadline method.
 | 
			
		||||
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
 | 
			
		||||
	return conn.sock.SetReadDeadline(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
 | 
			
		||||
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
 | 
			
		||||
	return conn.sock.SetWriteDeadline(t)
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,235 @@
 | 
			
		||||
// Package guid provides a GUID type. The backing structure for a GUID is
 | 
			
		||||
// identical to that used by the golang.org/x/sys/windows GUID type.
 | 
			
		||||
// There are two main binary encodings used for a GUID, the big-endian encoding,
 | 
			
		||||
// and the Windows (mixed-endian) encoding. See here for details:
 | 
			
		||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
 | 
			
		||||
package guid
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/sha1"
 | 
			
		||||
	"encoding"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/windows"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Variant specifies which GUID variant (or "type") of the GUID. It determines
 | 
			
		||||
// how the entirety of the rest of the GUID is interpreted.
 | 
			
		||||
type Variant uint8
 | 
			
		||||
 | 
			
		||||
// The variants specified by RFC 4122.
 | 
			
		||||
const (
 | 
			
		||||
	// VariantUnknown specifies a GUID variant which does not conform to one of
 | 
			
		||||
	// the variant encodings specified in RFC 4122.
 | 
			
		||||
	VariantUnknown Variant = iota
 | 
			
		||||
	VariantNCS
 | 
			
		||||
	VariantRFC4122
 | 
			
		||||
	VariantMicrosoft
 | 
			
		||||
	VariantFuture
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version specifies how the bits in the GUID were generated. For instance, a
 | 
			
		||||
// version 4 GUID is randomly generated, and a version 5 is generated from the
 | 
			
		||||
// hash of an input string.
 | 
			
		||||
type Version uint8
 | 
			
		||||
 | 
			
		||||
var _ = (encoding.TextMarshaler)(GUID{})
 | 
			
		||||
var _ = (encoding.TextUnmarshaler)(&GUID{})
 | 
			
		||||
 | 
			
		||||
// GUID represents a GUID/UUID. It has the same structure as
 | 
			
		||||
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
 | 
			
		||||
// that type. It is defined as its own type so that stringification and
 | 
			
		||||
// marshaling can be supported. The representation matches that used by native
 | 
			
		||||
// Windows code.
 | 
			
		||||
type GUID windows.GUID
 | 
			
		||||
 | 
			
		||||
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
 | 
			
		||||
func NewV4() (GUID, error) {
 | 
			
		||||
	var b [16]byte
 | 
			
		||||
	if _, err := rand.Read(b[:]); err != nil {
 | 
			
		||||
		return GUID{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g := FromArray(b)
 | 
			
		||||
	g.setVersion(4) // Version 4 means randomly generated.
 | 
			
		||||
	g.setVariant(VariantRFC4122)
 | 
			
		||||
 | 
			
		||||
	return g, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
 | 
			
		||||
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
 | 
			
		||||
// and the sample code treats it as a series of bytes, so we do the same here.
 | 
			
		||||
//
 | 
			
		||||
// Some implementations, such as those found on Windows, treat the name as a
 | 
			
		||||
// big-endian UTF16 stream of bytes. If that is desired, the string can be
 | 
			
		||||
// encoded as such before being passed to this function.
 | 
			
		||||
func NewV5(namespace GUID, name []byte) (GUID, error) {
 | 
			
		||||
	b := sha1.New()
 | 
			
		||||
	namespaceBytes := namespace.ToArray()
 | 
			
		||||
	b.Write(namespaceBytes[:])
 | 
			
		||||
	b.Write(name)
 | 
			
		||||
 | 
			
		||||
	a := [16]byte{}
 | 
			
		||||
	copy(a[:], b.Sum(nil))
 | 
			
		||||
 | 
			
		||||
	g := FromArray(a)
 | 
			
		||||
	g.setVersion(5) // Version 5 means generated from a string.
 | 
			
		||||
	g.setVariant(VariantRFC4122)
 | 
			
		||||
 | 
			
		||||
	return g, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
 | 
			
		||||
	var g GUID
 | 
			
		||||
	g.Data1 = order.Uint32(b[0:4])
 | 
			
		||||
	g.Data2 = order.Uint16(b[4:6])
 | 
			
		||||
	g.Data3 = order.Uint16(b[6:8])
 | 
			
		||||
	copy(g.Data4[:], b[8:16])
 | 
			
		||||
	return g
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
 | 
			
		||||
	b := [16]byte{}
 | 
			
		||||
	order.PutUint32(b[0:4], g.Data1)
 | 
			
		||||
	order.PutUint16(b[4:6], g.Data2)
 | 
			
		||||
	order.PutUint16(b[6:8], g.Data3)
 | 
			
		||||
	copy(b[8:16], g.Data4[:])
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
 | 
			
		||||
func FromArray(b [16]byte) GUID {
 | 
			
		||||
	return fromArray(b, binary.BigEndian)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToArray returns an array of 16 bytes representing the GUID in big-endian
 | 
			
		||||
// encoding.
 | 
			
		||||
func (g GUID) ToArray() [16]byte {
 | 
			
		||||
	return g.toArray(binary.BigEndian)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
 | 
			
		||||
func FromWindowsArray(b [16]byte) GUID {
 | 
			
		||||
	return fromArray(b, binary.LittleEndian)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
 | 
			
		||||
// encoding.
 | 
			
		||||
func (g GUID) ToWindowsArray() [16]byte {
 | 
			
		||||
	return g.toArray(binary.LittleEndian)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g GUID) String() string {
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"%08x-%04x-%04x-%04x-%012x",
 | 
			
		||||
		g.Data1,
 | 
			
		||||
		g.Data2,
 | 
			
		||||
		g.Data3,
 | 
			
		||||
		g.Data4[:2],
 | 
			
		||||
		g.Data4[2:])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FromString parses a string containing a GUID and returns the GUID. The only
 | 
			
		||||
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
 | 
			
		||||
// format.
 | 
			
		||||
func FromString(s string) (GUID, error) {
 | 
			
		||||
	if len(s) != 36 {
 | 
			
		||||
		return GUID{}, fmt.Errorf("invalid GUID %q", s)
 | 
			
		||||
	}
 | 
			
		||||
	if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
 | 
			
		||||
		return GUID{}, fmt.Errorf("invalid GUID %q", s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var g GUID
 | 
			
		||||
 | 
			
		||||
	data1, err := strconv.ParseUint(s[0:8], 16, 32)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return GUID{}, fmt.Errorf("invalid GUID %q", s)
 | 
			
		||||
	}
 | 
			
		||||
	g.Data1 = uint32(data1)
 | 
			
		||||
 | 
			
		||||
	data2, err := strconv.ParseUint(s[9:13], 16, 16)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return GUID{}, fmt.Errorf("invalid GUID %q", s)
 | 
			
		||||
	}
 | 
			
		||||
	g.Data2 = uint16(data2)
 | 
			
		||||
 | 
			
		||||
	data3, err := strconv.ParseUint(s[14:18], 16, 16)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return GUID{}, fmt.Errorf("invalid GUID %q", s)
 | 
			
		||||
	}
 | 
			
		||||
	g.Data3 = uint16(data3)
 | 
			
		||||
 | 
			
		||||
	for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
 | 
			
		||||
		v, err := strconv.ParseUint(s[x:x+2], 16, 8)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return GUID{}, fmt.Errorf("invalid GUID %q", s)
 | 
			
		||||
		}
 | 
			
		||||
		g.Data4[i] = uint8(v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return g, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GUID) setVariant(v Variant) {
 | 
			
		||||
	d := g.Data4[0]
 | 
			
		||||
	switch v {
 | 
			
		||||
	case VariantNCS:
 | 
			
		||||
		d = (d & 0x7f)
 | 
			
		||||
	case VariantRFC4122:
 | 
			
		||||
		d = (d & 0x3f) | 0x80
 | 
			
		||||
	case VariantMicrosoft:
 | 
			
		||||
		d = (d & 0x1f) | 0xc0
 | 
			
		||||
	case VariantFuture:
 | 
			
		||||
		d = (d & 0x0f) | 0xe0
 | 
			
		||||
	case VariantUnknown:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	default:
 | 
			
		||||
		panic(fmt.Sprintf("invalid variant: %d", v))
 | 
			
		||||
	}
 | 
			
		||||
	g.Data4[0] = d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Variant returns the GUID variant, as defined in RFC 4122.
 | 
			
		||||
func (g GUID) Variant() Variant {
 | 
			
		||||
	b := g.Data4[0]
 | 
			
		||||
	if b&0x80 == 0 {
 | 
			
		||||
		return VariantNCS
 | 
			
		||||
	} else if b&0xc0 == 0x80 {
 | 
			
		||||
		return VariantRFC4122
 | 
			
		||||
	} else if b&0xe0 == 0xc0 {
 | 
			
		||||
		return VariantMicrosoft
 | 
			
		||||
	} else if b&0xe0 == 0xe0 {
 | 
			
		||||
		return VariantFuture
 | 
			
		||||
	}
 | 
			
		||||
	return VariantUnknown
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GUID) setVersion(v Version) {
 | 
			
		||||
	g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Version returns the GUID version, as defined in RFC 4122.
 | 
			
		||||
func (g GUID) Version() Version {
 | 
			
		||||
	return Version((g.Data3 & 0xF000) >> 12)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalText returns the textual representation of the GUID.
 | 
			
		||||
func (g GUID) MarshalText() ([]byte, error) {
 | 
			
		||||
	return []byte(g.String()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
 | 
			
		||||
// into this GUID.
 | 
			
		||||
func (g *GUID) UnmarshalText(text []byte) error {
 | 
			
		||||
	g2, err := FromString(string(text))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	*g = g2
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -1,3 +1,3 @@
 | 
			
		||||
package winio
 | 
			
		||||
 | 
			
		||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
 | 
			
		||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
linters:
 | 
			
		||||
  enable:
 | 
			
		||||
    - structcheck
 | 
			
		||||
    - varcheck
 | 
			
		||||
    - staticcheck
 | 
			
		||||
    - unconvert
 | 
			
		||||
    - gofmt
 | 
			
		||||
    - goimports
 | 
			
		||||
    - golint
 | 
			
		||||
    - ineffassign
 | 
			
		||||
    - vet
 | 
			
		||||
    - unused
 | 
			
		||||
    - misspell
 | 
			
		||||
  disable:
 | 
			
		||||
    - errcheck
 | 
			
		||||
 | 
			
		||||
run:
 | 
			
		||||
  deadline: 2m
 | 
			
		||||
  skip-dirs:
 | 
			
		||||
    - api
 | 
			
		||||
    - design
 | 
			
		||||
    - docs
 | 
			
		||||
    - docs/man
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "Vendor": true,
 | 
			
		||||
  "Deadline": "2m",
 | 
			
		||||
  "Sort": ["linter", "severity", "path", "line"],
 | 
			
		||||
  "Exclude": [
 | 
			
		||||
    ".*\\.pb\\.go",
 | 
			
		||||
    "fetch\\.go:.*::error: unrecognized printf verb 'r'"
 | 
			
		||||
  ],
 | 
			
		||||
  "EnableGC": true,
 | 
			
		||||
 | 
			
		||||
  "Enable": [
 | 
			
		||||
    "structcheck",
 | 
			
		||||
    "varcheck",
 | 
			
		||||
    "staticcheck",
 | 
			
		||||
    "unconvert",
 | 
			
		||||
 | 
			
		||||
    "gofmt",
 | 
			
		||||
    "goimports",
 | 
			
		||||
    "golint",
 | 
			
		||||
    "ineffassign",
 | 
			
		||||
    "vet"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@ -1,28 +1,49 @@
 | 
			
		||||
Abhinandan Prativadi <abhi@docker.com> Abhinandan Prativadi <aprativadi@gmail.com>
 | 
			
		||||
Abhinandan Prativadi <abhi@docker.com> abhi <abhi@docker.com>
 | 
			
		||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> Akihiro Suda <suda.kyoto@gmail.com>
 | 
			
		||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
 | 
			
		||||
Andrei Vagin <avagin@virtuozzo.com> Andrei Vagin <avagin@openvz.org>
 | 
			
		||||
Brent Baude <bbaude@redhat.com> baude <bbaude@redhat.com>
 | 
			
		||||
Frank Yang <yyb196@gmail.com> frank yang <yyb196@gmail.com>
 | 
			
		||||
Georgia Panoutsakopoulou <gpanoutsak@gmail.com> gpanouts <gpanoutsak@gmail.com>
 | 
			
		||||
Jie Zhang <iamkadisi@163.com> kadisi <iamkadisi@163.com>
 | 
			
		||||
John Howard <john.howard@microsoft.com> John Howard <jhoward@microsoft.com>
 | 
			
		||||
Justin Terry <juterry@microsoft.com> Justin Terry (VM) <juterry@microsoft.com>
 | 
			
		||||
Justin Terry <juterry@microsoft.com> Justin <jterry75@users.noreply.github.com>
 | 
			
		||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com> Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
 | 
			
		||||
Kevin Xu <cming.xu@gmail.com> kevin.xu <cming.xu@gmail.com>
 | 
			
		||||
Lu Jingxiao <lujingxiao@huawei.com> l00397676 <lujingxiao@huawei.com>
 | 
			
		||||
Lantao Liu <lantaol@google.com> Lantao Liu <taotaotheripper@gmail.com>
 | 
			
		||||
Phil Estes <estesp@gmail.com> Phil Estes <estesp@linux.vnet.ibm.com>
 | 
			
		||||
Stephen J Day <stevvooe@gmail.com> Stephen J Day <stephen.day@docker.com>
 | 
			
		||||
Stephen J Day <stevvooe@gmail.com> Stephen Day <stevvooe@users.noreply.github.com>
 | 
			
		||||
Stephen J Day <stevvooe@gmail.com> Stephen Day <stephen.day@getcruise.com>
 | 
			
		||||
Sudeesh John <sudeesh@linux.vnet.ibm.com> sudeesh john <sudeesh@linux.vnet.ibm.com>
 | 
			
		||||
Tõnis Tiigi <tonistiigi@gmail.com> Tonis Tiigi <tonistiigi@gmail.com>
 | 
			
		||||
Lifubang <lifubang@aliyun.com> Lifubang <lifubang@acmcoder.com>
 | 
			
		||||
Xiaodong Zhang <a4012017@sina.com> nashasha1 <a4012017@sina.com>
 | 
			
		||||
Jian Liao <jliao@alauda.io> liaoj <jliao@alauda.io>
 | 
			
		||||
Jian Liao <jliao@alauda.io> liaojian <liaojian@Dabllo.local>
 | 
			
		||||
Rui Cao <ruicao@alauda.io> ruicao <ruicao@alauda.io>
 | 
			
		||||
Xuean Yan <yan.xuean@zte.com.cn> yanxuean <yan.xuean@zte.com.cn>
 | 
			
		||||
Abhinandan Prativadi <abhi@docker.com>
 | 
			
		||||
Abhinandan Prativadi <abhi@docker.com> <aprativadi@gmail.com>
 | 
			
		||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.akihiro@lab.ntt.co.jp>
 | 
			
		||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.kyoto@gmail.com>
 | 
			
		||||
Andrei Vagin <avagin@virtuozzo.com> <avagin@openvz.org>
 | 
			
		||||
Andrey Kolomentsev <andrey.kolomentsev@gmail.com>
 | 
			
		||||
Brent Baude <bbaude@redhat.com>
 | 
			
		||||
Carlos Eduardo <me@carlosedp.com> <me@carlosedp.com>
 | 
			
		||||
Eric Ren <renzhen.rz@alibaba-linux.com> <renzhen.rz@alibaba-inc.com>
 | 
			
		||||
Frank Yang <yyb196@gmail.com>
 | 
			
		||||
Georgia Panoutsakopoulou <gpanoutsak@gmail.com>
 | 
			
		||||
Guangming Wang <guangming.wang@daocloud.io>
 | 
			
		||||
Haiyan Meng <haiyanmeng@google.com>
 | 
			
		||||
Jian Liao <jliao@alauda.io>
 | 
			
		||||
Jian Liao <jliao@alauda.io> <liaojian@Dabllo.local>
 | 
			
		||||
Ji'an Liu <anthonyliu@zju.edu.cn>
 | 
			
		||||
Jie Zhang <iamkadisi@163.com>
 | 
			
		||||
John Howard <john.howard@microsoft.com> <jhoward@microsoft.com>
 | 
			
		||||
John Howard <john.howard@microsoft.com> <jhowardmsft@users.noreply.github.com>
 | 
			
		||||
Julien Balestra <julien.balestra@datadoghq.com>
 | 
			
		||||
Justin Cormack <justin.cormack@docker.com> <justin@specialbusservice.com>
 | 
			
		||||
Justin Terry <juterry@microsoft.com>
 | 
			
		||||
Justin Terry <juterry@microsoft.com> <jterry75@users.noreply.github.com>
 | 
			
		||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
 | 
			
		||||
Kevin Xu <cming.xu@gmail.com>
 | 
			
		||||
Lantao Liu <lantaol@google.com> <taotaotheripper@gmail.com>
 | 
			
		||||
Lifubang <lifubang@aliyun.com> <lifubang@acmcoder.com>
 | 
			
		||||
Lu Jingxiao <lujingxiao@huawei.com>
 | 
			
		||||
Maksym Pavlenko <makpav@amazon.com> <pavlenko.maksym@gmail.com>
 | 
			
		||||
Mark Gordon <msg555@gmail.com>
 | 
			
		||||
Michael Katsoulis <michaelkatsoulis88@gmail.com>
 | 
			
		||||
Mike Brown <brownwm@us.ibm.com> <mikebrow@users.noreply.github.com>
 | 
			
		||||
Nishchay Kumar <mrawesomenix@gmail.com>
 | 
			
		||||
Phil Estes <estesp@gmail.com> <estesp@linux.vnet.ibm.com>
 | 
			
		||||
Rui Cao <ruicao@alauda.io> <ruicao@alauda.io>
 | 
			
		||||
Stephen J Day <stevvooe@gmail.com> <stephen.day@getcruise.com>
 | 
			
		||||
Stephen J Day <stevvooe@gmail.com> <stevvooe@users.noreply.github.com>
 | 
			
		||||
Stephen J Day <stevvooe@gmail.com> <stephen.day@docker.com>
 | 
			
		||||
Sudeesh John <sudeesh@linux.vnet.ibm.com>
 | 
			
		||||
Su Fei  <fesu@ebay.com> <fesu@ebay.com>
 | 
			
		||||
Tõnis Tiigi <tonistiigi@gmail.com>
 | 
			
		||||
Wei Fu <fuweid89@gmail.com> <fhfuwei@163.com>
 | 
			
		||||
Xiaodong Zhang <a4012017@sina.com>
 | 
			
		||||
Xuean Yan <yan.xuean@zte.com.cn>
 | 
			
		||||
Yuxing Liu <starnop@163.com>
 | 
			
		||||
zhenguang zhu <zhengguang.zhu@daocloud.io>
 | 
			
		||||
zhongming chang<zhongming.chang@daocloud.io>
 | 
			
		||||
zhoulin xie <zhoulin.xie@daocloud.io>
 | 
			
		||||
zhoulin xie <zhoulin.xie@daocloud.io> <42261994+JoeWrightss@users.noreply.github.com>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
- project:
 | 
			
		||||
    name: containerd/containerd
 | 
			
		||||
    check:
 | 
			
		||||
      jobs:
 | 
			
		||||
        - containerd-build-arm64
 | 
			
		||||
 | 
			
		||||
- job:
 | 
			
		||||
    name: containerd-build-arm64
 | 
			
		||||
    parent: init-test
 | 
			
		||||
    description: |
 | 
			
		||||
      Containerd build in openlab cluster.
 | 
			
		||||
    run: .zuul/playbooks/containerd-build/run.yaml
 | 
			
		||||
    nodeset: ubuntu-xenial-arm64
 | 
			
		||||
    voting: false
 | 
			
		||||
								
									
										
											
										
									
									
										
											284
										
									
									vendor/github.com/containerd/containerd/api/services/introspection/v1/introspection.pb.go
									
										generated
									
									
										vendored
									
								
								
							
							
										
											284
										
									
									vendor/github.com/containerd/containerd/api/services/introspection/v1/introspection.pb.go
									
										generated
									
									
										vendored
									
								
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,59 @@
 | 
			
		||||
// +build linux
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package archive
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"archive/tar"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AufsConvertWhiteout converts whiteout files for aufs.
 | 
			
		||||
func AufsConvertWhiteout(_ *tar.Header, _ string) (bool, error) {
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OverlayConvertWhiteout converts whiteout files for overlay.
 | 
			
		||||
func OverlayConvertWhiteout(hdr *tar.Header, path string) (bool, error) {
 | 
			
		||||
	base := filepath.Base(path)
 | 
			
		||||
	dir := filepath.Dir(path)
 | 
			
		||||
 | 
			
		||||
	// if a directory is marked as opaque, we need to translate that to overlay
 | 
			
		||||
	if base == whiteoutOpaqueDir {
 | 
			
		||||
		// don't write the file itself
 | 
			
		||||
		return false, unix.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if a file was deleted and we are using overlay, we need to create a character device
 | 
			
		||||
	if strings.HasPrefix(base, whiteoutPrefix) {
 | 
			
		||||
		originalBase := base[len(whiteoutPrefix):]
 | 
			
		||||
		originalPath := filepath.Join(dir, originalBase)
 | 
			
		||||
 | 
			
		||||
		if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		// don't write the file itself
 | 
			
		||||
		return false, os.Chown(originalPath, hdr.Uid, hdr.Gid)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
@ -1,24 +0,0 @@
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package archive
 | 
			
		||||
 | 
			
		||||
// ApplyOptions provides additional options for an Apply operation
 | 
			
		||||
type ApplyOptions struct {
 | 
			
		||||
	Filter Filter // Filter tar headers
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,187 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package diff
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/containerd/archive/compression"
 | 
			
		||||
	"github.com/containerd/containerd/images"
 | 
			
		||||
	"github.com/gogo/protobuf/types"
 | 
			
		||||
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	handlers []Handler
 | 
			
		||||
 | 
			
		||||
	// ErrNoProcessor is returned when no stream processor is available for a media-type
 | 
			
		||||
	ErrNoProcessor = errors.New("no processor for media-type")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	// register the default compression handler
 | 
			
		||||
	RegisterProcessor(compressedHandler)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegisterProcessor registers a stream processor for media-types
 | 
			
		||||
func RegisterProcessor(handler Handler) {
 | 
			
		||||
	handlers = append(handlers, handler)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetProcessor returns the processor for a media-type
 | 
			
		||||
func GetProcessor(ctx context.Context, stream StreamProcessor, payloads map[string]*types.Any) (StreamProcessor, error) {
 | 
			
		||||
	// reverse this list so that user configured handlers come up first
 | 
			
		||||
	for i := len(handlers) - 1; i >= 0; i-- {
 | 
			
		||||
		processor, ok := handlers[i](ctx, stream.MediaType())
 | 
			
		||||
		if ok {
 | 
			
		||||
			return processor(ctx, stream, payloads)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, ErrNoProcessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handler checks a media-type and initializes the processor
 | 
			
		||||
type Handler func(ctx context.Context, mediaType string) (StreamProcessorInit, bool)
 | 
			
		||||
 | 
			
		||||
// StaticHandler returns the processor init func for a static media-type
 | 
			
		||||
func StaticHandler(expectedMediaType string, fn StreamProcessorInit) Handler {
 | 
			
		||||
	return func(ctx context.Context, mediaType string) (StreamProcessorInit, bool) {
 | 
			
		||||
		if mediaType == expectedMediaType {
 | 
			
		||||
			return fn, true
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StreamProcessorInit returns the initialized stream processor
 | 
			
		||||
type StreamProcessorInit func(ctx context.Context, stream StreamProcessor, payloads map[string]*types.Any) (StreamProcessor, error)
 | 
			
		||||
 | 
			
		||||
// RawProcessor provides access to direct fd for processing
 | 
			
		||||
type RawProcessor interface {
 | 
			
		||||
	// File returns the fd for the read stream of the underlying processor
 | 
			
		||||
	File() *os.File
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StreamProcessor handles processing a content stream and transforming it into a different media-type
 | 
			
		||||
type StreamProcessor interface {
 | 
			
		||||
	io.ReadCloser
 | 
			
		||||
 | 
			
		||||
	// MediaType is the resulting media-type that the processor processes the stream into
 | 
			
		||||
	MediaType() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compressedHandler(ctx context.Context, mediaType string) (StreamProcessorInit, bool) {
 | 
			
		||||
	compressed, err := images.DiffCompression(ctx, mediaType)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
	if compressed != "" {
 | 
			
		||||
		return func(ctx context.Context, stream StreamProcessor, payloads map[string]*types.Any) (StreamProcessor, error) {
 | 
			
		||||
			ds, err := compression.DecompressStream(stream)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return &compressedProcessor{
 | 
			
		||||
				rc: ds,
 | 
			
		||||
			}, nil
 | 
			
		||||
		}, true
 | 
			
		||||
	}
 | 
			
		||||
	return func(ctx context.Context, stream StreamProcessor, payloads map[string]*types.Any) (StreamProcessor, error) {
 | 
			
		||||
		return &stdProcessor{
 | 
			
		||||
			rc: stream,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewProcessorChain initialized the root StreamProcessor
 | 
			
		||||
func NewProcessorChain(mt string, r io.Reader) StreamProcessor {
 | 
			
		||||
	return &processorChain{
 | 
			
		||||
		mt: mt,
 | 
			
		||||
		rc: r,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type processorChain struct {
 | 
			
		||||
	mt string
 | 
			
		||||
	rc io.Reader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *processorChain) MediaType() string {
 | 
			
		||||
	return c.mt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *processorChain) Read(p []byte) (int, error) {
 | 
			
		||||
	return c.rc.Read(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *processorChain) Close() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type stdProcessor struct {
 | 
			
		||||
	rc StreamProcessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *stdProcessor) MediaType() string {
 | 
			
		||||
	return ocispec.MediaTypeImageLayer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *stdProcessor) Read(p []byte) (int, error) {
 | 
			
		||||
	return c.rc.Read(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *stdProcessor) Close() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type compressedProcessor struct {
 | 
			
		||||
	rc io.ReadCloser
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *compressedProcessor) MediaType() string {
 | 
			
		||||
	return ocispec.MediaTypeImageLayer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *compressedProcessor) Read(p []byte) (int, error) {
 | 
			
		||||
	return c.rc.Read(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *compressedProcessor) Close() error {
 | 
			
		||||
	return c.rc.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BinaryHandler(id, returnsMediaType string, mediaTypes []string, path string, args []string) Handler {
 | 
			
		||||
	set := make(map[string]struct{}, len(mediaTypes))
 | 
			
		||||
	for _, m := range mediaTypes {
 | 
			
		||||
		set[m] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	return func(_ context.Context, mediaType string) (StreamProcessorInit, bool) {
 | 
			
		||||
		if _, ok := set[mediaType]; ok {
 | 
			
		||||
			return func(ctx context.Context, stream StreamProcessor, payloads map[string]*types.Any) (StreamProcessor, error) {
 | 
			
		||||
				payload := payloads[id]
 | 
			
		||||
				return NewBinaryProcessor(ctx, mediaType, returnsMediaType, stream, path, args, payload)
 | 
			
		||||
			}, true
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mediaTypeEnvVar = "STREAM_PROCESSOR_MEDIATYPE"
 | 
			
		||||
@ -0,0 +1,146 @@
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package diff
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/gogo/protobuf/proto"
 | 
			
		||||
	"github.com/gogo/protobuf/types"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewBinaryProcessor returns a binary processor for use with processing content streams
 | 
			
		||||
func NewBinaryProcessor(ctx context.Context, imt, rmt string, stream StreamProcessor, name string, args []string, payload *types.Any) (StreamProcessor, error) {
 | 
			
		||||
	cmd := exec.CommandContext(ctx, name, args...)
 | 
			
		||||
	cmd.Env = os.Environ()
 | 
			
		||||
 | 
			
		||||
	var payloadC io.Closer
 | 
			
		||||
	if payload != nil {
 | 
			
		||||
		data, err := proto.Marshal(payload)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		r, w, err := os.Pipe()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		go func() {
 | 
			
		||||
			io.Copy(w, bytes.NewReader(data))
 | 
			
		||||
			w.Close()
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		cmd.ExtraFiles = append(cmd.ExtraFiles, r)
 | 
			
		||||
		payloadC = r
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", mediaTypeEnvVar, imt))
 | 
			
		||||
	var (
 | 
			
		||||
		stdin  io.Reader
 | 
			
		||||
		closer func() error
 | 
			
		||||
		err    error
 | 
			
		||||
	)
 | 
			
		||||
	if f, ok := stream.(RawProcessor); ok {
 | 
			
		||||
		stdin = f.File()
 | 
			
		||||
		closer = f.File().Close
 | 
			
		||||
	} else {
 | 
			
		||||
		stdin = stream
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Stdin = stdin
 | 
			
		||||
	r, w, err := os.Pipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Stdout = w
 | 
			
		||||
 | 
			
		||||
	stderr := bytes.NewBuffer(nil)
 | 
			
		||||
	cmd.Stderr = stderr
 | 
			
		||||
 | 
			
		||||
	if err := cmd.Start(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	p := &binaryProcessor{
 | 
			
		||||
		cmd:    cmd,
 | 
			
		||||
		r:      r,
 | 
			
		||||
		mt:     rmt,
 | 
			
		||||
		stderr: stderr,
 | 
			
		||||
	}
 | 
			
		||||
	go p.wait()
 | 
			
		||||
 | 
			
		||||
	// close after start and dup
 | 
			
		||||
	w.Close()
 | 
			
		||||
	if closer != nil {
 | 
			
		||||
		closer()
 | 
			
		||||
	}
 | 
			
		||||
	if payloadC != nil {
 | 
			
		||||
		payloadC.Close()
 | 
			
		||||
	}
 | 
			
		||||
	return p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type binaryProcessor struct {
 | 
			
		||||
	cmd    *exec.Cmd
 | 
			
		||||
	r      *os.File
 | 
			
		||||
	mt     string
 | 
			
		||||
	stderr *bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	mu  sync.Mutex
 | 
			
		||||
	err error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) Err() error {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	defer c.mu.Unlock()
 | 
			
		||||
	return c.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) wait() {
 | 
			
		||||
	if err := c.cmd.Wait(); err != nil {
 | 
			
		||||
		if _, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
			c.mu.Lock()
 | 
			
		||||
			c.err = errors.New(c.stderr.String())
 | 
			
		||||
			c.mu.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) File() *os.File {
 | 
			
		||||
	return c.r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) MediaType() string {
 | 
			
		||||
	return c.mt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) Read(p []byte) (int, error) {
 | 
			
		||||
	return c.r.Read(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) Close() error {
 | 
			
		||||
	err := c.r.Close()
 | 
			
		||||
	if kerr := c.cmd.Process.Kill(); err == nil {
 | 
			
		||||
		err = kerr
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,165 @@
 | 
			
		||||
// +build windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package diff
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	winio "github.com/Microsoft/go-winio"
 | 
			
		||||
	"github.com/gogo/protobuf/proto"
 | 
			
		||||
	"github.com/gogo/protobuf/types"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const processorPipe = "STREAM_PROCESSOR_PIPE"
 | 
			
		||||
 | 
			
		||||
// NewBinaryProcessor returns a binary processor for use with processing content streams
 | 
			
		||||
func NewBinaryProcessor(ctx context.Context, imt, rmt string, stream StreamProcessor, name string, args []string, payload *types.Any) (StreamProcessor, error) {
 | 
			
		||||
	cmd := exec.CommandContext(ctx, name, args...)
 | 
			
		||||
	cmd.Env = os.Environ()
 | 
			
		||||
 | 
			
		||||
	if payload != nil {
 | 
			
		||||
		data, err := proto.Marshal(payload)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		up, err := getUiqPath()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		path := fmt.Sprintf("\\\\.\\pipe\\containerd-processor-%s-pipe", up)
 | 
			
		||||
		l, err := winio.ListenPipe(path, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer l.Close()
 | 
			
		||||
			conn, err := l.Accept()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.WithError(err).Error("accept npipe connection")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			io.Copy(conn, bytes.NewReader(data))
 | 
			
		||||
			conn.Close()
 | 
			
		||||
		}()
 | 
			
		||||
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", processorPipe, path))
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", mediaTypeEnvVar, imt))
 | 
			
		||||
	var (
 | 
			
		||||
		stdin  io.Reader
 | 
			
		||||
		closer func() error
 | 
			
		||||
		err    error
 | 
			
		||||
	)
 | 
			
		||||
	if f, ok := stream.(RawProcessor); ok {
 | 
			
		||||
		stdin = f.File()
 | 
			
		||||
		closer = f.File().Close
 | 
			
		||||
	} else {
 | 
			
		||||
		stdin = stream
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Stdin = stdin
 | 
			
		||||
	r, w, err := os.Pipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Stdout = w
 | 
			
		||||
	stderr := bytes.NewBuffer(nil)
 | 
			
		||||
	cmd.Stderr = stderr
 | 
			
		||||
 | 
			
		||||
	if err := cmd.Start(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	p := &binaryProcessor{
 | 
			
		||||
		cmd:    cmd,
 | 
			
		||||
		r:      r,
 | 
			
		||||
		mt:     rmt,
 | 
			
		||||
		stderr: stderr,
 | 
			
		||||
	}
 | 
			
		||||
	go p.wait()
 | 
			
		||||
 | 
			
		||||
	// close after start and dup
 | 
			
		||||
	w.Close()
 | 
			
		||||
	if closer != nil {
 | 
			
		||||
		closer()
 | 
			
		||||
	}
 | 
			
		||||
	return p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type binaryProcessor struct {
 | 
			
		||||
	cmd    *exec.Cmd
 | 
			
		||||
	r      *os.File
 | 
			
		||||
	mt     string
 | 
			
		||||
	stderr *bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	mu  sync.Mutex
 | 
			
		||||
	err error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) Err() error {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	defer c.mu.Unlock()
 | 
			
		||||
	return c.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) wait() {
 | 
			
		||||
	if err := c.cmd.Wait(); err != nil {
 | 
			
		||||
		if _, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
			c.mu.Lock()
 | 
			
		||||
			c.err = errors.New(c.stderr.String())
 | 
			
		||||
			c.mu.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) File() *os.File {
 | 
			
		||||
	return c.r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) MediaType() string {
 | 
			
		||||
	return c.mt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) Read(p []byte) (int, error) {
 | 
			
		||||
	return c.r.Read(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *binaryProcessor) Close() error {
 | 
			
		||||
	err := c.r.Close()
 | 
			
		||||
	if kerr := c.cmd.Process.Kill(); err == nil {
 | 
			
		||||
		err = kerr
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getUiqPath() (string, error) {
 | 
			
		||||
	dir, err := ioutil.TempDir("", "")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	os.Remove(dir)
 | 
			
		||||
	return filepath.Base(dir), nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,468 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package archive
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"archive/tar"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"path"
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/containerd/content"
 | 
			
		||||
	"github.com/containerd/containerd/errdefs"
 | 
			
		||||
	"github.com/containerd/containerd/images"
 | 
			
		||||
	"github.com/containerd/containerd/platforms"
 | 
			
		||||
	digest "github.com/opencontainers/go-digest"
 | 
			
		||||
	ocispecs "github.com/opencontainers/image-spec/specs-go"
 | 
			
		||||
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type exportOptions struct {
 | 
			
		||||
	manifests          []ocispec.Descriptor
 | 
			
		||||
	platform           platforms.MatchComparer
 | 
			
		||||
	allPlatforms       bool
 | 
			
		||||
	skipDockerManifest bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExportOpt defines options for configuring exported descriptors
 | 
			
		||||
type ExportOpt func(context.Context, *exportOptions) error
 | 
			
		||||
 | 
			
		||||
// WithPlatform defines the platform to require manifest lists have
 | 
			
		||||
// not exporting all platforms.
 | 
			
		||||
// Additionally, platform is used to resolve image configs for
 | 
			
		||||
// Docker v1.1, v1.2 format compatibility.
 | 
			
		||||
func WithPlatform(p platforms.MatchComparer) ExportOpt {
 | 
			
		||||
	return func(ctx context.Context, o *exportOptions) error {
 | 
			
		||||
		o.platform = p
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithAllPlatforms exports all manifests from a manifest list.
 | 
			
		||||
// Missing content will fail the export.
 | 
			
		||||
func WithAllPlatforms() ExportOpt {
 | 
			
		||||
	return func(ctx context.Context, o *exportOptions) error {
 | 
			
		||||
		o.allPlatforms = true
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithSkipDockerManifest skips creation of the Docker compatible
 | 
			
		||||
// manifest.json file.
 | 
			
		||||
func WithSkipDockerManifest() ExportOpt {
 | 
			
		||||
	return func(ctx context.Context, o *exportOptions) error {
 | 
			
		||||
		o.skipDockerManifest = true
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithImage adds the provided images to the exported archive.
 | 
			
		||||
func WithImage(is images.Store, name string) ExportOpt {
 | 
			
		||||
	return func(ctx context.Context, o *exportOptions) error {
 | 
			
		||||
		img, err := is.Get(ctx, name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		img.Target.Annotations = addNameAnnotation(name, img.Target.Annotations)
 | 
			
		||||
		o.manifests = append(o.manifests, img.Target)
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithManifest adds a manifest to the exported archive.
 | 
			
		||||
// When names are given they will be set on the manifest in the
 | 
			
		||||
// exported archive, creating an index record for each name.
 | 
			
		||||
// When no names are provided, it is up to caller to put name annotation to
 | 
			
		||||
// on the manifest descriptor if needed.
 | 
			
		||||
func WithManifest(manifest ocispec.Descriptor, names ...string) ExportOpt {
 | 
			
		||||
	return func(ctx context.Context, o *exportOptions) error {
 | 
			
		||||
		if len(names) == 0 {
 | 
			
		||||
			o.manifests = append(o.manifests, manifest)
 | 
			
		||||
		}
 | 
			
		||||
		for _, name := range names {
 | 
			
		||||
			mc := manifest
 | 
			
		||||
			mc.Annotations = addNameAnnotation(name, manifest.Annotations)
 | 
			
		||||
			o.manifests = append(o.manifests, mc)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addNameAnnotation(name string, base map[string]string) map[string]string {
 | 
			
		||||
	annotations := map[string]string{}
 | 
			
		||||
	for k, v := range base {
 | 
			
		||||
		annotations[k] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	annotations[images.AnnotationImageName] = name
 | 
			
		||||
	annotations[ocispec.AnnotationRefName] = ociReferenceName(name)
 | 
			
		||||
 | 
			
		||||
	return annotations
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Export implements Exporter.
 | 
			
		||||
func Export(ctx context.Context, store content.Provider, writer io.Writer, opts ...ExportOpt) error {
 | 
			
		||||
	var eo exportOptions
 | 
			
		||||
	for _, opt := range opts {
 | 
			
		||||
		if err := opt(ctx, &eo); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	records := []tarRecord{
 | 
			
		||||
		ociLayoutFile(""),
 | 
			
		||||
		ociIndexRecord(eo.manifests),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	algorithms := map[string]struct{}{}
 | 
			
		||||
	dManifests := map[digest.Digest]*exportManifest{}
 | 
			
		||||
	resolvedIndex := map[digest.Digest]digest.Digest{}
 | 
			
		||||
	for _, desc := range eo.manifests {
 | 
			
		||||
		switch desc.MediaType {
 | 
			
		||||
		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
 | 
			
		||||
			mt, ok := dManifests[desc.Digest]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				// TODO(containerd): Skip if already added
 | 
			
		||||
				r, err := getRecords(ctx, store, desc, algorithms)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				records = append(records, r...)
 | 
			
		||||
 | 
			
		||||
				mt = &exportManifest{
 | 
			
		||||
					manifest: desc,
 | 
			
		||||
				}
 | 
			
		||||
				dManifests[desc.Digest] = mt
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			name := desc.Annotations[images.AnnotationImageName]
 | 
			
		||||
			if name != "" && !eo.skipDockerManifest {
 | 
			
		||||
				mt.names = append(mt.names, name)
 | 
			
		||||
			}
 | 
			
		||||
		case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
 | 
			
		||||
			d, ok := resolvedIndex[desc.Digest]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				records = append(records, blobRecord(store, desc))
 | 
			
		||||
 | 
			
		||||
				p, err := content.ReadBlob(ctx, store, desc)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var index ocispec.Index
 | 
			
		||||
				if err := json.Unmarshal(p, &index); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var manifests []ocispec.Descriptor
 | 
			
		||||
				for _, m := range index.Manifests {
 | 
			
		||||
					if eo.platform != nil {
 | 
			
		||||
						if m.Platform == nil || eo.platform.Match(*m.Platform) {
 | 
			
		||||
							manifests = append(manifests, m)
 | 
			
		||||
						} else if !eo.allPlatforms {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					r, err := getRecords(ctx, store, m, algorithms)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					records = append(records, r...)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !eo.skipDockerManifest {
 | 
			
		||||
					if len(manifests) >= 1 {
 | 
			
		||||
						if len(manifests) > 1 {
 | 
			
		||||
							sort.SliceStable(manifests, func(i, j int) bool {
 | 
			
		||||
								if manifests[i].Platform == nil {
 | 
			
		||||
									return false
 | 
			
		||||
								}
 | 
			
		||||
								if manifests[j].Platform == nil {
 | 
			
		||||
									return true
 | 
			
		||||
								}
 | 
			
		||||
								return eo.platform.Less(*manifests[i].Platform, *manifests[j].Platform)
 | 
			
		||||
							})
 | 
			
		||||
						}
 | 
			
		||||
						d = manifests[0].Digest
 | 
			
		||||
						dManifests[d] = &exportManifest{
 | 
			
		||||
							manifest: manifests[0],
 | 
			
		||||
						}
 | 
			
		||||
					} else if eo.platform != nil {
 | 
			
		||||
						return errors.Wrap(errdefs.ErrNotFound, "no manifest found for platform")
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				resolvedIndex[desc.Digest] = d
 | 
			
		||||
			}
 | 
			
		||||
			if d != "" {
 | 
			
		||||
				if name := desc.Annotations[images.AnnotationImageName]; name != "" {
 | 
			
		||||
					mt := dManifests[d]
 | 
			
		||||
					mt.names = append(mt.names, name)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.Wrap(errdefs.ErrInvalidArgument, "only manifests may be exported")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(dManifests) > 0 {
 | 
			
		||||
		tr, err := manifestsRecord(ctx, store, dManifests)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "unable to create manifests file")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		records = append(records, tr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(algorithms) > 0 {
 | 
			
		||||
		records = append(records, directoryRecord("blobs/", 0755))
 | 
			
		||||
		for alg := range algorithms {
 | 
			
		||||
			records = append(records, directoryRecord("blobs/"+alg+"/", 0755))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tw := tar.NewWriter(writer)
 | 
			
		||||
	defer tw.Close()
 | 
			
		||||
	return writeTar(ctx, tw, records)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descriptor, algorithms map[string]struct{}) ([]tarRecord, error) {
 | 
			
		||||
	var records []tarRecord
 | 
			
		||||
	exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
 | 
			
		||||
		records = append(records, blobRecord(store, desc))
 | 
			
		||||
		algorithms[desc.Digest.Algorithm().String()] = struct{}{}
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	childrenHandler := images.ChildrenHandler(store)
 | 
			
		||||
 | 
			
		||||
	handlers := images.Handlers(
 | 
			
		||||
		childrenHandler,
 | 
			
		||||
		images.HandlerFunc(exportHandler),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Walk sequentially since the number of fetches is likely one and doing in
 | 
			
		||||
	// parallel requires locking the export handler
 | 
			
		||||
	if err := images.Walk(ctx, handlers, desc); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return records, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tarRecord struct {
 | 
			
		||||
	Header *tar.Header
 | 
			
		||||
	CopyTo func(context.Context, io.Writer) (int64, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord {
 | 
			
		||||
	path := path.Join("blobs", desc.Digest.Algorithm().String(), desc.Digest.Encoded())
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     path,
 | 
			
		||||
			Mode:     0444,
 | 
			
		||||
			Size:     desc.Size,
 | 
			
		||||
			Typeflag: tar.TypeReg,
 | 
			
		||||
		},
 | 
			
		||||
		CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
 | 
			
		||||
			r, err := cs.ReaderAt(ctx, desc)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return 0, errors.Wrap(err, "failed to get reader")
 | 
			
		||||
			}
 | 
			
		||||
			defer r.Close()
 | 
			
		||||
 | 
			
		||||
			// Verify digest
 | 
			
		||||
			dgstr := desc.Digest.Algorithm().Digester()
 | 
			
		||||
 | 
			
		||||
			n, err := io.Copy(io.MultiWriter(w, dgstr.Hash()), content.NewReader(r))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return 0, errors.Wrap(err, "failed to copy to tar")
 | 
			
		||||
			}
 | 
			
		||||
			if dgstr.Digest() != desc.Digest {
 | 
			
		||||
				return 0, errors.Errorf("unexpected digest %s copied", dgstr.Digest())
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func directoryRecord(name string, mode int64) tarRecord {
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     name,
 | 
			
		||||
			Mode:     mode,
 | 
			
		||||
			Typeflag: tar.TypeDir,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ociLayoutFile(version string) tarRecord {
 | 
			
		||||
	if version == "" {
 | 
			
		||||
		version = ocispec.ImageLayoutVersion
 | 
			
		||||
	}
 | 
			
		||||
	layout := ocispec.ImageLayout{
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b, err := json.Marshal(layout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     ocispec.ImageLayoutFile,
 | 
			
		||||
			Mode:     0444,
 | 
			
		||||
			Size:     int64(len(b)),
 | 
			
		||||
			Typeflag: tar.TypeReg,
 | 
			
		||||
		},
 | 
			
		||||
		CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
 | 
			
		||||
			n, err := w.Write(b)
 | 
			
		||||
			return int64(n), err
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ociIndexRecord(manifests []ocispec.Descriptor) tarRecord {
 | 
			
		||||
	index := ocispec.Index{
 | 
			
		||||
		Versioned: ocispecs.Versioned{
 | 
			
		||||
			SchemaVersion: 2,
 | 
			
		||||
		},
 | 
			
		||||
		Manifests: manifests,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b, err := json.Marshal(index)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     "index.json",
 | 
			
		||||
			Mode:     0644,
 | 
			
		||||
			Size:     int64(len(b)),
 | 
			
		||||
			Typeflag: tar.TypeReg,
 | 
			
		||||
		},
 | 
			
		||||
		CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
 | 
			
		||||
			n, err := w.Write(b)
 | 
			
		||||
			return int64(n), err
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type exportManifest struct {
 | 
			
		||||
	manifest ocispec.Descriptor
 | 
			
		||||
	names    []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func manifestsRecord(ctx context.Context, store content.Provider, manifests map[digest.Digest]*exportManifest) (tarRecord, error) {
 | 
			
		||||
	mfsts := make([]struct {
 | 
			
		||||
		Config   string
 | 
			
		||||
		RepoTags []string
 | 
			
		||||
		Layers   []string
 | 
			
		||||
	}, len(manifests))
 | 
			
		||||
 | 
			
		||||
	var i int
 | 
			
		||||
	for _, m := range manifests {
 | 
			
		||||
		p, err := content.ReadBlob(ctx, store, m.manifest)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return tarRecord{}, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var manifest ocispec.Manifest
 | 
			
		||||
		if err := json.Unmarshal(p, &manifest); err != nil {
 | 
			
		||||
			return tarRecord{}, err
 | 
			
		||||
		}
 | 
			
		||||
		if err := manifest.Config.Digest.Validate(); err != nil {
 | 
			
		||||
			return tarRecord{}, errors.Wrapf(err, "invalid manifest %q", m.manifest.Digest)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dgst := manifest.Config.Digest
 | 
			
		||||
		mfsts[i].Config = path.Join("blobs", dgst.Algorithm().String(), dgst.Encoded())
 | 
			
		||||
		for _, l := range manifest.Layers {
 | 
			
		||||
			path := path.Join("blobs", l.Digest.Algorithm().String(), l.Digest.Encoded())
 | 
			
		||||
			mfsts[i].Layers = append(mfsts[i].Layers, path)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, name := range m.names {
 | 
			
		||||
			nname, err := familiarizeReference(name)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return tarRecord{}, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mfsts[i].RepoTags = append(mfsts[i].RepoTags, nname)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b, err := json.Marshal(mfsts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return tarRecord{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     "manifest.json",
 | 
			
		||||
			Mode:     0644,
 | 
			
		||||
			Size:     int64(len(b)),
 | 
			
		||||
			Typeflag: tar.TypeReg,
 | 
			
		||||
		},
 | 
			
		||||
		CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
 | 
			
		||||
			n, err := w.Write(b)
 | 
			
		||||
			return int64(n), err
 | 
			
		||||
		},
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error {
 | 
			
		||||
	sort.Slice(records, func(i, j int) bool {
 | 
			
		||||
		return records[i].Header.Name < records[j].Header.Name
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	var last string
 | 
			
		||||
	for _, record := range records {
 | 
			
		||||
		if record.Header.Name == last {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		last = record.Header.Name
 | 
			
		||||
		if err := tw.WriteHeader(record.Header); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if record.CopyTo != nil {
 | 
			
		||||
			n, err := record.CopyTo(ctx, tw)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if n != record.Header.Size {
 | 
			
		||||
				return errors.Errorf("unexpected copy size for %s", record.Header.Name)
 | 
			
		||||
			}
 | 
			
		||||
		} else if record.Header.Size > 0 {
 | 
			
		||||
			return errors.Errorf("no content to write to record with non-zero size for %s", record.Header.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -1,241 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package oci
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"archive/tar"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/containerd/content"
 | 
			
		||||
	"github.com/containerd/containerd/images"
 | 
			
		||||
	"github.com/containerd/containerd/platforms"
 | 
			
		||||
	ocispecs "github.com/opencontainers/image-spec/specs-go"
 | 
			
		||||
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// V1Exporter implements OCI Image Spec v1.
 | 
			
		||||
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
 | 
			
		||||
//
 | 
			
		||||
// TODO(AkihiroSuda): add V1Exporter{TranslateMediaTypes: true} that transforms media types,
 | 
			
		||||
//                    e.g. application/vnd.docker.image.rootfs.diff.tar.gzip
 | 
			
		||||
//                         -> application/vnd.oci.image.layer.v1.tar+gzip
 | 
			
		||||
type V1Exporter struct {
 | 
			
		||||
	AllPlatforms bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// V1ExporterOpt allows the caller to set additional options to a new V1Exporter
 | 
			
		||||
type V1ExporterOpt func(c *V1Exporter) error
 | 
			
		||||
 | 
			
		||||
// DefaultV1Exporter return a default V1Exporter pointer
 | 
			
		||||
func DefaultV1Exporter() *V1Exporter {
 | 
			
		||||
	return &V1Exporter{
 | 
			
		||||
		AllPlatforms: false,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResolveV1ExportOpt return a new V1Exporter with V1ExporterOpt
 | 
			
		||||
func ResolveV1ExportOpt(opts ...V1ExporterOpt) (*V1Exporter, error) {
 | 
			
		||||
	exporter := DefaultV1Exporter()
 | 
			
		||||
	for _, o := range opts {
 | 
			
		||||
		if err := o(exporter); err != nil {
 | 
			
		||||
			return exporter, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return exporter, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithAllPlatforms set V1Exporter`s AllPlatforms option
 | 
			
		||||
func WithAllPlatforms(allPlatforms bool) V1ExporterOpt {
 | 
			
		||||
	return func(c *V1Exporter) error {
 | 
			
		||||
		c.AllPlatforms = allPlatforms
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Export implements Exporter.
 | 
			
		||||
func (oe *V1Exporter) Export(ctx context.Context, store content.Provider, desc ocispec.Descriptor, writer io.Writer) error {
 | 
			
		||||
	tw := tar.NewWriter(writer)
 | 
			
		||||
	defer tw.Close()
 | 
			
		||||
 | 
			
		||||
	records := []tarRecord{
 | 
			
		||||
		ociLayoutFile(""),
 | 
			
		||||
		ociIndexRecord(desc),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	algorithms := map[string]struct{}{}
 | 
			
		||||
	exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
 | 
			
		||||
		records = append(records, blobRecord(store, desc))
 | 
			
		||||
		algorithms[desc.Digest.Algorithm().String()] = struct{}{}
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	childrenHandler := images.ChildrenHandler(store)
 | 
			
		||||
 | 
			
		||||
	if !oe.AllPlatforms {
 | 
			
		||||
		// get local default platform to fetch image manifest
 | 
			
		||||
		childrenHandler = images.FilterPlatforms(childrenHandler, platforms.Any(platforms.DefaultSpec()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	handlers := images.Handlers(
 | 
			
		||||
		childrenHandler,
 | 
			
		||||
		images.HandlerFunc(exportHandler),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Walk sequentially since the number of fetchs is likely one and doing in
 | 
			
		||||
	// parallel requires locking the export handler
 | 
			
		||||
	if err := images.Walk(ctx, handlers, desc); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(algorithms) > 0 {
 | 
			
		||||
		records = append(records, directoryRecord("blobs/", 0755))
 | 
			
		||||
		for alg := range algorithms {
 | 
			
		||||
			records = append(records, directoryRecord("blobs/"+alg+"/", 0755))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return writeTar(ctx, tw, records)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tarRecord struct {
 | 
			
		||||
	Header *tar.Header
 | 
			
		||||
	CopyTo func(context.Context, io.Writer) (int64, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord {
 | 
			
		||||
	path := "blobs/" + desc.Digest.Algorithm().String() + "/" + desc.Digest.Hex()
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     path,
 | 
			
		||||
			Mode:     0444,
 | 
			
		||||
			Size:     desc.Size,
 | 
			
		||||
			Typeflag: tar.TypeReg,
 | 
			
		||||
		},
 | 
			
		||||
		CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
 | 
			
		||||
			r, err := cs.ReaderAt(ctx, desc)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return 0, errors.Wrap(err, "failed to get reader")
 | 
			
		||||
			}
 | 
			
		||||
			defer r.Close()
 | 
			
		||||
 | 
			
		||||
			// Verify digest
 | 
			
		||||
			dgstr := desc.Digest.Algorithm().Digester()
 | 
			
		||||
 | 
			
		||||
			n, err := io.Copy(io.MultiWriter(w, dgstr.Hash()), content.NewReader(r))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return 0, errors.Wrap(err, "failed to copy to tar")
 | 
			
		||||
			}
 | 
			
		||||
			if dgstr.Digest() != desc.Digest {
 | 
			
		||||
				return 0, errors.Errorf("unexpected digest %s copied", dgstr.Digest())
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func directoryRecord(name string, mode int64) tarRecord {
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     name,
 | 
			
		||||
			Mode:     mode,
 | 
			
		||||
			Typeflag: tar.TypeDir,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ociLayoutFile(version string) tarRecord {
 | 
			
		||||
	if version == "" {
 | 
			
		||||
		version = ocispec.ImageLayoutVersion
 | 
			
		||||
	}
 | 
			
		||||
	layout := ocispec.ImageLayout{
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b, err := json.Marshal(layout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     ocispec.ImageLayoutFile,
 | 
			
		||||
			Mode:     0444,
 | 
			
		||||
			Size:     int64(len(b)),
 | 
			
		||||
			Typeflag: tar.TypeReg,
 | 
			
		||||
		},
 | 
			
		||||
		CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
 | 
			
		||||
			n, err := w.Write(b)
 | 
			
		||||
			return int64(n), err
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ociIndexRecord(manifests ...ocispec.Descriptor) tarRecord {
 | 
			
		||||
	index := ocispec.Index{
 | 
			
		||||
		Versioned: ocispecs.Versioned{
 | 
			
		||||
			SchemaVersion: 2,
 | 
			
		||||
		},
 | 
			
		||||
		Manifests: manifests,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b, err := json.Marshal(index)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tarRecord{
 | 
			
		||||
		Header: &tar.Header{
 | 
			
		||||
			Name:     "index.json",
 | 
			
		||||
			Mode:     0644,
 | 
			
		||||
			Size:     int64(len(b)),
 | 
			
		||||
			Typeflag: tar.TypeReg,
 | 
			
		||||
		},
 | 
			
		||||
		CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
 | 
			
		||||
			n, err := w.Write(b)
 | 
			
		||||
			return int64(n), err
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error {
 | 
			
		||||
	sort.Slice(records, func(i, j int) bool {
 | 
			
		||||
		return records[i].Header.Name < records[j].Header.Name
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	for _, record := range records {
 | 
			
		||||
		if err := tw.WriteHeader(record.Header); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if record.CopyTo != nil {
 | 
			
		||||
			n, err := record.CopyTo(ctx, tw)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if n != record.Header.Size {
 | 
			
		||||
				return errors.Errorf("unexpected copy size for %s", record.Header.Name)
 | 
			
		||||
			}
 | 
			
		||||
		} else if record.Header.Size > 0 {
 | 
			
		||||
			return errors.Errorf("no content to write to record with non-zero size for %s", record.Header.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package namespaces
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/ttrpc"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// TTRPCHeader defines the header name for specifying a containerd namespace
 | 
			
		||||
	TTRPCHeader = "containerd-namespace-ttrpc"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func copyMetadata(src ttrpc.MD) ttrpc.MD {
 | 
			
		||||
	md := ttrpc.MD{}
 | 
			
		||||
	for k, v := range src {
 | 
			
		||||
		md[k] = append(md[k], v...)
 | 
			
		||||
	}
 | 
			
		||||
	return md
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func withTTRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
 | 
			
		||||
	md, ok := ttrpc.GetMetadata(ctx)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		md = ttrpc.MD{}
 | 
			
		||||
	} else {
 | 
			
		||||
		md = copyMetadata(md)
 | 
			
		||||
	}
 | 
			
		||||
	md.Set(TTRPCHeader, namespace)
 | 
			
		||||
	return ttrpc.WithMetadata(ctx, md)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fromTTRPCHeader(ctx context.Context) (string, bool) {
 | 
			
		||||
	return ttrpc.GetMetadataValue(ctx, TTRPCHeader)
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,121 @@
 | 
			
		||||
// +build linux
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package oci
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/containerd/containers"
 | 
			
		||||
	specs "github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// WithHostDevices adds all the hosts device nodes to the container's spec
 | 
			
		||||
func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
 | 
			
		||||
	setLinux(s)
 | 
			
		||||
 | 
			
		||||
	devs, err := getDevices("/dev")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	s.Linux.Devices = append(s.Linux.Devices, devs...)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDevices(path string) ([]specs.LinuxDevice, error) {
 | 
			
		||||
	files, err := ioutil.ReadDir(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var out []specs.LinuxDevice
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		switch {
 | 
			
		||||
		case f.IsDir():
 | 
			
		||||
			switch f.Name() {
 | 
			
		||||
			// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
 | 
			
		||||
			// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
 | 
			
		||||
			case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
 | 
			
		||||
				continue
 | 
			
		||||
			default:
 | 
			
		||||
				sub, err := getDevices(filepath.Join(path, f.Name()))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				out = append(out, sub...)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		case f.Name() == "console":
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err == ErrNotADevice {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		out = append(out, *device)
 | 
			
		||||
	}
 | 
			
		||||
	return out, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
 | 
			
		||||
	var stat unix.Stat_t
 | 
			
		||||
	if err := unix.Lstat(path, &stat); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		// The type is 32bit on mips.
 | 
			
		||||
		devNumber = uint64(stat.Rdev) // nolint: unconvert
 | 
			
		||||
		major     = unix.Major(devNumber)
 | 
			
		||||
		minor     = unix.Minor(devNumber)
 | 
			
		||||
	)
 | 
			
		||||
	if major == 0 {
 | 
			
		||||
		return nil, ErrNotADevice
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		devType string
 | 
			
		||||
		mode    = stat.Mode
 | 
			
		||||
	)
 | 
			
		||||
	switch {
 | 
			
		||||
	case mode&unix.S_IFBLK == unix.S_IFBLK:
 | 
			
		||||
		devType = "b"
 | 
			
		||||
	case mode&unix.S_IFCHR == unix.S_IFCHR:
 | 
			
		||||
		devType = "c"
 | 
			
		||||
	}
 | 
			
		||||
	fm := os.FileMode(mode)
 | 
			
		||||
	return &specs.LinuxDevice{
 | 
			
		||||
		Type:     devType,
 | 
			
		||||
		Path:     path,
 | 
			
		||||
		Major:    int64(major),
 | 
			
		||||
		Minor:    int64(minor),
 | 
			
		||||
		FileMode: &fm,
 | 
			
		||||
		UID:      &stat.Uid,
 | 
			
		||||
		GID:      &stat.Gid,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,120 @@
 | 
			
		||||
// +build !linux,!windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package oci
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/containerd/containers"
 | 
			
		||||
	specs "github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// WithHostDevices adds all the hosts device nodes to the container's spec
 | 
			
		||||
func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
 | 
			
		||||
	setLinux(s)
 | 
			
		||||
 | 
			
		||||
	devs, err := getDevices("/dev")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	s.Linux.Devices = append(s.Linux.Devices, devs...)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDevices(path string) ([]specs.LinuxDevice, error) {
 | 
			
		||||
	files, err := ioutil.ReadDir(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var out []specs.LinuxDevice
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		switch {
 | 
			
		||||
		case f.IsDir():
 | 
			
		||||
			switch f.Name() {
 | 
			
		||||
			// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
 | 
			
		||||
			// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
 | 
			
		||||
			case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
 | 
			
		||||
				continue
 | 
			
		||||
			default:
 | 
			
		||||
				sub, err := getDevices(filepath.Join(path, f.Name()))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				out = append(out, sub...)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		case f.Name() == "console":
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err == ErrNotADevice {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		out = append(out, *device)
 | 
			
		||||
	}
 | 
			
		||||
	return out, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
 | 
			
		||||
	var stat unix.Stat_t
 | 
			
		||||
	if err := unix.Lstat(path, &stat); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		devNumber = uint64(stat.Rdev)
 | 
			
		||||
		major     = unix.Major(devNumber)
 | 
			
		||||
		minor     = unix.Minor(devNumber)
 | 
			
		||||
	)
 | 
			
		||||
	if major == 0 {
 | 
			
		||||
		return nil, ErrNotADevice
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		devType string
 | 
			
		||||
		mode    = stat.Mode
 | 
			
		||||
	)
 | 
			
		||||
	switch {
 | 
			
		||||
	case mode&unix.S_IFBLK == unix.S_IFBLK:
 | 
			
		||||
		devType = "b"
 | 
			
		||||
	case mode&unix.S_IFCHR == unix.S_IFCHR:
 | 
			
		||||
		devType = "c"
 | 
			
		||||
	}
 | 
			
		||||
	fm := os.FileMode(mode)
 | 
			
		||||
	return &specs.LinuxDevice{
 | 
			
		||||
		Type:     devType,
 | 
			
		||||
		Path:     path,
 | 
			
		||||
		Major:    int64(major),
 | 
			
		||||
		Minor:    int64(minor),
 | 
			
		||||
		FileMode: &fm,
 | 
			
		||||
		UID:      &stat.Uid,
 | 
			
		||||
		GID:      &stat.Gid,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,797 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// Package docker provides a general type to represent any way of referencing images within the registry.
 | 
			
		||||
// Its main purpose is to abstract tags and digests (content-addressable hash).
 | 
			
		||||
//
 | 
			
		||||
// Grammar
 | 
			
		||||
//
 | 
			
		||||
// 	reference                       := name [ ":" tag ] [ "@" digest ]
 | 
			
		||||
//	name                            := [domain '/'] path-component ['/' path-component]*
 | 
			
		||||
//	domain                          := domain-component ['.' domain-component]* [':' port-number]
 | 
			
		||||
//	domain-component                := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
 | 
			
		||||
//	port-number                     := /[0-9]+/
 | 
			
		||||
//	path-component                  := alpha-numeric [separator alpha-numeric]*
 | 
			
		||||
// 	alpha-numeric                   := /[a-z0-9]+/
 | 
			
		||||
//	separator                       := /[_.]|__|[-]*/
 | 
			
		||||
//
 | 
			
		||||
//	tag                             := /[\w][\w.-]{0,127}/
 | 
			
		||||
//
 | 
			
		||||
//	digest                          := digest-algorithm ":" digest-hex
 | 
			
		||||
//	digest-algorithm                := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
 | 
			
		||||
//	digest-algorithm-separator      := /[+.-_]/
 | 
			
		||||
//	digest-algorithm-component      := /[A-Za-z][A-Za-z0-9]*/
 | 
			
		||||
//	digest-hex                      := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
 | 
			
		||||
//
 | 
			
		||||
//	identifier                      := /[a-f0-9]{64}/
 | 
			
		||||
//	short-identifier                := /[a-f0-9]{6,64}/
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/opencontainers/go-digest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// NameTotalLengthMax is the maximum total number of characters in a repository name.
 | 
			
		||||
	NameTotalLengthMax = 255
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
 | 
			
		||||
	ErrReferenceInvalidFormat = errors.New("invalid reference format")
 | 
			
		||||
 | 
			
		||||
	// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
 | 
			
		||||
	ErrTagInvalidFormat = errors.New("invalid tag format")
 | 
			
		||||
 | 
			
		||||
	// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
 | 
			
		||||
	ErrDigestInvalidFormat = errors.New("invalid digest format")
 | 
			
		||||
 | 
			
		||||
	// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
 | 
			
		||||
	ErrNameContainsUppercase = errors.New("repository name must be lowercase")
 | 
			
		||||
 | 
			
		||||
	// ErrNameEmpty is returned for empty, invalid repository names.
 | 
			
		||||
	ErrNameEmpty = errors.New("repository name must have at least one component")
 | 
			
		||||
 | 
			
		||||
	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
 | 
			
		||||
	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
 | 
			
		||||
 | 
			
		||||
	// ErrNameNotCanonical is returned when a name is not canonical.
 | 
			
		||||
	ErrNameNotCanonical = errors.New("repository name must be canonical")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Reference is an opaque object reference identifier that may include
 | 
			
		||||
// modifiers such as a hostname, name, tag, and digest.
 | 
			
		||||
type Reference interface {
 | 
			
		||||
	// String returns the full reference
 | 
			
		||||
	String() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Field provides a wrapper type for resolving correct reference types when
 | 
			
		||||
// working with encoding.
 | 
			
		||||
type Field struct {
 | 
			
		||||
	reference Reference
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AsField wraps a reference in a Field for encoding.
 | 
			
		||||
func AsField(reference Reference) Field {
 | 
			
		||||
	return Field{reference}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reference unwraps the reference type from the field to
 | 
			
		||||
// return the Reference object. This object should be
 | 
			
		||||
// of the appropriate type to further check for different
 | 
			
		||||
// reference types.
 | 
			
		||||
func (f Field) Reference() Reference {
 | 
			
		||||
	return f.reference
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalText serializes the field to byte text which
 | 
			
		||||
// is the string of the reference.
 | 
			
		||||
func (f Field) MarshalText() (p []byte, err error) {
 | 
			
		||||
	return []byte(f.reference.String()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnmarshalText parses text bytes by invoking the
 | 
			
		||||
// reference parser to ensure the appropriately
 | 
			
		||||
// typed reference object is wrapped by field.
 | 
			
		||||
func (f *Field) UnmarshalText(p []byte) error {
 | 
			
		||||
	r, err := Parse(string(p))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	f.reference = r
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Named is an object with a full name
 | 
			
		||||
type Named interface {
 | 
			
		||||
	Reference
 | 
			
		||||
	Name() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Tagged is an object which has a tag
 | 
			
		||||
type Tagged interface {
 | 
			
		||||
	Reference
 | 
			
		||||
	Tag() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NamedTagged is an object including a name and tag.
 | 
			
		||||
type NamedTagged interface {
 | 
			
		||||
	Named
 | 
			
		||||
	Tag() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Digested is an object which has a digest
 | 
			
		||||
// in which it can be referenced by
 | 
			
		||||
type Digested interface {
 | 
			
		||||
	Reference
 | 
			
		||||
	Digest() digest.Digest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Canonical reference is an object with a fully unique
 | 
			
		||||
// name including a name with domain and digest
 | 
			
		||||
type Canonical interface {
 | 
			
		||||
	Named
 | 
			
		||||
	Digest() digest.Digest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// namedRepository is a reference to a repository with a name.
 | 
			
		||||
// A namedRepository has both domain and path components.
 | 
			
		||||
type namedRepository interface {
 | 
			
		||||
	Named
 | 
			
		||||
	Domain() string
 | 
			
		||||
	Path() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Domain returns the domain part of the Named reference
 | 
			
		||||
func Domain(named Named) string {
 | 
			
		||||
	if r, ok := named.(namedRepository); ok {
 | 
			
		||||
		return r.Domain()
 | 
			
		||||
	}
 | 
			
		||||
	domain, _ := splitDomain(named.Name())
 | 
			
		||||
	return domain
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Path returns the name without the domain part of the Named reference
 | 
			
		||||
func Path(named Named) (name string) {
 | 
			
		||||
	if r, ok := named.(namedRepository); ok {
 | 
			
		||||
		return r.Path()
 | 
			
		||||
	}
 | 
			
		||||
	_, path := splitDomain(named.Name())
 | 
			
		||||
	return path
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func splitDomain(name string) (string, string) {
 | 
			
		||||
	match := anchoredNameRegexp.FindStringSubmatch(name)
 | 
			
		||||
	if len(match) != 3 {
 | 
			
		||||
		return "", name
 | 
			
		||||
	}
 | 
			
		||||
	return match[1], match[2]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SplitHostname splits a named reference into a
 | 
			
		||||
// hostname and name string. If no valid hostname is
 | 
			
		||||
// found, the hostname is empty and the full value
 | 
			
		||||
// is returned as name
 | 
			
		||||
// DEPRECATED: Use Domain or Path
 | 
			
		||||
func SplitHostname(named Named) (string, string) {
 | 
			
		||||
	if r, ok := named.(namedRepository); ok {
 | 
			
		||||
		return r.Domain(), r.Path()
 | 
			
		||||
	}
 | 
			
		||||
	return splitDomain(named.Name())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse parses s and returns a syntactically valid Reference.
 | 
			
		||||
// If an error was encountered it is returned, along with a nil Reference.
 | 
			
		||||
// NOTE: Parse will not handle short digests.
 | 
			
		||||
func Parse(s string) (Reference, error) {
 | 
			
		||||
	matches := ReferenceRegexp.FindStringSubmatch(s)
 | 
			
		||||
	if matches == nil {
 | 
			
		||||
		if s == "" {
 | 
			
		||||
			return nil, ErrNameEmpty
 | 
			
		||||
		}
 | 
			
		||||
		if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
 | 
			
		||||
			return nil, ErrNameContainsUppercase
 | 
			
		||||
		}
 | 
			
		||||
		return nil, ErrReferenceInvalidFormat
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(matches[1]) > NameTotalLengthMax {
 | 
			
		||||
		return nil, ErrNameTooLong
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var repo repository
 | 
			
		||||
 | 
			
		||||
	nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
 | 
			
		||||
	if len(nameMatch) == 3 {
 | 
			
		||||
		repo.domain = nameMatch[1]
 | 
			
		||||
		repo.path = nameMatch[2]
 | 
			
		||||
	} else {
 | 
			
		||||
		repo.domain = ""
 | 
			
		||||
		repo.path = matches[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ref := reference{
 | 
			
		||||
		namedRepository: repo,
 | 
			
		||||
		tag:             matches[2],
 | 
			
		||||
	}
 | 
			
		||||
	if matches[3] != "" {
 | 
			
		||||
		var err error
 | 
			
		||||
		ref.digest, err = digest.Parse(matches[3])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := getBestReferenceType(ref)
 | 
			
		||||
	if r == nil {
 | 
			
		||||
		return nil, ErrNameEmpty
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseNamed parses s and returns a syntactically valid reference implementing
 | 
			
		||||
// the Named interface. The reference must have a name and be in the canonical
 | 
			
		||||
// form, otherwise an error is returned.
 | 
			
		||||
// If an error was encountered it is returned, along with a nil Reference.
 | 
			
		||||
// NOTE: ParseNamed will not handle short digests.
 | 
			
		||||
func ParseNamed(s string) (Named, error) {
 | 
			
		||||
	named, err := ParseNormalizedNamed(s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if named.String() != s {
 | 
			
		||||
		return nil, ErrNameNotCanonical
 | 
			
		||||
	}
 | 
			
		||||
	return named, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithName returns a named object representing the given string. If the input
 | 
			
		||||
// is invalid ErrReferenceInvalidFormat will be returned.
 | 
			
		||||
func WithName(name string) (Named, error) {
 | 
			
		||||
	if len(name) > NameTotalLengthMax {
 | 
			
		||||
		return nil, ErrNameTooLong
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	match := anchoredNameRegexp.FindStringSubmatch(name)
 | 
			
		||||
	if match == nil || len(match) != 3 {
 | 
			
		||||
		return nil, ErrReferenceInvalidFormat
 | 
			
		||||
	}
 | 
			
		||||
	return repository{
 | 
			
		||||
		domain: match[1],
 | 
			
		||||
		path:   match[2],
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithTag combines the name from "name" and the tag from "tag" to form a
 | 
			
		||||
// reference incorporating both the name and the tag.
 | 
			
		||||
func WithTag(name Named, tag string) (NamedTagged, error) {
 | 
			
		||||
	if !anchoredTagRegexp.MatchString(tag) {
 | 
			
		||||
		return nil, ErrTagInvalidFormat
 | 
			
		||||
	}
 | 
			
		||||
	var repo repository
 | 
			
		||||
	if r, ok := name.(namedRepository); ok {
 | 
			
		||||
		repo.domain = r.Domain()
 | 
			
		||||
		repo.path = r.Path()
 | 
			
		||||
	} else {
 | 
			
		||||
		repo.path = name.Name()
 | 
			
		||||
	}
 | 
			
		||||
	if canonical, ok := name.(Canonical); ok {
 | 
			
		||||
		return reference{
 | 
			
		||||
			namedRepository: repo,
 | 
			
		||||
			tag:             tag,
 | 
			
		||||
			digest:          canonical.Digest(),
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
	return taggedReference{
 | 
			
		||||
		namedRepository: repo,
 | 
			
		||||
		tag:             tag,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithDigest combines the name from "name" and the digest from "digest" to form
 | 
			
		||||
// a reference incorporating both the name and the digest.
 | 
			
		||||
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
 | 
			
		||||
	if !anchoredDigestRegexp.MatchString(digest.String()) {
 | 
			
		||||
		return nil, ErrDigestInvalidFormat
 | 
			
		||||
	}
 | 
			
		||||
	var repo repository
 | 
			
		||||
	if r, ok := name.(namedRepository); ok {
 | 
			
		||||
		repo.domain = r.Domain()
 | 
			
		||||
		repo.path = r.Path()
 | 
			
		||||
	} else {
 | 
			
		||||
		repo.path = name.Name()
 | 
			
		||||
	}
 | 
			
		||||
	if tagged, ok := name.(Tagged); ok {
 | 
			
		||||
		return reference{
 | 
			
		||||
			namedRepository: repo,
 | 
			
		||||
			tag:             tagged.Tag(),
 | 
			
		||||
			digest:          digest,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
	return canonicalReference{
 | 
			
		||||
		namedRepository: repo,
 | 
			
		||||
		digest:          digest,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TrimNamed removes any tag or digest from the named reference.
 | 
			
		||||
func TrimNamed(ref Named) Named {
 | 
			
		||||
	domain, path := SplitHostname(ref)
 | 
			
		||||
	return repository{
 | 
			
		||||
		domain: domain,
 | 
			
		||||
		path:   path,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getBestReferenceType(ref reference) Reference {
 | 
			
		||||
	if ref.Name() == "" {
 | 
			
		||||
		// Allow digest only references
 | 
			
		||||
		if ref.digest != "" {
 | 
			
		||||
			return digestReference(ref.digest)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if ref.tag == "" {
 | 
			
		||||
		if ref.digest != "" {
 | 
			
		||||
			return canonicalReference{
 | 
			
		||||
				namedRepository: ref.namedRepository,
 | 
			
		||||
				digest:          ref.digest,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return ref.namedRepository
 | 
			
		||||
	}
 | 
			
		||||
	if ref.digest == "" {
 | 
			
		||||
		return taggedReference{
 | 
			
		||||
			namedRepository: ref.namedRepository,
 | 
			
		||||
			tag:             ref.tag,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ref
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reference struct {
 | 
			
		||||
	namedRepository
 | 
			
		||||
	tag    string
 | 
			
		||||
	digest digest.Digest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r reference) String() string {
 | 
			
		||||
	return r.Name() + ":" + r.tag + "@" + r.digest.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r reference) Tag() string {
 | 
			
		||||
	return r.tag
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r reference) Digest() digest.Digest {
 | 
			
		||||
	return r.digest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type repository struct {
 | 
			
		||||
	domain string
 | 
			
		||||
	path   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r repository) String() string {
 | 
			
		||||
	return r.Name()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r repository) Name() string {
 | 
			
		||||
	if r.domain == "" {
 | 
			
		||||
		return r.path
 | 
			
		||||
	}
 | 
			
		||||
	return r.domain + "/" + r.path
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r repository) Domain() string {
 | 
			
		||||
	return r.domain
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r repository) Path() string {
 | 
			
		||||
	return r.path
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type digestReference digest.Digest
 | 
			
		||||
 | 
			
		||||
func (d digestReference) String() string {
 | 
			
		||||
	return digest.Digest(d).String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d digestReference) Digest() digest.Digest {
 | 
			
		||||
	return digest.Digest(d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type taggedReference struct {
 | 
			
		||||
	namedRepository
 | 
			
		||||
	tag string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t taggedReference) String() string {
 | 
			
		||||
	return t.Name() + ":" + t.tag
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t taggedReference) Tag() string {
 | 
			
		||||
	return t.tag
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type canonicalReference struct {
 | 
			
		||||
	namedRepository
 | 
			
		||||
	digest digest.Digest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c canonicalReference) String() string {
 | 
			
		||||
	return c.Name() + "@" + c.digest.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c canonicalReference) Digest() digest.Digest {
 | 
			
		||||
	return c.digest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// alphaNumericRegexp defines the alpha numeric atom, typically a
 | 
			
		||||
	// component of names. This only allows lower case characters and digits.
 | 
			
		||||
	alphaNumericRegexp = match(`[a-z0-9]+`)
 | 
			
		||||
 | 
			
		||||
	// separatorRegexp defines the separators allowed to be embedded in name
 | 
			
		||||
	// components. This allow one period, one or two underscore and multiple
 | 
			
		||||
	// dashes.
 | 
			
		||||
	separatorRegexp = match(`(?:[._]|__|[-]*)`)
 | 
			
		||||
 | 
			
		||||
	// nameComponentRegexp restricts registry path component names to start
 | 
			
		||||
	// with at least one letter or number, with following parts able to be
 | 
			
		||||
	// separated by one period, one or two underscore and multiple dashes.
 | 
			
		||||
	nameComponentRegexp = expression(
 | 
			
		||||
		alphaNumericRegexp,
 | 
			
		||||
		optional(repeated(separatorRegexp, alphaNumericRegexp)))
 | 
			
		||||
 | 
			
		||||
	// domainComponentRegexp restricts the registry domain component of a
 | 
			
		||||
	// repository name to start with a component as defined by DomainRegexp
 | 
			
		||||
	// and followed by an optional port.
 | 
			
		||||
	domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
 | 
			
		||||
 | 
			
		||||
	// DomainRegexp defines the structure of potential domain components
 | 
			
		||||
	// that may be part of image names. This is purposely a subset of what is
 | 
			
		||||
	// allowed by DNS to ensure backwards compatibility with Docker image
 | 
			
		||||
	// names.
 | 
			
		||||
	DomainRegexp = expression(
 | 
			
		||||
		domainComponentRegexp,
 | 
			
		||||
		optional(repeated(literal(`.`), domainComponentRegexp)),
 | 
			
		||||
		optional(literal(`:`), match(`[0-9]+`)))
 | 
			
		||||
 | 
			
		||||
	// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
 | 
			
		||||
	TagRegexp = match(`[\w][\w.-]{0,127}`)
 | 
			
		||||
 | 
			
		||||
	// anchoredTagRegexp matches valid tag names, anchored at the start and
 | 
			
		||||
	// end of the matched string.
 | 
			
		||||
	anchoredTagRegexp = anchored(TagRegexp)
 | 
			
		||||
 | 
			
		||||
	// DigestRegexp matches valid digests.
 | 
			
		||||
	DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
 | 
			
		||||
 | 
			
		||||
	// anchoredDigestRegexp matches valid digests, anchored at the start and
 | 
			
		||||
	// end of the matched string.
 | 
			
		||||
	anchoredDigestRegexp = anchored(DigestRegexp)
 | 
			
		||||
 | 
			
		||||
	// NameRegexp is the format for the name component of references. The
 | 
			
		||||
	// regexp has capturing groups for the domain and name part omitting
 | 
			
		||||
	// the separating forward slash from either.
 | 
			
		||||
	NameRegexp = expression(
 | 
			
		||||
		optional(DomainRegexp, literal(`/`)),
 | 
			
		||||
		nameComponentRegexp,
 | 
			
		||||
		optional(repeated(literal(`/`), nameComponentRegexp)))
 | 
			
		||||
 | 
			
		||||
	// anchoredNameRegexp is used to parse a name value, capturing the
 | 
			
		||||
	// domain and trailing components.
 | 
			
		||||
	anchoredNameRegexp = anchored(
 | 
			
		||||
		optional(capture(DomainRegexp), literal(`/`)),
 | 
			
		||||
		capture(nameComponentRegexp,
 | 
			
		||||
			optional(repeated(literal(`/`), nameComponentRegexp))))
 | 
			
		||||
 | 
			
		||||
	// ReferenceRegexp is the full supported format of a reference. The regexp
 | 
			
		||||
	// is anchored and has capturing groups for name, tag, and digest
 | 
			
		||||
	// components.
 | 
			
		||||
	ReferenceRegexp = anchored(capture(NameRegexp),
 | 
			
		||||
		optional(literal(":"), capture(TagRegexp)),
 | 
			
		||||
		optional(literal("@"), capture(DigestRegexp)))
 | 
			
		||||
 | 
			
		||||
	// IdentifierRegexp is the format for string identifier used as a
 | 
			
		||||
	// content addressable identifier using sha256. These identifiers
 | 
			
		||||
	// are like digests without the algorithm, since sha256 is used.
 | 
			
		||||
	IdentifierRegexp = match(`([a-f0-9]{64})`)
 | 
			
		||||
 | 
			
		||||
	// ShortIdentifierRegexp is the format used to represent a prefix
 | 
			
		||||
	// of an identifier. A prefix may be used to match a sha256 identifier
 | 
			
		||||
	// within a list of trusted identifiers.
 | 
			
		||||
	ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
 | 
			
		||||
 | 
			
		||||
	// anchoredIdentifierRegexp is used to check or match an
 | 
			
		||||
	// identifier value, anchored at start and end of string.
 | 
			
		||||
	anchoredIdentifierRegexp = anchored(IdentifierRegexp)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// match compiles the string to a regular expression.
 | 
			
		||||
var match = regexp.MustCompile
 | 
			
		||||
 | 
			
		||||
// literal compiles s into a literal regular expression, escaping any regexp
 | 
			
		||||
// reserved characters.
 | 
			
		||||
func literal(s string) *regexp.Regexp {
 | 
			
		||||
	re := match(regexp.QuoteMeta(s))
 | 
			
		||||
 | 
			
		||||
	if _, complete := re.LiteralPrefix(); !complete {
 | 
			
		||||
		panic("must be a literal")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return re
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// expression defines a full expression, where each regular expression must
 | 
			
		||||
// follow the previous.
 | 
			
		||||
func expression(res ...*regexp.Regexp) *regexp.Regexp {
 | 
			
		||||
	var s string
 | 
			
		||||
	for _, re := range res {
 | 
			
		||||
		s += re.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return match(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// optional wraps the expression in a non-capturing group and makes the
 | 
			
		||||
// production optional.
 | 
			
		||||
func optional(res ...*regexp.Regexp) *regexp.Regexp {
 | 
			
		||||
	return match(group(expression(res...)).String() + `?`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// repeated wraps the regexp in a non-capturing group to get one or more
 | 
			
		||||
// matches.
 | 
			
		||||
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
 | 
			
		||||
	return match(group(expression(res...)).String() + `+`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// group wraps the regexp in a non-capturing group.
 | 
			
		||||
func group(res ...*regexp.Regexp) *regexp.Regexp {
 | 
			
		||||
	return match(`(?:` + expression(res...).String() + `)`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// capture wraps the expression in a capturing group.
 | 
			
		||||
func capture(res ...*regexp.Regexp) *regexp.Regexp {
 | 
			
		||||
	return match(`(` + expression(res...).String() + `)`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// anchored anchors the regular expression by adding start and end delimiters.
 | 
			
		||||
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
 | 
			
		||||
	return match(`^` + expression(res...).String() + `$`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	legacyDefaultDomain = "index.docker.io"
 | 
			
		||||
	defaultDomain       = "docker.io"
 | 
			
		||||
	officialRepoName    = "library"
 | 
			
		||||
	defaultTag          = "latest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// normalizedNamed represents a name which has been
 | 
			
		||||
// normalized and has a familiar form. A familiar name
 | 
			
		||||
// is what is used in Docker UI. An example normalized
 | 
			
		||||
// name is "docker.io/library/ubuntu" and corresponding
 | 
			
		||||
// familiar name of "ubuntu".
 | 
			
		||||
type normalizedNamed interface {
 | 
			
		||||
	Named
 | 
			
		||||
	Familiar() Named
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseNormalizedNamed parses a string into a named reference
 | 
			
		||||
// transforming a familiar name from Docker UI to a fully
 | 
			
		||||
// qualified reference. If the value may be an identifier
 | 
			
		||||
// use ParseAnyReference.
 | 
			
		||||
func ParseNormalizedNamed(s string) (Named, error) {
 | 
			
		||||
	if ok := anchoredIdentifierRegexp.MatchString(s); ok {
 | 
			
		||||
		return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
 | 
			
		||||
	}
 | 
			
		||||
	domain, remainder := splitDockerDomain(s)
 | 
			
		||||
	var remoteName string
 | 
			
		||||
	if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
 | 
			
		||||
		remoteName = remainder[:tagSep]
 | 
			
		||||
	} else {
 | 
			
		||||
		remoteName = remainder
 | 
			
		||||
	}
 | 
			
		||||
	if strings.ToLower(remoteName) != remoteName {
 | 
			
		||||
		return nil, errors.New("invalid reference format: repository name must be lowercase")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ref, err := Parse(domain + "/" + remainder)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	named, isNamed := ref.(Named)
 | 
			
		||||
	if !isNamed {
 | 
			
		||||
		return nil, fmt.Errorf("reference %s has no name", ref.String())
 | 
			
		||||
	}
 | 
			
		||||
	return named, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseDockerRef normalizes the image reference following the docker convention. This is added
 | 
			
		||||
// mainly for backward compatibility.
 | 
			
		||||
// The reference returned can only be either tagged or digested. For reference contains both tag
 | 
			
		||||
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
 | 
			
		||||
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
 | 
			
		||||
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
 | 
			
		||||
func ParseDockerRef(ref string) (Named, error) {
 | 
			
		||||
	named, err := ParseNormalizedNamed(ref)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := named.(NamedTagged); ok {
 | 
			
		||||
		if canonical, ok := named.(Canonical); ok {
 | 
			
		||||
			// The reference is both tagged and digested, only
 | 
			
		||||
			// return digested.
 | 
			
		||||
			newNamed, err := WithName(canonical.Name())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			newCanonical, err := WithDigest(newNamed, canonical.Digest())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			return newCanonical, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return TagNameOnly(named), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// splitDockerDomain splits a repository name to domain and remotename string.
 | 
			
		||||
// If no valid domain is found, the default domain is used. Repository name
 | 
			
		||||
// needs to be already validated before.
 | 
			
		||||
func splitDockerDomain(name string) (domain, remainder string) {
 | 
			
		||||
	i := strings.IndexRune(name, '/')
 | 
			
		||||
	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
 | 
			
		||||
		domain, remainder = defaultDomain, name
 | 
			
		||||
	} else {
 | 
			
		||||
		domain, remainder = name[:i], name[i+1:]
 | 
			
		||||
	}
 | 
			
		||||
	if domain == legacyDefaultDomain {
 | 
			
		||||
		domain = defaultDomain
 | 
			
		||||
	}
 | 
			
		||||
	if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
 | 
			
		||||
		remainder = officialRepoName + "/" + remainder
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// familiarizeName returns a shortened version of the name familiar
 | 
			
		||||
// to to the Docker UI. Familiar names have the default domain
 | 
			
		||||
// "docker.io" and "library/" repository prefix removed.
 | 
			
		||||
// For example, "docker.io/library/redis" will have the familiar
 | 
			
		||||
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
 | 
			
		||||
// Returns a familiarized named only reference.
 | 
			
		||||
func familiarizeName(named namedRepository) repository {
 | 
			
		||||
	repo := repository{
 | 
			
		||||
		domain: named.Domain(),
 | 
			
		||||
		path:   named.Path(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if repo.domain == defaultDomain {
 | 
			
		||||
		repo.domain = ""
 | 
			
		||||
		// Handle official repositories which have the pattern "library/<official repo name>"
 | 
			
		||||
		if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
 | 
			
		||||
			repo.path = split[1]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return repo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r reference) Familiar() Named {
 | 
			
		||||
	return reference{
 | 
			
		||||
		namedRepository: familiarizeName(r.namedRepository),
 | 
			
		||||
		tag:             r.tag,
 | 
			
		||||
		digest:          r.digest,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r repository) Familiar() Named {
 | 
			
		||||
	return familiarizeName(r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t taggedReference) Familiar() Named {
 | 
			
		||||
	return taggedReference{
 | 
			
		||||
		namedRepository: familiarizeName(t.namedRepository),
 | 
			
		||||
		tag:             t.tag,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c canonicalReference) Familiar() Named {
 | 
			
		||||
	return canonicalReference{
 | 
			
		||||
		namedRepository: familiarizeName(c.namedRepository),
 | 
			
		||||
		digest:          c.digest,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TagNameOnly adds the default tag "latest" to a reference if it only has
 | 
			
		||||
// a repo name.
 | 
			
		||||
func TagNameOnly(ref Named) Named {
 | 
			
		||||
	if IsNameOnly(ref) {
 | 
			
		||||
		namedTagged, err := WithTag(ref, defaultTag)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Default tag must be valid, to create a NamedTagged
 | 
			
		||||
			// type with non-validated input the WithTag function
 | 
			
		||||
			// should be used instead
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
		return namedTagged
 | 
			
		||||
	}
 | 
			
		||||
	return ref
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseAnyReference parses a reference string as a possible identifier,
 | 
			
		||||
// full digest, or familiar name.
 | 
			
		||||
func ParseAnyReference(ref string) (Reference, error) {
 | 
			
		||||
	if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
 | 
			
		||||
		return digestReference("sha256:" + ref), nil
 | 
			
		||||
	}
 | 
			
		||||
	if dgst, err := digest.Parse(ref); err == nil {
 | 
			
		||||
		return digestReference(dgst), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ParseNormalizedNamed(ref)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsNameOnly returns true if reference only contains a repo name.
 | 
			
		||||
func IsNameOnly(ref Named) bool {
 | 
			
		||||
	if _, ok := ref.(NamedTagged); ok {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := ref.(Canonical); ok {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FamiliarName returns the familiar name string
 | 
			
		||||
// for the given named, familiarizing if needed.
 | 
			
		||||
func FamiliarName(ref Named) string {
 | 
			
		||||
	if nn, ok := ref.(normalizedNamed); ok {
 | 
			
		||||
		return nn.Familiar().Name()
 | 
			
		||||
	}
 | 
			
		||||
	return ref.Name()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FamiliarString returns the familiar string representation
 | 
			
		||||
// for the given reference, familiarizing if needed.
 | 
			
		||||
func FamiliarString(ref Reference) string {
 | 
			
		||||
	if nn, ok := ref.(normalizedNamed); ok {
 | 
			
		||||
		return nn.Familiar().String()
 | 
			
		||||
	}
 | 
			
		||||
	return ref.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FamiliarMatch reports whether ref matches the specified pattern.
 | 
			
		||||
// See https://godoc.org/path#Match for supported patterns.
 | 
			
		||||
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
 | 
			
		||||
	matched, err := path.Match(pattern, FamiliarString(ref))
 | 
			
		||||
	if namedRef, isNamed := ref.(Named); isNamed && !matched {
 | 
			
		||||
		matched, _ = path.Match(pattern, FamiliarName(namedRef))
 | 
			
		||||
	}
 | 
			
		||||
	return matched, err
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,283 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ErrorCoder is the base interface for ErrorCode and Error allowing
 | 
			
		||||
// users of each to just call ErrorCode to get the real ID of each
 | 
			
		||||
type ErrorCoder interface {
 | 
			
		||||
	ErrorCode() ErrorCode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrorCode represents the error type. The errors are serialized via strings
 | 
			
		||||
// and the integer format may change and should *never* be exported.
 | 
			
		||||
type ErrorCode int
 | 
			
		||||
 | 
			
		||||
var _ error = ErrorCode(0)
 | 
			
		||||
 | 
			
		||||
// ErrorCode just returns itself
 | 
			
		||||
func (ec ErrorCode) ErrorCode() ErrorCode {
 | 
			
		||||
	return ec
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error returns the ID/Value
 | 
			
		||||
func (ec ErrorCode) Error() string {
 | 
			
		||||
	// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
 | 
			
		||||
	return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Descriptor returns the descriptor for the error code.
 | 
			
		||||
func (ec ErrorCode) Descriptor() ErrorDescriptor {
 | 
			
		||||
	d, ok := errorCodeToDescriptors[ec]
 | 
			
		||||
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return ErrorCodeUnknown.Descriptor()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String returns the canonical identifier for this error code.
 | 
			
		||||
func (ec ErrorCode) String() string {
 | 
			
		||||
	return ec.Descriptor().Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Message returned the human-readable error message for this error code.
 | 
			
		||||
func (ec ErrorCode) Message() string {
 | 
			
		||||
	return ec.Descriptor().Message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalText encodes the receiver into UTF-8-encoded text and returns the
 | 
			
		||||
// result.
 | 
			
		||||
func (ec ErrorCode) MarshalText() (text []byte, err error) {
 | 
			
		||||
	return []byte(ec.String()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnmarshalText decodes the form generated by MarshalText.
 | 
			
		||||
func (ec *ErrorCode) UnmarshalText(text []byte) error {
 | 
			
		||||
	desc, ok := idToDescriptors[string(text)]
 | 
			
		||||
 | 
			
		||||
	if !ok {
 | 
			
		||||
		desc = ErrorCodeUnknown.Descriptor()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*ec = desc.Code
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithMessage creates a new Error struct based on the passed-in info and
 | 
			
		||||
// overrides the Message property.
 | 
			
		||||
func (ec ErrorCode) WithMessage(message string) Error {
 | 
			
		||||
	return Error{
 | 
			
		||||
		Code:    ec,
 | 
			
		||||
		Message: message,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithDetail creates a new Error struct based on the passed-in info and
 | 
			
		||||
// set the Detail property appropriately
 | 
			
		||||
func (ec ErrorCode) WithDetail(detail interface{}) Error {
 | 
			
		||||
	return Error{
 | 
			
		||||
		Code:    ec,
 | 
			
		||||
		Message: ec.Message(),
 | 
			
		||||
	}.WithDetail(detail)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithArgs creates a new Error struct and sets the Args slice
 | 
			
		||||
func (ec ErrorCode) WithArgs(args ...interface{}) Error {
 | 
			
		||||
	return Error{
 | 
			
		||||
		Code:    ec,
 | 
			
		||||
		Message: ec.Message(),
 | 
			
		||||
	}.WithArgs(args...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error provides a wrapper around ErrorCode with extra Details provided.
 | 
			
		||||
type Error struct {
 | 
			
		||||
	Code    ErrorCode   `json:"code"`
 | 
			
		||||
	Message string      `json:"message"`
 | 
			
		||||
	Detail  interface{} `json:"detail,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// TODO(duglin): See if we need an "args" property so we can do the
 | 
			
		||||
	// variable substitution right before showing the message to the user
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ error = Error{}
 | 
			
		||||
 | 
			
		||||
// ErrorCode returns the ID/Value of this Error
 | 
			
		||||
func (e Error) ErrorCode() ErrorCode {
 | 
			
		||||
	return e.Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error returns a human readable representation of the error.
 | 
			
		||||
func (e Error) Error() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithDetail will return a new Error, based on the current one, but with
 | 
			
		||||
// some Detail info added
 | 
			
		||||
func (e Error) WithDetail(detail interface{}) Error {
 | 
			
		||||
	return Error{
 | 
			
		||||
		Code:    e.Code,
 | 
			
		||||
		Message: e.Message,
 | 
			
		||||
		Detail:  detail,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithArgs uses the passed-in list of interface{} as the substitution
 | 
			
		||||
// variables in the Error's Message string, but returns a new Error
 | 
			
		||||
func (e Error) WithArgs(args ...interface{}) Error {
 | 
			
		||||
	return Error{
 | 
			
		||||
		Code:    e.Code,
 | 
			
		||||
		Message: fmt.Sprintf(e.Code.Message(), args...),
 | 
			
		||||
		Detail:  e.Detail,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrorDescriptor provides relevant information about a given error code.
 | 
			
		||||
type ErrorDescriptor struct {
 | 
			
		||||
	// Code is the error code that this descriptor describes.
 | 
			
		||||
	Code ErrorCode
 | 
			
		||||
 | 
			
		||||
	// Value provides a unique, string key, often captilized with
 | 
			
		||||
	// underscores, to identify the error code. This value is used as the
 | 
			
		||||
	// keyed value when serializing api errors.
 | 
			
		||||
	Value string
 | 
			
		||||
 | 
			
		||||
	// Message is a short, human readable decription of the error condition
 | 
			
		||||
	// included in API responses.
 | 
			
		||||
	Message string
 | 
			
		||||
 | 
			
		||||
	// Description provides a complete account of the errors purpose, suitable
 | 
			
		||||
	// for use in documentation.
 | 
			
		||||
	Description string
 | 
			
		||||
 | 
			
		||||
	// HTTPStatusCode provides the http status code that is associated with
 | 
			
		||||
	// this error condition.
 | 
			
		||||
	HTTPStatusCode int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseErrorCode returns the value by the string error code.
 | 
			
		||||
// `ErrorCodeUnknown` will be returned if the error is not known.
 | 
			
		||||
func ParseErrorCode(value string) ErrorCode {
 | 
			
		||||
	ed, ok := idToDescriptors[value]
 | 
			
		||||
	if ok {
 | 
			
		||||
		return ed.Code
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ErrorCodeUnknown
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Errors provides the envelope for multiple errors and a few sugar methods
 | 
			
		||||
// for use within the application.
 | 
			
		||||
type Errors []error
 | 
			
		||||
 | 
			
		||||
var _ error = Errors{}
 | 
			
		||||
 | 
			
		||||
func (errs Errors) Error() string {
 | 
			
		||||
	switch len(errs) {
 | 
			
		||||
	case 0:
 | 
			
		||||
		return "<nil>"
 | 
			
		||||
	case 1:
 | 
			
		||||
		return errs[0].Error()
 | 
			
		||||
	default:
 | 
			
		||||
		msg := "errors:\n"
 | 
			
		||||
		for _, err := range errs {
 | 
			
		||||
			msg += err.Error() + "\n"
 | 
			
		||||
		}
 | 
			
		||||
		return msg
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len returns the current number of errors.
 | 
			
		||||
func (errs Errors) Len() int {
 | 
			
		||||
	return len(errs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalJSON converts slice of error, ErrorCode or Error into a
 | 
			
		||||
// slice of Error - then serializes
 | 
			
		||||
func (errs Errors) MarshalJSON() ([]byte, error) {
 | 
			
		||||
	var tmpErrs struct {
 | 
			
		||||
		Errors []Error `json:"errors,omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, daErr := range errs {
 | 
			
		||||
		var err Error
 | 
			
		||||
 | 
			
		||||
		switch daErr := daErr.(type) {
 | 
			
		||||
		case ErrorCode:
 | 
			
		||||
			err = daErr.WithDetail(nil)
 | 
			
		||||
		case Error:
 | 
			
		||||
			err = daErr
 | 
			
		||||
		default:
 | 
			
		||||
			err = ErrorCodeUnknown.WithDetail(daErr)
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If the Error struct was setup and they forgot to set the
 | 
			
		||||
		// Message field (meaning its "") then grab it from the ErrCode
 | 
			
		||||
		msg := err.Message
 | 
			
		||||
		if msg == "" {
 | 
			
		||||
			msg = err.Code.Message()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tmpErrs.Errors = append(tmpErrs.Errors, Error{
 | 
			
		||||
			Code:    err.Code,
 | 
			
		||||
			Message: msg,
 | 
			
		||||
			Detail:  err.Detail,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return json.Marshal(tmpErrs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnmarshalJSON deserializes []Error and then converts it into slice of
 | 
			
		||||
// Error or ErrorCode
 | 
			
		||||
func (errs *Errors) UnmarshalJSON(data []byte) error {
 | 
			
		||||
	var tmpErrs struct {
 | 
			
		||||
		Errors []Error
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := json.Unmarshal(data, &tmpErrs); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var newErrs Errors
 | 
			
		||||
	for _, daErr := range tmpErrs.Errors {
 | 
			
		||||
		// If Message is empty or exactly matches the Code's message string
 | 
			
		||||
		// then just use the Code, no need for a full Error struct
 | 
			
		||||
		if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
 | 
			
		||||
			// Error's w/o details get converted to ErrorCode
 | 
			
		||||
			newErrs = append(newErrs, daErr.Code)
 | 
			
		||||
		} else {
 | 
			
		||||
			// Error's w/ details are untouched
 | 
			
		||||
			newErrs = append(newErrs, Error{
 | 
			
		||||
				Code:    daErr.Code,
 | 
			
		||||
				Message: daErr.Message,
 | 
			
		||||
				Detail:  daErr.Detail,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*errs = newErrs
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,154 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{}
 | 
			
		||||
	idToDescriptors        = map[string]ErrorDescriptor{}
 | 
			
		||||
	groupToDescriptors     = map[string][]ErrorDescriptor{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// ErrorCodeUnknown is a generic error that can be used as a last
 | 
			
		||||
	// resort if there is no situation-specific error message that can be used
 | 
			
		||||
	ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
 | 
			
		||||
		Value:   "UNKNOWN",
 | 
			
		||||
		Message: "unknown error",
 | 
			
		||||
		Description: `Generic error returned when the error does not have an
 | 
			
		||||
			                                            API classification.`,
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeUnsupported is returned when an operation is not supported.
 | 
			
		||||
	ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{
 | 
			
		||||
		Value:   "UNSUPPORTED",
 | 
			
		||||
		Message: "The operation is unsupported.",
 | 
			
		||||
		Description: `The operation was unsupported due to a missing
 | 
			
		||||
		implementation or invalid set of parameters.`,
 | 
			
		||||
		HTTPStatusCode: http.StatusMethodNotAllowed,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeUnauthorized is returned if a request requires
 | 
			
		||||
	// authentication.
 | 
			
		||||
	ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
 | 
			
		||||
		Value:   "UNAUTHORIZED",
 | 
			
		||||
		Message: "authentication required",
 | 
			
		||||
		Description: `The access controller was unable to authenticate
 | 
			
		||||
		the client. Often this will be accompanied by a
 | 
			
		||||
		Www-Authenticate HTTP response header indicating how to
 | 
			
		||||
		authenticate.`,
 | 
			
		||||
		HTTPStatusCode: http.StatusUnauthorized,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeDenied is returned if a client does not have sufficient
 | 
			
		||||
	// permission to perform an action.
 | 
			
		||||
	ErrorCodeDenied = Register("errcode", ErrorDescriptor{
 | 
			
		||||
		Value:   "DENIED",
 | 
			
		||||
		Message: "requested access to the resource is denied",
 | 
			
		||||
		Description: `The access controller denied access for the
 | 
			
		||||
		operation on a resource.`,
 | 
			
		||||
		HTTPStatusCode: http.StatusForbidden,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeUnavailable provides a common error to report unavailability
 | 
			
		||||
	// of a service or endpoint.
 | 
			
		||||
	ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{
 | 
			
		||||
		Value:          "UNAVAILABLE",
 | 
			
		||||
		Message:        "service unavailable",
 | 
			
		||||
		Description:    "Returned when a service is not available",
 | 
			
		||||
		HTTPStatusCode: http.StatusServiceUnavailable,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeTooManyRequests is returned if a client attempts too many
 | 
			
		||||
	// times to contact a service endpoint.
 | 
			
		||||
	ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{
 | 
			
		||||
		Value:   "TOOMANYREQUESTS",
 | 
			
		||||
		Message: "too many requests",
 | 
			
		||||
		Description: `Returned when a client attempts to contact a
 | 
			
		||||
		service too many times`,
 | 
			
		||||
		HTTPStatusCode: http.StatusTooManyRequests,
 | 
			
		||||
	})
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var nextCode = 1000
 | 
			
		||||
var registerLock sync.Mutex
 | 
			
		||||
 | 
			
		||||
// Register will make the passed-in error known to the environment and
 | 
			
		||||
// return a new ErrorCode
 | 
			
		||||
func Register(group string, descriptor ErrorDescriptor) ErrorCode {
 | 
			
		||||
	registerLock.Lock()
 | 
			
		||||
	defer registerLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	descriptor.Code = ErrorCode(nextCode)
 | 
			
		||||
 | 
			
		||||
	if _, ok := idToDescriptors[descriptor.Value]; ok {
 | 
			
		||||
		panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value))
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := errorCodeToDescriptors[descriptor.Code]; ok {
 | 
			
		||||
		panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	groupToDescriptors[group] = append(groupToDescriptors[group], descriptor)
 | 
			
		||||
	errorCodeToDescriptors[descriptor.Code] = descriptor
 | 
			
		||||
	idToDescriptors[descriptor.Value] = descriptor
 | 
			
		||||
 | 
			
		||||
	nextCode++
 | 
			
		||||
	return descriptor.Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type byValue []ErrorDescriptor
 | 
			
		||||
 | 
			
		||||
func (a byValue) Len() int           { return len(a) }
 | 
			
		||||
func (a byValue) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
			
		||||
func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value }
 | 
			
		||||
 | 
			
		||||
// GetGroupNames returns the list of Error group names that are registered
 | 
			
		||||
func GetGroupNames() []string {
 | 
			
		||||
	keys := []string{}
 | 
			
		||||
 | 
			
		||||
	for k := range groupToDescriptors {
 | 
			
		||||
		keys = append(keys, k)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(keys)
 | 
			
		||||
	return keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetErrorCodeGroup returns the named group of error descriptors
 | 
			
		||||
func GetErrorCodeGroup(name string) []ErrorDescriptor {
 | 
			
		||||
	desc := groupToDescriptors[name]
 | 
			
		||||
	sort.Sort(byValue(desc))
 | 
			
		||||
	return desc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are
 | 
			
		||||
// registered, irrespective of what group they're in
 | 
			
		||||
func GetErrorAllDescriptors() []ErrorDescriptor {
 | 
			
		||||
	result := []ErrorDescriptor{}
 | 
			
		||||
 | 
			
		||||
	for _, group := range GetGroupNames() {
 | 
			
		||||
		result = append(result, GetErrorCodeGroup(group)...)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(byValue(result))
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,202 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd Authors.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HostCapabilities represent the capabilities of the registry
 | 
			
		||||
// host. This also represents the set of operations for which
 | 
			
		||||
// the registry host may be trusted to perform.
 | 
			
		||||
//
 | 
			
		||||
// For example pushing is a capability which should only be
 | 
			
		||||
// performed on an upstream source, not a mirror.
 | 
			
		||||
// Resolving (the process of converting a name into a digest)
 | 
			
		||||
// must be considered a trusted operation and only done by
 | 
			
		||||
// a host which is trusted (or more preferably by secure process
 | 
			
		||||
// which can prove the provenance of the mapping). A public
 | 
			
		||||
// mirror should never be trusted to do a resolve action.
 | 
			
		||||
//
 | 
			
		||||
// | Registry Type    | Pull | Resolve | Push |
 | 
			
		||||
// |------------------|------|---------|------|
 | 
			
		||||
// | Public Registry  | yes  | yes     | yes  |
 | 
			
		||||
// | Private Registry | yes  | yes     | yes  |
 | 
			
		||||
// | Public Mirror    | yes  | no      | no   |
 | 
			
		||||
// | Private Mirror   | yes  | yes     | no   |
 | 
			
		||||
type HostCapabilities uint8
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// HostCapabilityPull represents the capability to fetch manifests
 | 
			
		||||
	// and blobs by digest
 | 
			
		||||
	HostCapabilityPull HostCapabilities = 1 << iota
 | 
			
		||||
 | 
			
		||||
	// HostCapabilityResolve represents the capability to fetch manifests
 | 
			
		||||
	// by name
 | 
			
		||||
	HostCapabilityResolve
 | 
			
		||||
 | 
			
		||||
	// HostCapabilityPush represents the capability to push blobs and
 | 
			
		||||
	// manifests
 | 
			
		||||
	HostCapabilityPush
 | 
			
		||||
 | 
			
		||||
	// Reserved for future capabilities (i.e. search, catalog, remove)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c HostCapabilities) Has(t HostCapabilities) bool {
 | 
			
		||||
	return c&t == t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistryHost represents a complete configuration for a registry
 | 
			
		||||
// host, representing the capabilities, authorizations, connection
 | 
			
		||||
// configuration, and location.
 | 
			
		||||
type RegistryHost struct {
 | 
			
		||||
	Client       *http.Client
 | 
			
		||||
	Authorizer   Authorizer
 | 
			
		||||
	Host         string
 | 
			
		||||
	Scheme       string
 | 
			
		||||
	Path         string
 | 
			
		||||
	Capabilities HostCapabilities
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistryHosts fetches the registry hosts for a given namespace,
 | 
			
		||||
// provided by the host component of an distribution image reference.
 | 
			
		||||
type RegistryHosts func(string) ([]RegistryHost, error)
 | 
			
		||||
 | 
			
		||||
// Registries joins multiple registry configuration functions, using the same
 | 
			
		||||
// order as provided within the arguments. When an empty registry configuration
 | 
			
		||||
// is returned with a nil error, the next function will be called.
 | 
			
		||||
// NOTE: This function will not join configurations, as soon as a non-empty
 | 
			
		||||
// configuration is returned from a configuration function, it will be returned
 | 
			
		||||
// to the caller.
 | 
			
		||||
func Registries(registries ...RegistryHosts) RegistryHosts {
 | 
			
		||||
	return func(host string) ([]RegistryHost, error) {
 | 
			
		||||
		for _, registry := range registries {
 | 
			
		||||
			config, err := registry(host)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return config, err
 | 
			
		||||
			}
 | 
			
		||||
			if len(config) > 0 {
 | 
			
		||||
				return config, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type registryOpts struct {
 | 
			
		||||
	authorizer Authorizer
 | 
			
		||||
	plainHTTP  func(string) (bool, error)
 | 
			
		||||
	host       func(string) (string, error)
 | 
			
		||||
	client     *http.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistryOpt defines a registry default option
 | 
			
		||||
type RegistryOpt func(*registryOpts)
 | 
			
		||||
 | 
			
		||||
// WithPlainHTTP configures registries to use plaintext http scheme
 | 
			
		||||
// for the provided host match function.
 | 
			
		||||
func WithPlainHTTP(f func(string) (bool, error)) RegistryOpt {
 | 
			
		||||
	return func(opts *registryOpts) {
 | 
			
		||||
		opts.plainHTTP = f
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithAuthorizer configures the default authorizer for a registry
 | 
			
		||||
func WithAuthorizer(a Authorizer) RegistryOpt {
 | 
			
		||||
	return func(opts *registryOpts) {
 | 
			
		||||
		opts.authorizer = a
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithHostTranslator defines the default translator to use for registry hosts
 | 
			
		||||
func WithHostTranslator(h func(string) (string, error)) RegistryOpt {
 | 
			
		||||
	return func(opts *registryOpts) {
 | 
			
		||||
		opts.host = h
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithClient configures the default http client for a registry
 | 
			
		||||
func WithClient(c *http.Client) RegistryOpt {
 | 
			
		||||
	return func(opts *registryOpts) {
 | 
			
		||||
		opts.client = c
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConfigureDefaultRegistries is used to create a default configuration for
 | 
			
		||||
// registries. For more advanced configurations or per-domain setups,
 | 
			
		||||
// the RegistryHosts interface should be used directly.
 | 
			
		||||
// NOTE: This function will always return a non-empty value or error
 | 
			
		||||
func ConfigureDefaultRegistries(ropts ...RegistryOpt) RegistryHosts {
 | 
			
		||||
	var opts registryOpts
 | 
			
		||||
	for _, opt := range ropts {
 | 
			
		||||
		opt(&opts)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return func(host string) ([]RegistryHost, error) {
 | 
			
		||||
		config := RegistryHost{
 | 
			
		||||
			Client:       opts.client,
 | 
			
		||||
			Authorizer:   opts.authorizer,
 | 
			
		||||
			Host:         host,
 | 
			
		||||
			Scheme:       "https",
 | 
			
		||||
			Path:         "/v2",
 | 
			
		||||
			Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if config.Client == nil {
 | 
			
		||||
			config.Client = http.DefaultClient
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if opts.plainHTTP != nil {
 | 
			
		||||
			match, err := opts.plainHTTP(host)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if match {
 | 
			
		||||
				config.Scheme = "http"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if opts.host != nil {
 | 
			
		||||
			var err error
 | 
			
		||||
			config.Host, err = opts.host(config.Host)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		} else if host == "docker.io" {
 | 
			
		||||
			config.Host = "registry-1.docker.io"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return []RegistryHost{config}, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MatchAllHosts is a host match function which is always true.
 | 
			
		||||
func MatchAllHosts(string) (bool, error) {
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MatchLocalhost is a host match function which returns true for
 | 
			
		||||
// localhost.
 | 
			
		||||
func MatchLocalhost(host string) (bool, error) {
 | 
			
		||||
	for _, s := range []string{"localhost", "127.0.0.1", "[::1]"} {
 | 
			
		||||
		if len(host) >= len(s) && host[0:len(s)] == s && (len(host) == len(s) || host[len(s)] == ':') {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return host == "::1", nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in New Issue