vndr docker/docker to docker/engine d2ecc7b
And update the associated packages that have also updated from docker/docker vendor.conf. Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
This commit is contained in:
110
vendor/github.com/containerd/console/console_windows.go
generated
vendored
110
vendor/github.com/containerd/console/console_windows.go
generated
vendored
@ -17,6 +17,7 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -28,55 +29,90 @@ var (
|
||||
ErrNotImplemented = errors.New("not implemented")
|
||||
)
|
||||
|
||||
func (m *master) init() {
|
||||
m.h = windows.Handle(m.f.Fd())
|
||||
if err := windows.GetConsoleMode(m.h, &m.mode); err == nil {
|
||||
if m.f == os.Stdin {
|
||||
// Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
|
||||
if err = windows.SetConsoleMode(m.h, m.mode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
|
||||
vtInputSupported = true
|
||||
}
|
||||
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
||||
// remembers invalid bits on input handles.
|
||||
windows.SetConsoleMode(m.h, m.mode)
|
||||
} else if err := windows.SetConsoleMode(m.h, m.mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
|
||||
m.mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
} else {
|
||||
windows.SetConsoleMode(m.h, m.mode)
|
||||
func (m *master) initStdios() {
|
||||
m.in = windows.Handle(os.Stdin.Fd())
|
||||
if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil {
|
||||
// Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
|
||||
if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
|
||||
vtInputSupported = true
|
||||
}
|
||||
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
||||
// remembers invalid bits on input handles.
|
||||
windows.SetConsoleMode(m.in, m.inMode)
|
||||
} else {
|
||||
fmt.Printf("failed to get console mode for stdin: %v\n", err)
|
||||
}
|
||||
|
||||
m.out = windows.Handle(os.Stdout.Fd())
|
||||
if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil {
|
||||
if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
|
||||
m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
} else {
|
||||
windows.SetConsoleMode(m.out, m.outMode)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("failed to get console mode for stdout: %v\n", err)
|
||||
}
|
||||
|
||||
m.err = windows.Handle(os.Stderr.Fd())
|
||||
if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil {
|
||||
if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
|
||||
m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
} else {
|
||||
windows.SetConsoleMode(m.err, m.errMode)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("failed to get console mode for stderr: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
type master struct {
|
||||
h windows.Handle
|
||||
mode uint32
|
||||
f *os.File
|
||||
in windows.Handle
|
||||
inMode uint32
|
||||
|
||||
out windows.Handle
|
||||
outMode uint32
|
||||
|
||||
err windows.Handle
|
||||
errMode uint32
|
||||
}
|
||||
|
||||
func (m *master) SetRaw() error {
|
||||
if m.f == os.Stdin {
|
||||
if err := makeInputRaw(m.h, m.mode); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Set StdOut and StdErr to raw mode, we ignore failures since
|
||||
// windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of
|
||||
// Windows.
|
||||
windows.SetConsoleMode(m.h, m.mode|windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||
if err := makeInputRaw(m.in, m.inMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set StdOut and StdErr to raw mode, we ignore failures since
|
||||
// windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of
|
||||
// Windows.
|
||||
|
||||
windows.SetConsoleMode(m.out, m.outMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||
|
||||
windows.SetConsoleMode(m.err, m.errMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *master) Reset() error {
|
||||
if err := windows.SetConsoleMode(m.h, m.mode); err != nil {
|
||||
return errors.Wrap(err, "unable to restore console mode")
|
||||
for _, s := range []struct {
|
||||
fd windows.Handle
|
||||
mode uint32
|
||||
}{
|
||||
{m.in, m.inMode},
|
||||
{m.out, m.outMode},
|
||||
{m.err, m.errMode},
|
||||
} {
|
||||
if err := windows.SetConsoleMode(s.fd, s.mode); err != nil {
|
||||
return errors.Wrap(err, "unable to restore console mode")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *master) Size() (WinSize, error) {
|
||||
var info windows.ConsoleScreenBufferInfo
|
||||
err := windows.GetConsoleScreenBufferInfo(m.h, &info)
|
||||
err := windows.GetConsoleScreenBufferInfo(m.out, &info)
|
||||
if err != nil {
|
||||
return WinSize{}, errors.Wrap(err, "unable to get console info")
|
||||
}
|
||||
@ -98,11 +134,11 @@ func (m *master) ResizeFrom(c Console) error {
|
||||
}
|
||||
|
||||
func (m *master) DisableEcho() error {
|
||||
mode := m.mode &^ windows.ENABLE_ECHO_INPUT
|
||||
mode := m.inMode &^ windows.ENABLE_ECHO_INPUT
|
||||
mode |= windows.ENABLE_PROCESSED_INPUT
|
||||
mode |= windows.ENABLE_LINE_INPUT
|
||||
|
||||
if err := windows.SetConsoleMode(m.h, mode); err != nil {
|
||||
if err := windows.SetConsoleMode(m.in, mode); err != nil {
|
||||
return errors.Wrap(err, "unable to set console to disable echo")
|
||||
}
|
||||
|
||||
@ -114,15 +150,15 @@ func (m *master) Close() error {
|
||||
}
|
||||
|
||||
func (m *master) Read(b []byte) (int, error) {
|
||||
return m.f.Read(b)
|
||||
return os.Stdin.Read(b)
|
||||
}
|
||||
|
||||
func (m *master) Write(b []byte) (int, error) {
|
||||
return m.f.Write(b)
|
||||
return os.Stdout.Write(b)
|
||||
}
|
||||
|
||||
func (m *master) Fd() uintptr {
|
||||
return uintptr(m.h)
|
||||
return uintptr(m.in)
|
||||
}
|
||||
|
||||
// on windows, console can only be made from os.Std{in,out,err}, hence there
|
||||
@ -174,7 +210,7 @@ func newMaster(f *os.File) (Console, error) {
|
||||
if f != os.Stdin && f != os.Stdout && f != os.Stderr {
|
||||
return nil, errors.New("creating a console from a file is not supported on windows")
|
||||
}
|
||||
m := &master{f: f}
|
||||
m.init()
|
||||
m := &master{}
|
||||
m.initStdios()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/containerd/containerd/README.md
generated
vendored
2
vendor/github.com/containerd/containerd/README.md
generated
vendored
@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
[](https://godoc.org/github.com/containerd/containerd)
|
||||
[](https://travis-ci.org/containerd/containerd)
|
||||
|
||||
2
vendor/github.com/containerd/containerd/archive/tar_windows.go
generated
vendored
2
vendor/github.com/containerd/containerd/archive/tar_windows.go
generated
vendored
@ -259,7 +259,7 @@ func fileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
}
|
||||
fileInfo.FileAttributes = uintptr(attr)
|
||||
fileInfo.FileAttributes = uint32(attr)
|
||||
} else {
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
|
||||
|
||||
4
vendor/github.com/containerd/containerd/cio/io_windows.go
generated
vendored
4
vendor/github.com/containerd/containerd/cio/io_windows.go
generated
vendored
@ -74,7 +74,7 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
|
||||
if fifos.Stdout != "" {
|
||||
l, err := winio.ListenPipe(fifos.Stdout, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdout)
|
||||
return nil, errors.Wrapf(err, "failed to create stdout pipe %s", fifos.Stdout)
|
||||
}
|
||||
defer func(l net.Listener) {
|
||||
if err != nil {
|
||||
@ -99,7 +99,7 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
|
||||
}()
|
||||
}
|
||||
|
||||
if !fifos.Terminal && fifos.Stderr != "" {
|
||||
if fifos.Stderr != "" {
|
||||
l, err := winio.ListenPipe(fifos.Stderr, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Stderr)
|
||||
|
||||
83
vendor/github.com/containerd/containerd/client.go
generated
vendored
83
vendor/github.com/containerd/containerd/client.go
generated
vendored
@ -82,6 +82,9 @@ func New(address string, opts ...ClientOpt) (*Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if copts.timeout == 0 {
|
||||
copts.timeout = 10 * time.Second
|
||||
}
|
||||
rt := fmt.Sprintf("%s.%s", plugin.RuntimePlugin, runtime.GOOS)
|
||||
if copts.defaultRuntime != "" {
|
||||
rt = copts.defaultRuntime
|
||||
@ -115,7 +118,7 @@ func New(address string, opts ...ClientOpt) (*Client, error) {
|
||||
)
|
||||
}
|
||||
connector := func() (*grpc.ClientConn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), copts.timeout)
|
||||
defer cancel()
|
||||
conn, err := grpc.DialContext(ctx, dialer.DialAddress(address), gopts...)
|
||||
if err != nil {
|
||||
@ -256,9 +259,10 @@ type RemoteContext struct {
|
||||
// If no resolver is provided, defaults to Docker registry resolver.
|
||||
Resolver remotes.Resolver
|
||||
|
||||
// Platforms defines which platforms to handle when doing the image operation.
|
||||
// If this field is empty, content for all platforms will be pulled.
|
||||
Platforms []string
|
||||
// PlatformMatcher is used to match the platforms for an image
|
||||
// operation and define the preference when a single match is required
|
||||
// from multiple platforms.
|
||||
PlatformMatcher platforms.MatchComparer
|
||||
|
||||
// Unpack is done after an image is pulled to extract into a snapshotter.
|
||||
// If an image is not unpacked on pull, it can be unpacked any time
|
||||
@ -280,6 +284,12 @@ type RemoteContext struct {
|
||||
// manifests. If this option is false then any image which resolves
|
||||
// to schema 1 will return an error since schema 1 is not supported.
|
||||
ConvertSchema1 bool
|
||||
|
||||
// Platforms defines which platforms to handle when doing the image operation.
|
||||
// Platforms is ignored when a PlatformMatcher is set, otherwise the
|
||||
// platforms will be used to create a PlatformMatcher with no ordering
|
||||
// preference.
|
||||
Platforms []string
|
||||
}
|
||||
|
||||
func defaultRemoteContext() *RemoteContext {
|
||||
@ -305,13 +315,30 @@ func (c *Client) Fetch(ctx context.Context, ref string, opts ...RemoteOpt) (imag
|
||||
return images.Image{}, errors.New("unpack on fetch not supported, try pull")
|
||||
}
|
||||
|
||||
if fetchCtx.PlatformMatcher == nil {
|
||||
if len(fetchCtx.Platforms) == 0 {
|
||||
fetchCtx.PlatformMatcher = platforms.All
|
||||
} else {
|
||||
var ps []ocispec.Platform
|
||||
for _, s := range fetchCtx.Platforms {
|
||||
p, err := platforms.Parse(s)
|
||||
if err != nil {
|
||||
return images.Image{}, errors.Wrapf(err, "invalid platform %s", s)
|
||||
}
|
||||
ps = append(ps, p)
|
||||
}
|
||||
|
||||
fetchCtx.PlatformMatcher = platforms.Any(ps...)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, done, err := c.WithLease(ctx)
|
||||
if err != nil {
|
||||
return images.Image{}, err
|
||||
}
|
||||
defer done(ctx)
|
||||
|
||||
return c.fetch(ctx, fetchCtx, ref)
|
||||
return c.fetch(ctx, fetchCtx, ref, 0)
|
||||
}
|
||||
|
||||
// Pull downloads the provided content into containerd's content store
|
||||
@ -324,10 +351,19 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
|
||||
}
|
||||
}
|
||||
|
||||
if len(pullCtx.Platforms) > 1 {
|
||||
return nil, errors.New("cannot pull multiplatform image locally, try Fetch")
|
||||
} else if len(pullCtx.Platforms) == 0 {
|
||||
pullCtx.Platforms = []string{platforms.Default()}
|
||||
if pullCtx.PlatformMatcher == nil {
|
||||
if len(pullCtx.Platforms) > 1 {
|
||||
return nil, errors.New("cannot pull multiplatform image locally, try Fetch")
|
||||
} else if len(pullCtx.Platforms) == 0 {
|
||||
pullCtx.PlatformMatcher = platforms.Default()
|
||||
} else {
|
||||
p, err := platforms.Parse(pullCtx.Platforms[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid platform %s", pullCtx.Platforms[0])
|
||||
}
|
||||
|
||||
pullCtx.PlatformMatcher = platforms.Only(p)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, done, err := c.WithLease(ctx)
|
||||
@ -336,12 +372,12 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
|
||||
}
|
||||
defer done(ctx)
|
||||
|
||||
img, err := c.fetch(ctx, pullCtx, ref)
|
||||
img, err := c.fetch(ctx, pullCtx, ref, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i := NewImageWithPlatform(c, img, pullCtx.Platforms[0])
|
||||
i := NewImageWithPlatform(c, img, pullCtx.PlatformMatcher)
|
||||
|
||||
if pullCtx.Unpack {
|
||||
if err := i.Unpack(ctx, pullCtx.Snapshotter); err != nil {
|
||||
@ -352,7 +388,7 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string) (images.Image, error) {
|
||||
func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string, limit int) (images.Image, error) {
|
||||
store := c.ContentStore()
|
||||
name, desc, err := rCtx.Resolver.Resolve(ctx, ref)
|
||||
if err != nil {
|
||||
@ -377,7 +413,11 @@ func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string) (im
|
||||
// Set any children labels for that content
|
||||
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
|
||||
// Filter children by platforms
|
||||
childrenHandler = images.FilterPlatforms(childrenHandler, rCtx.Platforms...)
|
||||
childrenHandler = images.FilterPlatforms(childrenHandler, rCtx.PlatformMatcher)
|
||||
// Sort and limit manifests if a finite number is needed
|
||||
if limit > 0 {
|
||||
childrenHandler = images.LimitManifests(childrenHandler, rCtx.PlatformMatcher, limit)
|
||||
}
|
||||
|
||||
handler = images.Handlers(append(rCtx.BaseHandlers,
|
||||
remotes.FetchHandler(store, fetcher),
|
||||
@ -434,13 +474,28 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor,
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pushCtx.PlatformMatcher == nil {
|
||||
if len(pushCtx.Platforms) > 0 {
|
||||
var ps []ocispec.Platform
|
||||
for _, platform := range pushCtx.Platforms {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "invalid platform %s", platform)
|
||||
}
|
||||
ps = append(ps, p)
|
||||
}
|
||||
pushCtx.PlatformMatcher = platforms.Any(ps...)
|
||||
} else {
|
||||
pushCtx.PlatformMatcher = platforms.All
|
||||
}
|
||||
}
|
||||
|
||||
pusher, err := pushCtx.Resolver.Pusher(ctx, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.Platforms, pushCtx.BaseHandlers...)
|
||||
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.PlatformMatcher, pushCtx.BaseHandlers...)
|
||||
}
|
||||
|
||||
// GetImage returns an existing image
|
||||
|
||||
23
vendor/github.com/containerd/containerd/client_opts.go
generated
vendored
23
vendor/github.com/containerd/containerd/client_opts.go
generated
vendored
@ -17,6 +17,8 @@
|
||||
package containerd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
@ -28,6 +30,7 @@ type clientOpts struct {
|
||||
defaultRuntime string
|
||||
services *services
|
||||
dialOptions []grpc.DialOption
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// ClientOpt allows callers to set options on the containerd client
|
||||
@ -71,6 +74,14 @@ func WithServices(opts ...ServicesOpt) ClientOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout sets the connection timeout for the client
|
||||
func WithTimeout(d time.Duration) ClientOpt {
|
||||
return func(c *clientOpts) error {
|
||||
c.timeout = d
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteOpt allows the caller to set distribution options for a remote
|
||||
type RemoteOpt func(*Client, *RemoteContext) error
|
||||
|
||||
@ -78,7 +89,7 @@ type RemoteOpt func(*Client, *RemoteContext) error
|
||||
// content for
|
||||
func WithPlatform(platform string) RemoteOpt {
|
||||
if platform == "" {
|
||||
platform = platforms.Default()
|
||||
platform = platforms.DefaultString()
|
||||
}
|
||||
return func(_ *Client, c *RemoteContext) error {
|
||||
for _, p := range c.Platforms {
|
||||
@ -92,6 +103,16 @@ func WithPlatform(platform string) RemoteOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPlatformMatcher specifies the matcher to use for
|
||||
// determining which platforms to pull content for.
|
||||
// This value supersedes anything set with `WithPlatform`.
|
||||
func WithPlatformMatcher(m platforms.MatchComparer) RemoteOpt {
|
||||
return func(_ *Client, c *RemoteContext) error {
|
||||
c.PlatformMatcher = m
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPullUnpack is used to unpack an image after pull. This
|
||||
// uses the snapshotter, content store, and diff service
|
||||
// configured for the client.
|
||||
|
||||
6
vendor/github.com/containerd/containerd/image.go
generated
vendored
6
vendor/github.com/containerd/containerd/image.go
generated
vendored
@ -63,7 +63,7 @@ func NewImage(client *Client, i images.Image) Image {
|
||||
}
|
||||
|
||||
// NewImageWithPlatform returns a client image object from the metadata image
|
||||
func NewImageWithPlatform(client *Client, i images.Image, platform string) Image {
|
||||
func NewImageWithPlatform(client *Client, i images.Image, platform platforms.MatchComparer) Image {
|
||||
return &image{
|
||||
client: client,
|
||||
i: i,
|
||||
@ -75,7 +75,7 @@ type image struct {
|
||||
client *Client
|
||||
|
||||
i images.Image
|
||||
platform string
|
||||
platform platforms.MatchComparer
|
||||
}
|
||||
|
||||
func (i *image) Name() string {
|
||||
@ -186,7 +186,7 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *image) getLayers(ctx context.Context, platform string) ([]rootfs.Layer, error) {
|
||||
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
|
||||
cs := i.client.ContentStore()
|
||||
|
||||
manifest, err := images.Manifest(ctx, cs, i.i.Target, platform)
|
||||
|
||||
55
vendor/github.com/containerd/containerd/images/handlers.go
generated
vendored
55
vendor/github.com/containerd/containerd/images/handlers.go
generated
vendored
@ -19,6 +19,7 @@ package images
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
@ -183,8 +184,8 @@ func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
|
||||
}
|
||||
|
||||
// FilterPlatforms is a handler wrapper which limits the descriptors returned
|
||||
// by a handler to the specified platforms.
|
||||
func FilterPlatforms(f HandlerFunc, platformList ...string) HandlerFunc {
|
||||
// based on matching the specified platform matcher.
|
||||
func FilterPlatforms(f HandlerFunc, m platforms.Matcher) HandlerFunc {
|
||||
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
children, err := f(ctx, desc)
|
||||
if err != nil {
|
||||
@ -193,20 +194,12 @@ func FilterPlatforms(f HandlerFunc, platformList ...string) HandlerFunc {
|
||||
|
||||
var descs []ocispec.Descriptor
|
||||
|
||||
if len(platformList) == 0 {
|
||||
if m == nil {
|
||||
descs = children
|
||||
} else {
|
||||
for _, platform := range platformList {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matcher := platforms.NewMatcher(p)
|
||||
|
||||
for _, d := range children {
|
||||
if d.Platform == nil || matcher.Match(*d.Platform) {
|
||||
descs = append(descs, d)
|
||||
}
|
||||
for _, d := range children {
|
||||
if d.Platform == nil || m.Match(*d.Platform) {
|
||||
descs = append(descs, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -214,3 +207,37 @@ func FilterPlatforms(f HandlerFunc, platformList ...string) HandlerFunc {
|
||||
return descs, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LimitManifests is a handler wrapper which filters the manifest descriptors
|
||||
// returned using the provided platform.
|
||||
// The results will be ordered according to the comparison operator and
|
||||
// use the ordering in the manifests for equal matches.
|
||||
// A limit of 0 or less is considered no limit.
|
||||
func LimitManifests(f HandlerFunc, m platforms.MatchComparer, n int) HandlerFunc {
|
||||
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
children, err := f(ctx, desc)
|
||||
if err != nil {
|
||||
return children, err
|
||||
}
|
||||
|
||||
switch desc.MediaType {
|
||||
case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList:
|
||||
sort.SliceStable(children, func(i, j int) bool {
|
||||
if children[i].Platform == nil {
|
||||
return false
|
||||
}
|
||||
if children[j].Platform == nil {
|
||||
return true
|
||||
}
|
||||
return m.Less(*children[i].Platform, *children[j].Platform)
|
||||
})
|
||||
|
||||
if n > 0 && len(children) > n {
|
||||
children = children[:n]
|
||||
}
|
||||
default:
|
||||
// only limit manifests from an index
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
}
|
||||
|
||||
61
vendor/github.com/containerd/containerd/images/image.go
generated
vendored
61
vendor/github.com/containerd/containerd/images/image.go
generated
vendored
@ -19,6 +19,7 @@ package images
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -93,7 +94,7 @@ type Store interface {
|
||||
//
|
||||
// The caller can then use the descriptor to resolve and process the
|
||||
// configuration of the image.
|
||||
func (image *Image) Config(ctx context.Context, provider content.Provider, platform string) (ocispec.Descriptor, error) {
|
||||
func (image *Image) Config(ctx context.Context, provider content.Provider, platform platforms.MatchComparer) (ocispec.Descriptor, error) {
|
||||
return Config(ctx, provider, image.Target, platform)
|
||||
}
|
||||
|
||||
@ -101,7 +102,7 @@ func (image *Image) Config(ctx context.Context, provider content.Provider, platf
|
||||
//
|
||||
// These are used to verify that a set of layers unpacked to the expected
|
||||
// values.
|
||||
func (image *Image) RootFS(ctx context.Context, provider content.Provider, platform string) ([]digest.Digest, error) {
|
||||
func (image *Image) RootFS(ctx context.Context, provider content.Provider, platform platforms.MatchComparer) ([]digest.Digest, error) {
|
||||
desc, err := image.Config(ctx, provider, platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -110,7 +111,7 @@ func (image *Image) RootFS(ctx context.Context, provider content.Provider, platf
|
||||
}
|
||||
|
||||
// Size returns the total size of an image's packed resources.
|
||||
func (image *Image) Size(ctx context.Context, provider content.Provider, platform string) (int64, error) {
|
||||
func (image *Image) Size(ctx context.Context, provider content.Provider, platform platforms.MatchComparer) (int64, error) {
|
||||
var size int64
|
||||
return size, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
if desc.Size < 0 {
|
||||
@ -121,27 +122,22 @@ func (image *Image) Size(ctx context.Context, provider content.Provider, platfor
|
||||
}), FilterPlatforms(ChildrenHandler(provider), platform)), image.Target)
|
||||
}
|
||||
|
||||
type platformManifest struct {
|
||||
p *ocispec.Platform
|
||||
m *ocispec.Manifest
|
||||
}
|
||||
|
||||
// Manifest resolves a manifest from the image for the given platform.
|
||||
//
|
||||
// TODO(stevvooe): This violates the current platform agnostic approach to this
|
||||
// package by returning a specific manifest type. We'll need to refactor this
|
||||
// to return a manifest descriptor or decide that we want to bring the API in
|
||||
// this direction because this abstraction is not needed.`
|
||||
func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Manifest, error) {
|
||||
func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (ocispec.Manifest, error) {
|
||||
var (
|
||||
matcher platforms.Matcher
|
||||
m *ocispec.Manifest
|
||||
p ocispec.Platform
|
||||
m []platformManifest
|
||||
wasIndex bool
|
||||
)
|
||||
if platform != "" {
|
||||
var err error
|
||||
p, err = platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return ocispec.Manifest{}, err
|
||||
}
|
||||
matcher = platforms.NewMatcher(p)
|
||||
}
|
||||
|
||||
if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
switch desc.MediaType {
|
||||
@ -156,8 +152,8 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if platform != "" {
|
||||
if desc.Platform != nil && !matcher.Match(*desc.Platform) {
|
||||
if platform != nil {
|
||||
if desc.Platform != nil && !platform.Match(*desc.Platform) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -172,14 +168,17 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !matcher.Match(platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) {
|
||||
if !platform.Match(platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
m = &manifest
|
||||
m = append(m, platformManifest{
|
||||
p: desc.Platform,
|
||||
m: &manifest,
|
||||
})
|
||||
|
||||
return nil, nil
|
||||
case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
||||
@ -193,13 +192,13 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if platform == "" {
|
||||
if platform == nil {
|
||||
return idx.Manifests, nil
|
||||
}
|
||||
|
||||
var descs []ocispec.Descriptor
|
||||
for _, d := range idx.Manifests {
|
||||
if d.Platform == nil || matcher.Match(*d.Platform) {
|
||||
if d.Platform == nil || platform.Match(*d.Platform) {
|
||||
descs = append(descs, d)
|
||||
}
|
||||
}
|
||||
@ -214,15 +213,25 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
|
||||
return ocispec.Manifest{}, err
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
if len(m) == 0 {
|
||||
err := errors.Wrapf(errdefs.ErrNotFound, "manifest %v", image.Digest)
|
||||
if wasIndex {
|
||||
err = errors.Wrapf(errdefs.ErrNotFound, "no match for current platform %s in manifest %v", platforms.Format(p), image.Digest)
|
||||
err = errors.Wrapf(errdefs.ErrNotFound, "no match for platform in manifest %v", image.Digest)
|
||||
}
|
||||
return ocispec.Manifest{}, err
|
||||
}
|
||||
|
||||
return *m, nil
|
||||
sort.SliceStable(m, func(i, j int) bool {
|
||||
if m[i].p == nil {
|
||||
return false
|
||||
}
|
||||
if m[j].p == nil {
|
||||
return true
|
||||
}
|
||||
return platform.Less(*m[i].p, *m[j].p)
|
||||
})
|
||||
|
||||
return *m[0].m, nil
|
||||
}
|
||||
|
||||
// Config resolves the image configuration descriptor using a content provided
|
||||
@ -230,7 +239,7 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
|
||||
//
|
||||
// The caller can then use the descriptor to resolve and process the
|
||||
// configuration of the image.
|
||||
func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Descriptor, error) {
|
||||
func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (ocispec.Descriptor, error) {
|
||||
manifest, err := Manifest(ctx, provider, image, platform)
|
||||
if err != nil {
|
||||
return ocispec.Descriptor{}, err
|
||||
@ -276,7 +285,7 @@ func Platforms(ctx context.Context, provider content.Provider, image ocispec.Des
|
||||
// in the provider.
|
||||
//
|
||||
// If there is a problem resolving content, an error will be returned.
|
||||
func Check(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (available bool, required, present, missing []ocispec.Descriptor, err error) {
|
||||
func Check(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (available bool, required, present, missing []ocispec.Descriptor, err error) {
|
||||
mfst, err := Manifest(ctx, provider, image, platform)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
|
||||
13
vendor/github.com/containerd/containerd/oci/spec_opts_unix.go
generated
vendored
13
vendor/github.com/containerd/containerd/oci/spec_opts_unix.go
generated
vendored
@ -143,6 +143,7 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
||||
cmd = args
|
||||
}
|
||||
s.Process.Args = append(config.Entrypoint, cmd...)
|
||||
|
||||
cwd := config.WorkingDir
|
||||
if cwd == "" {
|
||||
cwd = "/"
|
||||
@ -485,6 +486,18 @@ func getAllCapabilities() []string {
|
||||
return caps
|
||||
}
|
||||
|
||||
// WithAmbientCapabilities set the Linux ambient capabilities for the process
|
||||
// Ambient capabilities should only be set for non-root users or the caller should
|
||||
// understand how these capabilities are used and set
|
||||
func WithAmbientCapabilities(caps []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setCapabilities(s)
|
||||
|
||||
s.Process.Capabilities.Ambient = caps
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var errNoUsersFound = errors.New("no users found")
|
||||
|
||||
func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
|
||||
|
||||
192
vendor/github.com/containerd/containerd/platforms/compare.go
generated
vendored
Normal file
192
vendor/github.com/containerd/containerd/platforms/compare.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
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 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 == "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
|
||||
}
|
||||
9
vendor/github.com/containerd/containerd/platforms/defaults.go
generated
vendored
9
vendor/github.com/containerd/containerd/platforms/defaults.go
generated
vendored
@ -22,8 +22,13 @@ import (
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// Default returns the default specifier for the platform.
|
||||
func Default() string {
|
||||
// Default returns the default matcher for the platform.
|
||||
func Default() MatchComparer {
|
||||
return Only(DefaultSpec())
|
||||
}
|
||||
|
||||
// DefaultString returns the default string specifier for the platform.
|
||||
func DefaultString() string {
|
||||
return Format(DefaultSpec())
|
||||
}
|
||||
|
||||
|
||||
5
vendor/github.com/containerd/containerd/remotes/handlers.go
generated
vendored
5
vendor/github.com/containerd/containerd/remotes/handlers.go
generated
vendored
@ -27,6 +27,7 @@ import (
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -155,7 +156,7 @@ func push(ctx context.Context, provider content.Provider, pusher Pusher, desc oc
|
||||
//
|
||||
// Base handlers can be provided which will be called before any push specific
|
||||
// handlers.
|
||||
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, provider content.Provider, platforms []string, baseHandlers ...images.Handler) error {
|
||||
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, provider content.Provider, platform platforms.MatchComparer, baseHandlers ...images.Handler) error {
|
||||
var m sync.Mutex
|
||||
manifestStack := []ocispec.Descriptor{}
|
||||
|
||||
@ -175,7 +176,7 @@ func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, pr
|
||||
pushHandler := PushHandler(pusher, provider)
|
||||
|
||||
handlers := append(baseHandlers,
|
||||
images.FilterPlatforms(images.ChildrenHandler(provider), platforms...),
|
||||
images.FilterPlatforms(images.ChildrenHandler(provider), platform),
|
||||
filterHandler,
|
||||
pushHandler,
|
||||
)
|
||||
|
||||
10
vendor/github.com/containerd/containerd/vendor.conf
generated
vendored
10
vendor/github.com/containerd/containerd/vendor.conf
generated
vendored
@ -1,5 +1,5 @@
|
||||
github.com/containerd/go-runc edcf3de1f4971445c42d61f20d506b30612aa031
|
||||
github.com/containerd/console 4d8a41f4ce5b9bae77c41786ea2458330f43f081
|
||||
github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292
|
||||
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
||||
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
|
||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||
@ -20,7 +20,7 @@ github.com/gogo/protobuf v1.0.0
|
||||
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
|
||||
github.com/golang/protobuf v1.1.0
|
||||
github.com/opencontainers/runtime-spec d810dbc60d8c5aeeb3d054bd1132fab2121968ce # v1.0.1-43-gd810dbc
|
||||
github.com/opencontainers/runc 69663f0bd4b60df09991c08812a60108003fa340
|
||||
github.com/opencontainers/runc 20aff4f0488c6d4b8df4d85b4f63f1f704c11abd
|
||||
github.com/sirupsen/logrus v1.0.0
|
||||
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
||||
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
|
||||
@ -32,8 +32,8 @@ github.com/opencontainers/image-spec v1.0.1
|
||||
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
||||
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
||||
github.com/Microsoft/go-winio v0.4.7
|
||||
github.com/Microsoft/hcsshim v0.6.11
|
||||
github.com/Microsoft/go-winio v0.4.10
|
||||
github.com/Microsoft/hcsshim 44c060121b68e8bdc40b411beba551f3b4ee9e55
|
||||
github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
|
||||
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
||||
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
|
||||
|
||||
Reference in New Issue
Block a user