mirror of https://github.com/grafana/loki
Add support for memberlist dns-based discovery (#2288)
* Bump cortex to 0ea5a8b5 to enable memberlist dns-based discovery * Update docs for memberlist config DNS-based discovery support * Fix mock chunk store client DeleteChunk signaturepull/2290/head
parent
384520436d
commit
bc4f1f5833
@ -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,90 @@ |
||||
/* |
||||
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 log |
||||
|
||||
import ( |
||||
"context" |
||||
"sync/atomic" |
||||
|
||||
"github.com/sirupsen/logrus" |
||||
) |
||||
|
||||
var ( |
||||
// G is an alias for GetLogger.
|
||||
//
|
||||
// We may want to define this locally to a package to get package tagged log
|
||||
// messages.
|
||||
G = GetLogger |
||||
|
||||
// L is an alias for the standard logger.
|
||||
L = logrus.NewEntry(logrus.StandardLogger()) |
||||
) |
||||
|
||||
type ( |
||||
loggerKey struct{} |
||||
) |
||||
|
||||
// TraceLevel is the log level for tracing. Trace level is lower than debug level,
|
||||
// and is usually used to trace detailed behavior of the program.
|
||||
const TraceLevel = logrus.Level(uint32(logrus.DebugLevel + 1)) |
||||
|
||||
// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
|
||||
// ensure the formatted time is always the same number of characters.
|
||||
const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" |
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
// It supports trace level.
|
||||
func ParseLevel(lvl string) (logrus.Level, error) { |
||||
if lvl == "trace" { |
||||
return TraceLevel, nil |
||||
} |
||||
return logrus.ParseLevel(lvl) |
||||
} |
||||
|
||||
// WithLogger returns a new context with the provided logger. Use in
|
||||
// combination with logger.WithField(s) for great effect.
|
||||
func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context { |
||||
return context.WithValue(ctx, loggerKey{}, logger) |
||||
} |
||||
|
||||
// GetLogger retrieves the current logger from the context. If no logger is
|
||||
// available, the default logger is returned.
|
||||
func GetLogger(ctx context.Context) *logrus.Entry { |
||||
logger := ctx.Value(loggerKey{}) |
||||
|
||||
if logger == nil { |
||||
return L |
||||
} |
||||
|
||||
return logger.(*logrus.Entry) |
||||
} |
||||
|
||||
// Trace logs a message at level Trace with the log entry passed-in.
|
||||
func Trace(e *logrus.Entry, args ...interface{}) { |
||||
level := logrus.Level(atomic.LoadUint32((*uint32)(&e.Logger.Level))) |
||||
if level >= TraceLevel { |
||||
e.Debug(args...) |
||||
} |
||||
} |
||||
|
||||
// Tracef logs a message at level Trace with the log entry passed-in.
|
||||
func Tracef(e *logrus.Entry, format string, args ...interface{}) { |
||||
level := logrus.Level(atomic.LoadUint32((*uint32)(&e.Logger.Level))) |
||||
if level >= TraceLevel { |
||||
e.Debugf(format, args...) |
||||
} |
||||
} |
||||
@ -0,0 +1,229 @@ |
||||
/* |
||||
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 platforms |
||||
|
||||
import specs "github.com/opencontainers/image-spec/specs-go/v1" |
||||
|
||||
// MatchComparer is able to match and compare platforms to
|
||||
// filter and sort platforms.
|
||||
type MatchComparer interface { |
||||
Matcher |
||||
|
||||
Less(specs.Platform, specs.Platform) bool |
||||
} |
||||
|
||||
// Only returns a match comparer for a single platform
|
||||
// using default resolution logic for the platform.
|
||||
//
|
||||
// For ARMv8, will also match ARMv7, ARMv6 and ARMv5 (for 32bit runtimes)
|
||||
// For ARMv7, will also match ARMv6 and ARMv5
|
||||
// For ARMv6, will also match ARMv5
|
||||
func Only(platform specs.Platform) MatchComparer { |
||||
platform = Normalize(platform) |
||||
if platform.Architecture == "arm" { |
||||
if platform.Variant == "v8" { |
||||
return orderedPlatformComparer{ |
||||
matchers: []Matcher{ |
||||
&matcher{ |
||||
Platform: platform, |
||||
}, |
||||
&matcher{ |
||||
Platform: specs.Platform{ |
||||
Architecture: platform.Architecture, |
||||
OS: platform.OS, |
||||
OSVersion: platform.OSVersion, |
||||
OSFeatures: platform.OSFeatures, |
||||
Variant: "v7", |
||||
}, |
||||
}, |
||||
&matcher{ |
||||
Platform: specs.Platform{ |
||||
Architecture: platform.Architecture, |
||||
OS: platform.OS, |
||||
OSVersion: platform.OSVersion, |
||||
OSFeatures: platform.OSFeatures, |
||||
Variant: "v6", |
||||
}, |
||||
}, |
||||
&matcher{ |
||||
Platform: specs.Platform{ |
||||
Architecture: platform.Architecture, |
||||
OS: platform.OS, |
||||
OSVersion: platform.OSVersion, |
||||
OSFeatures: platform.OSFeatures, |
||||
Variant: "v5", |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
} |
||||
if platform.Variant == "v7" { |
||||
return orderedPlatformComparer{ |
||||
matchers: []Matcher{ |
||||
&matcher{ |
||||
Platform: platform, |
||||
}, |
||||
&matcher{ |
||||
Platform: specs.Platform{ |
||||
Architecture: platform.Architecture, |
||||
OS: platform.OS, |
||||
OSVersion: platform.OSVersion, |
||||
OSFeatures: platform.OSFeatures, |
||||
Variant: "v6", |
||||
}, |
||||
}, |
||||
&matcher{ |
||||
Platform: specs.Platform{ |
||||
Architecture: platform.Architecture, |
||||
OS: platform.OS, |
||||
OSVersion: platform.OSVersion, |
||||
OSFeatures: platform.OSFeatures, |
||||
Variant: "v5", |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
} |
||||
if platform.Variant == "v6" { |
||||
return orderedPlatformComparer{ |
||||
matchers: []Matcher{ |
||||
&matcher{ |
||||
Platform: platform, |
||||
}, |
||||
&matcher{ |
||||
Platform: specs.Platform{ |
||||
Architecture: platform.Architecture, |
||||
OS: platform.OS, |
||||
OSVersion: platform.OSVersion, |
||||
OSFeatures: platform.OSFeatures, |
||||
Variant: "v5", |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
} |
||||
} |
||||
|
||||
return singlePlatformComparer{ |
||||
Matcher: &matcher{ |
||||
Platform: platform, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// Ordered returns a platform MatchComparer which matches any of the platforms
|
||||
// but orders them in order they are provided.
|
||||
func Ordered(platforms ...specs.Platform) MatchComparer { |
||||
matchers := make([]Matcher, len(platforms)) |
||||
for i := range platforms { |
||||
matchers[i] = NewMatcher(platforms[i]) |
||||
} |
||||
return orderedPlatformComparer{ |
||||
matchers: matchers, |
||||
} |
||||
} |
||||
|
||||
// Any returns a platform MatchComparer which matches any of the platforms
|
||||
// with no preference for ordering.
|
||||
func Any(platforms ...specs.Platform) MatchComparer { |
||||
matchers := make([]Matcher, len(platforms)) |
||||
for i := range platforms { |
||||
matchers[i] = NewMatcher(platforms[i]) |
||||
} |
||||
return anyPlatformComparer{ |
||||
matchers: matchers, |
||||
} |
||||
} |
||||
|
||||
// All is a platform MatchComparer which matches all platforms
|
||||
// with preference for ordering.
|
||||
var All MatchComparer = allPlatformComparer{} |
||||
|
||||
type singlePlatformComparer struct { |
||||
Matcher |
||||
} |
||||
|
||||
func (c singlePlatformComparer) Less(p1, p2 specs.Platform) bool { |
||||
return c.Match(p1) && !c.Match(p2) |
||||
} |
||||
|
||||
type orderedPlatformComparer struct { |
||||
matchers []Matcher |
||||
} |
||||
|
||||
func (c orderedPlatformComparer) Match(platform specs.Platform) bool { |
||||
for _, m := range c.matchers { |
||||
if m.Match(platform) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool { |
||||
for _, m := range c.matchers { |
||||
p1m := m.Match(p1) |
||||
p2m := m.Match(p2) |
||||
if p1m && !p2m { |
||||
return true |
||||
} |
||||
if p1m || p2m { |
||||
return false |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
type anyPlatformComparer struct { |
||||
matchers []Matcher |
||||
} |
||||
|
||||
func (c anyPlatformComparer) Match(platform specs.Platform) bool { |
||||
for _, m := range c.matchers { |
||||
if m.Match(platform) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool { |
||||
var p1m, p2m bool |
||||
for _, m := range c.matchers { |
||||
if !p1m && m.Match(p1) { |
||||
p1m = true |
||||
} |
||||
if !p2m && m.Match(p2) { |
||||
p2m = true |
||||
} |
||||
if p1m && p2m { |
||||
return false |
||||
} |
||||
} |
||||
// If one matches, and the other does, sort match first
|
||||
return p1m && !p2m |
||||
} |
||||
|
||||
type allPlatformComparer struct{} |
||||
|
||||
func (allPlatformComparer) Match(specs.Platform) bool { |
||||
return true |
||||
} |
||||
|
||||
func (allPlatformComparer) Less(specs.Platform, specs.Platform) bool { |
||||
return false |
||||
} |
||||
@ -0,0 +1,117 @@ |
||||
/* |
||||
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 platforms |
||||
|
||||
import ( |
||||
"bufio" |
||||
"os" |
||||
"runtime" |
||||
"strings" |
||||
|
||||
"github.com/containerd/containerd/errdefs" |
||||
"github.com/containerd/containerd/log" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
// Present the ARM instruction set architecture, eg: v7, v8
|
||||
var cpuVariant string |
||||
|
||||
func init() { |
||||
if isArmArch(runtime.GOARCH) { |
||||
cpuVariant = getCPUVariant() |
||||
} else { |
||||
cpuVariant = "" |
||||
} |
||||
} |
||||
|
||||
// For Linux, the kernel has already detected the ABI, ISA and Features.
|
||||
// So we don't need to access the ARM registers to detect platform information
|
||||
// by ourselves. We can just parse these information from /proc/cpuinfo
|
||||
func getCPUInfo(pattern string) (info string, err error) { |
||||
if !isLinuxOS(runtime.GOOS) { |
||||
return "", errors.Wrapf(errdefs.ErrNotImplemented, "getCPUInfo for OS %s", runtime.GOOS) |
||||
} |
||||
|
||||
cpuinfo, err := os.Open("/proc/cpuinfo") |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
defer cpuinfo.Close() |
||||
|
||||
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
|
||||
// the first core is enough.
|
||||
scanner := bufio.NewScanner(cpuinfo) |
||||
for scanner.Scan() { |
||||
newline := scanner.Text() |
||||
list := strings.Split(newline, ":") |
||||
|
||||
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) { |
||||
return strings.TrimSpace(list[1]), nil |
||||
} |
||||
} |
||||
|
||||
// Check whether the scanner encountered errors
|
||||
err = scanner.Err() |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return "", errors.Wrapf(errdefs.ErrNotFound, "getCPUInfo for pattern: %s", pattern) |
||||
} |
||||
|
||||
func getCPUVariant() string { |
||||
if runtime.GOOS == "windows" { |
||||
// Windows only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
||||
// runtime.GOARCH to determine the variants
|
||||
var variant string |
||||
switch runtime.GOARCH { |
||||
case "arm64": |
||||
variant = "v8" |
||||
case "arm": |
||||
variant = "v7" |
||||
default: |
||||
variant = "unknown" |
||||
} |
||||
|
||||
return variant |
||||
} |
||||
|
||||
variant, err := getCPUInfo("Cpu architecture") |
||||
if err != nil { |
||||
log.L.WithError(err).Error("failure getting variant") |
||||
return "" |
||||
} |
||||
|
||||
switch variant { |
||||
case "8", "AArch64": |
||||
variant = "v8" |
||||
case "7", "7M", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)": |
||||
variant = "v7" |
||||
case "6", "6TEJ": |
||||
variant = "v6" |
||||
case "5", "5T", "5TE", "5TEJ": |
||||
variant = "v5" |
||||
case "4", "4T": |
||||
variant = "v4" |
||||
case "3": |
||||
variant = "v3" |
||||
default: |
||||
variant = "unknown" |
||||
} |
||||
|
||||
return variant |
||||
} |
||||
@ -0,0 +1,114 @@ |
||||
/* |
||||
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 platforms |
||||
|
||||
import ( |
||||
"runtime" |
||||
"strings" |
||||
) |
||||
|
||||
// isLinuxOS returns true if the operating system is Linux.
|
||||
//
|
||||
// The OS value should be normalized before calling this function.
|
||||
func isLinuxOS(os string) bool { |
||||
return os == "linux" |
||||
} |
||||
|
||||
// These function are generated from https://golang.org/src/go/build/syslist.go.
|
||||
//
|
||||
// We use switch statements because they are slightly faster than map lookups
|
||||
// and use a little less memory.
|
||||
|
||||
// isKnownOS returns true if we know about the operating system.
|
||||
//
|
||||
// The OS value should be normalized before calling this function.
|
||||
func isKnownOS(os string) bool { |
||||
switch os { |
||||
case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos": |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// isArmArch returns true if the architecture is ARM.
|
||||
//
|
||||
// The arch value should be normalized before being passed to this function.
|
||||
func isArmArch(arch string) bool { |
||||
switch arch { |
||||
case "arm", "arm64": |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// isKnownArch returns true if we know about the architecture.
|
||||
//
|
||||
// The arch value should be normalized before being passed to this function.
|
||||
func isKnownArch(arch string) bool { |
||||
switch arch { |
||||
case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm": |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func normalizeOS(os string) string { |
||||
if os == "" { |
||||
return runtime.GOOS |
||||
} |
||||
os = strings.ToLower(os) |
||||
|
||||
switch os { |
||||
case "macos": |
||||
os = "darwin" |
||||
} |
||||
return os |
||||
} |
||||
|
||||
// normalizeArch normalizes the architecture.
|
||||
func normalizeArch(arch, variant string) (string, string) { |
||||
arch, variant = strings.ToLower(arch), strings.ToLower(variant) |
||||
switch arch { |
||||
case "i386": |
||||
arch = "386" |
||||
variant = "" |
||||
case "x86_64", "x86-64": |
||||
arch = "amd64" |
||||
variant = "" |
||||
case "aarch64", "arm64": |
||||
arch = "arm64" |
||||
switch variant { |
||||
case "8", "v8": |
||||
variant = "" |
||||
} |
||||
case "armhf": |
||||
arch = "arm" |
||||
variant = "v7" |
||||
case "armel": |
||||
arch = "arm" |
||||
variant = "v6" |
||||
case "arm": |
||||
switch variant { |
||||
case "", "7": |
||||
variant = "v7" |
||||
case "5", "6", "8": |
||||
variant = "v" + variant |
||||
} |
||||
} |
||||
|
||||
return arch, variant |
||||
} |
||||
@ -0,0 +1,38 @@ |
||||
/* |
||||
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 platforms |
||||
|
||||
import ( |
||||
"runtime" |
||||
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1" |
||||
) |
||||
|
||||
// DefaultString returns the default string specifier for the platform.
|
||||
func DefaultString() string { |
||||
return Format(DefaultSpec()) |
||||
} |
||||
|
||||
// DefaultSpec returns the current platform's default platform specification.
|
||||
func DefaultSpec() specs.Platform { |
||||
return specs.Platform{ |
||||
OS: runtime.GOOS, |
||||
Architecture: runtime.GOARCH, |
||||
// The Variant field will be empty if arch != ARM.
|
||||
Variant: cpuVariant, |
||||
} |
||||
} |
||||
@ -0,0 +1,24 @@ |
||||
// +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 platforms |
||||
|
||||
// Default returns the default matcher for the platform.
|
||||
func Default() MatchComparer { |
||||
return Only(DefaultSpec()) |
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
// +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 platforms |
||||
|
||||
import ( |
||||
specs "github.com/opencontainers/image-spec/specs-go/v1" |
||||
) |
||||
|
||||
// Default returns the default matcher for the platform.
|
||||
func Default() MatchComparer { |
||||
return Ordered(DefaultSpec(), specs.Platform{ |
||||
OS: "linux", |
||||
Architecture: "amd64", |
||||
}) |
||||
} |
||||
@ -0,0 +1,278 @@ |
||||
/* |
||||
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 platforms provides a toolkit for normalizing, matching and
|
||||
// specifying container platforms.
|
||||
//
|
||||
// Centered around OCI platform specifications, we define a string-based
|
||||
// specifier syntax that can be used for user input. With a specifier, users
|
||||
// only need to specify the parts of the platform that are relevant to their
|
||||
// context, providing an operating system or architecture or both.
|
||||
//
|
||||
// How do I use this package?
|
||||
//
|
||||
// The vast majority of use cases should simply use the match function with
|
||||
// user input. The first step is to parse a specifier into a matcher:
|
||||
//
|
||||
// m, err := Parse("linux")
|
||||
// if err != nil { ... }
|
||||
//
|
||||
// Once you have a matcher, use it to match against the platform declared by a
|
||||
// component, typically from an image or runtime. Since extracting an images
|
||||
// platform is a little more involved, we'll use an example against the
|
||||
// platform default:
|
||||
//
|
||||
// if ok := m.Match(Default()); !ok { /* doesn't match */ }
|
||||
//
|
||||
// This can be composed in loops for resolving runtimes or used as a filter for
|
||||
// fetch and select images.
|
||||
//
|
||||
// More details of the specifier syntax and platform spec follow.
|
||||
//
|
||||
// Declaring Platform Support
|
||||
//
|
||||
// Components that have strict platform requirements should use the OCI
|
||||
// platform specification to declare their support. Typically, this will be
|
||||
// images and runtimes that should make these declaring which platform they
|
||||
// support specifically. This looks roughly as follows:
|
||||
//
|
||||
// type Platform struct {
|
||||
// Architecture string
|
||||
// OS string
|
||||
// Variant string
|
||||
// }
|
||||
//
|
||||
// Most images and runtimes should at least set Architecture and OS, according
|
||||
// to their GOARCH and GOOS values, respectively (follow the OCI image
|
||||
// specification when in doubt). ARM should set variant under certain
|
||||
// discussions, which are outlined below.
|
||||
//
|
||||
// Platform Specifiers
|
||||
//
|
||||
// While the OCI platform specifications provide a tool for components to
|
||||
// specify structured information, user input typically doesn't need the full
|
||||
// context and much can be inferred. To solve this problem, we introduced
|
||||
// "specifiers". A specifier has the format
|
||||
// `<os>|<arch>|<os>/<arch>[/<variant>]`. The user can provide either the
|
||||
// operating system or the architecture or both.
|
||||
//
|
||||
// An example of a common specifier is `linux/amd64`. If the host has a default
|
||||
// of runtime that matches this, the user can simply provide the component that
|
||||
// matters. For example, if a image provides amd64 and arm64 support, the
|
||||
// operating system, `linux` can be inferred, so they only have to provide
|
||||
// `arm64` or `amd64`. Similar behavior is implemented for operating systems,
|
||||
// where the architecture may be known but a runtime may support images from
|
||||
// different operating systems.
|
||||
//
|
||||
// Normalization
|
||||
//
|
||||
// Because not all users are familiar with the way the Go runtime represents
|
||||
// platforms, several normalizations have been provided to make this package
|
||||
// easier to user.
|
||||
//
|
||||
// The following are performed for architectures:
|
||||
//
|
||||
// Value Normalized
|
||||
// aarch64 arm64
|
||||
// armhf arm
|
||||
// armel arm/v6
|
||||
// i386 386
|
||||
// x86_64 amd64
|
||||
// x86-64 amd64
|
||||
//
|
||||
// We also normalize the operating system `macos` to `darwin`.
|
||||
//
|
||||
// ARM Support
|
||||
//
|
||||
// To qualify ARM architecture, the Variant field is used to qualify the arm
|
||||
// version. The most common arm version, v7, is represented without the variant
|
||||
// unless it is explicitly provided. This is treated as equivalent to armhf. A
|
||||
// previous architecture, armel, will be normalized to arm/v6.
|
||||
//
|
||||
// While these normalizations are provided, their support on arm platforms has
|
||||
// not yet been fully implemented and tested.
|
||||
package platforms |
||||
|
||||
import ( |
||||
"regexp" |
||||
"runtime" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"github.com/containerd/containerd/errdefs" |
||||
specs "github.com/opencontainers/image-spec/specs-go/v1" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
var ( |
||||
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) |
||||
) |
||||
|
||||
// Matcher matches platforms specifications, provided by an image or runtime.
|
||||
type Matcher interface { |
||||
Match(platform specs.Platform) bool |
||||
} |
||||
|
||||
// NewMatcher returns a simple matcher based on the provided platform
|
||||
// specification. The returned matcher only looks for equality based on os,
|
||||
// architecture and variant.
|
||||
//
|
||||
// One may implement their own matcher if this doesn't provide the required
|
||||
// functionality.
|
||||
//
|
||||
// Applications should opt to use `Match` over directly parsing specifiers.
|
||||
func NewMatcher(platform specs.Platform) Matcher { |
||||
return &matcher{ |
||||
Platform: Normalize(platform), |
||||
} |
||||
} |
||||
|
||||
type matcher struct { |
||||
specs.Platform |
||||
} |
||||
|
||||
func (m *matcher) Match(platform specs.Platform) bool { |
||||
normalized := Normalize(platform) |
||||
return m.OS == normalized.OS && |
||||
m.Architecture == normalized.Architecture && |
||||
m.Variant == normalized.Variant |
||||
} |
||||
|
||||
func (m *matcher) String() string { |
||||
return Format(m.Platform) |
||||
} |
||||
|
||||
// Parse parses the platform specifier syntax into a platform declaration.
|
||||
//
|
||||
// Platform specifiers are in the format `<os>|<arch>|<os>/<arch>[/<variant>]`.
|
||||
// The minimum required information for a platform specifier is the operating
|
||||
// system or architecture. If there is only a single string (no slashes), the
|
||||
// value will be matched against the known set of operating systems, then fall
|
||||
// back to the known set of architectures. The missing component will be
|
||||
// inferred based on the local environment.
|
||||
func Parse(specifier string) (specs.Platform, error) { |
||||
if strings.Contains(specifier, "*") { |
||||
// TODO(stevvooe): need to work out exact wildcard handling
|
||||
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: wildcards not yet supported", specifier) |
||||
} |
||||
|
||||
parts := strings.Split(specifier, "/") |
||||
|
||||
for _, part := range parts { |
||||
if !specifierRe.MatchString(part) { |
||||
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q is an invalid component of %q: platform specifier component must match %q", part, specifier, specifierRe.String()) |
||||
} |
||||
} |
||||
|
||||
var p specs.Platform |
||||
switch len(parts) { |
||||
case 1: |
||||
// in this case, we will test that the value might be an OS, then look
|
||||
// it up. If it is not known, we'll treat it as an architecture. Since
|
||||
// we have very little information about the platform here, we are
|
||||
// going to be a little more strict if we don't know about the argument
|
||||
// value.
|
||||
p.OS = normalizeOS(parts[0]) |
||||
if isKnownOS(p.OS) { |
||||
// picks a default architecture
|
||||
p.Architecture = runtime.GOARCH |
||||
if p.Architecture == "arm" && cpuVariant != "v7" { |
||||
p.Variant = cpuVariant |
||||
} |
||||
|
||||
return p, nil |
||||
} |
||||
|
||||
p.Architecture, p.Variant = normalizeArch(parts[0], "") |
||||
if p.Architecture == "arm" && p.Variant == "v7" { |
||||
p.Variant = "" |
||||
} |
||||
if isKnownArch(p.Architecture) { |
||||
p.OS = runtime.GOOS |
||||
return p, nil |
||||
} |
||||
|
||||
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: unknown operating system or architecture", specifier) |
||||
case 2: |
||||
// In this case, we treat as a regular os/arch pair. We don't care
|
||||
// about whether or not we know of the platform.
|
||||
p.OS = normalizeOS(parts[0]) |
||||
p.Architecture, p.Variant = normalizeArch(parts[1], "") |
||||
if p.Architecture == "arm" && p.Variant == "v7" { |
||||
p.Variant = "" |
||||
} |
||||
|
||||
return p, nil |
||||
case 3: |
||||
// we have a fully specified variant, this is rare
|
||||
p.OS = normalizeOS(parts[0]) |
||||
p.Architecture, p.Variant = normalizeArch(parts[1], parts[2]) |
||||
if p.Architecture == "arm64" && p.Variant == "" { |
||||
p.Variant = "v8" |
||||
} |
||||
|
||||
return p, nil |
||||
} |
||||
|
||||
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: cannot parse platform specifier", specifier) |
||||
} |
||||
|
||||
// MustParse is like Parses but panics if the specifier cannot be parsed.
|
||||
// Simplifies initialization of global variables.
|
||||
func MustParse(specifier string) specs.Platform { |
||||
p, err := Parse(specifier) |
||||
if err != nil { |
||||
panic("platform: Parse(" + strconv.Quote(specifier) + "): " + err.Error()) |
||||
} |
||||
return p |
||||
} |
||||
|
||||
// Format returns a string specifier from the provided platform specification.
|
||||
func Format(platform specs.Platform) string { |
||||
if platform.OS == "" { |
||||
return "unknown" |
||||
} |
||||
|
||||
return joinNotEmpty(platform.OS, platform.Architecture, platform.Variant) |
||||
} |
||||
|
||||
func joinNotEmpty(s ...string) string { |
||||
var ss []string |
||||
for _, s := range s { |
||||
if s == "" { |
||||
continue |
||||
} |
||||
|
||||
ss = append(ss, s) |
||||
} |
||||
|
||||
return strings.Join(ss, "/") |
||||
} |
||||
|
||||
// Normalize validates and translate the platform to the canonical value.
|
||||
//
|
||||
// For example, if "Aarch64" is encountered, we change it to "arm64" or if
|
||||
// "x86_64" is encountered, it becomes "amd64".
|
||||
func Normalize(platform specs.Platform) specs.Platform { |
||||
platform.OS = normalizeOS(platform.OS) |
||||
platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant) |
||||
|
||||
// these fields are deprecated, remove them
|
||||
platform.OSFeatures = nil |
||||
platform.OSVersion = "" |
||||
|
||||
return platform |
||||
} |
||||
21
vendor/github.com/cortexproject/cortex/pkg/chunk/aws/dynamodb_storage_client.go
generated
vendored
21
vendor/github.com/cortexproject/cortex/pkg/chunk/aws/dynamodb_storage_client.go
generated
vendored
18
vendor/github.com/cortexproject/cortex/pkg/chunk/gcp/bigtable_object_client.go
generated
vendored
18
vendor/github.com/cortexproject/cortex/pkg/chunk/gcp/bigtable_object_client.go
generated
vendored
71
vendor/github.com/cortexproject/cortex/pkg/querier/queryrange/querysharding.go
generated
vendored
71
vendor/github.com/cortexproject/cortex/pkg/querier/queryrange/querysharding.go
generated
vendored
59
vendor/github.com/cortexproject/cortex/pkg/ring/kv/memberlist/memberlist_client.go
generated
vendored
59
vendor/github.com/cortexproject/cortex/pkg/ring/kv/memberlist/memberlist_client.go
generated
vendored
@ -0,0 +1,247 @@ |
||||
package digestset |
||||
|
||||
import ( |
||||
"errors" |
||||
"sort" |
||||
"strings" |
||||
"sync" |
||||
|
||||
digest "github.com/opencontainers/go-digest" |
||||
) |
||||
|
||||
var ( |
||||
// ErrDigestNotFound is used when a matching digest
|
||||
// could not be found in a set.
|
||||
ErrDigestNotFound = errors.New("digest not found") |
||||
|
||||
// ErrDigestAmbiguous is used when multiple digests
|
||||
// are found in a set. None of the matching digests
|
||||
// should be considered valid matches.
|
||||
ErrDigestAmbiguous = errors.New("ambiguous digest string") |
||||
) |
||||
|
||||
// Set is used to hold a unique set of digests which
|
||||
// may be easily referenced by easily referenced by a string
|
||||
// representation of the digest as well as short representation.
|
||||
// The uniqueness of the short representation is based on other
|
||||
// digests in the set. If digests are omitted from this set,
|
||||
// collisions in a larger set may not be detected, therefore it
|
||||
// is important to always do short representation lookups on
|
||||
// the complete set of digests. To mitigate collisions, an
|
||||
// appropriately long short code should be used.
|
||||
type Set struct { |
||||
mutex sync.RWMutex |
||||
entries digestEntries |
||||
} |
||||
|
||||
// NewSet creates an empty set of digests
|
||||
// which may have digests added.
|
||||
func NewSet() *Set { |
||||
return &Set{ |
||||
entries: digestEntries{}, |
||||
} |
||||
} |
||||
|
||||
// checkShortMatch checks whether two digests match as either whole
|
||||
// values or short values. This function does not test equality,
|
||||
// rather whether the second value could match against the first
|
||||
// value.
|
||||
func checkShortMatch(alg digest.Algorithm, hex, shortAlg, shortHex string) bool { |
||||
if len(hex) == len(shortHex) { |
||||
if hex != shortHex { |
||||
return false |
||||
} |
||||
if len(shortAlg) > 0 && string(alg) != shortAlg { |
||||
return false |
||||
} |
||||
} else if !strings.HasPrefix(hex, shortHex) { |
||||
return false |
||||
} else if len(shortAlg) > 0 && string(alg) != shortAlg { |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// Lookup looks for a digest matching the given string representation.
|
||||
// If no digests could be found ErrDigestNotFound will be returned
|
||||
// with an empty digest value. If multiple matches are found
|
||||
// ErrDigestAmbiguous will be returned with an empty digest value.
|
||||
func (dst *Set) Lookup(d string) (digest.Digest, error) { |
||||
dst.mutex.RLock() |
||||
defer dst.mutex.RUnlock() |
||||
if len(dst.entries) == 0 { |
||||
return "", ErrDigestNotFound |
||||
} |
||||
var ( |
||||
searchFunc func(int) bool |
||||
alg digest.Algorithm |
||||
hex string |
||||
) |
||||
dgst, err := digest.Parse(d) |
||||
if err == digest.ErrDigestInvalidFormat { |
||||
hex = d |
||||
searchFunc = func(i int) bool { |
||||
return dst.entries[i].val >= d |
||||
} |
||||
} else { |
||||
hex = dgst.Hex() |
||||
alg = dgst.Algorithm() |
||||
searchFunc = func(i int) bool { |
||||
if dst.entries[i].val == hex { |
||||
return dst.entries[i].alg >= alg |
||||
} |
||||
return dst.entries[i].val >= hex |
||||
} |
||||
} |
||||
idx := sort.Search(len(dst.entries), searchFunc) |
||||
if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) { |
||||
return "", ErrDigestNotFound |
||||
} |
||||
if dst.entries[idx].alg == alg && dst.entries[idx].val == hex { |
||||
return dst.entries[idx].digest, nil |
||||
} |
||||
if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) { |
||||
return "", ErrDigestAmbiguous |
||||
} |
||||
|
||||
return dst.entries[idx].digest, nil |
||||
} |
||||
|
||||
// Add adds the given digest to the set. An error will be returned
|
||||
// if the given digest is invalid. If the digest already exists in the
|
||||
// set, this operation will be a no-op.
|
||||
func (dst *Set) Add(d digest.Digest) error { |
||||
if err := d.Validate(); err != nil { |
||||
return err |
||||
} |
||||
dst.mutex.Lock() |
||||
defer dst.mutex.Unlock() |
||||
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d} |
||||
searchFunc := func(i int) bool { |
||||
if dst.entries[i].val == entry.val { |
||||
return dst.entries[i].alg >= entry.alg |
||||
} |
||||
return dst.entries[i].val >= entry.val |
||||
} |
||||
idx := sort.Search(len(dst.entries), searchFunc) |
||||
if idx == len(dst.entries) { |
||||
dst.entries = append(dst.entries, entry) |
||||
return nil |
||||
} else if dst.entries[idx].digest == d { |
||||
return nil |
||||
} |
||||
|
||||
entries := append(dst.entries, nil) |
||||
copy(entries[idx+1:], entries[idx:len(entries)-1]) |
||||
entries[idx] = entry |
||||
dst.entries = entries |
||||
return nil |
||||
} |
||||
|
||||
// Remove removes the given digest from the set. An err will be
|
||||
// returned if the given digest is invalid. If the digest does
|
||||
// not exist in the set, this operation will be a no-op.
|
||||
func (dst *Set) Remove(d digest.Digest) error { |
||||
if err := d.Validate(); err != nil { |
||||
return err |
||||
} |
||||
dst.mutex.Lock() |
||||
defer dst.mutex.Unlock() |
||||
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d} |
||||
searchFunc := func(i int) bool { |
||||
if dst.entries[i].val == entry.val { |
||||
return dst.entries[i].alg >= entry.alg |
||||
} |
||||
return dst.entries[i].val >= entry.val |
||||
} |
||||
idx := sort.Search(len(dst.entries), searchFunc) |
||||
// Not found if idx is after or value at idx is not digest
|
||||
if idx == len(dst.entries) || dst.entries[idx].digest != d { |
||||
return nil |
||||
} |
||||
|
||||
entries := dst.entries |
||||
copy(entries[idx:], entries[idx+1:]) |
||||
entries = entries[:len(entries)-1] |
||||
dst.entries = entries |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// All returns all the digests in the set
|
||||
func (dst *Set) All() []digest.Digest { |
||||
dst.mutex.RLock() |
||||
defer dst.mutex.RUnlock() |
||||
retValues := make([]digest.Digest, len(dst.entries)) |
||||
for i := range dst.entries { |
||||
retValues[i] = dst.entries[i].digest |
||||
} |
||||
|
||||
return retValues |
||||
} |
||||
|
||||
// ShortCodeTable returns a map of Digest to unique short codes. The
|
||||
// length represents the minimum value, the maximum length may be the
|
||||
// entire value of digest if uniqueness cannot be achieved without the
|
||||
// full value. This function will attempt to make short codes as short
|
||||
// as possible to be unique.
|
||||
func ShortCodeTable(dst *Set, length int) map[digest.Digest]string { |
||||
dst.mutex.RLock() |
||||
defer dst.mutex.RUnlock() |
||||
m := make(map[digest.Digest]string, len(dst.entries)) |
||||
l := length |
||||
resetIdx := 0 |
||||
for i := 0; i < len(dst.entries); i++ { |
||||
var short string |
||||
extended := true |
||||
for extended { |
||||
extended = false |
||||
if len(dst.entries[i].val) <= l { |
||||
short = dst.entries[i].digest.String() |
||||
} else { |
||||
short = dst.entries[i].val[:l] |
||||
for j := i + 1; j < len(dst.entries); j++ { |
||||
if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) { |
||||
if j > resetIdx { |
||||
resetIdx = j |
||||
} |
||||
extended = true |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
if extended { |
||||
l++ |
||||
} |
||||
} |
||||
} |
||||
m[dst.entries[i].digest] = short |
||||
if i >= resetIdx { |
||||
l = length |
||||
} |
||||
} |
||||
return m |
||||
} |
||||
|
||||
type digestEntry struct { |
||||
alg digest.Algorithm |
||||
val string |
||||
digest digest.Digest |
||||
} |
||||
|
||||
type digestEntries []*digestEntry |
||||
|
||||
func (d digestEntries) Len() int { |
||||
return len(d) |
||||
} |
||||
|
||||
func (d digestEntries) Less(i, j int) bool { |
||||
if d[i].val != d[j].val { |
||||
return d[i].val < d[j].val |
||||
} |
||||
return d[i].alg < d[j].alg |
||||
} |
||||
|
||||
func (d digestEntries) Swap(i, j int) { |
||||
d[i], d[j] = d[j], d[i] |
||||
} |
||||
@ -0,0 +1,42 @@ |
||||
package reference |
||||
|
||||
import "path" |
||||
|
||||
// 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,170 @@ |
||||
package reference |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/docker/distribution/digestset" |
||||
"github.com/opencontainers/go-digest" |
||||
) |
||||
|
||||
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 |
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
// ParseAnyReferenceWithSet parses a reference string as a possible short
|
||||
// identifier to be matched in a digest set, a full digest, or familiar name.
|
||||
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) { |
||||
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok { |
||||
dgst, err := ds.Lookup(ref) |
||||
if err == nil { |
||||
return digestReference(dgst), nil |
||||
} |
||||
} else { |
||||
if dgst, err := digest.Parse(ref); err == nil { |
||||
return digestReference(dgst), nil |
||||
} |
||||
} |
||||
|
||||
return ParseNormalizedNamed(ref) |
||||
} |
||||
@ -0,0 +1,433 @@ |
||||
// Package reference 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 reference |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"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 nameMatch != nil && 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 |
||||
} |
||||
@ -0,0 +1,143 @@ |
||||
package reference |
||||
|
||||
import "regexp" |
||||
|
||||
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) |
||||
|
||||
// anchoredShortIdentifierRegexp is used to check if a value
|
||||
// is a possible identifier prefix, anchored at start and end
|
||||
// of string.
|
||||
anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp) |
||||
) |
||||
|
||||
// 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() + `$`) |
||||
} |
||||
@ -0,0 +1,42 @@ |
||||
# Working on the Engine API |
||||
|
||||
The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon. |
||||
|
||||
It consists of various components in this repository: |
||||
|
||||
- `api/swagger.yaml` A Swagger definition of the API. |
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this. |
||||
- `cli/` The command-line client. |
||||
- `client/` The Go client used by the command-line client. It can also be used by third-party Go programs. |
||||
- `daemon/` The daemon, which serves the API. |
||||
|
||||
## Swagger definition |
||||
|
||||
The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to: |
||||
|
||||
1. Automatically generate documentation. |
||||
2. Automatically generate the Go server and client. (A work-in-progress.) |
||||
3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc. |
||||
|
||||
## Updating the API documentation |
||||
|
||||
The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, edit this file to represent the change in the documentation. |
||||
|
||||
The file is split into two main sections: |
||||
|
||||
- `definitions`, which defines re-usable objects used in requests and responses |
||||
- `paths`, which defines the API endpoints (and some inline objects which don't need to be reusable) |
||||
|
||||
To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section. |
||||
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919). |
||||
|
||||
`swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful when making edits to ensure you are doing the right thing. |
||||
|
||||
## Viewing the API documentation |
||||
|
||||
When you make edits to `swagger.yaml`, you may want to check the generated API documentation to ensure it renders correctly. |
||||
|
||||
Run `make swagger-docs` and a preview will be running at `http://localhost`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation. |
||||
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io). |
||||
@ -0,0 +1,11 @@ |
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const ( |
||||
// DefaultVersion of Current REST API
|
||||
DefaultVersion = "1.41" |
||||
|
||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||
// command to specify that no base image is to be used.
|
||||
NoBaseImageSpecifier = "scratch" |
||||
) |
||||
@ -0,0 +1,6 @@ |
||||
// +build !windows
|
||||
|
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// MinVersion represents Minimum REST API version supported
|
||||
const MinVersion = "1.12" |
||||
@ -0,0 +1,8 @@ |
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// MinVersion represents Minimum REST API version supported
|
||||
// Technically the first daemon API version released on Windows is v1.25 in
|
||||
// engine version 1.13. However, some clients are explicitly using downlevel
|
||||
// APIs (e.g. docker-compose v2.1 file format) and that is just too restrictive.
|
||||
// Hence also allowing 1.24 on Windows.
|
||||
const MinVersion string = "1.24" |
||||
@ -0,0 +1,12 @@ |
||||
|
||||
layout: |
||||
models: |
||||
- name: definition |
||||
source: asset:model |
||||
target: "{{ joinFilePath .Target .ModelPackage }}" |
||||
file_name: "{{ (snakize (pascalize .Name)) }}.go" |
||||
operations: |
||||
- name: handler |
||||
source: asset:serverOperation |
||||
target: "{{ joinFilePath .Target .APIPackage .Package }}" |
||||
file_name: "{{ (snakize (pascalize .Name)) }}.go" |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@ |
||||
package events // import "github.com/docker/docker/api/types/events"
|
||||
|
||||
const ( |
||||
// ContainerEventType is the event type that containers generate
|
||||
ContainerEventType = "container" |
||||
// DaemonEventType is the event type that daemon generate
|
||||
DaemonEventType = "daemon" |
||||
// ImageEventType is the event type that images generate
|
||||
ImageEventType = "image" |
||||
// NetworkEventType is the event type that networks generate
|
||||
NetworkEventType = "network" |
||||
// PluginEventType is the event type that plugins generate
|
||||
PluginEventType = "plugin" |
||||
// VolumeEventType is the event type that volumes generate
|
||||
VolumeEventType = "volume" |
||||
// ServiceEventType is the event type that services generate
|
||||
ServiceEventType = "service" |
||||
// NodeEventType is the event type that nodes generate
|
||||
NodeEventType = "node" |
||||
// SecretEventType is the event type that secrets generate
|
||||
SecretEventType = "secret" |
||||
// ConfigEventType is the event type that configs generate
|
||||
ConfigEventType = "config" |
||||
) |
||||
|
||||
// Actor describes something that generates events,
|
||||
// like a container, or a network, or a volume.
|
||||
// It has a defined name and a set or attributes.
|
||||
// The container attributes are its labels, other actors
|
||||
// can generate these attributes from other properties.
|
||||
type Actor struct { |
||||
ID string |
||||
Attributes map[string]string |
||||
} |
||||
|
||||
// Message represents the information an event contains
|
||||
type Message struct { |
||||
// Deprecated information from JSONMessage.
|
||||
// With data only in container events.
|
||||
Status string `json:"status,omitempty"` |
||||
ID string `json:"id,omitempty"` |
||||
From string `json:"from,omitempty"` |
||||
|
||||
Type string |
||||
Action string |
||||
Actor Actor |
||||
// Engine events are local scope. Cluster events are swarm scope.
|
||||
Scope string `json:"scope,omitempty"` |
||||
|
||||
Time int64 `json:"time,omitempty"` |
||||
TimeNano int64 `json:"timeNano,omitempty"` |
||||
} |
||||
@ -0,0 +1,36 @@ |
||||
package image // import "github.com/docker/docker/api/types/image"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Code generated by `swagger generate operation`. DO NOT EDIT.
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// HistoryResponseItem individual image layer information in response to ImageHistory operation
|
||||
// swagger:model HistoryResponseItem
|
||||
type HistoryResponseItem struct { |
||||
|
||||
// comment
|
||||
// Required: true
|
||||
Comment string `json:"Comment"` |
||||
|
||||
// created
|
||||
// Required: true
|
||||
Created int64 `json:"Created"` |
||||
|
||||
// created by
|
||||
// Required: true
|
||||
CreatedBy string `json:"CreatedBy"` |
||||
|
||||
// Id
|
||||
// Required: true
|
||||
ID string `json:"Id"` |
||||
|
||||
// size
|
||||
// Required: true
|
||||
Size int64 `json:"Size"` |
||||
|
||||
// tags
|
||||
// Required: true
|
||||
Tags []string `json:"Tags"` |
||||
} |
||||
@ -0,0 +1,12 @@ |
||||
package time // import "github.com/docker/docker/api/types/time"
|
||||
|
||||
import ( |
||||
"strconv" |
||||
"time" |
||||
) |
||||
|
||||
// DurationToSecondsString converts the specified duration to the number
|
||||
// seconds it represents, formatted as a string.
|
||||
func DurationToSecondsString(duration time.Duration) string { |
||||
return strconv.FormatFloat(duration.Seconds(), 'f', 0, 64) |
||||
} |
||||
@ -0,0 +1,129 @@ |
||||
package time // import "github.com/docker/docker/api/types/time"
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"math" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// These are additional predefined layouts for use in Time.Format and Time.Parse
|
||||
// with --since and --until parameters for `docker logs` and `docker events`
|
||||
const ( |
||||
rFC3339Local = "2006-01-02T15:04:05" // RFC3339 with local timezone
|
||||
rFC3339NanoLocal = "2006-01-02T15:04:05.999999999" // RFC3339Nano with local timezone
|
||||
dateWithZone = "2006-01-02Z07:00" // RFC3339 with time at 00:00:00
|
||||
dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00
|
||||
) |
||||
|
||||
// GetTimestamp tries to parse given string as golang duration,
|
||||
// then RFC3339 time and finally as a Unix timestamp. If
|
||||
// any of these were successful, it returns a Unix timestamp
|
||||
// as string otherwise returns the given value back.
|
||||
// In case of duration input, the returned timestamp is computed
|
||||
// as the given reference time minus the amount of the duration.
|
||||
func GetTimestamp(value string, reference time.Time) (string, error) { |
||||
if d, err := time.ParseDuration(value); value != "0" && err == nil { |
||||
return strconv.FormatInt(reference.Add(-d).Unix(), 10), nil |
||||
} |
||||
|
||||
var format string |
||||
// if the string has a Z or a + or three dashes use parse otherwise use parseinlocation
|
||||
parseInLocation := !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3) |
||||
|
||||
if strings.Contains(value, ".") { |
||||
if parseInLocation { |
||||
format = rFC3339NanoLocal |
||||
} else { |
||||
format = time.RFC3339Nano |
||||
} |
||||
} else if strings.Contains(value, "T") { |
||||
// we want the number of colons in the T portion of the timestamp
|
||||
tcolons := strings.Count(value, ":") |
||||
// if parseInLocation is off and we have a +/- zone offset (not Z) then
|
||||
// there will be an extra colon in the input for the tz offset subtract that
|
||||
// colon from the tcolons count
|
||||
if !parseInLocation && !strings.ContainsAny(value, "zZ") && tcolons > 0 { |
||||
tcolons-- |
||||
} |
||||
if parseInLocation { |
||||
switch tcolons { |
||||
case 0: |
||||
format = "2006-01-02T15" |
||||
case 1: |
||||
format = "2006-01-02T15:04" |
||||
default: |
||||
format = rFC3339Local |
||||
} |
||||
} else { |
||||
switch tcolons { |
||||
case 0: |
||||
format = "2006-01-02T15Z07:00" |
||||
case 1: |
||||
format = "2006-01-02T15:04Z07:00" |
||||
default: |
||||
format = time.RFC3339 |
||||
} |
||||
} |
||||
} else if parseInLocation { |
||||
format = dateLocal |
||||
} else { |
||||
format = dateWithZone |
||||
} |
||||
|
||||
var t time.Time |
||||
var err error |
||||
|
||||
if parseInLocation { |
||||
t, err = time.ParseInLocation(format, value, time.FixedZone(reference.Zone())) |
||||
} else { |
||||
t, err = time.Parse(format, value) |
||||
} |
||||
|
||||
if err != nil { |
||||
// if there is a `-` then it's an RFC3339 like timestamp
|
||||
if strings.Contains(value, "-") { |
||||
return "", err // was probably an RFC3339 like timestamp but the parser failed with an error
|
||||
} |
||||
if _, _, err := parseTimestamp(value); err != nil { |
||||
return "", fmt.Errorf("failed to parse value as time or duration: %q", value) |
||||
} |
||||
return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server)
|
||||
} |
||||
|
||||
return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil |
||||
} |
||||
|
||||
// ParseTimestamps returns seconds and nanoseconds from a timestamp that has the
|
||||
// format "%d.%09d", time.Unix(), int64(time.Nanosecond()))
|
||||
// if the incoming nanosecond portion is longer or shorter than 9 digits it is
|
||||
// converted to nanoseconds. The expectation is that the seconds and
|
||||
// seconds will be used to create a time variable. For example:
|
||||
// seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0)
|
||||
// if err == nil since := time.Unix(seconds, nanoseconds)
|
||||
// returns seconds as def(aultSeconds) if value == ""
|
||||
func ParseTimestamps(value string, def int64) (int64, int64, error) { |
||||
if value == "" { |
||||
return def, 0, nil |
||||
} |
||||
return parseTimestamp(value) |
||||
} |
||||
|
||||
func parseTimestamp(value string) (int64, int64, error) { |
||||
sa := strings.SplitN(value, ".", 2) |
||||
s, err := strconv.ParseInt(sa[0], 10, 64) |
||||
if err != nil { |
||||
return s, 0, err |
||||
} |
||||
if len(sa) != 2 { |
||||
return s, 0, nil |
||||
} |
||||
n, err := strconv.ParseInt(sa[1], 10, 64) |
||||
if err != nil { |
||||
return s, n, err |
||||
} |
||||
// should already be in nanoseconds but just in case convert n to nanoseconds
|
||||
n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) |
||||
return s, n, nil |
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
package volume // import "github.com/docker/docker/api/types/volume"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Code generated by `swagger generate operation`. DO NOT EDIT.
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// VolumeCreateBody Volume configuration
|
||||
// swagger:model VolumeCreateBody
|
||||
type VolumeCreateBody struct { |
||||
|
||||
// Name of the volume driver to use.
|
||||
// Required: true
|
||||
Driver string `json:"Driver"` |
||||
|
||||
// A mapping of driver options and values. These options are
|
||||
// passed directly to the driver and are driver specific.
|
||||
//
|
||||
// Required: true
|
||||
DriverOpts map[string]string `json:"DriverOpts"` |
||||
|
||||
// User-defined key/value metadata.
|
||||
// Required: true
|
||||
Labels map[string]string `json:"Labels"` |
||||
|
||||
// The new volume's name. If not specified, Docker generates a name.
|
||||
//
|
||||
// Required: true
|
||||
Name string `json:"Name"` |
||||
} |
||||
@ -0,0 +1,23 @@ |
||||
package volume // import "github.com/docker/docker/api/types/volume"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Code generated by `swagger generate operation`. DO NOT EDIT.
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
import "github.com/docker/docker/api/types" |
||||
|
||||
// VolumeListOKBody Volume list response
|
||||
// swagger:model VolumeListOKBody
|
||||
type VolumeListOKBody struct { |
||||
|
||||
// List of volumes
|
||||
// Required: true
|
||||
Volumes []*types.Volume `json:"Volumes"` |
||||
|
||||
// Warnings that occurred when fetching the list of volumes.
|
||||
//
|
||||
// Required: true
|
||||
Warnings []string `json:"Warnings"` |
||||
} |
||||
@ -0,0 +1,16 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
"net/url" |
||||
) |
||||
|
||||
// BuildCancel requests the daemon to cancel ongoing build request
|
||||
func (cli *Client) BuildCancel(ctx context.Context, id string) error { |
||||
query := url.Values{} |
||||
query.Set("id", id) |
||||
|
||||
serverResp, err := cli.post(ctx, "/build/cancel", query, nil, nil) |
||||
ensureReaderClosed(serverResp) |
||||
return err |
||||
} |
||||
@ -0,0 +1,45 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"net/url" |
||||
|
||||
"github.com/docker/docker/api/types" |
||||
"github.com/docker/docker/api/types/filters" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
// BuildCachePrune requests the daemon to delete unused cache data
|
||||
func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) { |
||||
if err := cli.NewVersionError("1.31", "build prune"); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
report := types.BuildCachePruneReport{} |
||||
|
||||
query := url.Values{} |
||||
if opts.All { |
||||
query.Set("all", "1") |
||||
} |
||||
query.Set("keep-storage", fmt.Sprintf("%d", opts.KeepStorage)) |
||||
filters, err := filters.ToJSON(opts.Filters) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, "prune could not marshal filters option") |
||||
} |
||||
query.Set("filters", filters) |
||||
|
||||
serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil) |
||||
defer ensureReaderClosed(serverResp) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil { |
||||
return nil, fmt.Errorf("Error retrieving disk usage: %v", err) |
||||
} |
||||
|
||||
return &report, nil |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/docker/docker/api/types" |
||||
) |
||||
|
||||
// CheckpointCreate creates a checkpoint from the given container with the given name
|
||||
func (cli *Client) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error { |
||||
resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil) |
||||
ensureReaderClosed(resp) |
||||
return err |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
"net/url" |
||||
|
||||
"github.com/docker/docker/api/types" |
||||
) |
||||
|
||||
// CheckpointDelete deletes the checkpoint with the given name from the given container
|
||||
func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, options types.CheckpointDeleteOptions) error { |
||||
query := url.Values{} |
||||
if options.CheckpointDir != "" { |
||||
query.Set("dir", options.CheckpointDir) |
||||
} |
||||
|
||||
resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+options.CheckpointID, query, nil) |
||||
ensureReaderClosed(resp) |
||||
return err |
||||
} |
||||
@ -0,0 +1,28 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"net/url" |
||||
|
||||
"github.com/docker/docker/api/types" |
||||
) |
||||
|
||||
// CheckpointList returns the checkpoints of the given container in the docker host
|
||||
func (cli *Client) CheckpointList(ctx context.Context, container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) { |
||||
var checkpoints []types.Checkpoint |
||||
|
||||
query := url.Values{} |
||||
if options.CheckpointDir != "" { |
||||
query.Set("dir", options.CheckpointDir) |
||||
} |
||||
|
||||
resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil) |
||||
defer ensureReaderClosed(resp) |
||||
if err != nil { |
||||
return checkpoints, wrapResponseError(err, resp, "container", container) |
||||
} |
||||
|
||||
err = json.NewDecoder(resp.body).Decode(&checkpoints) |
||||
return checkpoints, err |
||||
} |
||||
@ -0,0 +1,310 @@ |
||||
/* |
||||
Package client is a Go client for the Docker Engine API. |
||||
|
||||
For more information about the Engine API, see the documentation: |
||||
https://docs.docker.com/engine/reference/api/
|
||||
|
||||
Usage |
||||
|
||||
You use the library by creating a client object and calling methods on it. The |
||||
client can be created either from environment variables with NewEnvClient, or |
||||
configured manually with NewClient. |
||||
|
||||
For example, to list running containers (the equivalent of "docker ps"): |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"github.com/docker/docker/api/types" |
||||
"github.com/docker/docker/client" |
||||
) |
||||
|
||||
func main() { |
||||
cli, err := client.NewClientWithOpts(client.FromEnv) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
for _, container := range containers { |
||||
fmt.Printf("%s %s\n", container.ID[:10], container.Image) |
||||
} |
||||
} |
||||
|
||||
*/ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"path" |
||||
"strings" |
||||
|
||||
"github.com/docker/docker/api" |
||||
"github.com/docker/docker/api/types" |
||||
"github.com/docker/docker/api/types/versions" |
||||
"github.com/docker/go-connections/sockets" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
|
||||
var ErrRedirect = errors.New("unexpected redirect in response") |
||||
|
||||
// Client is the API client that performs all operations
|
||||
// against a docker server.
|
||||
type Client struct { |
||||
// scheme sets the scheme for the client
|
||||
scheme string |
||||
// host holds the server address to connect to
|
||||
host string |
||||
// proto holds the client protocol i.e. unix.
|
||||
proto string |
||||
// addr holds the client address.
|
||||
addr string |
||||
// basePath holds the path to prepend to the requests.
|
||||
basePath string |
||||
// client used to send and receive http requests.
|
||||
client *http.Client |
||||
// version of the server to talk to.
|
||||
version string |
||||
// custom http headers configured by users.
|
||||
customHTTPHeaders map[string]string |
||||
// manualOverride is set to true when the version was set by users.
|
||||
manualOverride bool |
||||
|
||||
// negotiateVersion indicates if the client should automatically negotiate
|
||||
// the API version to use when making requests. API version negotiation is
|
||||
// performed on the first request, after which negotiated is set to "true"
|
||||
// so that subsequent requests do not re-negotiate.
|
||||
negotiateVersion bool |
||||
|
||||
// negotiated indicates that API version negotiation took place
|
||||
negotiated bool |
||||
} |
||||
|
||||
// CheckRedirect specifies the policy for dealing with redirect responses:
|
||||
// If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
|
||||
//
|
||||
// Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client .
|
||||
// The Docker client (and by extension docker API client) can be made to send a request
|
||||
// like POST /containers//start where what would normally be in the name section of the URL is empty.
|
||||
// This triggers an HTTP 301 from the daemon.
|
||||
// In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
|
||||
// This behavior change manifests in the client in that before the 301 was not followed and
|
||||
// the client did not generate an error, but now results in a message like Error response from daemon: page not found.
|
||||
func CheckRedirect(req *http.Request, via []*http.Request) error { |
||||
if via[0].Method == http.MethodGet { |
||||
return http.ErrUseLastResponse |
||||
} |
||||
return ErrRedirect |
||||
} |
||||
|
||||
// NewClientWithOpts initializes a new API client with default values. It takes functors
|
||||
// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
|
||||
// It also initializes the custom http headers to add to each request.
|
||||
//
|
||||
// It won't send any version information if the version number is empty. It is
|
||||
// highly recommended that you set a version or your client may break if the
|
||||
// server is upgraded.
|
||||
func NewClientWithOpts(ops ...Opt) (*Client, error) { |
||||
client, err := defaultHTTPClient(DefaultDockerHost) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
c := &Client{ |
||||
host: DefaultDockerHost, |
||||
version: api.DefaultVersion, |
||||
client: client, |
||||
proto: defaultProto, |
||||
addr: defaultAddr, |
||||
} |
||||
|
||||
for _, op := range ops { |
||||
if err := op(c); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
if _, ok := c.client.Transport.(http.RoundTripper); !ok { |
||||
return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport) |
||||
} |
||||
if c.scheme == "" { |
||||
c.scheme = "http" |
||||
|
||||
tlsConfig := resolveTLSConfig(c.client.Transport) |
||||
if tlsConfig != nil { |
||||
// TODO(stevvooe): This isn't really the right way to write clients in Go.
|
||||
// `NewClient` should probably only take an `*http.Client` and work from there.
|
||||
// Unfortunately, the model of having a host-ish/url-thingy as the connection
|
||||
// string has us confusing protocol and transport layers. We continue doing
|
||||
// this to avoid breaking existing clients but this should be addressed.
|
||||
c.scheme = "https" |
||||
} |
||||
} |
||||
|
||||
return c, nil |
||||
} |
||||
|
||||
func defaultHTTPClient(host string) (*http.Client, error) { |
||||
url, err := ParseHostURL(host) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
transport := new(http.Transport) |
||||
sockets.ConfigureTransport(transport, url.Scheme, url.Host) |
||||
return &http.Client{ |
||||
Transport: transport, |
||||
CheckRedirect: CheckRedirect, |
||||
}, nil |
||||
} |
||||
|
||||
// Close the transport used by the client
|
||||
func (cli *Client) Close() error { |
||||
if t, ok := cli.client.Transport.(*http.Transport); ok { |
||||
t.CloseIdleConnections() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// getAPIPath returns the versioned request path to call the api.
|
||||
// It appends the query parameters to the path if they are not empty.
|
||||
func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string { |
||||
var apiPath string |
||||
if cli.negotiateVersion && !cli.negotiated { |
||||
cli.NegotiateAPIVersion(ctx) |
||||
} |
||||
if cli.version != "" { |
||||
v := strings.TrimPrefix(cli.version, "v") |
||||
apiPath = path.Join(cli.basePath, "/v"+v, p) |
||||
} else { |
||||
apiPath = path.Join(cli.basePath, p) |
||||
} |
||||
return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String() |
||||
} |
||||
|
||||
// ClientVersion returns the API version used by this client.
|
||||
func (cli *Client) ClientVersion() string { |
||||
return cli.version |
||||
} |
||||
|
||||
// NegotiateAPIVersion queries the API and updates the version to match the
|
||||
// API version. Any errors are silently ignored. If a manual override is in place,
|
||||
// either through the `DOCKER_API_VERSION` environment variable, or if the client
|
||||
// was initialized with a fixed version (`opts.WithVersion(xx)`), no negotiation
|
||||
// will be performed.
|
||||
func (cli *Client) NegotiateAPIVersion(ctx context.Context) { |
||||
if !cli.manualOverride { |
||||
ping, _ := cli.Ping(ctx) |
||||
cli.negotiateAPIVersionPing(ping) |
||||
} |
||||
} |
||||
|
||||
// NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
|
||||
// if the ping version is less than the default version. If a manual override is
|
||||
// in place, either through the `DOCKER_API_VERSION` environment variable, or if
|
||||
// the client was initialized with a fixed version (`opts.WithVersion(xx)`), no
|
||||
// negotiation is performed.
|
||||
func (cli *Client) NegotiateAPIVersionPing(p types.Ping) { |
||||
if !cli.manualOverride { |
||||
cli.negotiateAPIVersionPing(p) |
||||
} |
||||
} |
||||
|
||||
// negotiateAPIVersionPing queries the API and updates the version to match the
|
||||
// API version. Any errors are silently ignored.
|
||||
func (cli *Client) negotiateAPIVersionPing(p types.Ping) { |
||||
// try the latest version before versioning headers existed
|
||||
if p.APIVersion == "" { |
||||
p.APIVersion = "1.24" |
||||
} |
||||
|
||||
// if the client is not initialized with a version, start with the latest supported version
|
||||
if cli.version == "" { |
||||
cli.version = api.DefaultVersion |
||||
} |
||||
|
||||
// if server version is lower than the client version, downgrade
|
||||
if versions.LessThan(p.APIVersion, cli.version) { |
||||
cli.version = p.APIVersion |
||||
} |
||||
|
||||
// Store the results, so that automatic API version negotiation (if enabled)
|
||||
// won't be performed on the next request.
|
||||
if cli.negotiateVersion { |
||||
cli.negotiated = true |
||||
} |
||||
} |
||||
|
||||
// DaemonHost returns the host address used by the client
|
||||
func (cli *Client) DaemonHost() string { |
||||
return cli.host |
||||
} |
||||
|
||||
// HTTPClient returns a copy of the HTTP client bound to the server
|
||||
func (cli *Client) HTTPClient() *http.Client { |
||||
c := *cli.client |
||||
return &c |
||||
} |
||||
|
||||
// ParseHostURL parses a url string, validates the string is a host url, and
|
||||
// returns the parsed URL
|
||||
func ParseHostURL(host string) (*url.URL, error) { |
||||
protoAddrParts := strings.SplitN(host, "://", 2) |
||||
if len(protoAddrParts) == 1 { |
||||
return nil, fmt.Errorf("unable to parse docker host `%s`", host) |
||||
} |
||||
|
||||
var basePath string |
||||
proto, addr := protoAddrParts[0], protoAddrParts[1] |
||||
if proto == "tcp" { |
||||
parsed, err := url.Parse("tcp://" + addr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
addr = parsed.Host |
||||
basePath = parsed.Path |
||||
} |
||||
return &url.URL{ |
||||
Scheme: proto, |
||||
Host: addr, |
||||
Path: basePath, |
||||
}, nil |
||||
} |
||||
|
||||
// CustomHTTPHeaders returns the custom http headers stored by the client.
|
||||
func (cli *Client) CustomHTTPHeaders() map[string]string { |
||||
m := make(map[string]string) |
||||
for k, v := range cli.customHTTPHeaders { |
||||
m[k] = v |
||||
} |
||||
return m |
||||
} |
||||
|
||||
// SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
|
||||
// Deprecated: use WithHTTPHeaders when creating the client.
|
||||
func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) { |
||||
cli.customHTTPHeaders = headers |
||||
} |
||||
|
||||
// Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
|
||||
// Used by `docker dial-stdio` (docker/cli#889).
|
||||
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) { |
||||
return func(ctx context.Context) (net.Conn, error) { |
||||
if transport, ok := cli.client.Transport.(*http.Transport); ok { |
||||
if transport.DialContext != nil && transport.TLSClientConfig == nil { |
||||
return transport.DialContext(ctx, cli.proto, cli.addr) |
||||
} |
||||
} |
||||
return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) |
||||
} |
||||
} |
||||
@ -0,0 +1,23 @@ |
||||
package client |
||||
|
||||
import "net/http" |
||||
|
||||
// NewClient initializes a new API client for the given host and API version.
|
||||
// It uses the given http client as transport.
|
||||
// It also initializes the custom http headers to add to each request.
|
||||
//
|
||||
// It won't send any version information if the version number is empty. It is
|
||||
// highly recommended that you set a version or your client may break if the
|
||||
// server is upgraded.
|
||||
// Deprecated: use NewClientWithOpts
|
||||
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) { |
||||
return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders)) |
||||
} |
||||
|
||||
// NewEnvClient initializes a new API client based on environment variables.
|
||||
// See FromEnv for a list of support environment variables.
|
||||
//
|
||||
// Deprecated: use NewClientWithOpts(FromEnv)
|
||||
func NewEnvClient() (*Client, error) { |
||||
return NewClientWithOpts(FromEnv) |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
// +build linux freebsd openbsd netbsd darwin solaris illumos dragonfly
|
||||
|
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
||||
const DefaultDockerHost = "unix:///var/run/docker.sock" |
||||
|
||||
const defaultProto = "unix" |
||||
const defaultAddr = "/var/run/docker.sock" |
||||
@ -0,0 +1,7 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
||||
const DefaultDockerHost = "npipe:////./pipe/docker_engine" |
||||
|
||||
const defaultProto = "npipe" |
||||
const defaultAddr = "//./pipe/docker_engine" |
||||
@ -0,0 +1,25 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
|
||||
"github.com/docker/docker/api/types" |
||||
"github.com/docker/docker/api/types/swarm" |
||||
) |
||||
|
||||
// ConfigCreate creates a new Config.
|
||||
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) { |
||||
var response types.ConfigCreateResponse |
||||
if err := cli.NewVersionError("1.30", "config create"); err != nil { |
||||
return response, err |
||||
} |
||||
resp, err := cli.post(ctx, "/configs/create", nil, config, nil) |
||||
defer ensureReaderClosed(resp) |
||||
if err != nil { |
||||
return response, err |
||||
} |
||||
|
||||
err = json.NewDecoder(resp.body).Decode(&response) |
||||
return response, err |
||||
} |
||||
@ -0,0 +1,36 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"encoding/json" |
||||
"io/ioutil" |
||||
|
||||
"github.com/docker/docker/api/types/swarm" |
||||
) |
||||
|
||||
// ConfigInspectWithRaw returns the config information with raw data
|
||||
func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) { |
||||
if id == "" { |
||||
return swarm.Config{}, nil, objectNotFoundError{object: "config", id: id} |
||||
} |
||||
if err := cli.NewVersionError("1.30", "config inspect"); err != nil { |
||||
return swarm.Config{}, nil, err |
||||
} |
||||
resp, err := cli.get(ctx, "/configs/"+id, nil, nil) |
||||
defer ensureReaderClosed(resp) |
||||
if err != nil { |
||||
return swarm.Config{}, nil, wrapResponseError(err, resp, "config", id) |
||||
} |
||||
|
||||
body, err := ioutil.ReadAll(resp.body) |
||||
if err != nil { |
||||
return swarm.Config{}, nil, err |
||||
} |
||||
|
||||
var config swarm.Config |
||||
rdr := bytes.NewReader(body) |
||||
err = json.NewDecoder(rdr).Decode(&config) |
||||
|
||||
return config, body, err |
||||
} |
||||
@ -0,0 +1,38 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"net/url" |
||||
|
||||
"github.com/docker/docker/api/types" |
||||
"github.com/docker/docker/api/types/filters" |
||||
"github.com/docker/docker/api/types/swarm" |
||||
) |
||||
|
||||
// ConfigList returns the list of configs.
|
||||
func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { |
||||
if err := cli.NewVersionError("1.30", "config list"); err != nil { |
||||
return nil, err |
||||
} |
||||
query := url.Values{} |
||||
|
||||
if options.Filters.Len() > 0 { |
||||
filterJSON, err := filters.ToJSON(options.Filters) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
query.Set("filters", filterJSON) |
||||
} |
||||
|
||||
resp, err := cli.get(ctx, "/configs", query, nil) |
||||
defer ensureReaderClosed(resp) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var configs []swarm.Config |
||||
err = json.NewDecoder(resp.body).Decode(&configs) |
||||
return configs, err |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
package client // import "github.com/docker/docker/client"
|
||||
|
||||
import "context" |
||||
|
||||
// ConfigRemove removes a Config.
|
||||
func (cli *Client) ConfigRemove(ctx context.Context, id string) error { |
||||
if err := cli.NewVersionError("1.30", "config remove"); err != nil { |
||||
return err |
||||
} |
||||
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil) |
||||
defer ensureReaderClosed(resp) |
||||
return wrapResponseError(err, resp, "config", id) |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue