mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-17 09:15:59 +02:00
chore(deps): bump github.com/docker/docker from 25.0.4+incompatible to 26.0.0+incompatible (#3085)
This commit is contained in:
+11
-2
@@ -2,8 +2,17 @@ package api // import "github.com/docker/docker/api"
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const (
|
||||
// DefaultVersion of Current REST API
|
||||
DefaultVersion = "1.44"
|
||||
// DefaultVersion of the current REST API.
|
||||
DefaultVersion = "1.45"
|
||||
|
||||
// MinSupportedAPIVersion is the minimum API version that can be supported
|
||||
// by the API server, specified as "major.minor". Note that the daemon
|
||||
// may be configured with a different minimum API version, as returned
|
||||
// in [github.com/docker/docker/api/types.Version.MinAPIVersion].
|
||||
//
|
||||
// API requests for API versions lower than the configured version produce
|
||||
// an error.
|
||||
MinSupportedAPIVersion = "1.24"
|
||||
|
||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||
// command to specify that no base image is to be used.
|
||||
|
||||
+9
-11
@@ -19,10 +19,10 @@ produces:
|
||||
consumes:
|
||||
- "application/json"
|
||||
- "text/plain"
|
||||
basePath: "/v1.44"
|
||||
basePath: "/v1.45"
|
||||
info:
|
||||
title: "Docker Engine API"
|
||||
version: "1.44"
|
||||
version: "1.45"
|
||||
x-logo:
|
||||
url: "https://docs.docker.com/assets/images/logo-docker-main.png"
|
||||
description: |
|
||||
@@ -55,8 +55,8 @@ info:
|
||||
the URL is not supported by the daemon, a HTTP `400 Bad Request` error message
|
||||
is returned.
|
||||
|
||||
If you omit the version-prefix, the current version of the API (v1.44) is used.
|
||||
For example, calling `/info` is the same as calling `/v1.44/info`. Using the
|
||||
If you omit the version-prefix, the current version of the API (v1.45) is used.
|
||||
For example, calling `/info` is the same as calling `/v1.45/info`. Using the
|
||||
API without a version-prefix is deprecated and will be removed in a future release.
|
||||
|
||||
Engine releases in the near future should support this version of the API,
|
||||
@@ -427,6 +427,10 @@ definitions:
|
||||
type: "object"
|
||||
additionalProperties:
|
||||
type: "string"
|
||||
Subpath:
|
||||
description: "Source path inside the volume. Must be relative without any back traversals."
|
||||
type: "string"
|
||||
example: "dir-inside-volume/subdirectory"
|
||||
TmpfsOptions:
|
||||
description: "Optional configuration for the `tmpfs` type."
|
||||
type: "object"
|
||||
@@ -8770,8 +8774,7 @@ paths:
|
||||
|
||||
<p><br /></p>
|
||||
|
||||
> **Deprecated**: This field is deprecated and will always
|
||||
> be "false" in future.
|
||||
> **Deprecated**: This field is deprecated and will always be "false".
|
||||
type: "boolean"
|
||||
example: false
|
||||
name:
|
||||
@@ -8814,13 +8817,8 @@ paths:
|
||||
description: |
|
||||
A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters:
|
||||
|
||||
- `is-automated=(true|false)` (deprecated, see below)
|
||||
- `is-official=(true|false)`
|
||||
- `stars=<number>` Matches images that has at least 'number' stars.
|
||||
|
||||
The `is-automated` filter is deprecated. The `is_automated` field has
|
||||
been deprecated by Docker Hub's search API. Consequently, searching
|
||||
for `is-automated=true` will yield no results.
|
||||
type: "string"
|
||||
tags: ["Image"]
|
||||
/images/prune:
|
||||
|
||||
+7
-2
@@ -18,7 +18,6 @@ type ContainerCreateConfig struct {
|
||||
HostConfig *container.HostConfig
|
||||
NetworkingConfig *network.NetworkingConfig
|
||||
Platform *ocispec.Platform
|
||||
AdjustCPUShares bool
|
||||
DefaultReadOnlyNonRecursive bool
|
||||
}
|
||||
|
||||
@@ -91,7 +90,6 @@ type ContainerStatsConfig struct {
|
||||
Stream bool
|
||||
OneShot bool
|
||||
OutStream io.Writer
|
||||
Version string
|
||||
}
|
||||
|
||||
// ExecInspect holds information about a running process started
|
||||
@@ -131,6 +129,13 @@ type CreateImageConfig struct {
|
||||
Changes []string
|
||||
}
|
||||
|
||||
// GetImageOpts holds parameters to retrieve image information
|
||||
// from the backend.
|
||||
type GetImageOpts struct {
|
||||
Platform *ocispec.Platform
|
||||
Details bool
|
||||
}
|
||||
|
||||
// CommitConfig is the configuration for creating an image as part of a build.
|
||||
type CommitConfig struct {
|
||||
Author string
|
||||
|
||||
-47
@@ -157,42 +157,12 @@ type ImageBuildResponse struct {
|
||||
OSType string
|
||||
}
|
||||
|
||||
// ImageCreateOptions holds information to create images.
|
||||
type ImageCreateOptions struct {
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
|
||||
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
|
||||
}
|
||||
|
||||
// ImageImportSource holds source information for ImageImport
|
||||
type ImageImportSource struct {
|
||||
Source io.Reader // Source is the data to send to the server to create this image from. You must set SourceName to "-" to leverage this.
|
||||
SourceName string // SourceName is the name of the image to pull. Set to "-" to leverage the Source attribute.
|
||||
}
|
||||
|
||||
// ImageImportOptions holds information to import images from the client host.
|
||||
type ImageImportOptions struct {
|
||||
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
|
||||
Message string // Message is the message to tag the image with
|
||||
Changes []string // Changes are the raw changes to apply to this image
|
||||
Platform string // Platform is the target platform of the image
|
||||
}
|
||||
|
||||
// ImageListOptions holds parameters to list images with.
|
||||
type ImageListOptions struct {
|
||||
// All controls whether all images in the graph are filtered, or just
|
||||
// the heads.
|
||||
All bool
|
||||
|
||||
// Filters is a JSON-encoded set of filter arguments.
|
||||
Filters filters.Args
|
||||
|
||||
// SharedSize indicates whether the shared size of images should be computed.
|
||||
SharedSize bool
|
||||
|
||||
// ContainerCount indicates whether container count should be computed.
|
||||
ContainerCount bool
|
||||
}
|
||||
|
||||
// ImageLoadResponse returns information to the client about a load process.
|
||||
type ImageLoadResponse struct {
|
||||
// Body must be closed to avoid a resource leak
|
||||
@@ -200,14 +170,6 @@ type ImageLoadResponse struct {
|
||||
JSON bool
|
||||
}
|
||||
|
||||
// ImagePullOptions holds information to pull images.
|
||||
type ImagePullOptions struct {
|
||||
All bool
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||
PrivilegeFunc RequestPrivilegeFunc
|
||||
Platform string
|
||||
}
|
||||
|
||||
// RequestPrivilegeFunc is a function interface that
|
||||
// clients can supply to retry operations after
|
||||
// getting an authorization error.
|
||||
@@ -216,15 +178,6 @@ type ImagePullOptions struct {
|
||||
// if the privilege request fails.
|
||||
type RequestPrivilegeFunc func() (string, error)
|
||||
|
||||
// ImagePushOptions holds information to push images.
|
||||
type ImagePushOptions ImagePullOptions
|
||||
|
||||
// ImageRemoveOptions holds parameters to remove images.
|
||||
type ImageRemoveOptions struct {
|
||||
Force bool
|
||||
PruneChildren bool
|
||||
}
|
||||
|
||||
// ImageSearchOptions holds parameters to search images with.
|
||||
type ImageSearchOptions struct {
|
||||
RegistryAuth string
|
||||
|
||||
+1
-1
@@ -5,8 +5,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
dockerspec "github.com/docker/docker/image/spec/specs-go/v1"
|
||||
"github.com/docker/go-connections/nat"
|
||||
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// MinimumDuration puts a minimum on user configured duration.
|
||||
|
||||
+53
-5
@@ -1,9 +1,57 @@
|
||||
package image
|
||||
|
||||
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
import "github.com/docker/docker/api/types/filters"
|
||||
|
||||
// GetImageOpts holds parameters to inspect an image.
|
||||
type GetImageOpts struct {
|
||||
Platform *ocispec.Platform
|
||||
Details bool
|
||||
// ImportOptions holds information to import images from the client host.
|
||||
type ImportOptions struct {
|
||||
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
|
||||
Message string // Message is the message to tag the image with
|
||||
Changes []string // Changes are the raw changes to apply to this image
|
||||
Platform string // Platform is the target platform of the image
|
||||
}
|
||||
|
||||
// CreateOptions holds information to create images.
|
||||
type CreateOptions struct {
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
|
||||
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
|
||||
}
|
||||
|
||||
// PullOptions holds information to pull images.
|
||||
type PullOptions struct {
|
||||
All bool
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||
|
||||
// PrivilegeFunc is a function that clients can supply to retry operations
|
||||
// after getting an authorization error. This function returns the registry
|
||||
// authentication header value in base64 encoded format, or an error if the
|
||||
// privilege request fails.
|
||||
//
|
||||
// Also see [github.com/docker/docker/api/types.RequestPrivilegeFunc].
|
||||
PrivilegeFunc func() (string, error)
|
||||
Platform string
|
||||
}
|
||||
|
||||
// PushOptions holds information to push images.
|
||||
type PushOptions PullOptions
|
||||
|
||||
// ListOptions holds parameters to list images with.
|
||||
type ListOptions struct {
|
||||
// All controls whether all images in the graph are filtered, or just
|
||||
// the heads.
|
||||
All bool
|
||||
|
||||
// Filters is a JSON-encoded set of filter arguments.
|
||||
Filters filters.Args
|
||||
|
||||
// SharedSize indicates whether the shared size of images should be computed.
|
||||
SharedSize bool
|
||||
|
||||
// ContainerCount indicates whether container count should be computed.
|
||||
ContainerCount bool
|
||||
}
|
||||
|
||||
// RemoveOptions holds parameters to remove images.
|
||||
type RemoveOptions struct {
|
||||
Force bool
|
||||
PruneChildren bool
|
||||
}
|
||||
|
||||
+1
@@ -96,6 +96,7 @@ type BindOptions struct {
|
||||
type VolumeOptions struct {
|
||||
NoCopy bool `json:",omitempty"`
|
||||
Labels map[string]string `json:",omitempty"`
|
||||
Subpath string `json:",omitempty"`
|
||||
DriverConfig *Driver `json:",omitempty"`
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ type SearchResult struct {
|
||||
Name string `json:"name"`
|
||||
// IsAutomated indicates whether the result is automated.
|
||||
//
|
||||
// Deprecated: the "is_automated" field is deprecated and will always be "false" in the future.
|
||||
// Deprecated: the "is_automated" field is deprecated and will always be "false".
|
||||
IsAutomated bool `json:"is_automated"`
|
||||
// Description is a textual description of the repository
|
||||
Description string `json:"description"`
|
||||
|
||||
+2
-2
@@ -82,7 +82,7 @@ type ImageInspect struct {
|
||||
// Depending on how the image was created, this field may be empty.
|
||||
//
|
||||
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
|
||||
Container string
|
||||
Container string `json:",omitempty"`
|
||||
|
||||
// ContainerConfig is an optional field containing the configuration of the
|
||||
// container that was last committed when creating the image.
|
||||
@@ -91,7 +91,7 @@ type ImageInspect struct {
|
||||
// and it is not in active use anymore.
|
||||
//
|
||||
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
|
||||
ContainerConfig *container.Config
|
||||
ContainerConfig *container.Config `json:",omitempty"`
|
||||
|
||||
// DockerVersion is the version of Docker that was used to build the image.
|
||||
//
|
||||
|
||||
+18
-121
@@ -1,138 +1,35 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
)
|
||||
|
||||
// CheckpointCreateOptions holds parameters to create a checkpoint from a container.
|
||||
// ImageImportOptions holds information to import images from the client host.
|
||||
//
|
||||
// Deprecated: use [checkpoint.CreateOptions].
|
||||
type CheckpointCreateOptions = checkpoint.CreateOptions
|
||||
// Deprecated: use [image.ImportOptions].
|
||||
type ImageImportOptions = image.ImportOptions
|
||||
|
||||
// CheckpointListOptions holds parameters to list checkpoints for a container
|
||||
// ImageCreateOptions holds information to create images.
|
||||
//
|
||||
// Deprecated: use [checkpoint.ListOptions].
|
||||
type CheckpointListOptions = checkpoint.ListOptions
|
||||
// Deprecated: use [image.CreateOptions].
|
||||
type ImageCreateOptions = image.CreateOptions
|
||||
|
||||
// CheckpointDeleteOptions holds parameters to delete a checkpoint from a container
|
||||
// ImagePullOptions holds information to pull images.
|
||||
//
|
||||
// Deprecated: use [checkpoint.DeleteOptions].
|
||||
type CheckpointDeleteOptions = checkpoint.DeleteOptions
|
||||
// Deprecated: use [image.PullOptions].
|
||||
type ImagePullOptions = image.PullOptions
|
||||
|
||||
// Checkpoint represents the details of a checkpoint when listing endpoints.
|
||||
// ImagePushOptions holds information to push images.
|
||||
//
|
||||
// Deprecated: use [checkpoint.Summary].
|
||||
type Checkpoint = checkpoint.Summary
|
||||
// Deprecated: use [image.PushOptions].
|
||||
type ImagePushOptions = image.PushOptions
|
||||
|
||||
// Info contains response of Engine API:
|
||||
// GET "/info"
|
||||
// ImageListOptions holds parameters to list images with.
|
||||
//
|
||||
// Deprecated: use [system.Info].
|
||||
type Info = system.Info
|
||||
// Deprecated: use [image.ListOptions].
|
||||
type ImageListOptions = image.ListOptions
|
||||
|
||||
// Commit holds the Git-commit (SHA1) that a binary was built from, as reported
|
||||
// in the version-string of external tools, such as containerd, or runC.
|
||||
// ImageRemoveOptions holds parameters to remove images.
|
||||
//
|
||||
// Deprecated: use [system.Commit].
|
||||
type Commit = system.Commit
|
||||
|
||||
// PluginsInfo is a temp struct holding Plugins name
|
||||
// registered with docker daemon. It is used by [system.Info] struct
|
||||
//
|
||||
// Deprecated: use [system.PluginsInfo].
|
||||
type PluginsInfo = system.PluginsInfo
|
||||
|
||||
// NetworkAddressPool is a temp struct used by [system.Info] struct.
|
||||
//
|
||||
// Deprecated: use [system.NetworkAddressPool].
|
||||
type NetworkAddressPool = system.NetworkAddressPool
|
||||
|
||||
// Runtime describes an OCI runtime.
|
||||
//
|
||||
// Deprecated: use [system.Runtime].
|
||||
type Runtime = system.Runtime
|
||||
|
||||
// SecurityOpt contains the name and options of a security option.
|
||||
//
|
||||
// Deprecated: use [system.SecurityOpt].
|
||||
type SecurityOpt = system.SecurityOpt
|
||||
|
||||
// KeyValue holds a key/value pair.
|
||||
//
|
||||
// Deprecated: use [system.KeyValue].
|
||||
type KeyValue = system.KeyValue
|
||||
|
||||
// ImageDeleteResponseItem image delete response item.
|
||||
//
|
||||
// Deprecated: use [image.DeleteResponse].
|
||||
type ImageDeleteResponseItem = image.DeleteResponse
|
||||
|
||||
// ImageSummary image summary.
|
||||
//
|
||||
// Deprecated: use [image.Summary].
|
||||
type ImageSummary = image.Summary
|
||||
|
||||
// ImageMetadata contains engine-local data about the image.
|
||||
//
|
||||
// Deprecated: use [image.Metadata].
|
||||
type ImageMetadata = image.Metadata
|
||||
|
||||
// ServiceCreateResponse contains the information returned to a client
|
||||
// on the creation of a new service.
|
||||
//
|
||||
// Deprecated: use [swarm.ServiceCreateResponse].
|
||||
type ServiceCreateResponse = swarm.ServiceCreateResponse
|
||||
|
||||
// ServiceUpdateResponse service update response.
|
||||
//
|
||||
// Deprecated: use [swarm.ServiceUpdateResponse].
|
||||
type ServiceUpdateResponse = swarm.ServiceUpdateResponse
|
||||
|
||||
// ContainerStartOptions holds parameters to start containers.
|
||||
//
|
||||
// Deprecated: use [container.StartOptions].
|
||||
type ContainerStartOptions = container.StartOptions
|
||||
|
||||
// ResizeOptions holds parameters to resize a TTY.
|
||||
// It can be used to resize container TTYs and
|
||||
// exec process TTYs too.
|
||||
//
|
||||
// Deprecated: use [container.ResizeOptions].
|
||||
type ResizeOptions = container.ResizeOptions
|
||||
|
||||
// ContainerAttachOptions holds parameters to attach to a container.
|
||||
//
|
||||
// Deprecated: use [container.AttachOptions].
|
||||
type ContainerAttachOptions = container.AttachOptions
|
||||
|
||||
// ContainerCommitOptions holds parameters to commit changes into a container.
|
||||
//
|
||||
// Deprecated: use [container.CommitOptions].
|
||||
type ContainerCommitOptions = container.CommitOptions
|
||||
|
||||
// ContainerListOptions holds parameters to list containers with.
|
||||
//
|
||||
// Deprecated: use [container.ListOptions].
|
||||
type ContainerListOptions = container.ListOptions
|
||||
|
||||
// ContainerLogsOptions holds parameters to filter logs with.
|
||||
//
|
||||
// Deprecated: use [container.LogsOptions].
|
||||
type ContainerLogsOptions = container.LogsOptions
|
||||
|
||||
// ContainerRemoveOptions holds parameters to remove containers.
|
||||
//
|
||||
// Deprecated: use [container.RemoveOptions].
|
||||
type ContainerRemoveOptions = container.RemoveOptions
|
||||
|
||||
// DecodeSecurityOptions decodes a security options string slice to a type safe
|
||||
// [system.SecurityOpt].
|
||||
//
|
||||
// Deprecated: use [system.DecodeSecurityOptions].
|
||||
func DecodeSecurityOptions(opts []string) ([]system.SecurityOpt, error) {
|
||||
return system.DecodeSecurityOptions(opts)
|
||||
}
|
||||
// Deprecated: use [image.RemoveOptions].
|
||||
type ImageRemoveOptions = image.RemoveOptions
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
# Legacy API type versions
|
||||
|
||||
This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`.
|
||||
|
||||
Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`.
|
||||
|
||||
## Package name conventions
|
||||
|
||||
The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention:
|
||||
|
||||
1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`.
|
||||
2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`.
|
||||
|
||||
For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`.
|
||||
+5
-5
@@ -238,13 +238,13 @@ type TopologyRequirement struct {
|
||||
// If requisite is specified, all topologies in preferred list MUST
|
||||
// also be present in the list of requisite topologies.
|
||||
//
|
||||
// If the SP is unable to to make the provisioned volume available
|
||||
// If the SP is unable to make the provisioned volume available
|
||||
// from any of the preferred topologies, the SP MAY choose a topology
|
||||
// from the list of requisite topologies.
|
||||
// If the list of requisite topologies is not specified, then the SP
|
||||
// MAY choose from the list of all possible topologies.
|
||||
// If the list of requisite topologies is specified and the SP is
|
||||
// unable to to make the provisioned volume available from any of the
|
||||
// unable to make the provisioned volume available from any of the
|
||||
// requisite topologies it MUST fail the CreateVolume call.
|
||||
//
|
||||
// Example 1:
|
||||
@@ -254,7 +254,7 @@ type TopologyRequirement struct {
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// then the SP SHOULD first attempt to make the provisioned volume
|
||||
// available from "zone" "Z3" in the "region" "R1" and fall back to
|
||||
// "zone" "Z2" in the "region" "R1" if that is not possible.
|
||||
//
|
||||
@@ -268,7 +268,7 @@ type TopologyRequirement struct {
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// then the SP SHOULD first attempt to make the provisioned volume
|
||||
// accessible from "zone" "Z4" in the "region" "R1" and fall back to
|
||||
// "zone" "Z2" in the "region" "R1" if that is not possible. If that
|
||||
// is not possible, the SP may choose between either the "zone"
|
||||
@@ -287,7 +287,7 @@ type TopologyRequirement struct {
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z5"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// then the SP SHOULD first attempt to make the provisioned volume
|
||||
// accessible from the combination of the two "zones" "Z5" and "Z3" in
|
||||
// the "region" "R1". If that's not possible, it should fall back to
|
||||
// a combination of "Z5" and other possibilities from the list of
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ type ExecBackend interface {
|
||||
// ContainerRm removes a container specified by `id`.
|
||||
ContainerRm(name string, config *backend.ContainerRmConfig) error
|
||||
// ContainerStart starts a new container
|
||||
ContainerStart(ctx context.Context, containerID string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
|
||||
ContainerStart(ctx context.Context, containerID string, checkpoint string, checkpointDir string) error
|
||||
// ContainerWait stops processing until the given container is stopped.
|
||||
ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error)
|
||||
}
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ func (c *containerManager) Run(ctx context.Context, cID string, stdout, stderr i
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.backend.ContainerStart(ctx, cID, nil, "", ""); err != nil {
|
||||
if err := c.backend.ContainerStart(ctx, cID, "", ""); err != nil {
|
||||
close(finished)
|
||||
logCancellationError(cancelErrCh, "error from ContainerStart: "+err.Error())
|
||||
return err
|
||||
|
||||
+4
-4
@@ -10,11 +10,11 @@ import (
|
||||
)
|
||||
|
||||
// DistributionInspect returns the image digest with the full manifest.
|
||||
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error) {
|
||||
func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedRegistryAuth string) (registry.DistributionInspect, error) {
|
||||
// Contact the registry to retrieve digest and platform information
|
||||
var distributionInspect registry.DistributionInspect
|
||||
if image == "" {
|
||||
return distributionInspect, objectNotFoundError{object: "distribution", id: image}
|
||||
if imageRef == "" {
|
||||
return distributionInspect, objectNotFoundError{object: "distribution", id: imageRef}
|
||||
}
|
||||
|
||||
if err := cli.NewVersionError(ctx, "1.30", "distribution inspect"); err != nil {
|
||||
@@ -28,7 +28,7 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers)
|
||||
resp, err := cli.get(ctx, "/distribution/"+imageRef+"/json", url.Values{}, headers)
|
||||
defer ensureReaderClosed(resp)
|
||||
if err != nil {
|
||||
return distributionInspect, err
|
||||
|
||||
+2
-2
@@ -8,13 +8,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// ImageCreate creates a new image based on the parent options.
|
||||
// It returns the JSON content in the response body.
|
||||
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
|
||||
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(parentReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+2
-1
@@ -8,11 +8,12 @@ import (
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
)
|
||||
|
||||
// ImageImport creates a new image based on the source options.
|
||||
// It returns the JSON content in the response body.
|
||||
func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
|
||||
func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
if ref != "" {
|
||||
// Check if the given image name can be resolved
|
||||
if _, err := reference.ParseNormalizedNamed(ref); err != nil {
|
||||
|
||||
+1
-2
@@ -5,14 +5,13 @@ import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
)
|
||||
|
||||
// ImageList returns a list of images in the docker host.
|
||||
func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]image.Summary, error) {
|
||||
func (cli *Client) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) {
|
||||
var images []image.Summary
|
||||
|
||||
// Make sure we negotiated (if the client is configured to do so),
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/errdefs"
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
// FIXME(vdemeester): there is currently used in a few way in docker/docker
|
||||
// - if not in trusted content, ref is used to pass the whole reference, and tag is empty
|
||||
// - if in trusted content, ref is used to pass the reference name, and tag for the digest
|
||||
func (cli *Client) ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) {
|
||||
func (cli *Client) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(refStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/errdefs"
|
||||
)
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// It executes the privileged function if the operation is unauthorized
|
||||
// and it tries one more time.
|
||||
// It's up to the caller to handle the io.ReadCloser and close it properly.
|
||||
func (cli *Client) ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) {
|
||||
func (cli *Client) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+1
-2
@@ -5,12 +5,11 @@ import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
)
|
||||
|
||||
// ImageRemove removes an image from the docker host.
|
||||
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]image.DeleteResponse, error) {
|
||||
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options image.RemoveOptions) ([]image.DeleteResponse, error) {
|
||||
query := url.Values{}
|
||||
|
||||
if options.Force {
|
||||
|
||||
+6
-6
@@ -90,15 +90,15 @@ type ImageAPIClient interface {
|
||||
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
|
||||
BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error)
|
||||
BuildCancel(ctx context.Context, id string) error
|
||||
ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||
ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error)
|
||||
ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error)
|
||||
ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
|
||||
ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
|
||||
ImageInspectWithRaw(ctx context.Context, image string) (types.ImageInspect, []byte, error)
|
||||
ImageList(ctx context.Context, options types.ImageListOptions) ([]image.Summary, error)
|
||||
ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
|
||||
ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error)
|
||||
ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error)
|
||||
ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error)
|
||||
ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]image.DeleteResponse, error)
|
||||
ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error)
|
||||
ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error)
|
||||
ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error)
|
||||
ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error)
|
||||
ImageSave(ctx context.Context, images []string) (io.ReadCloser, error)
|
||||
ImageTag(ctx context.Context, image, ref string) error
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// attachContext is the context used for for attach calls.
|
||||
// attachContext is the context used for attach calls.
|
||||
type attachContext struct {
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
|
||||
+2
-2
@@ -514,14 +514,14 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu
|
||||
}
|
||||
|
||||
// UnmountVolumes unmounts all volumes
|
||||
func (container *Container) UnmountVolumes(volumeEventLog func(name string, action events.Action, attributes map[string]string)) error {
|
||||
func (container *Container) UnmountVolumes(ctx context.Context, volumeEventLog func(name string, action events.Action, attributes map[string]string)) error {
|
||||
var errs []string
|
||||
for _, volumeMount := range container.MountPoints {
|
||||
if volumeMount.Volume == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := volumeMount.Cleanup(); err != nil {
|
||||
if err := volumeMount.Cleanup(ctx); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
+9
-30
@@ -15,8 +15,6 @@ import (
|
||||
"github.com/docker/docker/api/types/events"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/volume"
|
||||
volumemounts "github.com/docker/docker/volume/mounts"
|
||||
"github.com/moby/sys/mount"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
@@ -129,34 +127,11 @@ func (container *Container) NetworkMounts() []Mount {
|
||||
}
|
||||
|
||||
// CopyImagePathContent copies files in destination to the volume.
|
||||
func (container *Container) CopyImagePathContent(v volume.Volume, destination string) error {
|
||||
rootfs, err := container.GetResourcePath(destination)
|
||||
if err != nil {
|
||||
func (container *Container) CopyImagePathContent(volumePath, destination string) error {
|
||||
if err := label.Relabel(volumePath, container.MountLabel, true); err != nil && !errors.Is(err, syscall.ENOTSUP) {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(rootfs); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
id := stringid.GenerateRandomID()
|
||||
path, err := v.Mount(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := v.Unmount(id); err != nil {
|
||||
log.G(context.TODO()).Warnf("error while unmounting volume %s: %v", v.Name(), err)
|
||||
}
|
||||
}()
|
||||
if err := label.Relabel(path, container.MountLabel, true); err != nil && !errors.Is(err, syscall.ENOTSUP) {
|
||||
return err
|
||||
}
|
||||
return copyExistingContents(rootfs, path)
|
||||
return copyExistingContents(destination, volumePath)
|
||||
}
|
||||
|
||||
// ShmResourcePath returns path to shm
|
||||
@@ -396,7 +371,7 @@ func (container *Container) DetachAndUnmount(volumeEventLog func(name string, ac
|
||||
Warn("Unable to unmount")
|
||||
}
|
||||
}
|
||||
return container.UnmountVolumes(volumeEventLog)
|
||||
return container.UnmountVolumes(ctx, volumeEventLog)
|
||||
}
|
||||
|
||||
// ignoreUnsupportedXAttrs ignores errors when extended attributes
|
||||
@@ -419,9 +394,13 @@ func copyExistingContents(source, destination string) error {
|
||||
return err
|
||||
}
|
||||
if len(dstList) != 0 {
|
||||
// destination is not empty, do not copy
|
||||
log.G(context.TODO()).WithFields(log.Fields{
|
||||
"source": source,
|
||||
"destination": destination,
|
||||
}).Debug("destination is not empty, do not copy")
|
||||
return nil
|
||||
}
|
||||
|
||||
return fs.CopyDir(destination, source, ignoreUnsupportedXAttrs())
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package container // import "github.com/docker/docker/container"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -128,7 +129,7 @@ func (container *Container) ConfigMounts() []Mount {
|
||||
// On Windows it only delegates to `UnmountVolumes` since there is nothing to
|
||||
// force unmount.
|
||||
func (container *Container) DetachAndUnmount(volumeEventLog func(name string, action events.Action, attributes map[string]string)) error {
|
||||
return container.UnmountVolumes(volumeEventLog)
|
||||
return container.UnmountVolumes(context.TODO(), volumeEventLog)
|
||||
}
|
||||
|
||||
// TmpfsMounts returns the list of tmpfs mounts
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ func (fl *follow) nextPos(current logPos) (next logPos, ok bool) {
|
||||
case st = <-fl.LogFile.read:
|
||||
}
|
||||
|
||||
// Have any any logs been written since we last checked?
|
||||
// Have any logs been written since we last checked?
|
||||
if st.pos == current { // Nope.
|
||||
// Add ourself to the notify list.
|
||||
st.wait = append(st.wait, fl.c)
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ type LogFile struct {
|
||||
// passing along ownership is expressed with function argument types.
|
||||
// Methods which take a pointer *logReadState argument borrow the state,
|
||||
// analogous to functions which require a lock to be held when calling.
|
||||
// The caller retains ownership. Calling a method which which takes a
|
||||
// The caller retains ownership. Calling a method which takes a
|
||||
// value logFileState argument gives ownership to the callee.
|
||||
read chan logReadState
|
||||
|
||||
|
||||
+1
-1
@@ -138,7 +138,7 @@ type messageRing struct {
|
||||
wait *sync.Cond
|
||||
|
||||
sizeBytes int64 // current buffer size
|
||||
maxBytes int64 // max buffer size size
|
||||
maxBytes int64 // max buffer size
|
||||
queue []*Message
|
||||
closed bool
|
||||
}
|
||||
|
||||
-7
@@ -28,13 +28,6 @@ func (id ID) Digest() digest.Digest {
|
||||
return digest.Digest(id)
|
||||
}
|
||||
|
||||
// IDFromDigest creates an ID from a digest
|
||||
//
|
||||
// Deprecated: cast to an ID using ID(digest).
|
||||
func IDFromDigest(digest digest.Digest) ID {
|
||||
return ID(digest)
|
||||
}
|
||||
|
||||
// V1Image stores the V1 image configuration.
|
||||
type V1Image struct {
|
||||
// ID is a unique 64 character identifier of the image
|
||||
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
const DockerOCIImageMediaType = "application/vnd.docker.container.image.v1+json"
|
||||
|
||||
// DockerOCIImage is a ocispec.Image extended with Docker specific Config.
|
||||
type DockerOCIImage struct {
|
||||
ocispec.Image
|
||||
|
||||
// Shadow ocispec.Image.Config
|
||||
Config DockerOCIImageConfig `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
// DockerOCIImageConfig is a ocispec.ImageConfig extended with Docker specific fields.
|
||||
type DockerOCIImageConfig struct {
|
||||
ocispec.ImageConfig
|
||||
|
||||
DockerOCIImageConfigExt
|
||||
}
|
||||
|
||||
// DockerOCIImageConfigExt contains Docker-specific fields in DockerImageConfig.
|
||||
type DockerOCIImageConfigExt struct {
|
||||
Healthcheck *HealthcheckConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy
|
||||
|
||||
OnBuild []string `json:",omitempty"` // ONBUILD metadata that were defined on the image Dockerfile
|
||||
Shell []string `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
|
||||
}
|
||||
|
||||
// HealthcheckConfig holds configuration settings for the HEALTHCHECK feature.
|
||||
type HealthcheckConfig struct {
|
||||
// Test is the test to perform to check that the container is healthy.
|
||||
// An empty slice means to inherit the default.
|
||||
// The options are:
|
||||
// {} : inherit healthcheck
|
||||
// {"NONE"} : disable healthcheck
|
||||
// {"CMD", args...} : exec arguments directly
|
||||
// {"CMD-SHELL", command} : run command with system's default shell
|
||||
Test []string `json:",omitempty"`
|
||||
|
||||
// Zero means to inherit. Durations are expressed as integer nanoseconds.
|
||||
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
|
||||
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
|
||||
StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.
|
||||
StartInterval time.Duration `json:",omitempty"` // The interval to attempt healthchecks at during the start period
|
||||
|
||||
// Retries is the number of consecutive failures needed to consider a container as unhealthy.
|
||||
// Zero means inherit.
|
||||
Retries int `json:",omitempty"`
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cleanups
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/internal/multierror"
|
||||
)
|
||||
|
||||
type Composite struct {
|
||||
cleanups []func(context.Context) error
|
||||
}
|
||||
|
||||
// Add adds a cleanup to be called.
|
||||
func (c *Composite) Add(f func(context.Context) error) {
|
||||
c.cleanups = append(c.cleanups, f)
|
||||
}
|
||||
|
||||
// Call calls all cleanups in reverse order and returns an error combining all
|
||||
// non-nil errors.
|
||||
func (c *Composite) Call(ctx context.Context) error {
|
||||
err := call(ctx, c.cleanups)
|
||||
c.cleanups = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Release removes all cleanups, turning Call into a no-op.
|
||||
// Caller still can call the cleanups by calling the returned function
|
||||
// which is equivalent to calling the Call before Release was called.
|
||||
func (c *Composite) Release() func(context.Context) error {
|
||||
cleanups := c.cleanups
|
||||
c.cleanups = nil
|
||||
return func(ctx context.Context) error {
|
||||
return call(ctx, cleanups)
|
||||
}
|
||||
}
|
||||
|
||||
func call(ctx context.Context, cleanups []func(context.Context) error) error {
|
||||
var errs []error
|
||||
for idx := len(cleanups) - 1; idx >= 0; idx-- {
|
||||
c := cleanups[idx]
|
||||
errs = append(errs, c(ctx))
|
||||
}
|
||||
return multierror.Join(errs...)
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
//go:build !go1.21
|
||||
|
||||
// Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// - Redistributions of source code must retain the above copyright
|
||||
//
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// - Redistributions in binary form must reproduce the above
|
||||
//
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// - Neither the name of Google Inc. nor the names of its
|
||||
//
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Source: https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/context/context.go
|
||||
// The only modifications to the original source were:
|
||||
// - replacing the usage of internal reflectlite with reflect
|
||||
// - replacing the usage of private value function with Value method call
|
||||
package compatcontext // import "github.com/docker/docker/internal/compatcontext"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WithoutCancel returns a copy of parent that is not canceled when parent is canceled.
|
||||
// The returned context returns no Deadline or Err, and its Done channel is nil.
|
||||
// Calling [Cause] on the returned context returns nil.
|
||||
func WithoutCancel(parent context.Context) context.Context {
|
||||
if parent == nil {
|
||||
panic("cannot create context from nil parent")
|
||||
}
|
||||
return withoutCancelCtx{parent}
|
||||
}
|
||||
|
||||
type withoutCancelCtx struct {
|
||||
c context.Context
|
||||
}
|
||||
|
||||
func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (withoutCancelCtx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (withoutCancelCtx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c withoutCancelCtx) Value(key any) any {
|
||||
return c.c.Value(key)
|
||||
}
|
||||
|
||||
func (c withoutCancelCtx) String() string {
|
||||
return contextName(c.c) + ".WithoutCancel"
|
||||
}
|
||||
|
||||
type stringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
func contextName(c context.Context) string {
|
||||
if s, ok := c.(stringer); ok {
|
||||
return s.String()
|
||||
}
|
||||
return reflect.TypeOf(c).String()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
//go:build go1.21
|
||||
|
||||
package compatcontext // import "github.com/docker/docker/internal/compatcontext"
|
||||
|
||||
import "context"
|
||||
|
||||
func WithoutCancel(ctx context.Context) context.Context {
|
||||
return context.WithoutCancel(ctx)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package mountopts
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// UnprivilegedMountFlags gets the set of mount flags that are set on the mount that contains the given
|
||||
// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that
|
||||
// bind-mounting "with options" will not fail with user namespaces, due to
|
||||
// kernel restrictions that require user namespace mounts to preserve
|
||||
// CL_UNPRIVILEGED locked flags.
|
||||
//
|
||||
// TODO: Move to github.com/moby/sys/mount, and update BuildKit copy of this code as well (https://github.com/moby/buildkit/blob/v0.13.0/util/rootless/mountopts/mountopts_linux.go#L11-L18)
|
||||
func UnprivilegedMountFlags(path string) ([]string, error) {
|
||||
var statfs unix.Statfs_t
|
||||
if err := unix.Statfs(path, &statfs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The set of keys come from https://github.com/torvalds/linux/blob/v4.13/fs/namespace.c#L1034-L1048.
|
||||
unprivilegedFlags := map[uint64]string{
|
||||
unix.MS_RDONLY: "ro",
|
||||
unix.MS_NODEV: "nodev",
|
||||
unix.MS_NOEXEC: "noexec",
|
||||
unix.MS_NOSUID: "nosuid",
|
||||
unix.MS_NOATIME: "noatime",
|
||||
unix.MS_RELATIME: "relatime",
|
||||
unix.MS_NODIRATIME: "nodiratime",
|
||||
}
|
||||
|
||||
var flags []string
|
||||
for mask, flag := range unprivilegedFlags {
|
||||
if uint64(statfs.Flags)&mask == mask {
|
||||
flags = append(flags, flag)
|
||||
}
|
||||
}
|
||||
|
||||
return flags, nil
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package safepath
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// evaluatePath evaluates symlinks in the concatenation of path and subpath. If
|
||||
// err is nil, resolvedBasePath will contain result of resolving all symlinks
|
||||
// in the given path, and resolvedSubpath will contain a relative path rooted
|
||||
// at the resolvedBasePath pointing to the concatenation after resolving all
|
||||
// symlinks.
|
||||
func evaluatePath(path, subpath string) (resolvedBasePath string, resolvedSubpath string, err error) {
|
||||
baseResolved, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "", "", &ErrNotAccessible{Path: path, Cause: err}
|
||||
}
|
||||
return "", "", errors.Wrapf(err, "error while resolving symlinks in base directory %q", path)
|
||||
}
|
||||
|
||||
combinedPath := filepath.Join(baseResolved, subpath)
|
||||
combinedResolved, err := filepath.EvalSymlinks(combinedPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "", "", &ErrNotAccessible{Path: combinedPath, Cause: err}
|
||||
}
|
||||
return "", "", errors.Wrapf(err, "error while resolving symlinks in combined path %q", combinedPath)
|
||||
}
|
||||
|
||||
subpart, err := filepath.Rel(baseResolved, combinedResolved)
|
||||
if err != nil {
|
||||
return "", "", &ErrEscapesBase{Base: baseResolved, Subpath: subpath}
|
||||
}
|
||||
|
||||
if !filepath.IsLocal(subpart) {
|
||||
return "", "", &ErrEscapesBase{Base: baseResolved, Subpath: subpath}
|
||||
}
|
||||
|
||||
return baseResolved, subpart, nil
|
||||
}
|
||||
|
||||
// isLocalTo reports whether path, using lexical analysis only, has all of these properties:
|
||||
// - is within the subtree rooted at basepath
|
||||
// - is not empty
|
||||
// - on Windows, is not a reserved name such as "NUL"
|
||||
//
|
||||
// If isLocalTo(path, basepath) returns true, then
|
||||
//
|
||||
// filepath.Rel(basepath, path)
|
||||
//
|
||||
// will always produce an unrooted path with no `..` elements.
|
||||
//
|
||||
// isLocalTo is a purely lexical operation. In particular, it does not account for the effect of any symbolic links that may exist in the filesystem.
|
||||
//
|
||||
// Both path and basepath are expected to be absolute paths.
|
||||
func isLocalTo(path, basepath string) bool {
|
||||
rel, err := filepath.Rel(basepath, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return filepath.IsLocal(rel)
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package safepath
|
||||
|
||||
// ErrNotAccessible is returned by Join when the resulting path doesn't exist,
|
||||
// is not accessible, or any of the path components was replaced with a symlink
|
||||
// during the path traversal.
|
||||
type ErrNotAccessible struct {
|
||||
Path string
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (*ErrNotAccessible) NotFound() {}
|
||||
|
||||
func (e *ErrNotAccessible) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
func (e *ErrNotAccessible) Error() string {
|
||||
msg := "cannot access path " + e.Path
|
||||
if e.Cause != nil {
|
||||
msg += ": " + e.Cause.Error()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// ErrEscapesBase is returned by Join when the resulting concatenation would
|
||||
// point outside of the specified base directory.
|
||||
type ErrEscapesBase struct {
|
||||
Base, Subpath string
|
||||
}
|
||||
|
||||
func (*ErrEscapesBase) InvalidParameter() {}
|
||||
|
||||
func (e *ErrEscapesBase) Error() string {
|
||||
msg := "path concatenation escapes the base directory"
|
||||
if e.Base != "" {
|
||||
msg += ", base: " + e.Base
|
||||
}
|
||||
if e.Subpath != "" {
|
||||
msg += ", subpath: " + e.Subpath
|
||||
}
|
||||
return msg
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package safepath
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/internal/unix_noeintr"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Join makes sure that the concatenation of path and subpath doesn't
|
||||
// resolve to a path outside of path and returns a path to a temporary file that is
|
||||
// a bind mount to the exact same file/directory that was validated.
|
||||
//
|
||||
// After use, it is the caller's responsibility to call Close on the returned
|
||||
// SafePath object, which will unmount the temporary file/directory
|
||||
// and remove it.
|
||||
func Join(_ context.Context, path, subpath string) (*SafePath, error) {
|
||||
base, subpart, err := evaluatePath(path, subpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
fd, err := safeOpenFd(base, subpart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer unix_noeintr.Close(fd)
|
||||
|
||||
tmpMount, err := tempMountPoint(fd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create temporary file for safe mount")
|
||||
}
|
||||
|
||||
pid := strconv.Itoa(unix.Gettid())
|
||||
// Using explicit pid path, because /proc/self/fd/<fd> fails with EACCES
|
||||
// when running under "Enhanced Container Isolation" in Docker Desktop
|
||||
// which uses sysbox runtime under the hood.
|
||||
// TODO(vvoland): Investigate.
|
||||
mountSource := "/proc/" + pid + "/fd/" + strconv.Itoa(fd)
|
||||
|
||||
if err := unix_noeintr.Mount(mountSource, tmpMount, "none", unix.MS_BIND, ""); err != nil {
|
||||
os.Remove(tmpMount)
|
||||
return nil, errors.Wrap(err, "failed to mount resolved path")
|
||||
}
|
||||
|
||||
return &SafePath{
|
||||
path: tmpMount,
|
||||
sourceBase: base,
|
||||
sourceSubpath: subpart,
|
||||
cleanup: cleanupSafePath(tmpMount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// safeOpenFd opens the file at filepath.Join(path, subpath) in O_PATH
|
||||
// mode and returns the file descriptor if subpath is within the subtree
|
||||
// rooted at path. It is an error if any of components of path or subpath
|
||||
// are symbolic links.
|
||||
//
|
||||
// It is a caller's responsibility to close the returned file descriptor, if no
|
||||
// error was returned.
|
||||
func safeOpenFd(path, subpath string) (int, error) {
|
||||
// Open base volume path (_data directory).
|
||||
prevFd, err := unix_noeintr.Open(path, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return -1, &ErrNotAccessible{Path: path, Cause: err}
|
||||
}
|
||||
defer unix_noeintr.Close(prevFd)
|
||||
|
||||
// Try to use the Openat2 syscall first (available on Linux 5.6+).
|
||||
fd, err := unix_noeintr.Openat2(prevFd, subpath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Mode: 0,
|
||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS | unix.RESOLVE_NO_SYMLINKS,
|
||||
})
|
||||
|
||||
switch {
|
||||
case errors.Is(err, unix.ENOSYS):
|
||||
// Openat2 is not available, fallback to Openat loop.
|
||||
return kubernetesSafeOpen(path, subpath)
|
||||
case errors.Is(err, unix.EXDEV):
|
||||
return -1, &ErrEscapesBase{Base: path, Subpath: subpath}
|
||||
case errors.Is(err, unix.ENOENT), errors.Is(err, unix.ELOOP):
|
||||
return -1, &ErrNotAccessible{Path: filepath.Join(path, subpath), Cause: err}
|
||||
case err != nil:
|
||||
return -1, &os.PathError{Op: "openat2", Path: subpath, Err: err}
|
||||
}
|
||||
|
||||
// Openat2 is available and succeeded.
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// tempMountPoint creates a temporary file/directory to act as mount
|
||||
// point for the file descriptor.
|
||||
func tempMountPoint(sourceFd int) (string, error) {
|
||||
var stat unix.Stat_t
|
||||
err := unix_noeintr.Fstat(sourceFd, &stat)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to Fstat mount source fd")
|
||||
}
|
||||
|
||||
isDir := (stat.Mode & unix.S_IFMT) == unix.S_IFDIR
|
||||
if isDir {
|
||||
return os.MkdirTemp("", "safe-mount")
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "safe-mount")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
p := f.Name()
|
||||
if err := f.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// cleanupSafePaths returns a function that unmounts the path and removes the
|
||||
// mountpoint.
|
||||
func cleanupSafePath(path string) func(context.Context) error {
|
||||
return func(ctx context.Context) error {
|
||||
log.G(ctx).WithField("path", path).Debug("removing safe temp mount")
|
||||
|
||||
if err := unix_noeintr.Unmount(path, unix.MNT_DETACH); err != nil {
|
||||
if errors.Is(err, unix.EINVAL) {
|
||||
log.G(ctx).WithField("path", path).Warn("safe temp mount no longer exists?")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "error unmounting safe mount %s", path)
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
log.G(ctx).WithField("path", path).Warn("safe temp mount no longer exists?")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "failed to delete temporary safe mount")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package safepath
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/internal/cleanups"
|
||||
"github.com/docker/docker/internal/compatcontext"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Join locks all individual components of the path which is the concatenation
|
||||
// of provided path and its subpath, checks that it doesn't escape the base path
|
||||
// and returns the concatenated path.
|
||||
//
|
||||
// The path is safe (the path target won't change) until the returned SafePath
|
||||
// is Closed.
|
||||
// Caller is responsible for calling the Close function which unlocks the path.
|
||||
func Join(ctx context.Context, path, subpath string) (*SafePath, error) {
|
||||
base, subpart, err := evaluatePath(path, subpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts := strings.Split(subpart, string(os.PathSeparator))
|
||||
|
||||
cleanups := cleanups.Composite{}
|
||||
defer func() {
|
||||
if cErr := cleanups.Call(compatcontext.WithoutCancel(ctx)); cErr != nil {
|
||||
log.G(ctx).WithError(cErr).Warn("failed to close handles after error")
|
||||
}
|
||||
}()
|
||||
|
||||
fullPath := base
|
||||
for _, part := range parts {
|
||||
fullPath = filepath.Join(fullPath, part)
|
||||
|
||||
handle, err := lockFile(fullPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
|
||||
return nil, &ErrNotAccessible{Path: fullPath, Cause: err}
|
||||
}
|
||||
return nil, errors.Wrapf(err, "failed to lock file %s", fullPath)
|
||||
}
|
||||
cleanups.Add(func(context.Context) error {
|
||||
if err := windows.CloseHandle(handle); err != nil {
|
||||
return &os.PathError{Op: "CloseHandle", Path: fullPath, Err: err}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
realPath, err := filepath.EvalSymlinks(fullPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to eval symlinks of %s", fullPath)
|
||||
}
|
||||
|
||||
if realPath != fullPath && !isLocalTo(realPath, base) {
|
||||
return nil, &ErrEscapesBase{Base: base, Subpath: subpart}
|
||||
}
|
||||
|
||||
var info windows.ByHandleFileInformation
|
||||
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
|
||||
return nil, errors.WithStack(&os.PathError{Op: "GetFileInformationByHandle", Path: fullPath, Err: err})
|
||||
}
|
||||
|
||||
if (info.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
|
||||
return nil, &ErrNotAccessible{Path: fullPath, Cause: err}
|
||||
}
|
||||
}
|
||||
|
||||
return &SafePath{
|
||||
path: fullPath,
|
||||
sourceBase: base,
|
||||
sourceSubpath: subpart,
|
||||
cleanup: cleanups.Release(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func lockFile(path string) (windows.Handle, error) {
|
||||
p, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return windows.InvalidHandle, &os.PathError{Op: "UTF16PtrFromString", Path: path, Err: err}
|
||||
}
|
||||
const flags = windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OPEN_REPARSE_POINT
|
||||
handle, err := windows.CreateFile(p, windows.GENERIC_READ, windows.FILE_SHARE_READ, nil, windows.OPEN_EXISTING, flags, 0)
|
||||
if err != nil {
|
||||
return handle, &os.PathError{Op: "CreateFile", Path: path, Err: err}
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package safepath
|
||||
|
||||
/*
|
||||
Copyright 2014 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/internal/unix_noeintr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// kubernetesSafeOpen open path formed by concatenation of the base directory
|
||||
// and its subpath and return its fd.
|
||||
// Symlinks are disallowed (pathname must already resolve symlinks) and the
|
||||
// path must be within the base directory.
|
||||
// This is minimally modified code from https://github.com/kubernetes/kubernetes/blob/55fb1805a1217b91b36fa8fe8f2bf3a28af2454d/pkg/volume/util/subpath/subpath_linux.go#L530
|
||||
func kubernetesSafeOpen(base, subpath string) (int, error) {
|
||||
// syscall.Openat flags used to traverse directories not following symlinks
|
||||
const nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
|
||||
// flags for getting file descriptor without following the symlink
|
||||
const openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
|
||||
|
||||
pathname := filepath.Join(base, subpath)
|
||||
segments := strings.Split(subpath, string(filepath.Separator))
|
||||
|
||||
// Assumption: base is the only directory that we have under control.
|
||||
// Base dir is not allowed to be a symlink.
|
||||
parentFD, err := unix_noeintr.Open(base, nofollowFlags|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return -1, &ErrNotAccessible{Path: base, Cause: err}
|
||||
}
|
||||
defer func() {
|
||||
if parentFD != -1 {
|
||||
if err = unix_noeintr.Close(parentFD); err != nil {
|
||||
log.G(context.TODO()).Errorf("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
childFD := -1
|
||||
defer func() {
|
||||
if childFD != -1 {
|
||||
if err = unix_noeintr.Close(childFD); err != nil {
|
||||
log.G(context.TODO()).Errorf("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
currentPath := base
|
||||
|
||||
// Follow the segments one by one using openat() to make
|
||||
// sure the user cannot change already existing directories into symlinks.
|
||||
for _, seg := range segments {
|
||||
var deviceStat unix.Stat_t
|
||||
|
||||
currentPath = filepath.Join(currentPath, seg)
|
||||
if !isLocalTo(currentPath, base) {
|
||||
return -1, &ErrEscapesBase{Base: currentPath, Subpath: seg}
|
||||
}
|
||||
|
||||
// Trigger auto mount if it's an auto-mounted directory, ignore error if not a directory.
|
||||
// Notice the trailing slash is mandatory, see "automount" in openat(2) and open_by_handle_at(2).
|
||||
unix_noeintr.Fstatat(parentFD, seg+"/", &deviceStat, unix.AT_SYMLINK_NOFOLLOW)
|
||||
|
||||
log.G(context.TODO()).Debugf("Opening path %s", currentPath)
|
||||
childFD, err = unix_noeintr.Openat(parentFD, seg, openFDFlags|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return -1, &ErrNotAccessible{Path: currentPath, Cause: err}
|
||||
}
|
||||
|
||||
err := unix_noeintr.Fstat(childFD, &deviceStat)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error running fstat on %s with %v", currentPath, err)
|
||||
}
|
||||
fileFmt := deviceStat.Mode & unix.S_IFMT
|
||||
if fileFmt == unix.S_IFLNK {
|
||||
return -1, fmt.Errorf("unexpected symlink found %s", currentPath)
|
||||
}
|
||||
|
||||
// Close parentFD
|
||||
if err = unix_noeintr.Close(parentFD); err != nil {
|
||||
return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
|
||||
}
|
||||
// Set child to new parent
|
||||
parentFD = childFD
|
||||
childFD = -1
|
||||
}
|
||||
|
||||
// We made it to the end, return this fd, don't close it
|
||||
finalFD := parentFD
|
||||
parentFD = -1
|
||||
|
||||
return finalFD, nil
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package safepath
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/log"
|
||||
)
|
||||
|
||||
type SafePath struct {
|
||||
path string
|
||||
cleanup func(ctx context.Context) error
|
||||
mutex sync.Mutex
|
||||
|
||||
// Immutable fields
|
||||
sourceBase, sourceSubpath string
|
||||
}
|
||||
|
||||
// Close releases the resources used by the path.
|
||||
func (s *SafePath) Close(ctx context.Context) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.path == "" {
|
||||
base, sub := s.SourcePath()
|
||||
log.G(ctx).WithFields(log.Fields{
|
||||
"path": s.Path(),
|
||||
"sourceBase": base,
|
||||
"sourceSubpath": sub,
|
||||
}).Warn("an attempt to close an already closed SafePath")
|
||||
return nil
|
||||
}
|
||||
|
||||
s.path = ""
|
||||
if s.cleanup != nil {
|
||||
return s.cleanup(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid return true when path can still be used and wasn't cleaned up by Close.
|
||||
func (s *SafePath) IsValid() bool {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
return s.path != ""
|
||||
}
|
||||
|
||||
// Path returns a safe, temporary path that can be used to access the original path.
|
||||
func (s *SafePath) Path() string {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
if s.path == "" {
|
||||
panic(fmt.Sprintf("use-after-close attempted for safepath with source [%s, %s]", s.sourceBase, s.sourceSubpath))
|
||||
}
|
||||
return s.path
|
||||
}
|
||||
|
||||
// SourcePath returns the source path the safepath points to.
|
||||
func (s *SafePath) SourcePath() (string, string) {
|
||||
// No mutex lock because these are immutable.
|
||||
return s.sourceBase, s.sourceSubpath
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
|
||||
package sliceutil
|
||||
|
||||
func Dedup[T comparable](slice []T) []T {
|
||||
keys := make(map[T]struct{})
|
||||
out := make([]T, 0, len(slice))
|
||||
for _, s := range slice {
|
||||
if _, ok := keys[s]; !ok {
|
||||
out = append(out, s)
|
||||
keys[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func Map[S ~[]In, In, Out any](s S, fn func(In) Out) []Out {
|
||||
res := make([]Out, len(s))
|
||||
for i, v := range s {
|
||||
res[i] = fn(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func Mapper[In, Out any](fn func(In) Out) func([]In) []Out {
|
||||
return func(s []In) []Out {
|
||||
res := make([]Out, len(s))
|
||||
for i, v := range s {
|
||||
res[i] = fn(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
//go:build !windows
|
||||
|
||||
// Wrappers for unix syscalls that retry on EINTR
|
||||
// TODO: Consider moving (for example to moby/sys) and making the wrappers
|
||||
// auto-generated.
|
||||
package unix_noeintr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func Retry(f func() error) {
|
||||
for {
|
||||
err := f()
|
||||
if !errors.Is(err, unix.EINTR) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||
Retry(func() error {
|
||||
err = unix.Mount(source, target, fstype, flags, data)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func Unmount(target string, flags int) (err error) {
|
||||
Retry(func() error {
|
||||
err = unix.Unmount(target, flags)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func Open(path string, mode int, perm uint32) (fd int, err error) {
|
||||
Retry(func() error {
|
||||
fd, err = unix.Open(path, mode, perm)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func Close(fd int) (err error) {
|
||||
Retry(func() error {
|
||||
err = unix.Close(fd)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func Openat(dirfd int, path string, mode int, perms uint32) (fd int, err error) {
|
||||
Retry(func() error {
|
||||
fd, err = unix.Openat(dirfd, path, mode, perms)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func Openat2(dirfd int, path string, how *unix.OpenHow) (fd int, err error) {
|
||||
Retry(func() error {
|
||||
fd, err = unix.Openat2(dirfd, path, how)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func Fstat(fd int, stat *unix.Stat_t) (err error) {
|
||||
Retry(func() error {
|
||||
err = unix.Fstat(fd, stat)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func Fstatat(fd int, path string, stat *unix.Stat_t, flags int) (err error) {
|
||||
Retry(func() error {
|
||||
err = unix.Fstatat(fd, path, stat, flags)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
+11
@@ -14,3 +14,14 @@ func RemoveNamespace(s *specs.Spec, nsType specs.LinuxNamespaceType) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NamespacePath returns the configured Path of the first namespace in
|
||||
// s.Linux.Namespaces of type nsType.
|
||||
func NamespacePath(s *specs.Spec, nsType specs.LinuxNamespaceType) (path string, ok bool) {
|
||||
for _, n := range s.Linux.Namespaces {
|
||||
if n.Type == nsType {
|
||||
return n.Path, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
+1
-13
@@ -1,10 +1,6 @@
|
||||
package containerfs // import "github.com/docker/docker/pkg/containerfs"
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/moby/sys/symlink"
|
||||
)
|
||||
import "path/filepath"
|
||||
|
||||
// CleanScopedPath prepares the given path to be combined with a mount path or
|
||||
// a drive-letter. On Windows, it removes any existing driveletter (e.g. "C:").
|
||||
@@ -17,11 +13,3 @@ func CleanScopedPath(path string) string {
|
||||
}
|
||||
return filepath.Join(string(filepath.Separator), path)
|
||||
}
|
||||
|
||||
// ResolveScopedPath evaluates the given path scoped to the root.
|
||||
// For example, if root=/a, and path=/b/c, then this function would return /a/b/c.
|
||||
//
|
||||
// Deprecated: use [symlink.FollowSymlinkInScope].
|
||||
func ResolveScopedPath(root, path string) (string, error) {
|
||||
return symlink.FollowSymlinkInScope(filepath.Join(root, path), root)
|
||||
}
|
||||
|
||||
-16
@@ -6,14 +6,6 @@ import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Key returns the env var name for the user's home dir based on
|
||||
// the platform being run on.
|
||||
//
|
||||
// Deprecated: this function is no longer used, and will be removed in the next release.
|
||||
func Key() string {
|
||||
return envKeyName
|
||||
}
|
||||
|
||||
// Get returns the home directory of the current user with the help of
|
||||
// environment variables depending on the target operating system.
|
||||
// Returned path should be used with "path/filepath" to form new paths.
|
||||
@@ -34,11 +26,3 @@ func Get() string {
|
||||
}
|
||||
return home
|
||||
}
|
||||
|
||||
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||
// in the native shell of the platform running on.
|
||||
//
|
||||
// Deprecated: this function is no longer used, and will be removed in the next release.
|
||||
func GetShortcutString() string {
|
||||
return homeShortCut
|
||||
}
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
||||
|
||||
const (
|
||||
envKeyName = "HOME"
|
||||
homeShortCut = "~"
|
||||
)
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
||||
|
||||
const (
|
||||
envKeyName = "USERPROFILE"
|
||||
homeShortCut = "%USERPROFILE%" // be careful while using in format functions
|
||||
)
|
||||
+10
-2
@@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/pkg/userns"
|
||||
"github.com/containerd/log"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -56,10 +58,16 @@ func (l *LocalRegistry) Scan() ([]string, error) {
|
||||
|
||||
for _, p := range l.specsPaths {
|
||||
dirEntries, err = os.ReadDir(p)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if os.IsPermission(err) && userns.RunningInUserNS() {
|
||||
log.L.Debug(err.Error())
|
||||
continue
|
||||
}
|
||||
return nil, errors.Wrap(err, "error reading dir entries")
|
||||
}
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
infos, err := os.ReadDir(filepath.Join(p, entry.Name()))
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrNotSupportedOperatingSystem means the operating system is not supported.
|
||||
//
|
||||
// Deprecated: use [github.com/docker/docker/image.CheckOS] and check the error returned.
|
||||
var ErrNotSupportedOperatingSystem = errors.New("operating system is not supported")
|
||||
|
||||
// IsOSSupported determines if an operating system is supported by the host.
|
||||
//
|
||||
// Deprecated: use [github.com/docker/docker/image.CheckOS] and check the error returned.
|
||||
func IsOSSupported(os string) bool {
|
||||
return strings.EqualFold(runtime.GOOS, os)
|
||||
}
|
||||
+36
@@ -1,3 +1,6 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
|
||||
package v2 // import "github.com/docker/docker/plugin/v2"
|
||||
|
||||
import (
|
||||
@@ -6,7 +9,10 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/pkg/userns"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/internal/rootless/mountopts"
|
||||
"github.com/docker/docker/internal/sliceutil"
|
||||
"github.com/docker/docker/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
@@ -136,5 +142,35 @@ func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) {
|
||||
p.modifyRuntimeSpec(&s)
|
||||
}
|
||||
|
||||
// Rootless mode requires modifying the mount flags
|
||||
// https://github.com/moby/moby/issues/47248#issuecomment-1927776700
|
||||
// https://github.com/moby/moby/pull/47558
|
||||
if userns.RunningInUserNS() {
|
||||
for i := range s.Mounts {
|
||||
m := &s.Mounts[i]
|
||||
for _, o := range m.Options {
|
||||
switch o {
|
||||
case "bind", "rbind":
|
||||
if _, err := os.Lstat(m.Source); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// UnprivilegedMountFlags gets the set of mount flags that are set on the mount that contains the given
|
||||
// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that
|
||||
// bind-mounting "with options" will not fail with user namespaces, due to
|
||||
// kernel restrictions that require user namespace mounts to preserve
|
||||
// CL_UNPRIVILEGED locked flags.
|
||||
unpriv, err := mountopts.UnprivilegedMountFlags(m.Source)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get unprivileged mount flags for %+v", m)
|
||||
}
|
||||
m.Options = sliceutil.Dedup(append(m.Options, unpriv...))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ func (rm *RestartManager) ShouldRestart(exitCode uint32, hasBeenManuallyStopped
|
||||
if rm.active {
|
||||
return false, nil, fmt.Errorf("invalid call on an active restart manager")
|
||||
}
|
||||
// if the container ran for more than 10s, regardless of status and policy reset the
|
||||
// if the container ran for more than 10s, regardless of status and policy reset
|
||||
// the timeout back to the default.
|
||||
if executionDuration.Seconds() >= 10 {
|
||||
rm.timeout = 0
|
||||
|
||||
-5
@@ -27,11 +27,6 @@ func (r ContainerDecoder) DecodeConfig(src io.Reader) (*container.Config, *conta
|
||||
return decodeContainerConfig(src, si)
|
||||
}
|
||||
|
||||
// DecodeHostConfig makes ContainerDecoder to implement httputils.ContainerDecoder
|
||||
func (r ContainerDecoder) DecodeHostConfig(src io.Reader) (*container.HostConfig, error) {
|
||||
return decodeHostConfig(src)
|
||||
}
|
||||
|
||||
// decodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
|
||||
// struct and returns both a Config and a HostConfig struct, and performs some
|
||||
// validation. Certain parameters need daemon-side validation that cannot be done
|
||||
|
||||
-11
@@ -1,23 +1,12 @@
|
||||
package runconfig // import "github.com/docker/docker/runconfig"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
// DecodeHostConfig creates a HostConfig based on the specified Reader.
|
||||
// It assumes the content of the reader will be JSON, and decodes it.
|
||||
func decodeHostConfig(src io.Reader) (*container.HostConfig, error) {
|
||||
var w ContainerConfigWrapper
|
||||
if err := loadJSON(src, &w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.getHostConfig(), nil
|
||||
}
|
||||
|
||||
// SetDefaultNetModeIfBlank changes the NetworkMode in a HostConfig structure
|
||||
// to default if it is not populated. This ensures backwards compatibility after
|
||||
// the validation of the network mode was moved from the docker CLI to the
|
||||
|
||||
+11
-1
@@ -96,8 +96,18 @@ func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSour
|
||||
if mnt.BindOptions != nil {
|
||||
return &errMountConfig{mnt, errExtraField("BindOptions")}
|
||||
}
|
||||
anonymousVolume := len(mnt.Source) == 0
|
||||
|
||||
if len(mnt.Source) == 0 && mnt.ReadOnly {
|
||||
if mnt.VolumeOptions != nil && mnt.VolumeOptions.Subpath != "" {
|
||||
if anonymousVolume {
|
||||
return &errMountConfig{mnt, errAnonymousVolumeWithSubpath}
|
||||
}
|
||||
|
||||
if !filepath.IsLocal(mnt.VolumeOptions.Subpath) {
|
||||
return &errMountConfig{mnt, errInvalidSubpath}
|
||||
}
|
||||
}
|
||||
if mnt.ReadOnly && anonymousVolume {
|
||||
return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
||||
}
|
||||
case mount.TypeTmpfs:
|
||||
|
||||
+88
-20
@@ -5,10 +5,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/log"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/internal/safepath"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/volume"
|
||||
@@ -74,19 +76,50 @@ type MountPoint struct {
|
||||
// Specifically needed for containers which are running and calls to `docker cp`
|
||||
// because both these actions require mounting the volumes.
|
||||
active int
|
||||
|
||||
// SafePaths created by Setup that should be cleaned up before unmounting
|
||||
// the volume.
|
||||
safePaths []*safepath.SafePath
|
||||
}
|
||||
|
||||
// Cleanup frees resources used by the mountpoint
|
||||
func (m *MountPoint) Cleanup() error {
|
||||
// Cleanup frees resources used by the mountpoint and cleans up all the paths
|
||||
// returned by Setup that hasn't been cleaned up by the caller.
|
||||
func (m *MountPoint) Cleanup(ctx context.Context) error {
|
||||
if m.Volume == nil || m.ID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger := log.G(ctx).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
|
||||
}
|
||||
|
||||
for _, p := range m.safePaths {
|
||||
if !p.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
err := p.Close(ctx)
|
||||
base, sub := p.SourcePath()
|
||||
log.G(ctx).WithFields(log.Fields{
|
||||
"error": err,
|
||||
"path": p.Path(),
|
||||
"sourceBase": base,
|
||||
"sourceSubpath": sub,
|
||||
}).Warn("cleaning up SafePath that hasn't been cleaned up by the caller")
|
||||
}
|
||||
|
||||
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 = ""
|
||||
}
|
||||
@@ -97,30 +130,42 @@ func (m *MountPoint) Cleanup() error {
|
||||
// configured, or creating the source directory if supplied.
|
||||
// The, optional, checkFun parameter allows doing additional checking
|
||||
// before creating the source directory on the host.
|
||||
func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun func(m *MountPoint) error) (path string, err error) {
|
||||
//
|
||||
// The returned path can be a temporary path, caller is responsible to
|
||||
// call the returned cleanup function as soon as the path is not needed.
|
||||
// Cleanup doesn't unmount the underlying volumes (if any), it only
|
||||
// frees up the resources that were needed to guarantee that the path
|
||||
// still points to the same target (to avoid TOCTOU attack).
|
||||
//
|
||||
// Cleanup function doesn't need to be called when error is returned.
|
||||
func (m *MountPoint) Setup(ctx context.Context, mountLabel string, rootIDs idtools.Identity, checkFun func(m *MountPoint) error) (path string, cleanup func(context.Context) error, retErr error) {
|
||||
if m.SkipMountpointCreation {
|
||||
return m.Source, nil
|
||||
return m.Source, noCleanup, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil || !label.RelabelNeeded(m.Mode) {
|
||||
if retErr != nil || !label.RelabelNeeded(m.Mode) {
|
||||
return
|
||||
}
|
||||
|
||||
var sourcePath string
|
||||
sourcePath, err = filepath.EvalSymlinks(m.Source)
|
||||
sourcePath, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
path = ""
|
||||
err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
|
||||
retErr = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
|
||||
if cleanupErr := cleanup(ctx); cleanupErr != nil {
|
||||
log.G(ctx).WithError(cleanupErr).Warn("failed to cleanup after error")
|
||||
}
|
||||
cleanup = noCleanup
|
||||
return
|
||||
}
|
||||
err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
|
||||
if errors.Is(err, syscall.ENOTSUP) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, syscall.ENOTSUP) {
|
||||
path = ""
|
||||
err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
|
||||
retErr = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
|
||||
if cleanupErr := cleanup(ctx); cleanupErr != nil {
|
||||
log.G(ctx).WithError(cleanupErr).Warn("failed to cleanup after error")
|
||||
}
|
||||
cleanup = noCleanup
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -129,18 +174,36 @@ func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun
|
||||
if id == "" {
|
||||
id = stringid.GenerateRandomID()
|
||||
}
|
||||
path, err := m.Volume.Mount(id)
|
||||
volumePath, err := m.Volume.Mount(id)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
|
||||
return "", noCleanup, errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
|
||||
}
|
||||
|
||||
m.ID = id
|
||||
clean := noCleanup
|
||||
if m.Spec.VolumeOptions != nil && m.Spec.VolumeOptions.Subpath != "" {
|
||||
subpath := m.Spec.VolumeOptions.Subpath
|
||||
|
||||
safePath, err := safepath.Join(ctx, volumePath, subpath)
|
||||
if err != nil {
|
||||
if err := m.Volume.Unmount(id); err != nil {
|
||||
log.G(ctx).WithError(err).Error("failed to unmount after safepath.Join failed")
|
||||
}
|
||||
return "", noCleanup, err
|
||||
}
|
||||
m.safePaths = append(m.safePaths, safePath)
|
||||
log.G(ctx).Debugf("mounting (%s|%s) via %s", volumePath, subpath, safePath.Path())
|
||||
|
||||
clean = safePath.Close
|
||||
volumePath = safePath.Path()
|
||||
}
|
||||
|
||||
m.active++
|
||||
return path, nil
|
||||
return volumePath, clean, nil
|
||||
}
|
||||
|
||||
if len(m.Source) == 0 {
|
||||
return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
||||
return "", noCleanup, fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
||||
}
|
||||
|
||||
if m.Type == mounttypes.TypeBind {
|
||||
@@ -149,7 +212,7 @@ func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun
|
||||
// the process of shutting down.
|
||||
if checkFun != nil {
|
||||
if err := checkFun(m); err != nil {
|
||||
return "", err
|
||||
return "", noCleanup, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,12 +221,12 @@ func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun
|
||||
if err := idtools.MkdirAllAndChownNew(m.Source, 0o755, rootIDs); err != nil {
|
||||
if perr, ok := err.(*os.PathError); ok {
|
||||
if perr.Err != syscall.ENOTDIR {
|
||||
return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
|
||||
return "", noCleanup, errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.Source, nil
|
||||
return m.Source, noCleanup, nil
|
||||
}
|
||||
|
||||
func (m *MountPoint) LiveRestore(ctx context.Context) error {
|
||||
@@ -207,3 +270,8 @@ func errInvalidMode(mode string) error {
|
||||
func errInvalidSpec(spec string) error {
|
||||
return errors.Errorf("invalid volume specification: '%s'", spec)
|
||||
}
|
||||
|
||||
// noCleanup is a no-op cleanup function.
|
||||
func noCleanup(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+8
@@ -11,6 +11,14 @@ import (
|
||||
// It's used by both LCOW and Linux parsers.
|
||||
var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
|
||||
|
||||
// errAnonymousVolumeWithSubpath is returned when Subpath is specified for
|
||||
// anonymous volume.
|
||||
var errAnonymousVolumeWithSubpath = errors.New("must not set Subpath when using anonymous volumes")
|
||||
|
||||
// errInvalidSubpath is returned when the provided Subpath is not lexically an
|
||||
// relative path within volume.
|
||||
var errInvalidSubpath = errors.New("subpath must be a relative path within the volume")
|
||||
|
||||
// read-write modes
|
||||
var rwModes = map[string]bool{
|
||||
"rw": true,
|
||||
|
||||
+14
-1
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -258,7 +259,19 @@ func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, additionalValid
|
||||
return &errMountConfig{mnt, errExtraField("BindOptions")}
|
||||
}
|
||||
|
||||
if len(mnt.Source) == 0 && mnt.ReadOnly {
|
||||
anonymousVolume := len(mnt.Source) == 0
|
||||
if mnt.VolumeOptions != nil && mnt.VolumeOptions.Subpath != "" {
|
||||
if anonymousVolume {
|
||||
return errAnonymousVolumeWithSubpath
|
||||
}
|
||||
|
||||
// Check if path is relative but without any back traversals
|
||||
if !filepath.IsLocal(mnt.VolumeOptions.Subpath) {
|
||||
return &errMountConfig{mnt, errInvalidSubpath}
|
||||
}
|
||||
}
|
||||
|
||||
if anonymousVolume && mnt.ReadOnly {
|
||||
return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user