mirror of
https://github.com/moby/moby.git
synced 2026-01-13 20:08:05 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
852759a7df | ||
|
|
55fa16e22f | ||
|
|
f91551869e | ||
|
|
e0ce0ff8d5 | ||
|
|
02aa896934 | ||
|
|
e0b4fc1aae | ||
|
|
e264217787 | ||
|
|
e633d64982 | ||
|
|
76b88fb5ff | ||
|
|
b8bc11af70 | ||
|
|
8ffaef6d61 | ||
|
|
69b5ded97b | ||
|
|
257d4ec79a | ||
|
|
c593074455 | ||
|
|
f11b55ffec | ||
|
|
b3888ed899 | ||
|
|
a162f3c0f4 | ||
|
|
4977af06ee | ||
|
|
f87c7381b4 | ||
|
|
12627c2449 | ||
|
|
1ea6c4be57 | ||
|
|
4f26525b13 | ||
|
|
00f3d189e4 | ||
|
|
f110a9d310 | ||
|
|
b81579adf4 | ||
|
|
9d3a6a2133 | ||
|
|
f406728e41 | ||
|
|
6f483e7dbb | ||
|
|
a9f4dd168e | ||
|
|
d22068f8e3 | ||
|
|
fca702de7f | ||
|
|
f78a7726d7 | ||
|
|
61afffeeb3 | ||
|
|
b38e74c4e0 | ||
|
|
dac56638ad | ||
|
|
20e1af3616 | ||
|
|
858919d399 | ||
|
|
141ad39e38 | ||
|
|
db968c672b | ||
|
|
44e6f3da60 | ||
|
|
8a19bb7193 | ||
|
|
17af50f46b | ||
|
|
ffb63c0bae | ||
|
|
593b754d8f |
@@ -197,7 +197,7 @@ RUN git init . && git remote add origin "https://github.com/containerd/container
|
||||
# When updating the binary version you may also need to update the vendor
|
||||
# version to pick up bug fixes or new APIs, however, usually the Go packages
|
||||
# are built from a commit from the master branch.
|
||||
ARG CONTAINERD_VERSION=v1.7.12
|
||||
ARG CONTAINERD_VERSION=v1.7.13
|
||||
RUN git fetch -q --depth 1 origin "${CONTAINERD_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS containerd-build
|
||||
@@ -280,7 +280,7 @@ RUN git init . && git remote add origin "https://github.com/opencontainers/runc.
|
||||
# that is used. If you need to update runc, open a pull request in the containerd
|
||||
# project first, and update both after that is merged. When updating RUNC_VERSION,
|
||||
# consider updating runc in vendor.mod accordingly.
|
||||
ARG RUNC_VERSION=v1.1.11
|
||||
ARG RUNC_VERSION=v1.1.12
|
||||
RUN git fetch -q --depth 1 origin "${RUNC_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS runc-build
|
||||
|
||||
@@ -168,7 +168,7 @@ SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPref
|
||||
ARG GO_VERSION=1.20.13
|
||||
ARG GOTESTSUM_VERSION=v1.8.2
|
||||
ARG GOWINRES_VERSION=v0.3.1
|
||||
ARG CONTAINERD_VERSION=v1.7.12
|
||||
ARG CONTAINERD_VERSION=v1.7.13
|
||||
|
||||
# Environment variable notes:
|
||||
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
|
||||
@@ -22,6 +22,9 @@ func (s *snapshotter) GetDiffIDs(ctx context.Context, key string) ([]layer.DiffI
|
||||
}
|
||||
|
||||
func (s *snapshotter) EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error) {
|
||||
s.layerCreateLocker.Lock(key)
|
||||
defer s.layerCreateLocker.Unlock(key)
|
||||
|
||||
diffIDs, err := s.GetDiffIDs(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/snapshot"
|
||||
"github.com/moby/locker"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
@@ -48,10 +49,11 @@ type checksumCalculator interface {
|
||||
type snapshotter struct {
|
||||
opt Opt
|
||||
|
||||
refs map[string]layer.Layer
|
||||
db *bolt.DB
|
||||
mu sync.Mutex
|
||||
reg graphIDRegistrar
|
||||
refs map[string]layer.Layer
|
||||
db *bolt.DB
|
||||
mu sync.Mutex
|
||||
reg graphIDRegistrar
|
||||
layerCreateLocker *locker.Locker
|
||||
}
|
||||
|
||||
// NewSnapshotter creates a new snapshotter
|
||||
@@ -68,10 +70,11 @@ func NewSnapshotter(opt Opt, prevLM leases.Manager) (snapshot.Snapshotter, lease
|
||||
}
|
||||
|
||||
s := &snapshotter{
|
||||
opt: opt,
|
||||
db: db,
|
||||
refs: map[string]layer.Layer{},
|
||||
reg: reg,
|
||||
opt: opt,
|
||||
db: db,
|
||||
refs: map[string]layer.Layer{},
|
||||
reg: reg,
|
||||
layerCreateLocker: locker.New(),
|
||||
}
|
||||
|
||||
lm := newLeaseManager(s, prevLM)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -89,7 +90,7 @@ type ImageCacheBuilder interface {
|
||||
type ImageCache interface {
|
||||
// GetCache returns a reference to a cached image whose parent equals `parent`
|
||||
// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
|
||||
GetCache(parentID string, cfg *container.Config) (imageID string, err error)
|
||||
GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error)
|
||||
}
|
||||
|
||||
// Image represents a Docker image used by the builder.
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -74,7 +73,7 @@ type copier struct {
|
||||
source builder.Source
|
||||
pathCache pathCache
|
||||
download sourceDownloader
|
||||
platform *ocispec.Platform
|
||||
platform ocispec.Platform
|
||||
// for cleanup. TODO: having copier.cleanup() is error prone and hard to
|
||||
// follow. Code calling performCopy should manage the lifecycle of its params.
|
||||
// Copier should take override source as input, not imageMount.
|
||||
@@ -83,19 +82,7 @@ type copier struct {
|
||||
}
|
||||
|
||||
func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, imageSource *imageMount) copier {
|
||||
platform := req.builder.platform
|
||||
if platform == nil {
|
||||
// May be nil if not explicitly set in API/dockerfile
|
||||
platform = &ocispec.Platform{}
|
||||
}
|
||||
if platform.OS == "" {
|
||||
// Default to the dispatch requests operating system if not explicit in API/dockerfile
|
||||
platform.OS = req.state.operatingSystem
|
||||
}
|
||||
if platform.OS == "" {
|
||||
// This is a failsafe just in case. Shouldn't be hit.
|
||||
platform.OS = runtime.GOOS
|
||||
}
|
||||
platform := req.builder.getPlatform(req.state)
|
||||
|
||||
return copier{
|
||||
source: req.source,
|
||||
|
||||
@@ -349,9 +349,16 @@ func dispatchRun(ctx context.Context, d dispatchRequest, c *instructions.RunComm
|
||||
saveCmd = prependEnvOnCmd(d.state.buildArgs, buildArgs, cmdFromArgs)
|
||||
}
|
||||
|
||||
cacheArgsEscaped := argsEscaped
|
||||
// ArgsEscaped is not persisted in the committed image on Windows.
|
||||
// Use the original from previous build steps for cache probing.
|
||||
if d.state.operatingSystem == "windows" {
|
||||
cacheArgsEscaped = stateRunConfig.ArgsEscaped
|
||||
}
|
||||
|
||||
runConfigForCacheProbe := copyRunConfig(stateRunConfig,
|
||||
withCmd(saveCmd),
|
||||
withArgsEscaped(argsEscaped),
|
||||
withArgsEscaped(cacheArgsEscaped),
|
||||
withEntrypointOverride(saveCmd, nil))
|
||||
if hit, err := d.builder.probeCache(d.state, runConfigForCacheProbe); err != nil || hit {
|
||||
return err
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/builder"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
// cache.
|
||||
type ImageProber interface {
|
||||
Reset(ctx context.Context) error
|
||||
Probe(parentID string, runConfig *container.Config) (string, error)
|
||||
Probe(parentID string, runConfig *container.Config, platform ocispec.Platform) (string, error)
|
||||
}
|
||||
|
||||
type resetFunc func(context.Context) (builder.ImageCache, error)
|
||||
@@ -51,11 +52,11 @@ func (c *imageProber) Reset(ctx context.Context) error {
|
||||
|
||||
// Probe checks if cache match can be found for current build instruction.
|
||||
// It returns the cachedID if there is a hit, and the empty string on miss
|
||||
func (c *imageProber) Probe(parentID string, runConfig *container.Config) (string, error) {
|
||||
func (c *imageProber) Probe(parentID string, runConfig *container.Config, platform ocispec.Platform) (string, error) {
|
||||
if c.cacheBusted {
|
||||
return "", nil
|
||||
}
|
||||
cacheID, err := c.cache.GetCache(parentID, runConfig)
|
||||
cacheID, err := c.cache.GetCache(parentID, runConfig, platform)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -74,6 +75,6 @@ func (c *nopProber) Reset(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *nopProber) Probe(_ string, _ *container.Config) (string, error) {
|
||||
func (c *nopProber) Probe(_ string, _ *container.Config, _ ocispec.Platform) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
@@ -328,7 +329,7 @@ func getShell(c *container.Config, os string) []string {
|
||||
}
|
||||
|
||||
func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
|
||||
cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig)
|
||||
cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig, b.getPlatform(dispatchState))
|
||||
if cachedID == "" || err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -388,3 +389,17 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
|
||||
}
|
||||
return hc
|
||||
}
|
||||
|
||||
func (b *Builder) getPlatform(state *dispatchState) ocispec.Platform {
|
||||
// May be nil if not explicitly set in API/dockerfile
|
||||
out := platforms.DefaultSpec()
|
||||
if b.platform != nil {
|
||||
out = *b.platform
|
||||
}
|
||||
|
||||
if state.operatingSystem != "" {
|
||||
out.OS = state.operatingSystem
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// MockBackend implements the builder.Backend interface for unit testing
|
||||
@@ -111,7 +112,7 @@ type mockImageCache struct {
|
||||
getCacheFunc func(parentID string, cfg *container.Config) (string, error)
|
||||
}
|
||||
|
||||
func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config) (string, error) {
|
||||
func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config, _ ocispec.Platform) (string, error) {
|
||||
if mic.getCacheFunc != nil {
|
||||
return mic.getCacheFunc(parentID, cfg)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
imagetype "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// MakeImageCache creates a stateful image cache.
|
||||
@@ -29,7 +31,7 @@ type imageCache struct {
|
||||
c *ImageService
|
||||
}
|
||||
|
||||
func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
|
||||
func (ic *imageCache) GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
if parentID == "" {
|
||||
@@ -37,8 +39,11 @@ func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parent, err := ic.c.GetImage(ctx, parentID, imagetype.GetImageOpts{})
|
||||
parent, err := ic.c.GetImage(ctx, parentID, imagetype.GetImageOpts{Platform: &platform})
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -54,8 +59,11 @@ func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID
|
||||
}
|
||||
|
||||
for _, children := range children {
|
||||
childImage, err := ic.c.GetImage(ctx, children.String(), imagetype.GetImageOpts{})
|
||||
childImage, err := ic.c.GetImage(ctx, children.String(), imagetype.GetImageOpts{Platform: &platform})
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr
|
||||
return err
|
||||
}
|
||||
if lower != "" {
|
||||
if err := ioutils.AtomicWriteFile(path.Join(dir, lowerFile), []byte(lower), 0o666); err != nil {
|
||||
if err := ioutils.AtomicWriteFile(path.Join(dir, lowerFile), []byte(lower), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +257,9 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st
|
||||
return nil, errors.Wrapf(err, "failed to set parent %s", parent)
|
||||
}
|
||||
}
|
||||
if err := i.imageStore.SetBuiltLocally(id); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to mark image %s as built locally", id)
|
||||
}
|
||||
|
||||
return i.imageStore.Get(id)
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ func (i *ImageService) CommitImage(ctx context.Context, c backend.CommitConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := i.imageStore.SetBuiltLocally(id); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if c.ParentImageID != "" {
|
||||
if err := i.imageStore.SetParent(id, image.ID(c.ParentImageID)); err != nil {
|
||||
|
||||
@@ -66,7 +66,7 @@ func getTailReader(ctx context.Context, r loggerutils.SizeReaderAt, req int) (io
|
||||
}
|
||||
|
||||
if msgLen != binary.BigEndian.Uint32(buf) {
|
||||
return nil, 0, errdefs.DataLoss(errors.Wrap(err, "log message header and footer indicate different message sizes"))
|
||||
return nil, 0, errdefs.DataLoss(errors.New("log message header and footer indicate different message sizes"))
|
||||
}
|
||||
|
||||
found++
|
||||
|
||||
@@ -15,7 +15,7 @@ set -e
|
||||
# the binary version you may also need to update the vendor version to pick up
|
||||
# bug fixes or new APIs, however, usually the Go packages are built from a
|
||||
# commit from the master branch.
|
||||
: "${CONTAINERD_VERSION:=v1.7.12}"
|
||||
: "${CONTAINERD_VERSION:=v1.7.13}"
|
||||
|
||||
install_containerd() (
|
||||
echo "Install containerd version $CONTAINERD_VERSION"
|
||||
|
||||
@@ -9,7 +9,7 @@ set -e
|
||||
# the containerd project first, and update both after that is merged.
|
||||
#
|
||||
# When updating RUNC_VERSION, consider updating runc in vendor.mod accordingly
|
||||
: "${RUNC_VERSION:=v1.1.11}"
|
||||
: "${RUNC_VERSION:=v1.1.12}"
|
||||
|
||||
install_runc() {
|
||||
RUNC_BUILDTAGS="${RUNC_BUILDTAGS:-"seccomp"}"
|
||||
|
||||
@@ -348,7 +348,7 @@ Function Run-UnitTests() {
|
||||
Function Run-IntegrationTests() {
|
||||
$escRoot = [Regex]::Escape($root)
|
||||
$env:DOCKER_INTEGRATION_DAEMON_DEST = $bundlesDir + "\tmp"
|
||||
$dirs = go list -test -f '{{- if ne .ForTest `"`" -}}{{- .Dir -}}{{- end -}}' .\integration\...
|
||||
$dirs = go list -test -f '{{- if ne .ForTest "" -}}{{- .Dir -}}{{- end -}}' .\integration\...
|
||||
ForEach($dir in $dirs) {
|
||||
# Normalize directory name for using in the test results files.
|
||||
$normDir = $dir.Trim()
|
||||
|
||||
82
image/cache/cache.go
vendored
82
image/cache/cache.go
vendored
@@ -10,7 +10,9 @@ import (
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// NewLocal returns a local image cache, based on parent chain
|
||||
@@ -26,8 +28,8 @@ type LocalImageCache struct {
|
||||
}
|
||||
|
||||
// GetCache returns the image id found in the cache
|
||||
func (lic *LocalImageCache) GetCache(imgID string, config *containertypes.Config) (string, error) {
|
||||
return getImageIDAndError(getLocalCachedImage(lic.store, image.ID(imgID), config))
|
||||
func (lic *LocalImageCache) GetCache(imgID string, config *containertypes.Config, platform ocispec.Platform) (string, error) {
|
||||
return getImageIDAndError(getLocalCachedImage(lic.store, image.ID(imgID), config, platform))
|
||||
}
|
||||
|
||||
// New returns an image cache, based on history objects
|
||||
@@ -51,8 +53,8 @@ func (ic *ImageCache) Populate(image *image.Image) {
|
||||
}
|
||||
|
||||
// GetCache returns the image id found in the cache
|
||||
func (ic *ImageCache) GetCache(parentID string, cfg *containertypes.Config) (string, error) {
|
||||
imgID, err := ic.localImageCache.GetCache(parentID, cfg)
|
||||
func (ic *ImageCache) GetCache(parentID string, cfg *containertypes.Config, platform ocispec.Platform) (string, error) {
|
||||
imgID, err := ic.localImageCache.GetCache(parentID, cfg, platform)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -215,7 +217,23 @@ func getImageIDAndError(img *image.Image, err error) (string, error) {
|
||||
// of the image with imgID, that had the same config when it was
|
||||
// created. nil is returned if a child cannot be found. An error is
|
||||
// returned if the parent image cannot be found.
|
||||
func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *containertypes.Config) (*image.Image, error) {
|
||||
func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *containertypes.Config, platform ocispec.Platform) (*image.Image, error) {
|
||||
if config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
isBuiltLocally := func(id image.ID) bool {
|
||||
builtLocally, err := imageStore.IsBuiltLocally(id)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"id": id,
|
||||
}).Warn("failed to check if image was built locally")
|
||||
return false
|
||||
}
|
||||
return builtLocally
|
||||
}
|
||||
|
||||
// Loop on the children of the given image and check the config
|
||||
getMatch := func(siblings []image.ID) (*image.Image, error) {
|
||||
var match *image.Image
|
||||
@@ -225,6 +243,26 @@ func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *contain
|
||||
return nil, fmt.Errorf("unable to find image %q", id)
|
||||
}
|
||||
|
||||
if !isBuiltLocally(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
imgPlatform := ocispec.Platform{
|
||||
Architecture: img.Architecture,
|
||||
OS: img.OS,
|
||||
OSVersion: img.OSVersion,
|
||||
OSFeatures: img.OSFeatures,
|
||||
Variant: img.Variant,
|
||||
}
|
||||
|
||||
// Discard old linux/amd64 images with empty platform.
|
||||
if imgPlatform.OS == "" && imgPlatform.Architecture == "" {
|
||||
continue
|
||||
}
|
||||
if !comparePlatform(platform, imgPlatform) {
|
||||
continue
|
||||
}
|
||||
|
||||
if compare(&img.ContainerConfig, config) {
|
||||
// check for the most up to date match
|
||||
if match == nil || match.Created.Before(img.Created) {
|
||||
@@ -238,11 +276,29 @@ func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *contain
|
||||
// In this case, this is `FROM scratch`, which isn't an actual image.
|
||||
if imgID == "" {
|
||||
images := imageStore.Map()
|
||||
|
||||
var siblings []image.ID
|
||||
for id, img := range images {
|
||||
if img.Parent == imgID {
|
||||
siblings = append(siblings, id)
|
||||
if img.Parent != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isBuiltLocally(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Do a quick initial filter on the Cmd to avoid adding all
|
||||
// non-local images with empty parent to the siblings slice and
|
||||
// performing a full config compare.
|
||||
//
|
||||
// config.Cmd is set to the current Dockerfile instruction so we
|
||||
// check it against the img.ContainerConfig.Cmd which is the
|
||||
// command of the last layer.
|
||||
if !strSliceEqual(img.ContainerConfig.Cmd, config.Cmd) {
|
||||
continue
|
||||
}
|
||||
|
||||
siblings = append(siblings, id)
|
||||
}
|
||||
return getMatch(siblings)
|
||||
}
|
||||
@@ -251,3 +307,15 @@ func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *contain
|
||||
siblings := imageStore.Children(imgID)
|
||||
return getMatch(siblings)
|
||||
}
|
||||
|
||||
func strSliceEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
195
image/cache/compare.go
vendored
195
image/cache/compare.go
vendored
@@ -1,45 +1,106 @@
|
||||
package cache // import "github.com/docker/docker/image/cache"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// compare two Config struct. Do not compare the "Image" nor "Hostname" fields
|
||||
// If OpenStdin is set, then it differs
|
||||
func compare(a, b *container.Config) bool {
|
||||
if a == nil || b == nil ||
|
||||
a.OpenStdin || b.OpenStdin {
|
||||
return false
|
||||
}
|
||||
if a.AttachStdout != b.AttachStdout ||
|
||||
a.AttachStderr != b.AttachStderr ||
|
||||
a.User != b.User ||
|
||||
a.OpenStdin != b.OpenStdin ||
|
||||
a.Tty != b.Tty {
|
||||
return false
|
||||
}
|
||||
// TODO: Remove once containerd image service directly uses the ImageCache and
|
||||
// LocalImageCache structs.
|
||||
func CompareConfig(a, b *container.Config) bool {
|
||||
return compare(a, b)
|
||||
}
|
||||
|
||||
if len(a.Cmd) != len(b.Cmd) ||
|
||||
len(a.Env) != len(b.Env) ||
|
||||
len(a.Labels) != len(b.Labels) ||
|
||||
len(a.ExposedPorts) != len(b.ExposedPorts) ||
|
||||
len(a.Entrypoint) != len(b.Entrypoint) ||
|
||||
len(a.Volumes) != len(b.Volumes) {
|
||||
return false
|
||||
}
|
||||
func comparePlatform(builderPlatform, imagePlatform ocispec.Platform) bool {
|
||||
// On Windows, only check the Major and Minor versions.
|
||||
// The Build and Revision compatibility depends on whether `process` or
|
||||
// `hyperv` isolation used.
|
||||
//
|
||||
// Fixes https://github.com/moby/moby/issues/47307
|
||||
if builderPlatform.OS == "windows" && imagePlatform.OS == builderPlatform.OS {
|
||||
// OSVersion format is:
|
||||
// Major.Minor.Build.Revision
|
||||
builderParts := strings.Split(builderPlatform.OSVersion, ".")
|
||||
imageParts := strings.Split(imagePlatform.OSVersion, ".")
|
||||
|
||||
for i := 0; i < len(a.Cmd); i++ {
|
||||
if a.Cmd[i] != b.Cmd[i] {
|
||||
return false
|
||||
// Major and minor must match.
|
||||
for i := 0; i < 2; i++ {
|
||||
if len(builderParts) > i && len(imageParts) > i && builderParts[i] != imageParts[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(builderParts) >= 3 && len(imageParts) >= 3 {
|
||||
// Keep only Major & Minor.
|
||||
builderParts[0] = imageParts[0]
|
||||
builderParts[1] = imageParts[1]
|
||||
imagePlatform.OSVersion = strings.Join(builderParts, ".")
|
||||
}
|
||||
}
|
||||
|
||||
return platforms.Only(builderPlatform).Match(imagePlatform)
|
||||
}
|
||||
|
||||
// compare two Config struct. Do not container-specific fields:
|
||||
// - Image
|
||||
// - Hostname
|
||||
// - Domainname
|
||||
// - MacAddress
|
||||
func compare(a, b *container.Config) bool {
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(a.Env) != len(b.Env) {
|
||||
return false
|
||||
}
|
||||
if len(a.Cmd) != len(b.Cmd) {
|
||||
return false
|
||||
}
|
||||
if len(a.Entrypoint) != len(b.Entrypoint) {
|
||||
return false
|
||||
}
|
||||
if len(a.Shell) != len(b.Shell) {
|
||||
return false
|
||||
}
|
||||
if len(a.ExposedPorts) != len(b.ExposedPorts) {
|
||||
return false
|
||||
}
|
||||
if len(a.Volumes) != len(b.Volumes) {
|
||||
return false
|
||||
}
|
||||
if len(a.Labels) != len(b.Labels) {
|
||||
return false
|
||||
}
|
||||
if len(a.OnBuild) != len(b.OnBuild) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(a.Env); i++ {
|
||||
if a.Env[i] != b.Env[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for k, v := range a.Labels {
|
||||
if v != b.Labels[k] {
|
||||
for i := 0; i < len(a.OnBuild); i++ {
|
||||
if a.OnBuild[i] != b.OnBuild[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(a.Cmd); i++ {
|
||||
if a.Cmd[i] != b.Cmd[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(a.Entrypoint); i++ {
|
||||
if a.Entrypoint[i] != b.Entrypoint[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(a.Shell); i++ {
|
||||
if a.Shell[i] != b.Shell[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -48,16 +109,84 @@ func compare(a, b *container.Config) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(a.Entrypoint); i++ {
|
||||
if a.Entrypoint[i] != b.Entrypoint[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for key := range a.Volumes {
|
||||
if _, exists := b.Volumes[key]; !exists {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for k, v := range a.Labels {
|
||||
if v != b.Labels[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if a.AttachStdin != b.AttachStdin {
|
||||
return false
|
||||
}
|
||||
if a.AttachStdout != b.AttachStdout {
|
||||
return false
|
||||
}
|
||||
if a.AttachStderr != b.AttachStderr {
|
||||
return false
|
||||
}
|
||||
if a.NetworkDisabled != b.NetworkDisabled {
|
||||
return false
|
||||
}
|
||||
if a.Tty != b.Tty {
|
||||
return false
|
||||
}
|
||||
if a.OpenStdin != b.OpenStdin {
|
||||
return false
|
||||
}
|
||||
if a.StdinOnce != b.StdinOnce {
|
||||
return false
|
||||
}
|
||||
if a.ArgsEscaped != b.ArgsEscaped {
|
||||
return false
|
||||
}
|
||||
if a.User != b.User {
|
||||
return false
|
||||
}
|
||||
if a.WorkingDir != b.WorkingDir {
|
||||
return false
|
||||
}
|
||||
if a.StopSignal != b.StopSignal {
|
||||
return false
|
||||
}
|
||||
|
||||
if (a.StopTimeout == nil) != (b.StopTimeout == nil) {
|
||||
return false
|
||||
}
|
||||
if a.StopTimeout != nil && b.StopTimeout != nil {
|
||||
if *a.StopTimeout != *b.StopTimeout {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (a.Healthcheck == nil) != (b.Healthcheck == nil) {
|
||||
return false
|
||||
}
|
||||
if a.Healthcheck != nil && b.Healthcheck != nil {
|
||||
if a.Healthcheck.Interval != b.Healthcheck.Interval {
|
||||
return false
|
||||
}
|
||||
if a.Healthcheck.StartPeriod != b.Healthcheck.StartPeriod {
|
||||
return false
|
||||
}
|
||||
if a.Healthcheck.Timeout != b.Healthcheck.Timeout {
|
||||
return false
|
||||
}
|
||||
if a.Healthcheck.Retries != b.Healthcheck.Retries {
|
||||
return false
|
||||
}
|
||||
if len(a.Healthcheck.Test) != len(b.Healthcheck.Test) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a.Healthcheck.Test); i++ {
|
||||
if a.Healthcheck.Test[i] != b.Healthcheck.Test[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
74
image/cache/compare_test.go
vendored
74
image/cache/compare_test.go
vendored
@@ -1,11 +1,15 @@
|
||||
package cache // import "github.com/docker/docker/image/cache"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/go-connections/nat"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
// Just to make life easier
|
||||
@@ -124,3 +128,73 @@ func TestCompare(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformCompare(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
builder ocispec.Platform
|
||||
image ocispec.Platform
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "same os and arch",
|
||||
builder: ocispec.Platform{Architecture: "amd64", OS: runtime.GOOS},
|
||||
image: ocispec.Platform{Architecture: "amd64", OS: runtime.GOOS},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "same os different arch",
|
||||
builder: ocispec.Platform{Architecture: "amd64", OS: runtime.GOOS},
|
||||
image: ocispec.Platform{Architecture: "arm64", OS: runtime.GOOS},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "same os smaller host variant",
|
||||
builder: ocispec.Platform{Variant: "v7", Architecture: "arm", OS: runtime.GOOS},
|
||||
image: ocispec.Platform{Variant: "v8", Architecture: "arm", OS: runtime.GOOS},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "same os higher host variant",
|
||||
builder: ocispec.Platform{Variant: "v8", Architecture: "arm", OS: runtime.GOOS},
|
||||
image: ocispec.Platform{Variant: "v7", Architecture: "arm", OS: runtime.GOOS},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
// Test for https://github.com/moby/moby/issues/47307
|
||||
name: "different build and revision",
|
||||
builder: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.22621"},
|
||||
image: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.5329"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "different revision",
|
||||
builder: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.1234"},
|
||||
image: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.5329"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "different major",
|
||||
builder: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "11.0.17763.5329"},
|
||||
image: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.5329"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "different minor same osver",
|
||||
builder: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.5329"},
|
||||
image: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.1.17763.5329"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "different arch same osver",
|
||||
builder: ocispec.Platform{Architecture: "arm64", OS: "windows", OSVersion: "10.0.17763.5329"},
|
||||
image: ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.17763.5329"},
|
||||
expected: false,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Check(t, is.Equal(comparePlatform(tc.builder, tc.image), tc.expected))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package image // import "github.com/docker/docker/image"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -24,6 +25,8 @@ type Store interface {
|
||||
GetParent(id ID) (ID, error)
|
||||
SetLastUpdated(id ID) error
|
||||
GetLastUpdated(id ID) (time.Time, error)
|
||||
SetBuiltLocally(id ID) error
|
||||
IsBuiltLocally(id ID) (bool, error)
|
||||
Children(id ID) []ID
|
||||
Map() map[ID]*Image
|
||||
Heads() map[ID]*Image
|
||||
@@ -295,6 +298,23 @@ func (is *store) GetLastUpdated(id ID) (time.Time, error) {
|
||||
return time.Parse(time.RFC3339Nano, string(bytes))
|
||||
}
|
||||
|
||||
// SetBuiltLocally sets whether image can be used as a builder cache
|
||||
func (is *store) SetBuiltLocally(id ID) error {
|
||||
return is.fs.SetMetadata(id.Digest(), "builtLocally", []byte{1})
|
||||
}
|
||||
|
||||
// IsBuiltLocally returns whether image can be used as a builder cache
|
||||
func (is *store) IsBuiltLocally(id ID) (bool, error) {
|
||||
bytes, err := is.fs.GetMetadata(id.Digest(), "builtLocally")
|
||||
if err != nil || len(bytes) == 0 {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return bytes[0] == 1, nil
|
||||
}
|
||||
|
||||
func (is *store) Children(id ID) []ID {
|
||||
is.RLock()
|
||||
defer is.RUnlock()
|
||||
|
||||
@@ -117,6 +117,49 @@ func TestPluginInstall(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
|
||||
t.Run("with digest", func(t *testing.T) {
|
||||
defer setupTest(t)()
|
||||
|
||||
reg := registry.NewV2(t)
|
||||
defer reg.Close()
|
||||
|
||||
name := "test-" + strings.ToLower(t.Name())
|
||||
repo := path.Join(registry.DefaultURL, name+":latest")
|
||||
err := plugin.Create(ctx, client, repo)
|
||||
assert.NilError(t, err)
|
||||
|
||||
rdr, err := client.PluginPush(ctx, repo, "")
|
||||
assert.NilError(t, err)
|
||||
defer rdr.Close()
|
||||
|
||||
buf := &strings.Builder{}
|
||||
assert.NilError(t, err)
|
||||
var digest string
|
||||
assert.NilError(t, jsonmessage.DisplayJSONMessagesStream(rdr, buf, 0, false, func(j jsonmessage.JSONMessage) {
|
||||
if j.Aux != nil {
|
||||
var r types.PushResult
|
||||
assert.NilError(t, json.Unmarshal(*j.Aux, &r))
|
||||
digest = r.Digest
|
||||
}
|
||||
}), buf)
|
||||
|
||||
err = client.PluginRemove(ctx, repo, types.PluginRemoveOptions{Force: true})
|
||||
assert.NilError(t, err)
|
||||
|
||||
rdr, err = client.PluginInstall(ctx, repo, types.PluginInstallOptions{
|
||||
Disabled: true,
|
||||
RemoteRef: repo + "@" + digest,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
defer rdr.Close()
|
||||
|
||||
_, err = io.Copy(io.Discard, rdr)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, _, err = client.PluginInspectWithRaw(ctx, repo)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
|
||||
t.Run("with htpasswd", func(t *testing.T) {
|
||||
defer setupTest(t)()
|
||||
|
||||
|
||||
@@ -185,12 +185,13 @@ func (r *remote) startContainerd() error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
err := cmd.Start()
|
||||
startedCh <- err
|
||||
if err != nil {
|
||||
startedCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
r.daemonWaitCh = make(chan struct{})
|
||||
startedCh <- nil
|
||||
|
||||
// Reap our child when needed
|
||||
if err := cmd.Wait(); err != nil {
|
||||
r.logger.WithError(err).Errorf("containerd did not exit successfully")
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
@@ -52,10 +54,23 @@ type Ctx struct {
|
||||
authReq *Request
|
||||
}
|
||||
|
||||
func isChunked(r *http.Request) bool {
|
||||
// RFC 7230 specifies that content length is to be ignored if Transfer-Encoding is chunked
|
||||
if strings.EqualFold(r.Header.Get("Transfer-Encoding"), "chunked") {
|
||||
return true
|
||||
}
|
||||
for _, v := range r.TransferEncoding {
|
||||
if strings.EqualFold(v, "chunked") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthZRequest authorized the request to the docker daemon using authZ plugins
|
||||
func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
|
||||
var body []byte
|
||||
if sendBody(ctx.requestURI, r.Header) && r.ContentLength > 0 && r.ContentLength < maxBodySize {
|
||||
if sendBody(ctx.requestURI, r.Header) && (r.ContentLength > 0 || isChunked(r)) && r.ContentLength < maxBodySize {
|
||||
var err error
|
||||
body, r.Body, err = drainBody(r.Body)
|
||||
if err != nil {
|
||||
@@ -108,7 +123,6 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
|
||||
if sendBody(ctx.requestURI, rm.Header()) {
|
||||
ctx.authReq.ResponseBody = rm.RawBody()
|
||||
}
|
||||
|
||||
for _, plugin := range ctx.plugins {
|
||||
logrus.Debugf("AuthZ response using plugin %s", plugin.Name())
|
||||
|
||||
@@ -146,10 +160,26 @@ func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
|
||||
return nil, newBody, err
|
||||
}
|
||||
|
||||
func isAuthEndpoint(urlPath string) (bool, error) {
|
||||
// eg www.test.com/v1.24/auth/optional?optional1=something&optional2=something (version optional)
|
||||
matched, err := regexp.MatchString(`^[^\/]*\/(v\d[\d\.]*\/)?auth.*`, urlPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// sendBody returns true when request/response body should be sent to AuthZPlugin
|
||||
func sendBody(url string, header http.Header) bool {
|
||||
func sendBody(inURL string, header http.Header) bool {
|
||||
u, err := url.Parse(inURL)
|
||||
// Assume no if the URL cannot be parsed - an empty request will still be forwarded to the plugin and should be rejected
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip body for auth endpoint
|
||||
if strings.HasSuffix(url, "/auth") {
|
||||
isAuth, err := isAuthEndpoint(u.Path)
|
||||
if isAuth || err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -175,8 +175,8 @@ func TestDrainBody(t *testing.T) {
|
||||
|
||||
func TestSendBody(t *testing.T) {
|
||||
var (
|
||||
url = "nothing.com"
|
||||
testcases = []struct {
|
||||
url string
|
||||
contentType string
|
||||
expected bool
|
||||
}{
|
||||
@@ -220,15 +220,93 @@ func TestSendBody(t *testing.T) {
|
||||
contentType: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/auth",
|
||||
contentType: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/auth",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/auth?p1=test",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/test?p1=/auth",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/something/auth",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/auth/test",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/v1.24/auth/test",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "nothing.com/v1/auth/test",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "www.nothing.com/v1.24/auth/test",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "https://www.nothing.com/v1.24/auth/test",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "http://nothing.com/v1.24/auth/test",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
url: "www.nothing.com/test?p1=/auth",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
url: "http://www.nothing.com/test?p1=/auth",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
url: "www.nothing.com/something/auth",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
url: "https://www.nothing.com/something/auth",
|
||||
contentType: "application/json;charset=UTF8",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
for _, testcase := range testcases {
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", testcase.contentType)
|
||||
if testcase.url == "" {
|
||||
testcase.url = "nothing.com"
|
||||
}
|
||||
|
||||
if b := sendBody(url, header); b != testcase.expected {
|
||||
t.Fatalf("Unexpected Content-Type; Expected: %t, Actual: %t", testcase.expected, b)
|
||||
if b := sendBody(testcase.url, header); b != testcase.expected {
|
||||
t.Fatalf("sendBody failed: url: %s, content-type: %s; Expected: %t, Actual: %t", testcase.url, testcase.contentType, testcase.expected, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,8 +200,13 @@ func withFetchProgress(cs content.Store, out progress.Output, ref reference.Name
|
||||
switch desc.MediaType {
|
||||
case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest:
|
||||
tn := reference.TagNameOnly(ref)
|
||||
tagged := tn.(reference.Tagged)
|
||||
progress.Messagef(out, tagged.Tag(), "Pulling from %s", reference.FamiliarName(ref))
|
||||
var tagOrDigest string
|
||||
if tagged, ok := tn.(reference.Tagged); ok {
|
||||
tagOrDigest = tagged.Tag()
|
||||
} else {
|
||||
tagOrDigest = tn.String()
|
||||
}
|
||||
progress.Messagef(out, tagOrDigest, "Pulling from %s", reference.FamiliarName(ref))
|
||||
progress.Messagef(out, "", "Digest: %s", desc.Digest.String())
|
||||
return nil, nil
|
||||
case
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"alarm",
|
||||
"bind",
|
||||
"brk",
|
||||
"cachestat",
|
||||
"capget",
|
||||
"capset",
|
||||
"chdir",
|
||||
@@ -109,6 +110,7 @@
|
||||
"fchdir",
|
||||
"fchmod",
|
||||
"fchmodat",
|
||||
"fchmodat2",
|
||||
"fchown",
|
||||
"fchown32",
|
||||
"fchownat",
|
||||
@@ -130,8 +132,11 @@
|
||||
"ftruncate",
|
||||
"ftruncate64",
|
||||
"futex",
|
||||
"futex_requeue",
|
||||
"futex_time64",
|
||||
"futex_wait",
|
||||
"futex_waitv",
|
||||
"futex_wake",
|
||||
"futimesat",
|
||||
"getcpu",
|
||||
"getcwd",
|
||||
@@ -206,6 +211,7 @@
|
||||
"lstat",
|
||||
"lstat64",
|
||||
"madvise",
|
||||
"map_shadow_stack",
|
||||
"membarrier",
|
||||
"memfd_create",
|
||||
"memfd_secret",
|
||||
@@ -783,7 +789,8 @@
|
||||
"names": [
|
||||
"get_mempolicy",
|
||||
"mbind",
|
||||
"set_mempolicy"
|
||||
"set_mempolicy",
|
||||
"set_mempolicy_home_node"
|
||||
],
|
||||
"action": "SCMP_ACT_ALLOW",
|
||||
"includes": {
|
||||
|
||||
@@ -56,6 +56,7 @@ func DefaultProfile() *Seccomp {
|
||||
"alarm",
|
||||
"bind",
|
||||
"brk",
|
||||
"cachestat", // kernel v6.5, libseccomp v2.5.5
|
||||
"capget",
|
||||
"capset",
|
||||
"chdir",
|
||||
@@ -101,6 +102,7 @@ func DefaultProfile() *Seccomp {
|
||||
"fchdir",
|
||||
"fchmod",
|
||||
"fchmodat",
|
||||
"fchmodat2", // kernel v6.6, libseccomp v2.5.5
|
||||
"fchown",
|
||||
"fchown32",
|
||||
"fchownat",
|
||||
@@ -122,8 +124,11 @@ func DefaultProfile() *Seccomp {
|
||||
"ftruncate",
|
||||
"ftruncate64",
|
||||
"futex",
|
||||
"futex_requeue", // kernel v6.7, libseccomp v2.5.5
|
||||
"futex_time64",
|
||||
"futex_wait", // kernel v6.7, libseccomp v2.5.5
|
||||
"futex_waitv",
|
||||
"futex_wake", // kernel v6.7, libseccomp v2.5.5
|
||||
"futimesat",
|
||||
"getcpu",
|
||||
"getcwd",
|
||||
@@ -198,6 +203,7 @@ func DefaultProfile() *Seccomp {
|
||||
"lstat",
|
||||
"lstat64",
|
||||
"madvise",
|
||||
"map_shadow_stack", // kernel v6.6, libseccomp v2.5.5
|
||||
"membarrier",
|
||||
"memfd_create",
|
||||
"memfd_secret",
|
||||
@@ -771,6 +777,7 @@ func DefaultProfile() *Seccomp {
|
||||
"get_mempolicy",
|
||||
"mbind",
|
||||
"set_mempolicy",
|
||||
"set_mempolicy_home_node", // kernel v5.17, libseccomp v2.5.4
|
||||
},
|
||||
Action: specs.ActAllow,
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@ require (
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3
|
||||
github.com/opencontainers/runc v1.1.11
|
||||
github.com/opencontainers/runc v1.1.12
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.2
|
||||
github.com/opencontainers/selinux v1.11.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
|
||||
@@ -1147,8 +1147,8 @@ github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rm
|
||||
github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE=
|
||||
github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
|
||||
github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
|
||||
github.com/opencontainers/runc v1.1.11 h1:9LjxyVlE0BPMRP2wuQDRlHV4941Jp9rc3F0+YKimopA=
|
||||
github.com/opencontainers/runc v1.1.11/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
|
||||
31
vendor/github.com/opencontainers/runc/libcontainer/cgroups/file.go
generated
vendored
31
vendor/github.com/opencontainers/runc/libcontainer/cgroups/file.go
generated
vendored
@@ -77,16 +77,16 @@ var (
|
||||
// TestMode is set to true by unit tests that need "fake" cgroupfs.
|
||||
TestMode bool
|
||||
|
||||
cgroupFd int = -1
|
||||
prepOnce sync.Once
|
||||
prepErr error
|
||||
resolveFlags uint64
|
||||
cgroupRootHandle *os.File
|
||||
prepOnce sync.Once
|
||||
prepErr error
|
||||
resolveFlags uint64
|
||||
)
|
||||
|
||||
func prepareOpenat2() error {
|
||||
prepOnce.Do(func() {
|
||||
fd, err := unix.Openat2(-1, cgroupfsDir, &unix.OpenHow{
|
||||
Flags: unix.O_DIRECTORY | unix.O_PATH,
|
||||
Flags: unix.O_DIRECTORY | unix.O_PATH | unix.O_CLOEXEC,
|
||||
})
|
||||
if err != nil {
|
||||
prepErr = &os.PathError{Op: "openat2", Path: cgroupfsDir, Err: err}
|
||||
@@ -97,15 +97,16 @@ func prepareOpenat2() error {
|
||||
}
|
||||
return
|
||||
}
|
||||
file := os.NewFile(uintptr(fd), cgroupfsDir)
|
||||
|
||||
var st unix.Statfs_t
|
||||
if err = unix.Fstatfs(fd, &st); err != nil {
|
||||
if err := unix.Fstatfs(int(file.Fd()), &st); err != nil {
|
||||
prepErr = &os.PathError{Op: "statfs", Path: cgroupfsDir, Err: err}
|
||||
logrus.Warnf("falling back to securejoin: %s", prepErr)
|
||||
return
|
||||
}
|
||||
|
||||
cgroupFd = fd
|
||||
|
||||
cgroupRootHandle = file
|
||||
resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS
|
||||
if st.Type == unix.CGROUP2_SUPER_MAGIC {
|
||||
// cgroupv2 has a single mountpoint and no "cpu,cpuacct" symlinks
|
||||
@@ -132,7 +133,7 @@ func openFile(dir, file string, flags int) (*os.File, error) {
|
||||
return openFallback(path, flags, mode)
|
||||
}
|
||||
|
||||
fd, err := unix.Openat2(cgroupFd, relPath,
|
||||
fd, err := unix.Openat2(int(cgroupRootHandle.Fd()), relPath,
|
||||
&unix.OpenHow{
|
||||
Resolve: resolveFlags,
|
||||
Flags: uint64(flags) | unix.O_CLOEXEC,
|
||||
@@ -140,20 +141,20 @@ func openFile(dir, file string, flags int) (*os.File, error) {
|
||||
})
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "openat2", Path: path, Err: err}
|
||||
// Check if cgroupFd is still opened to cgroupfsDir
|
||||
// Check if cgroupRootHandle is still opened to cgroupfsDir
|
||||
// (happens when this package is incorrectly used
|
||||
// across the chroot/pivot_root/mntns boundary, or
|
||||
// when /sys/fs/cgroup is remounted).
|
||||
//
|
||||
// TODO: if such usage will ever be common, amend this
|
||||
// to reopen cgroupFd and retry openat2.
|
||||
fdStr := strconv.Itoa(cgroupFd)
|
||||
// to reopen cgroupRootHandle and retry openat2.
|
||||
fdStr := strconv.Itoa(int(cgroupRootHandle.Fd()))
|
||||
fdDest, _ := os.Readlink("/proc/self/fd/" + fdStr)
|
||||
if fdDest != cgroupfsDir {
|
||||
// Wrap the error so it is clear that cgroupFd
|
||||
// Wrap the error so it is clear that cgroupRootHandle
|
||||
// is opened to an unexpected/wrong directory.
|
||||
err = fmt.Errorf("cgroupFd %s unexpectedly opened to %s != %s: %w",
|
||||
fdStr, fdDest, cgroupfsDir, err)
|
||||
err = fmt.Errorf("cgroupRootHandle %d unexpectedly opened to %s != %s: %w",
|
||||
cgroupRootHandle.Fd(), fdDest, cgroupfsDir, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
64
vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go
generated
vendored
64
vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go
generated
vendored
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -23,9 +24,11 @@ func EnsureProcHandle(fh *os.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseExecFrom applies O_CLOEXEC to all file descriptors currently open for
|
||||
// the process (except for those below the given fd value).
|
||||
func CloseExecFrom(minFd int) error {
|
||||
type fdFunc func(fd int)
|
||||
|
||||
// fdRangeFrom calls the passed fdFunc for each file descriptor that is open in
|
||||
// the current process.
|
||||
func fdRangeFrom(minFd int, fn fdFunc) error {
|
||||
fdDir, err := os.Open("/proc/self/fd")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,15 +53,60 @@ func CloseExecFrom(minFd int) error {
|
||||
if fd < minFd {
|
||||
continue
|
||||
}
|
||||
// Intentionally ignore errors from unix.CloseOnExec -- the cases where
|
||||
// this might fail are basically file descriptors that have already
|
||||
// been closed (including and especially the one that was created when
|
||||
// os.ReadDir did the "opendir" syscall).
|
||||
unix.CloseOnExec(fd)
|
||||
// Ignore the file descriptor we used for readdir, as it will be closed
|
||||
// when we return.
|
||||
if uintptr(fd) == fdDir.Fd() {
|
||||
continue
|
||||
}
|
||||
// Run the closure.
|
||||
fn(fd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseExecFrom sets the O_CLOEXEC flag on all file descriptors greater or
|
||||
// equal to minFd in the current process.
|
||||
func CloseExecFrom(minFd int) error {
|
||||
return fdRangeFrom(minFd, unix.CloseOnExec)
|
||||
}
|
||||
|
||||
//go:linkname runtime_IsPollDescriptor internal/poll.IsPollDescriptor
|
||||
|
||||
// In order to make sure we do not close the internal epoll descriptors the Go
|
||||
// runtime uses, we need to ensure that we skip descriptors that match
|
||||
// "internal/poll".IsPollDescriptor. Yes, this is a Go runtime internal thing,
|
||||
// unfortunately there's no other way to be sure we're only keeping the file
|
||||
// descriptors the Go runtime needs. Hopefully nothing blows up doing this...
|
||||
func runtime_IsPollDescriptor(fd uintptr) bool //nolint:revive
|
||||
|
||||
// UnsafeCloseFrom closes all file descriptors greater or equal to minFd in the
|
||||
// current process, except for those critical to Go's runtime (such as the
|
||||
// netpoll management descriptors).
|
||||
//
|
||||
// NOTE: That this function is incredibly dangerous to use in most Go code, as
|
||||
// closing file descriptors from underneath *os.File handles can lead to very
|
||||
// bad behaviour (the closed file descriptor can be re-used and then any
|
||||
// *os.File operations would apply to the wrong file). This function is only
|
||||
// intended to be called from the last stage of runc init.
|
||||
func UnsafeCloseFrom(minFd int) error {
|
||||
// We must not close some file descriptors.
|
||||
return fdRangeFrom(minFd, func(fd int) {
|
||||
if runtime_IsPollDescriptor(uintptr(fd)) {
|
||||
// These are the Go runtimes internal netpoll file descriptors.
|
||||
// These file descriptors are operated on deep in the Go scheduler,
|
||||
// and closing those files from underneath Go can result in panics.
|
||||
// There is no issue with keeping them because they are not
|
||||
// executable and are not useful to an attacker anyway. Also we
|
||||
// don't have any choice.
|
||||
return
|
||||
}
|
||||
// There's nothing we can do about errors from close(2), and the
|
||||
// only likely error to be seen is EBADF which indicates the fd was
|
||||
// already closed (in which case, we got what we wanted).
|
||||
_ = unix.Close(fd)
|
||||
})
|
||||
}
|
||||
|
||||
// NewSockPair returns a new unix socket pair
|
||||
func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
|
||||
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -837,7 +837,7 @@ github.com/opencontainers/go-digest/digestset
|
||||
github.com/opencontainers/image-spec/identity
|
||||
github.com/opencontainers/image-spec/specs-go
|
||||
github.com/opencontainers/image-spec/specs-go/v1
|
||||
# github.com/opencontainers/runc v1.1.11
|
||||
# github.com/opencontainers/runc v1.1.12
|
||||
## explicit; go 1.17
|
||||
github.com/opencontainers/runc/libcontainer/cgroups
|
||||
github.com/opencontainers/runc/libcontainer/configs
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -335,8 +336,15 @@ func (v *localVolume) Unmount(id string) error {
|
||||
// ultimately there's nothing that can be done. If we don't decrement the count
|
||||
// this volume can never be removed until a daemon restart occurs.
|
||||
if v.needsMount() {
|
||||
v.active.count--
|
||||
logger.WithField("active mounts", v.active).Debug("Decremented active mount count")
|
||||
// TODO: Remove once the real bug is fixed: https://github.com/moby/moby/issues/46508
|
||||
if v.active.count > 0 {
|
||||
v.active.count--
|
||||
logger.WithField("active mounts", v.active).Debug("Decremented active mount count")
|
||||
} else {
|
||||
logger.Error("An attempt to decrement a zero mount count")
|
||||
logger.Error(string(debug.Stack()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if v.active.count > 0 {
|
||||
|
||||
@@ -82,7 +82,9 @@ func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSour
|
||||
if err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
if !exists {
|
||||
|
||||
createMountpoint := mnt.BindOptions != nil && mnt.BindOptions.CreateMountpoint
|
||||
if !exists && !createMountpoint {
|
||||
return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
@@ -83,11 +84,22 @@ func (m *MountPoint) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger := log.G(context.TODO()).WithFields(log.Fields{"active": m.active, "id": m.ID})
|
||||
|
||||
// TODO: Remove once the real bug is fixed: https://github.com/moby/moby/issues/46508
|
||||
if m.active == 0 {
|
||||
logger.Error("An attempt to decrement a zero mount count")
|
||||
logger.Error(string(debug.Stack()))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := m.Volume.Unmount(m.ID); err != nil {
|
||||
return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
|
||||
}
|
||||
|
||||
m.active--
|
||||
logger.Debug("MountPoint.Cleanup Decrement active count")
|
||||
|
||||
if m.active == 0 {
|
||||
m.ID = ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user