chore: update gcr and docker cred helpers (#2910)

This commit is contained in:
Aaron Prindle
2023-12-19 12:36:13 -05:00
committed by GitHub
parent 6489eefaf0
commit dd752d9bbc
1231 changed files with 133904 additions and 18208 deletions
+3 -1
View File
@@ -37,7 +37,6 @@ type ConfigFile struct {
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"`
StackOrchestrator string `json:"stackOrchestrator,omitempty"` // Deprecated: swarm is now the default orchestrator, and this option is ignored.
CurrentContext string `json:"currentContext,omitempty"`
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
Plugins map[string]map[string]string `json:"plugins,omitempty"`
@@ -95,6 +94,9 @@ func (configFile *ConfigFile) ContainsAuth() bool {
// GetAuthConfigs returns the mapping of repo to auth configuration
func (configFile *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig {
if configFile.AuthConfigs == nil {
configFile.AuthConfigs = make(map[string]types.AuthConfig)
}
return configFile.AuthConfigs
}
+2 -1
View File
@@ -52,7 +52,8 @@ func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
// Store saves the given credentials in the file store.
func (c *fileStore) Store(authConfig types.AuthConfig) error {
c.file.GetAuthConfigs()[authConfig.ServerAddress] = authConfig
authConfigs := c.file.GetAuthConfigs()
authConfigs[authConfig.ServerAddress] = authConfig
return c.file.Save()
}
+18
View File
@@ -29,6 +29,7 @@ Adam Pointer <adam.pointer@skybettingandgaming.com>
Adam Singer <financeCoding@gmail.com>
Adam Walz <adam@adamwalz.net>
Adam Williams <awilliams@mirantis.com>
AdamKorcz <adam@adalogics.com>
Addam Hardy <addam.hardy@gmail.com>
Aditi Rajagopal <arajagopal@us.ibm.com>
Aditya <aditya@netroy.in>
@@ -81,6 +82,7 @@ Alex Goodman <wagoodman@gmail.com>
Alex Nordlund <alexander.nordlund@nasdaq.com>
Alex Olshansky <i@creagenics.com>
Alex Samorukov <samm@os2.kiev.ua>
Alex Stockinger <alex@atomicjar.com>
Alex Warhawk <ax.warhawk@gmail.com>
Alexander Artemenko <svetlyak.40wt@gmail.com>
Alexander Boyd <alex@opengroove.org>
@@ -198,6 +200,7 @@ Anusha Ragunathan <anusha.ragunathan@docker.com>
Anyu Wang <wanganyu@outlook.com>
apocas <petermdias@gmail.com>
Arash Deshmeh <adeshmeh@ca.ibm.com>
arcosx <arcosx@outlook.com>
ArikaChen <eaglesora@gmail.com>
Arko Dasgupta <arko@tetrate.io>
Arnaud Lefebvre <a.lefebvre@outlook.fr>
@@ -241,6 +244,7 @@ Benjamin Atkin <ben@benatkin.com>
Benjamin Baker <Benjamin.baker@utexas.edu>
Benjamin Boudreau <boudreau.benjamin@gmail.com>
Benjamin Böhmke <benjamin@boehmke.net>
Benjamin Wang <wachao@vmware.com>
Benjamin Yolken <yolken@stripe.com>
Benny Ng <benny.tpng@gmail.com>
Benoit Chesneau <bchesneau@gmail.com>
@@ -634,6 +638,7 @@ Eng Zer Jun <engzerjun@gmail.com>
Enguerran <engcolson@gmail.com>
Eohyung Lee <liquidnuker@gmail.com>
epeterso <epeterson@breakpoint-labs.com>
er0k <er0k@er0k.net>
Eric Barch <barch@tomesoftware.com>
Eric Curtin <ericcurtin17@gmail.com>
Eric G. Noriega <enoriega@vizuri.com>
@@ -754,6 +759,7 @@ Félix Baylac-Jacqué <baylac.felix@gmail.com>
Félix Cantournet <felix.cantournet@cloudwatt.com>
Gabe Rosenhouse <gabe@missionst.com>
Gabor Nagy <mail@aigeruth.hu>
Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
Gabriel Goller <gabrielgoller123@gmail.com>
Gabriel L. Somlo <gsomlo@gmail.com>
Gabriel Linder <linder.gabriel@gmail.com>
@@ -855,6 +861,7 @@ Hongbin Lu <hongbin034@gmail.com>
Hongxu Jia <hongxu.jia@windriver.com>
Honza Pokorny <me@honza.ca>
Hsing-Hui Hsu <hsinghui@amazon.com>
Hsing-Yu (David) Chen <davidhsingyuchen@gmail.com>
hsinko <21551195@zju.edu.cn>
Hu Keping <hukeping@huawei.com>
Hu Tao <hutao@cn.fujitsu.com>
@@ -887,6 +894,7 @@ Igor Dolzhikov <bluesriverz@gmail.com>
Igor Karpovich <i.karpovich@currencysolutions.com>
Iliana Weller <iweller@amazon.com>
Ilkka Laukkanen <ilkka@ilkka.io>
Illia Antypenko <ilya@antipenko.pp.ua>
Illo Abdulrahim <abdulrahim.illo@nokia.com>
Ilya Dmitrichenko <errordeveloper@gmail.com>
Ilya Gusev <mail@igusev.ru>
@@ -938,6 +946,7 @@ Jamie Hannaford <jamie@limetree.org>
Jamshid Afshar <jafshar@yahoo.com>
Jan Breig <git@pygos.space>
Jan Chren <dev.rindeal@gmail.com>
Jan Garcia <github-public@n-garcia.com>
Jan Götte <jaseg@jaseg.net>
Jan Keromnes <janx@linux.com>
Jan Koprowski <jan.koprowski@gmail.com>
@@ -1206,6 +1215,7 @@ Kimbro Staken <kstaken@kstaken.com>
Kir Kolyshkin <kolyshkin@gmail.com>
Kiran Gangadharan <kiran.daredevil@gmail.com>
Kirill SIbirev <l0kix2@gmail.com>
Kirk Easterson <kirk.easterson@gmail.com>
knappe <tyler.knappe@gmail.com>
Kohei Tsuruta <coheyxyz@gmail.com>
Koichi Shiraishi <k@zchee.io>
@@ -1240,10 +1250,12 @@ Lars Kellogg-Stedman <lars@redhat.com>
Lars R. Damerow <lars@pixar.com>
Lars-Magnus Skog <ralphtheninja@riseup.net>
Laszlo Meszaros <lacienator@gmail.com>
Laura Brehm <laurabrehm@hey.com>
Laura Frank <ljfrank@gmail.com>
Laurent Bernaille <laurent.bernaille@datadoghq.com>
Laurent Erignoux <lerignoux@gmail.com>
Laurie Voss <github@seldo.com>
Leandro Motta Barros <lmb@stackedboxes.org>
Leandro Siqueira <leandro.siqueira@gmail.com>
Lee Calcote <leecalcote@gmail.com>
Lee Chao <932819864@qq.com>
@@ -1563,6 +1575,7 @@ Nick Neisen <nwneisen@gmail.com>
Nick Parker <nikaios@gmail.com>
Nick Payne <nick@kurai.co.uk>
Nick Russo <nicholasjamesrusso@gmail.com>
Nick Santos <nick.santos@docker.com>
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
Nick Stinemates <nick@stinemates.org>
Nick Wood <nwood@microsoft.com>
@@ -1584,6 +1597,7 @@ NikolaMandic <mn080202@gmail.com>
Nikolas Garofil <nikolas.garofil@uantwerpen.be>
Nikolay Edigaryev <edigaryev@gmail.com>
Nikolay Milovanov <nmil@itransformers.net>
ningmingxiao <ning.mingxiao@zte.com.cn>
Nirmal Mehta <nirmalkmehta@gmail.com>
Nishant Totla <nishanttotla@gmail.com>
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
@@ -1615,6 +1629,7 @@ Omri Shiv <Omri.Shiv@teradata.com>
Onur Filiz <onur.filiz@microsoft.com>
Oriol Francès <oriolfa@gmail.com>
Oscar Bonilla <6f6231@gmail.com>
oscar.chen <2972789494@qq.com>
Oskar Niburski <oskarniburski@gmail.com>
Otto Kekäläinen <otto@seravo.fi>
Ouyang Liduo <oyld0210@163.com>
@@ -1822,6 +1837,7 @@ Rory Hunter <roryhunter2@gmail.com>
Rory McCune <raesene@gmail.com>
Ross Boucher <rboucher@gmail.com>
Rovanion Luckey <rovanion.luckey@gmail.com>
Roy Reznik <roy@wiz.io>
Royce Remer <royceremer@gmail.com>
Rozhnov Alexandr <nox73@ya.ru>
Rudolph Gottesheim <r.gottesheim@loot.at>
@@ -2271,6 +2287,7 @@ Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn>
xichengliudui <1693291525@qq.com>
xiekeyang <xiekeyang@huawei.com>
Ximo Guanter Gonzálbez <joaquin.guantergonzalbez@telefonica.com>
xin.li <xin.li@daocloud.io>
Xinbo Weng <xihuanbo_0521@zju.edu.cn>
Xinfeng Liu <xinfeng.liu@gmail.com>
Xinzi Zhou <imdreamrunner@gmail.com>
@@ -2282,6 +2299,7 @@ Yahya <ya7yaz@gmail.com>
yalpul <yalpul@gmail.com>
YAMADA Tsuyoshi <tyamada@minimum2scp.org>
Yamasaki Masahide <masahide.y@gmail.com>
Yamazaki Masashi <masi19bw@gmail.com>
Yan Feng <yanfeng2@huawei.com>
Yan Zhu <yanzhu@alauda.io>
Yang Bai <hamo.by@gmail.com>
+1 -1
View File
@@ -3,7 +3,7 @@ package api // import "github.com/docker/docker/api"
// Common constants for daemon and client.
const (
// DefaultVersion of Current REST API
DefaultVersion = "1.42"
DefaultVersion = "1.43"
// NoBaseImageSpecifier is the symbol used by the FROM
// command to specify that no base image is to be used.
+71 -78
View File
@@ -19,10 +19,10 @@ produces:
consumes:
- "application/json"
- "text/plain"
basePath: "/v1.42"
basePath: "/v1.43"
info:
title: "Docker Engine API"
version: "1.42"
version: "1.43"
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.42) is used.
For example, calling `/info` is the same as calling `/v1.42/info`. Using the
If you omit the version-prefix, the current version of the API (v1.43) is used.
For example, calling `/info` is the same as calling `/v1.43/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,
@@ -976,6 +976,13 @@ definitions:
items:
type: "integer"
minimum: 0
Annotations:
type: "object"
description: |
Arbitrary non-identifying metadata attached to container and
provided to the runtime when the container is started.
additionalProperties:
type: "string"
# Applicable to UNIX platforms
CapAdd:
@@ -1122,6 +1129,7 @@ definitions:
remapping option is enabled.
ShmSize:
type: "integer"
format: "int64"
description: |
Size of `/dev/shm` in bytes. If omitted, the system uses 64MB.
minimum: 0
@@ -1610,6 +1618,34 @@ definitions:
"WorkDir": "/var/lib/docker/overlay2/ef749362d13333e65fc95c572eb525abbe0052e16e086cb64bc3b98ae9aa6d74/work"
}
FilesystemChange:
description: |
Change in the container's filesystem.
type: "object"
required: [Path, Kind]
properties:
Path:
description: |
Path to file or directory that has changed.
type: "string"
x-nullable: false
Kind:
$ref: "#/definitions/ChangeType"
ChangeType:
description: |
Kind of change
Can be one of:
- `0`: Modified ("C")
- `1`: Added ("A")
- `2`: Deleted ("D")
type: "integer"
format: "uint8"
enum: [0, 1, 2]
x-nullable: false
ImageInspect:
description: |
Information about an image in the local image cache.
@@ -1746,15 +1782,14 @@ definitions:
Total size of the image including all layers it is composed of.
In versions of Docker before v1.10, this field was calculated from
the image itself and all of its parent images. Docker v1.10 and up
store images self-contained, and no longer use a parent-chain, making
this field an equivalent of the Size field.
the image itself and all of its parent images. Images are now stored
self-contained, and no longer use a parent-chain, making this field
an equivalent of the Size field.
This field is kept for backward compatibility, but may be removed in
a future version of the API.
> **Deprecated**: this field is kept for backward compatibility, but
> will be removed in API v1.44.
type: "integer"
format: "int64"
x-nullable: false
example: 1239828
GraphDriver:
$ref: "#/definitions/GraphDriverData"
@@ -1802,7 +1837,6 @@ definitions:
- Created
- Size
- SharedSize
- VirtualSize
- Labels
- Containers
properties:
@@ -1888,19 +1922,17 @@ definitions:
x-nullable: false
example: 1239828
VirtualSize:
description: |
description: |-
Total size of the image including all layers it is composed of.
In versions of Docker before v1.10, this field was calculated from
the image itself and all of its parent images. Docker v1.10 and up
store images self-contained, and no longer use a parent-chain, making
this field an equivalent of the Size field.
the image itself and all of its parent images. Images are now stored
self-contained, and no longer use a parent-chain, making this field
an equivalent of the Size field.
This field is kept for backward compatibility, but may be removed in
a future version of the API.
Deprecated: this field is kept for backward compatibility, and will be removed in API v1.44.
type: "integer"
format: "int64"
x-nullable: false
example: 172064416
Labels:
description: "User-defined key/value metadata."
@@ -4652,7 +4684,8 @@ definitions:
example: false
OOMKilled:
description: |
Whether this container has been killed because it ran out of memory.
Whether a process within this container has been killed because it ran
out of memory since the container was last started.
type: "boolean"
example: false
Dead:
@@ -5035,7 +5068,7 @@ definitions:
Go runtime (`GOOS`).
Currently returned values are "linux" and "windows". A full list of
possible values can be found in the [Go documentation](https://golang.org/doc/install/source#environment).
possible values can be found in the [Go documentation](https://go.dev/doc/install/source#environment).
type: "string"
example: "linux"
Architecture:
@@ -5043,7 +5076,7 @@ definitions:
Hardware architecture of the host, as returned by the Go runtime
(`GOARCH`).
A full list of possible values can be found in the [Go documentation](https://golang.org/doc/install/source#environment).
A full list of possible values can be found in the [Go documentation](https://go.dev/doc/install/source#environment).
type: "string"
example: "x86_64"
NCPU:
@@ -5129,42 +5162,8 @@ definitions:
ServerVersion:
description: |
Version string of the daemon.
> **Note**: the [standalone Swarm API](https://docs.docker.com/swarm/swarm-api/)
> returns the Swarm version instead of the daemon version, for example
> `swarm/1.2.8`.
type: "string"
example: "17.06.0-ce"
ClusterStore:
description: |
URL of the distributed storage backend.
The storage backend is used for multihost networking (to store
network and endpoint information) and by the node discovery mechanism.
<p><br /></p>
> **Deprecated**: This field is only propagated when using standalone Swarm
> mode, and overlay networking using an external k/v store. Overlay
> networks with Swarm mode enabled use the built-in raft store, and
> this field will be empty.
type: "string"
example: "consul://consul.corp.example.com:8600/some/path"
ClusterAdvertise:
description: |
The network endpoint that the Engine advertises for the purpose of
node discovery. ClusterAdvertise is a `host:port` combination on which
the daemon is reachable by other hosts.
<p><br /></p>
> **Deprecated**: This field is only propagated when using standalone Swarm
> mode, and overlay networking using an external k/v store. Overlay
> networks with Swarm mode enabled use the built-in raft store, and
> this field will be empty.
type: "string"
example: "node5.corp.example.com:8000"
example: "24.0.2"
Runtimes:
description: |
List of [OCI compliant](https://github.com/opencontainers/runtime-spec)
@@ -5242,7 +5241,8 @@ definitions:
SecurityOptions:
description: |
List of security features that are enabled on the daemon, such as
apparmor, seccomp, SELinux, user-namespaces (userns), and rootless.
apparmor, seccomp, SELinux, user-namespaces (userns), rootless and
no-new-privileges.
Additional configuration options for each security feature may
be present, and are included as a comma-separated list of key/value
@@ -6875,9 +6875,9 @@ paths:
Returns which files in a container's filesystem have been added, deleted,
or modified. The `Kind` of modification can be one of:
- `0`: Modified
- `1`: Added
- `2`: Deleted
- `0`: Modified ("C")
- `1`: Added ("A")
- `2`: Deleted ("D")
operationId: "ContainerChanges"
produces: ["application/json"]
responses:
@@ -6886,22 +6886,7 @@ paths:
schema:
type: "array"
items:
type: "object"
x-go-name: "ContainerChangeResponseItem"
title: "ContainerChangeResponseItem"
description: "change item in response to ContainerChanges operation"
required: [Path, Kind]
properties:
Path:
description: "Path to file that has changed"
type: "string"
x-nullable: false
Kind:
description: "Kind of change"
type: "integer"
format: "uint8"
enum: [0, 1, 2]
x-nullable: false
$ref: "#/definitions/FilesystemChange"
examples:
application/json:
- Path: "/dev"
@@ -8228,7 +8213,7 @@ paths:
Available filters:
- `until=<duration>`: duration relative to daemon's time, during which build cache was not used, in Go's duration format (e.g., '24h')
- `until=<timestamp>` remove cache older than `<timestamp>`. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon's local time.
- `id=<id>`
- `parent=<id>`
- `type=<string>`
@@ -9911,7 +9896,9 @@ paths:
Id: "22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30"
Warning: ""
403:
description: "operation not supported for pre-defined networks"
description: |
Forbidden operation. This happens when trying to create a network named after a pre-defined network,
or when trying to create an overlay network on a daemon which is not part of a Swarm cluster.
schema:
$ref: "#/definitions/ErrorResponse"
404:
@@ -10374,6 +10361,12 @@ paths:
default if omitted.
required: true
type: "string"
- name: "force"
in: "query"
description: |
Force disable a plugin even if still in use.
required: false
type: "boolean"
tags: ["Plugin"]
/plugins/{name}/upgrade:
post:
+5 -20
View File
@@ -1,22 +1,7 @@
package types // import "github.com/docker/docker/api/types"
import "github.com/docker/docker/api/types/registry"
// AuthConfig contains authorization information for connecting to a Registry
type AuthConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
// Email is an optional value associated with the username.
// This field is deprecated and will be removed in a later
// version of docker.
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string `json:"identitytoken,omitempty"`
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string `json:"registrytoken,omitempty"`
}
// AuthConfig contains authorization information for connecting to a Registry.
//
// Deprecated: use github.com/docker/docker/api/types/registry.AuthConfig
type AuthConfig = registry.AuthConfig
+2 -2
View File
@@ -5,6 +5,7 @@ import (
"io"
"time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types/container"
)
@@ -102,8 +103,7 @@ type ExecProcessConfig struct {
// CreateImageConfig is the configuration for creating an image from a
// container.
type CreateImageConfig struct {
Repo string
Tag string
Tag reference.NamedTagged
Pause bool
Author string
Comment string
+4 -3
View File
@@ -4,8 +4,9 @@ import (
"io"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/streamformatter"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// PullOption defines different modes for accessing images
@@ -39,7 +40,7 @@ type BuildConfig struct {
// GetImageAndLayerOptions are the options supported by GetImageAndReleasableLayer
type GetImageAndLayerOptions struct {
PullOption PullOption
AuthConfig map[string]types.AuthConfig
AuthConfig map[string]registry.AuthConfig
Output io.Writer
Platform *specs.Platform
Platform *ocispec.Platform
}
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/registry"
units "github.com/docker/go-units"
)
@@ -180,7 +181,7 @@ type ImageBuildOptions struct {
// at all (nil). See the parsing of buildArgs in
// api/server/router/build/build_routes.go for even more info.
BuildArgs map[string]*string
AuthConfigs map[string]AuthConfig
AuthConfigs map[string]registry.AuthConfig
Context io.Reader
Labels map[string]string
// squash the resulting image's layers to the parent
+2 -2
View File
@@ -3,7 +3,7 @@ package types // import "github.com/docker/docker/api/types"
import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// configs holds structs used for internal communication between the
@@ -16,7 +16,7 @@ type ContainerCreateConfig struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *specs.Platform
Platform *ocispec.Platform
AdjustCPUShares bool
}
@@ -0,0 +1,6 @@
package container
// ContainerChangeResponseItem change item in response to ContainerChanges operation
//
// Deprecated: use [FilesystemChange].
type ContainerChangeResponseItem = FilesystemChange
+15
View File
@@ -0,0 +1,15 @@
package container
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// ChangeType Kind of change
//
// Can be one of:
//
// - `0`: Modified ("C")
// - `1`: Added ("A")
// - `2`: Deleted ("D")
//
// swagger:model ChangeType
type ChangeType uint8
+23
View File
@@ -0,0 +1,23 @@
package container
const (
// ChangeModify represents the modify operation.
ChangeModify ChangeType = 0
// ChangeAdd represents the add operation.
ChangeAdd ChangeType = 1
// ChangeDelete represents the delete operation.
ChangeDelete ChangeType = 2
)
func (ct ChangeType) String() string {
switch ct {
case ChangeModify:
return "C"
case ChangeAdd:
return "A"
case ChangeDelete:
return "D"
default:
return ""
}
}
@@ -1,20 +0,0 @@
package container // import "github.com/docker/docker/api/types/container"
// ----------------------------------------------------------------------------
// Code generated by `swagger generate operation`. DO NOT EDIT.
//
// See hack/generate-swagger-api.sh
// ----------------------------------------------------------------------------
// ContainerChangeResponseItem change item in response to ContainerChanges operation
// swagger:model ContainerChangeResponseItem
type ContainerChangeResponseItem struct {
// Kind of change
// Required: true
Kind uint8 `json:"Kind"`
// Path to file that has changed
// Required: true
Path string `json:"Path"`
}
-16
View File
@@ -1,16 +0,0 @@
package container // import "github.com/docker/docker/api/types/container"
// ContainerCreateCreatedBody OK response to ContainerCreate operation
//
// Deprecated: use CreateResponse
type ContainerCreateCreatedBody = CreateResponse
// ContainerWaitOKBody OK response to ContainerWait operation
//
// Deprecated: use WaitResponse
type ContainerWaitOKBody = WaitResponse
// ContainerWaitOKBodyError container waiting error, if any
//
// Deprecated: use WaitExitError
type ContainerWaitOKBodyError = WaitExitError
@@ -0,0 +1,19 @@
package container
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// FilesystemChange Change in the container's filesystem.
//
// swagger:model FilesystemChange
type FilesystemChange struct {
// kind
// Required: true
Kind ChangeType `json:"Kind"`
// Path to file or directory that has changed.
//
// Required: true
Path string `json:"Path"`
}
@@ -101,7 +101,8 @@ func (n IpcMode) IsShareable() bool {
// IsContainer indicates whether the container uses another container's ipc namespace.
func (n IpcMode) IsContainer() bool {
return strings.HasPrefix(string(n), string(IPCModeContainer)+":")
_, ok := containerID(string(n))
return ok
}
// IsNone indicates whether container IpcMode is set to "none".
@@ -116,15 +117,14 @@ func (n IpcMode) IsEmpty() bool {
// Valid indicates whether the ipc mode is valid.
func (n IpcMode) Valid() bool {
// TODO(thaJeztah): align with PidMode, and consider container-mode without a container name/ID to be invalid.
return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer()
}
// Container returns the name of the container ipc stack is going to be used.
func (n IpcMode) Container() string {
if n.IsContainer() {
return strings.TrimPrefix(string(n), string(IPCModeContainer)+":")
}
return ""
func (n IpcMode) Container() (idOrName string) {
idOrName, _ = containerID(string(n))
return idOrName
}
// NetworkMode represents the container network stack.
@@ -147,17 +147,14 @@ func (n NetworkMode) IsPrivate() bool {
// IsContainer indicates whether container uses a container network stack.
func (n NetworkMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container"
_, ok := containerID(string(n))
return ok
}
// ConnectedContainer is the id of the container which network this container is connected to.
func (n NetworkMode) ConnectedContainer() string {
parts := strings.SplitN(string(n), ":", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
func (n NetworkMode) ConnectedContainer() (idOrName string) {
idOrName, _ = containerID(string(n))
return idOrName
}
// UserDefined indicates user-created network
@@ -178,18 +175,12 @@ func (n UsernsMode) IsHost() bool {
// IsPrivate indicates whether the container uses the a private userns.
func (n UsernsMode) IsPrivate() bool {
return !(n.IsHost())
return !n.IsHost()
}
// Valid indicates whether the userns is valid.
func (n UsernsMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
default:
return false
}
return true
return n == "" || n.IsHost()
}
// CgroupSpec represents the cgroup to use for the container.
@@ -197,22 +188,20 @@ type CgroupSpec string
// IsContainer indicates whether the container is using another container cgroup
func (c CgroupSpec) IsContainer() bool {
parts := strings.SplitN(string(c), ":", 2)
return len(parts) > 1 && parts[0] == "container"
_, ok := containerID(string(c))
return ok
}
// Valid indicates whether the cgroup spec is valid.
func (c CgroupSpec) Valid() bool {
return c.IsContainer() || c == ""
// TODO(thaJeztah): align with PidMode, and consider container-mode without a container name/ID to be invalid.
return c == "" || c.IsContainer()
}
// Container returns the name of the container whose cgroup will be used.
func (c CgroupSpec) Container() string {
parts := strings.SplitN(string(c), ":", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
// Container returns the ID or name of the container whose cgroup will be used.
func (c CgroupSpec) Container() (idOrName string) {
idOrName, _ = containerID(string(c))
return idOrName
}
// UTSMode represents the UTS namespace of the container.
@@ -220,7 +209,7 @@ type UTSMode string
// IsPrivate indicates whether the container uses its private UTS namespace.
func (n UTSMode) IsPrivate() bool {
return !(n.IsHost())
return !n.IsHost()
}
// IsHost indicates whether the container uses the host's UTS namespace.
@@ -230,13 +219,7 @@ func (n UTSMode) IsHost() bool {
// Valid indicates whether the UTS namespace is valid.
func (n UTSMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
default:
return false
}
return true
return n == "" || n.IsHost()
}
// PidMode represents the pid namespace of the container.
@@ -254,32 +237,19 @@ func (n PidMode) IsHost() bool {
// IsContainer indicates whether the container uses a container's pid namespace.
func (n PidMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container"
_, ok := containerID(string(n))
return ok
}
// Valid indicates whether the pid namespace is valid.
func (n PidMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
case "container":
if len(parts) != 2 || parts[1] == "" {
return false
}
default:
return false
}
return true
return n == "" || n.IsHost() || validContainer(string(n))
}
// Container returns the name of the container whose pid namespace is going to be used.
func (n PidMode) Container() string {
parts := strings.SplitN(string(n), ":", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
func (n PidMode) Container() (idOrName string) {
idOrName, _ = containerID(string(n))
return idOrName
}
// DeviceRequest represents a request for devices from a device driver.
@@ -408,16 +378,17 @@ type UpdateConfig struct {
// Portable information *should* appear in Config.
type HostConfig struct {
// Applicable to all platforms
Binds []string // List of volume bindings for this container
ContainerIDFile string // File (path) where the containerId is written
LogConfig LogConfig // Configuration of the logs for this container
NetworkMode NetworkMode // Network mode to use for the container
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
RestartPolicy RestartPolicy // Restart policy to be used for the container
AutoRemove bool // Automatically remove container when it exits
VolumeDriver string // Name of the volume driver used to mount volumes
VolumesFrom []string // List of volumes to take from other container
ConsoleSize [2]uint // Initial console size (height,width)
Binds []string // List of volume bindings for this container
ContainerIDFile string // File (path) where the containerId is written
LogConfig LogConfig // Configuration of the logs for this container
NetworkMode NetworkMode // Network mode to use for the container
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
RestartPolicy RestartPolicy // Restart policy to be used for the container
AutoRemove bool // Automatically remove container when it exits
VolumeDriver string // Name of the volume driver used to mount volumes
VolumesFrom []string // List of volumes to take from other container
ConsoleSize [2]uint // Initial console size (height,width)
Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime
// Applicable to UNIX platforms
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
@@ -463,3 +434,23 @@ type HostConfig struct {
// Run a custom init inside the container, if null, use the daemon's configured settings
Init *bool `json:",omitempty"`
}
// containerID splits "container:<ID|name>" values. It returns the container
// ID or name, and whether an ID/name was found. It returns an empty string and
// a "false" if the value does not have a "container:" prefix. Further validation
// of the returned, including checking if the value is empty, should be handled
// by the caller.
func containerID(val string) (idOrName string, ok bool) {
k, v, hasSep := strings.Cut(val, ":")
if !hasSep || k != "container" {
return "", false
}
return v, true
}
// validContainer checks if the given value is a "container:" mode with
// a non-empty name/ID.
func validContainer(val string) bool {
id, ok := containerID(val)
return ok && id != ""
}
-14
View File
@@ -1,14 +0,0 @@
package types // import "github.com/docker/docker/api/types"
import "github.com/docker/docker/api/types/volume"
// Volume volume
//
// Deprecated: use github.com/docker/docker/api/types/volume.Volume
type Volume = volume.Volume
// VolumeUsageData Usage details about the volume. This information is used by the
// `GET /system/df` endpoint, and omitted in other endpoints.
//
// Deprecated: use github.com/docker/docker/api/types/volume.UsageData
type VolumeUsageData = volume.UsageData
+37
View File
@@ -0,0 +1,37 @@
package filters
import "fmt"
// invalidFilter indicates that the provided filter or its value is invalid
type invalidFilter struct {
Filter string
Value []string
}
func (e invalidFilter) Error() string {
msg := "invalid filter"
if e.Filter != "" {
msg += " '" + e.Filter
if e.Value != nil {
msg = fmt.Sprintf("%s=%s", msg, e.Value)
}
msg += "'"
}
return msg
}
// InvalidParameter marks this error as ErrInvalidParameter
func (e invalidFilter) InvalidParameter() {}
// unreachableCode is an error indicating that the code path was not expected to be reached.
type unreachableCode struct {
Filter string
Value []string
}
// System marks this error as ErrSystem
func (e unreachableCode) System() {}
func (e unreachableCode) Error() string {
return fmt.Sprintf("unreachable code reached for filter: %q with values: %s", e.Filter, e.Value)
}
+37 -14
View File
@@ -10,7 +10,6 @@ import (
"strings"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
)
// Args stores a mapping of keys to a set of multiple values.
@@ -99,7 +98,7 @@ func FromJSON(p string) (Args, error) {
// Fallback to parsing arguments in the legacy slice format
deprecated := map[string][]string{}
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
return args, invalidFilter{errors.Wrap(err, "invalid filter")}
return args, &invalidFilter{}
}
args.fields = deprecatedArgs(deprecated)
@@ -163,13 +162,13 @@ func (args Args) MatchKVList(key string, sources map[string]string) bool {
}
for value := range fieldValues {
testKV := strings.SplitN(value, "=", 2)
testK, testV, hasValue := strings.Cut(value, "=")
v, ok := sources[testKV[0]]
v, ok := sources[testK]
if !ok {
return false
}
if len(testKV) == 2 && testKV[1] != v {
if hasValue && testV != v {
return false
}
}
@@ -196,6 +195,38 @@ func (args Args) Match(field, source string) bool {
return false
}
// GetBoolOrDefault returns a boolean value of the key if the key is present
// and is intepretable as a boolean value. Otherwise the default value is returned.
// Error is not nil only if the filter values are not valid boolean or are conflicting.
func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
fieldValues, ok := args.fields[key]
if !ok {
return defaultValue, nil
}
if len(fieldValues) == 0 {
return defaultValue, &invalidFilter{key, nil}
}
isFalse := fieldValues["0"] || fieldValues["false"]
isTrue := fieldValues["1"] || fieldValues["true"]
conflicting := isFalse && isTrue
invalid := !isFalse && !isTrue
if conflicting || invalid {
return defaultValue, &invalidFilter{key, args.Get(key)}
} else if isFalse {
return false, nil
} else if isTrue {
return true, nil
}
// This code shouldn't be reached.
return defaultValue, &unreachableCode{Filter: key, Value: args.Get(key)}
}
// ExactMatch returns true if the source matches exactly one of the values.
func (args Args) ExactMatch(key, source string) bool {
fieldValues, ok := args.fields[key]
@@ -246,20 +277,12 @@ func (args Args) Contains(field string) bool {
return ok
}
type invalidFilter struct{ error }
func (e invalidFilter) Error() string {
return e.error.Error()
}
func (invalidFilter) InvalidParameter() {}
// Validate compared the set of accepted keys against the keys in the mapping.
// An error is returned if any mapping keys are not in the accepted set.
func (args Args) Validate(accepted map[string]bool) error {
for name := range args.fields {
if !accepted[name] {
return invalidFilter{errors.New("invalid filter '" + name + "'")}
return &invalidFilter{name, nil}
}
}
return nil
+9
View File
@@ -0,0 +1,9 @@
package image
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
// GetImageOpts holds parameters to inspect an image.
type GetImageOpts struct {
Platform *ocispec.Platform
Details bool
}
+5 -8
View File
@@ -85,13 +85,10 @@ type ImageSummary struct {
// Total size of the image including all layers it is composed of.
//
// In versions of Docker before v1.10, this field was calculated from
// the image itself and all of its parent images. Docker v1.10 and up
// store images self-contained, and no longer use a parent-chain, making
// this field an equivalent of the Size field.
// the image itself and all of its parent images. Images are now stored
// self-contained, and no longer use a parent-chain, making this field
// an equivalent of the Size field.
//
// This field is kept for backward compatibility, but may be removed in
// a future version of the API.
//
// Required: true
VirtualSize int64 `json:"VirtualSize"`
// Deprecated: this field is kept for backward compatibility, and will be removed in API v1.44.
VirtualSize int64 `json:"VirtualSize,omitempty"`
}
+99
View File
@@ -0,0 +1,99 @@
package registry // import "github.com/docker/docker/api/types/registry"
import (
"encoding/base64"
"encoding/json"
"io"
"strings"
"github.com/pkg/errors"
)
// AuthHeader is the name of the header used to send encoded registry
// authorization credentials for registry operations (push/pull).
const AuthHeader = "X-Registry-Auth"
// AuthConfig contains authorization information for connecting to a Registry.
type AuthConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
// Email is an optional value associated with the username.
// This field is deprecated and will be removed in a later
// version of docker.
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string `json:"identitytoken,omitempty"`
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string `json:"registrytoken,omitempty"`
}
// EncodeAuthConfig serializes the auth configuration as a base64url encoded
// RFC4648, section 5) JSON string for sending through the X-Registry-Auth header.
//
// For details on base64url encoding, see:
// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
func EncodeAuthConfig(authConfig AuthConfig) (string, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return "", errInvalidParameter{err}
}
return base64.URLEncoding.EncodeToString(buf), nil
}
// DecodeAuthConfig decodes base64url encoded (RFC4648, section 5) JSON
// authentication information as sent through the X-Registry-Auth header.
//
// This function always returns an AuthConfig, even if an error occurs. It is up
// to the caller to decide if authentication is required, and if the error can
// be ignored.
//
// For details on base64url encoding, see:
// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
func DecodeAuthConfig(authEncoded string) (*AuthConfig, error) {
if authEncoded == "" {
return &AuthConfig{}, nil
}
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
return decodeAuthConfigFromReader(authJSON)
}
// DecodeAuthConfigBody decodes authentication information as sent as JSON in the
// body of a request. This function is to provide backward compatibility with old
// clients and API versions. Current clients and API versions expect authentication
// to be provided through the X-Registry-Auth header.
//
// Like DecodeAuthConfig, this function always returns an AuthConfig, even if an
// error occurs. It is up to the caller to decide if authentication is required,
// and if the error can be ignored.
func DecodeAuthConfigBody(rdr io.ReadCloser) (*AuthConfig, error) {
return decodeAuthConfigFromReader(rdr)
}
func decodeAuthConfigFromReader(rdr io.Reader) (*AuthConfig, error) {
authConfig := &AuthConfig{}
if err := json.NewDecoder(rdr).Decode(authConfig); err != nil {
// always return an (empty) AuthConfig to increase compatibility with
// the existing API.
return &AuthConfig{}, invalid(err)
}
return authConfig, nil
}
func invalid(err error) error {
return errInvalidParameter{errors.Wrap(err, "invalid X-Registry-Auth header")}
}
type errInvalidParameter struct{ error }
func (errInvalidParameter) InvalidParameter() {}
func (e errInvalidParameter) Cause() error { return e.error }
func (e errInvalidParameter) Unwrap() error { return e.error }
+3 -3
View File
@@ -4,7 +4,7 @@ import (
"encoding/json"
"net"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ServiceConfig stores daemon registry services configuration.
@@ -113,8 +113,8 @@ type SearchResults struct {
type DistributionInspect struct {
// Descriptor contains information about the manifest, including
// the content addressable digest
Descriptor v1.Descriptor
Descriptor ocispec.Descriptor
// Platforms contains the list of platforms supported by the image,
// obtained by parsing the manifest
Platforms []v1.Platform
Platforms []ocispec.Platform
}
+20 -20
View File
@@ -95,37 +95,37 @@ func GetTimestamp(value string, reference time.Time) (string, error) {
return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil
}
// ParseTimestamps returns seconds and nanoseconds from a timestamp that has the
// format "%d.%09d", time.Unix(), int64(time.Nanosecond()))
// if the incoming nanosecond portion is longer or shorter than 9 digits it is
// converted to nanoseconds. The expectation is that the seconds and
// seconds will be used to create a time variable. For example:
// ParseTimestamps returns seconds and nanoseconds from a timestamp that has
// the format ("%d.%09d", time.Unix(), int64(time.Nanosecond())).
// If the incoming nanosecond portion is longer than 9 digits it is truncated.
// The expectation is that the seconds and nanoseconds will be used to create a
// time variable. For example:
//
// seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0)
// if err == nil since := time.Unix(seconds, nanoseconds)
// seconds, nanoseconds, _ := ParseTimestamp("1136073600.000000001",0)
// since := time.Unix(seconds, nanoseconds)
//
// returns seconds as def(aultSeconds) if value == ""
func ParseTimestamps(value string, def int64) (int64, int64, error) {
// returns seconds as defaultSeconds if value == ""
func ParseTimestamps(value string, defaultSeconds int64) (seconds int64, nanoseconds int64, err error) {
if value == "" {
return def, 0, nil
return defaultSeconds, 0, nil
}
return parseTimestamp(value)
}
func parseTimestamp(value string) (int64, int64, error) {
sa := strings.SplitN(value, ".", 2)
s, err := strconv.ParseInt(sa[0], 10, 64)
func parseTimestamp(value string) (sec int64, nsec int64, err error) {
s, n, ok := strings.Cut(value, ".")
sec, err = strconv.ParseInt(s, 10, 64)
if err != nil {
return s, 0, err
return sec, 0, err
}
if len(sa) != 2 {
return s, 0, nil
if !ok {
return sec, 0, nil
}
n, err := strconv.ParseInt(sa[1], 10, 64)
nsec, err = strconv.ParseInt(n, 10, 64)
if err != nil {
return s, n, err
return sec, nsec, err
}
// should already be in nanoseconds but just in case convert n to nanoseconds
n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1]))))
return s, n, nil
nsec = int64(float64(nsec) * math.Pow(float64(10), float64(9-len(n))))
return sec, nsec, nil
}
+18 -16
View File
@@ -123,9 +123,8 @@ type ImageInspect struct {
// store images self-contained, and no longer use a parent-chain, making
// this field an equivalent of the Size field.
//
// This field is kept for backward compatibility, but may be removed in
// a future version of the API.
VirtualSize int64 // TODO(thaJeztah): deprecate this field
// Deprecated: Unused in API 1.43 and up, but kept for backward compatibility with older API versions.
VirtualSize int64 `json:"VirtualSize,omitempty"`
// GraphDriver holds information about the storage driver used to store the
// container's and image's filesystem.
@@ -297,8 +296,6 @@ type Info struct {
Labels []string
ExperimentalBuild bool
ServerVersion string
ClusterStore string `json:",omitempty"` // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated
ClusterAdvertise string `json:",omitempty"` // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated
Runtimes map[string]Runtime
DefaultRuntime string
Swarm swarm.Info
@@ -350,20 +347,19 @@ func DecodeSecurityOptions(opts []string) ([]SecurityOpt, error) {
continue
}
secopt := SecurityOpt{}
split := strings.Split(opt, ",")
for _, s := range split {
kv := strings.SplitN(s, "=", 2)
if len(kv) != 2 {
for _, s := range strings.Split(opt, ",") {
k, v, ok := strings.Cut(s, "=")
if !ok {
return nil, fmt.Errorf("invalid security option %q", s)
}
if kv[0] == "" || kv[1] == "" {
if k == "" || v == "" {
return nil, errors.New("invalid empty security option")
}
if kv[0] == "name" {
secopt.Name = kv[1]
if k == "name" {
secopt.Name = v
continue
}
secopt.Options = append(secopt.Options, KeyValue{Key: kv[0], Value: kv[1]})
secopt.Options = append(secopt.Options, KeyValue{Key: k, Value: v})
}
so = append(so, secopt)
}
@@ -656,12 +652,18 @@ type Checkpoint struct {
// Runtime describes an OCI runtime
type Runtime struct {
Path string `json:"path"`
// "Legacy" runtime configuration for runc-compatible runtimes.
Path string `json:"path,omitempty"`
Args []string `json:"runtimeArgs,omitempty"`
// Shimv2 runtime configuration. Mutually exclusive with the legacy config above.
Type string `json:"runtimeType,omitempty"`
Options map[string]interface{} `json:"options,omitempty"`
// This is exposed here only for internal use
// It is not currently supported to specify custom shim configs
Shim *ShimConfig `json:"-"`
ShimConfig *ShimConfig `json:"-"`
}
// ShimConfig is used by runtime to configure containerd shims
+4 -4
View File
@@ -16,11 +16,11 @@ func compare(v1, v2 string) int {
otherTab = strings.Split(v2, ".")
)
max := len(currTab)
if len(otherTab) > max {
max = len(otherTab)
maxVer := len(currTab)
if len(otherTab) > maxVer {
maxVer = len(otherTab)
}
for i := 0; i < max; i++ {
for i := 0; i < maxVer; i++ {
var currInt, otherInt int
if len(currTab) > i {
-11
View File
@@ -1,11 +0,0 @@
package volume // import "github.com/docker/docker/api/types/volume"
// VolumeCreateBody Volume configuration
//
// Deprecated: use CreateOptions
type VolumeCreateBody = CreateOptions
// VolumeListOKBody Volume list response
//
// Deprecated: use ListResponse
type VolumeListOKBody = ListResponse
+9 -8
View File
@@ -14,7 +14,7 @@ import (
containerpkg "github.com/docker/docker/container"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/containerfs"
"github.com/opencontainers/go-digest"
)
const (
@@ -26,7 +26,7 @@ const (
// instructions in the builder.
type Source interface {
// Root returns root path for accessing source
Root() containerfs.ContainerFS
Root() string
// Close allows to signal that the filesystem tree won't be used anymore.
// For Context implementations using a temporary directory, it is recommended to
// delete the temporary directory in Close().
@@ -42,11 +42,11 @@ type Backend interface {
// CommitBuildStep creates a new Docker image from the config generated by
// a build step.
CommitBuildStep(backend.CommitConfig) (image.ID, error)
CommitBuildStep(context.Context, backend.CommitConfig) (image.ID, error)
// ContainerCreateWorkdir creates the workdir
ContainerCreateWorkdir(containerID string) error
CreateImage(config []byte, parent string) (Image, error)
CreateImage(ctx context.Context, config []byte, parent string, contentStoreDigest digest.Digest) (Image, error)
ImageCacheBuilder
}
@@ -61,13 +61,13 @@ type ExecBackend interface {
// ContainerAttachRaw attaches to container.
ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool, attached chan struct{}) error
// ContainerCreateIgnoreImagesArgsEscaped creates a new Docker container and returns potential warnings
ContainerCreateIgnoreImagesArgsEscaped(config types.ContainerCreateConfig) (container.CreateResponse, error)
ContainerCreateIgnoreImagesArgsEscaped(ctx context.Context, config types.ContainerCreateConfig) (container.CreateResponse, error)
// ContainerRm removes a container specified by `id`.
ContainerRm(name string, config *types.ContainerRmConfig) error
// ContainerKill stops the container execution abruptly.
ContainerKill(containerID string, sig string) error
// ContainerStart starts a new container
ContainerStart(containerID string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
ContainerStart(ctx context.Context, containerID string, hostConfig *container.HostConfig, 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)
}
@@ -81,7 +81,7 @@ type Result struct {
// ImageCacheBuilder represents a generator for stateful image cache.
type ImageCacheBuilder interface {
// MakeImageCache creates a stateful image cache.
MakeImageCache(cacheFrom []string) ImageCache
MakeImageCache(ctx context.Context, cacheFrom []string) (ImageCache, error)
}
// ImageCache abstracts an image cache.
@@ -105,11 +105,12 @@ type ROLayer interface {
Release() error
NewRWLayer() (RWLayer, error)
DiffID() layer.DiffID
ContentStoreDigest() digest.Digest
}
// RWLayer is active layer that can be read/modified
type RWLayer interface {
Release() error
Root() containerfs.ContainerFS
Root() string
Commit() (ROLayer, error)
}
+21 -18
View File
@@ -21,7 +21,7 @@ import (
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/moby/buildkit/frontend/dockerfile/shell"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/syncmap"
@@ -95,7 +95,7 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
if err != nil {
return nil, err
}
return b.build(source, dockerfile)
return b.build(ctx, source, dockerfile)
}
// builderOptions are the dependencies required by the builder
@@ -117,8 +117,7 @@ type Builder struct {
Aux *streamformatter.AuxFormatter
Output io.Writer
docker builder.Backend
clientCtx context.Context
docker builder.Backend
idMapping idtools.IdentityMapping
disableCommit bool
@@ -126,18 +125,22 @@ type Builder struct {
pathCache pathCache
containerManager *containerManager
imageProber ImageProber
platform *specs.Platform
platform *ocispec.Platform
}
// newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
func newBuilder(clientCtx context.Context, options builderOptions) (*Builder, error) {
func newBuilder(ctx context.Context, options builderOptions) (*Builder, error) {
config := options.Options
if config == nil {
config = new(types.ImageBuildOptions)
}
imageProber, err := newImageProber(ctx, options.Backend, config.CacheFrom, config.NoCache)
if err != nil {
return nil, err
}
b := &Builder{
clientCtx: clientCtx,
options: config,
Stdout: options.ProgressWriter.StdoutFormatter,
Stderr: options.ProgressWriter.StderrFormatter,
@@ -145,9 +148,9 @@ func newBuilder(clientCtx context.Context, options builderOptions) (*Builder, er
Output: options.ProgressWriter.Output,
docker: options.Backend,
idMapping: options.IDMapping,
imageSources: newImageSources(clientCtx, options),
imageSources: newImageSources(options),
pathCache: options.PathCache,
imageProber: newImageProber(options.Backend, config.CacheFrom, config.NoCache),
imageProber: imageProber,
containerManager: newContainerManager(options.Backend),
}
@@ -181,7 +184,7 @@ func buildLabelOptions(labels map[string]string, stages []instructions.Stage) {
// Build runs the Dockerfile builder by parsing the Dockerfile and executing
// the instructions from the file.
func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*builder.Result, error) {
func (b *Builder) build(ctx context.Context, source builder.Source, dockerfile *parser.Result) (*builder.Result, error) {
defer b.imageSources.Unmount()
stages, metaArgs, err := instructions.Parse(dockerfile.AST)
@@ -205,7 +208,7 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil
buildLabelOptions(b.options.Labels, stages)
dockerfile.PrintWarnings(b.Stderr)
dispatchState, err := b.dispatchDockerfileWithCancellation(stages, metaArgs, dockerfile.EscapeToken, source)
dispatchState, err := b.dispatchDockerfileWithCancellation(ctx, stages, metaArgs, dockerfile.EscapeToken, source)
if err != nil {
return nil, err
}
@@ -244,7 +247,7 @@ func printCommand(out io.Writer, currentCommandIndex int, totalCommands int, cmd
return currentCommandIndex + 1
}
func (b *Builder) dispatchDockerfileWithCancellation(parseResult []instructions.Stage, metaArgs []instructions.ArgCommand, escapeToken rune, source builder.Source) (*dispatchState, error) {
func (b *Builder) dispatchDockerfileWithCancellation(ctx context.Context, parseResult []instructions.Stage, metaArgs []instructions.ArgCommand, escapeToken rune, source builder.Source) (*dispatchState, error) {
dispatchRequest := dispatchRequest{}
buildArgs := NewBuildArgs(b.options.BuildArgs)
totalCommands := len(metaArgs) + len(parseResult)
@@ -272,14 +275,14 @@ func (b *Builder) dispatchDockerfileWithCancellation(parseResult []instructions.
dispatchRequest = newDispatchRequest(b, escapeToken, source, buildArgs, stagesResults)
currentCommandIndex = printCommand(b.Stdout, currentCommandIndex, totalCommands, stage.SourceCode)
if err := initializeStage(dispatchRequest, &stage); err != nil {
if err := initializeStage(ctx, dispatchRequest, &stage); err != nil {
return nil, err
}
dispatchRequest.state.updateRunConfig()
fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(dispatchRequest.state.imageID))
for _, cmd := range stage.Commands {
select {
case <-b.clientCtx.Done():
case <-ctx.Done():
logrus.Debug("Builder: build cancelled!")
fmt.Fprint(b.Stdout, "Build cancelled\n")
buildsFailed.WithValues(metricsBuildCanceled).Inc()
@@ -290,7 +293,7 @@ func (b *Builder) dispatchDockerfileWithCancellation(parseResult []instructions.
currentCommandIndex = printCommand(b.Stdout, currentCommandIndex, totalCommands, cmd)
if err := dispatch(dispatchRequest, cmd); err != nil {
if err := dispatch(ctx, dispatchRequest, cmd); err != nil {
return nil, err
}
dispatchRequest.state.updateRunConfig()
@@ -317,7 +320,7 @@ func (b *Builder) dispatchDockerfileWithCancellation(parseResult []instructions.
// coming from the query parameter of the same name.
//
// TODO: Remove?
func BuildFromConfig(config *container.Config, changes []string, os string) (*container.Config, error) {
func BuildFromConfig(ctx context.Context, config *container.Config, changes []string, os string) (*container.Config, error) {
if len(changes) == 0 {
return config, nil
}
@@ -327,7 +330,7 @@ func BuildFromConfig(config *container.Config, changes []string, os string) (*co
return nil, errdefs.InvalidParameter(err)
}
b, err := newBuilder(context.Background(), builderOptions{
b, err := newBuilder(ctx, builderOptions{
Options: &types.ImageBuildOptions{NoCache: true},
})
if err != nil {
@@ -360,7 +363,7 @@ func BuildFromConfig(config *container.Config, changes []string, os string) (*co
dispatchRequest.state.imageID = config.Image
dispatchRequest.state.operatingSystem = os
for _, cmd := range commands {
err := dispatch(dispatchRequest, cmd)
err := dispatch(ctx, dispatchRequest, cmd)
if err != nil {
return nil, errdefs.InvalidParameter(err)
}
+3 -3
View File
@@ -28,8 +28,8 @@ func newContainerManager(docker builder.ExecBackend) *containerManager {
}
// Create a container
func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig) (container.CreateResponse, error) {
container, err := c.backend.ContainerCreateIgnoreImagesArgsEscaped(types.ContainerCreateConfig{
func (c *containerManager) Create(ctx context.Context, runConfig *container.Config, hostConfig *container.HostConfig) (container.CreateResponse, error) {
container, err := c.backend.ContainerCreateIgnoreImagesArgsEscaped(ctx, types.ContainerCreateConfig{
Config: runConfig,
HostConfig: hostConfig,
})
@@ -69,7 +69,7 @@ func (c *containerManager) Run(ctx context.Context, cID string, stdout, stderr i
}
}()
if err := c.backend.ContainerStart(cID, nil, "", ""); err != nil {
if err := c.backend.ContainerStart(ctx, cID, nil, "", ""); err != nil {
close(finished)
logCancellationError(cancelErrCh, "error from ContainerStart: "+err.Error())
return err
+33 -58
View File
@@ -1,7 +1,6 @@
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
import (
"archive/tar"
"fmt"
"io"
"mime"
@@ -20,12 +19,12 @@ import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/longpath"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/system"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
@@ -39,14 +38,14 @@ type pathCache interface {
// copyInfo is a data object which stores the metadata about each source file in
// a copyInstruction
type copyInfo struct {
root containerfs.ContainerFS
root string
path string
hash string
noDecompress bool
}
func (c copyInfo) fullPath() (string, error) {
return c.root.ResolveScopedPath(c.path, true)
return containerfs.ResolveScopedPath(c.root, c.path)
}
func newCopyInfoFromSource(source builder.Source, path string, hash string) copyInfo {
@@ -75,7 +74,7 @@ type copier struct {
source builder.Source
pathCache pathCache
download sourceDownloader
platform *specs.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.
@@ -87,7 +86,7 @@ func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, i
platform := req.builder.platform
if platform == nil {
// May be nil if not explicitly set in API/dockerfile
platform = &specs.Platform{}
platform = &ocispec.Platform{}
}
if platform.OS == "" {
// Default to the dispatch requests operating system if not explicit in API/dockerfile
@@ -159,7 +158,7 @@ func (o *copier) getCopyInfoForSourcePath(orig, dest string) ([]copyInfo, error)
}
path = unnamedFilename
}
o.tmpPaths = append(o.tmpPaths, remote.Root().Path())
o.tmpPaths = append(o.tmpPaths, remote.Root())
hash, err := remote.Hash(path)
ci := newCopyInfoFromSource(remote, path, hash)
@@ -202,7 +201,7 @@ func (o *copier) calcCopyInfo(origPath string, allowWildcards bool) ([]copyInfo,
o.source, err = remotecontext.NewLazySource(rwLayer.Root())
if err != nil {
return nil, errors.Wrapf(err, "failed to create context for copy from %s", rwLayer.Root().Path())
return nil, errors.Wrapf(err, "failed to create context for copy from %s", rwLayer.Root())
}
}
@@ -259,7 +258,7 @@ func (o *copier) storeInPathCache(im *imageMount, path string, hash string) {
func (o *copier) copyWithWildcards(origPath string) ([]copyInfo, error) {
root := o.source.Root()
var copyInfos []copyInfo
if err := root.Walk(root.Path(), func(path string, info os.FileInfo, err error) error {
if err := filepath.WalkDir(root, func(path string, _ os.DirEntry, err error) error {
if err != nil {
return err
}
@@ -271,7 +270,7 @@ func (o *copier) copyWithWildcards(origPath string) ([]copyInfo, error) {
if rel == "." {
return nil
}
if match, _ := root.Match(origPath, rel); !match {
if match, _ := filepath.Match(origPath, rel); !match {
return nil
}
@@ -317,7 +316,7 @@ func walkSource(source builder.Source, origPath string) ([]string, error) {
}
// Must be a dir
var subfiles []string
err = source.Root().Walk(fp, func(path string, info os.FileInfo, err error) error {
err = filepath.WalkDir(fp, func(path string, _ os.DirEntry, err error) error {
if err != nil {
return err
}
@@ -391,7 +390,7 @@ func downloadSource(output io.Writer, stdout io.Writer, srcURL string) (remote b
filename := getFilenameForDownload(u.Path, resp)
// Prepare file in a tmp dir
tmpDir, err := ioutils.TempDir("", "docker-remote")
tmpDir, err := longpath.MkdirTemp("", "docker-remote")
if err != nil {
return
}
@@ -442,19 +441,14 @@ func downloadSource(output io.Writer, stdout io.Writer, srcURL string) (remote b
return
}
lc, err := remotecontext.NewLazySource(containerfs.NewLocalContainerFS(tmpDir))
lc, err := remotecontext.NewLazySource(tmpDir)
return lc, filename, err
}
type copyFileOptions struct {
decompress bool
identity *idtools.Identity
archiver Archiver
}
type copyEndpoint struct {
driver containerfs.Driver
path string
archiver *archive.Archiver
}
func performCopyForInfo(dest copyInfo, source copyInfo, options copyFileOptions) error {
@@ -470,96 +464,77 @@ func performCopyForInfo(dest copyInfo, source copyInfo, options copyFileOptions)
archiver := options.archiver
srcEndpoint := &copyEndpoint{driver: source.root, path: srcPath}
destEndpoint := &copyEndpoint{driver: dest.root, path: destPath}
src, err := source.root.Stat(srcPath)
src, err := os.Stat(srcPath)
if err != nil {
return errors.Wrapf(err, "source path not found")
}
if src.IsDir() {
return copyDirectory(archiver, srcEndpoint, destEndpoint, options.identity)
return copyDirectory(archiver, srcPath, destPath, options.identity)
}
if options.decompress && isArchivePath(source.root, srcPath) && !source.noDecompress {
if options.decompress && archive.IsArchivePath(srcPath) && !source.noDecompress {
return archiver.UntarPath(srcPath, destPath)
}
destExistsAsDir, err := isExistingDirectory(destEndpoint)
destExistsAsDir, err := isExistingDirectory(destPath)
if err != nil {
return err
}
// dest.path must be used because destPath has already been cleaned of any
// trailing slash
if endsInSlash(dest.root, dest.path) || destExistsAsDir {
if endsInSlash(dest.path) || destExistsAsDir {
// source.path must be used to get the correct filename when the source
// is a symlink
destPath = dest.root.Join(destPath, source.root.Base(source.path))
destEndpoint = &copyEndpoint{driver: dest.root, path: destPath}
destPath = filepath.Join(destPath, filepath.Base(source.path))
}
return copyFile(archiver, srcEndpoint, destEndpoint, options.identity)
return copyFile(archiver, srcPath, destPath, options.identity)
}
func isArchivePath(driver containerfs.ContainerFS, path string) bool {
file, err := driver.Open(path)
if err != nil {
return false
}
defer file.Close()
rdr, err := archive.DecompressStream(file)
if err != nil {
return false
}
r := tar.NewReader(rdr)
_, err = r.Next()
return err == nil
}
func copyDirectory(archiver Archiver, source, dest *copyEndpoint, identity *idtools.Identity) error {
func copyDirectory(archiver *archive.Archiver, source, dest string, identity *idtools.Identity) error {
destExists, err := isExistingDirectory(dest)
if err != nil {
return errors.Wrapf(err, "failed to query destination path")
}
if err := archiver.CopyWithTar(source.path, dest.path); err != nil {
if err := archiver.CopyWithTar(source, dest); err != nil {
return errors.Wrapf(err, "failed to copy directory")
}
if identity != nil {
return fixPermissions(source.path, dest.path, *identity, !destExists)
return fixPermissions(source, dest, *identity, !destExists)
}
return nil
}
func copyFile(archiver Archiver, source, dest *copyEndpoint, identity *idtools.Identity) error {
func copyFile(archiver *archive.Archiver, source, dest string, identity *idtools.Identity) error {
if identity == nil {
// Use system.MkdirAll here, which is a custom version of os.MkdirAll
// modified for use on Windows to handle volume GUID paths. These paths
// are of the form \\?\Volume{<GUID>}\<path>. An example would be:
// \\?\Volume{dae8d3ac-b9a1-11e9-88eb-e8554b2ba1db}\bin\busybox.exe
if err := system.MkdirAll(filepath.Dir(dest.path), 0755); err != nil {
if err := system.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return err
}
} else {
if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest.path), 0755, *identity); err != nil {
if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest), 0755, *identity); err != nil {
return errors.Wrapf(err, "failed to create new directory")
}
}
if err := archiver.CopyFileWithTar(source.path, dest.path); err != nil {
if err := archiver.CopyFileWithTar(source, dest); err != nil {
return errors.Wrapf(err, "failed to copy file")
}
if identity != nil {
return fixPermissions(source.path, dest.path, *identity, false)
return fixPermissions(source, dest, *identity, false)
}
return nil
}
func endsInSlash(driver containerfs.Driver, path string) bool {
return strings.HasSuffix(path, string(driver.Separator()))
func endsInSlash(path string) bool {
return strings.HasSuffix(path, string(filepath.Separator))
}
// isExistingDirectory returns true if the path exists and is a directory
func isExistingDirectory(point *copyEndpoint) (bool, error) {
destStat, err := point.driver.Stat(point.path)
func isExistingDirectory(path string) (bool, error) {
destStat, err := os.Stat(path)
switch {
case errors.Is(err, os.ErrNotExist):
return false, nil
+2 -4
View File
@@ -9,7 +9,6 @@ import (
"path/filepath"
"strings"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
)
@@ -19,8 +18,7 @@ func fixPermissions(source, destination string, identity idtools.Identity, overr
err error
)
if !overrideSkip {
destEndpoint := &copyEndpoint{driver: containerfs.NewLocalDriver(), path: destination}
skipChownRoot, err = isExistingDirectory(destEndpoint)
skipChownRoot, err = isExistingDirectory(destination)
if err != nil {
return err
}
@@ -28,7 +26,7 @@ func fixPermissions(source, destination string, identity idtools.Identity, overr
// We Walk on the source rather than on the destination because we don't
// want to change permissions on things we haven't created or modified.
return filepath.Walk(source, func(fullpath string, _ os.FileInfo, _ error) error {
return filepath.WalkDir(source, func(fullpath string, _ os.DirEntry, _ error) error {
// Do not alter the walk root iff. it existed before, as it doesn't fall under
// the domain of "things we should chown".
if skipChownRoot && source == fullpath {
+2 -2
View File
@@ -15,8 +15,8 @@ import (
)
var pathDenyList = map[string]bool{
"c:\\": true,
"c:\\windows": true,
`c:\`: true,
`c:\windows`: true,
}
func init() {
+61 -57
View File
@@ -9,6 +9,7 @@ package dockerfile // import "github.com/docker/docker/builder/dockerfile"
import (
"bytes"
"context"
"fmt"
"runtime"
"sort"
@@ -27,7 +28,7 @@ import (
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/moby/buildkit/frontend/dockerfile/shell"
"github.com/moby/sys/signal"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
@@ -35,7 +36,7 @@ import (
//
// Sets the environment variable foo to bar, also makes interpolation
// in the dockerfile available from the next statement on via ${foo}.
func dispatchEnv(d dispatchRequest, c *instructions.EnvCommand) error {
func dispatchEnv(ctx context.Context, d dispatchRequest, c *instructions.EnvCommand) error {
runConfig := d.state.runConfig
commitMessage := bytes.NewBufferString("ENV")
for _, e := range c.Env {
@@ -45,8 +46,7 @@ func dispatchEnv(d dispatchRequest, c *instructions.EnvCommand) error {
commitMessage.WriteString(" " + newVar)
gotOne := false
for i, envVar := range runConfig.Env {
envParts := strings.SplitN(envVar, "=", 2)
compareFrom := envParts[0]
compareFrom, _, _ := strings.Cut(envVar, "=")
if shell.EqualEnvKeys(compareFrom, name) {
runConfig.Env[i] = newVar
gotOne = true
@@ -57,21 +57,21 @@ func dispatchEnv(d dispatchRequest, c *instructions.EnvCommand) error {
runConfig.Env = append(runConfig.Env, newVar)
}
}
return d.builder.commit(d.state, commitMessage.String())
return d.builder.commit(ctx, d.state, commitMessage.String())
}
// MAINTAINER some text <maybe@an.email.address>
//
// Sets the maintainer metadata.
func dispatchMaintainer(d dispatchRequest, c *instructions.MaintainerCommand) error {
func dispatchMaintainer(ctx context.Context, d dispatchRequest, c *instructions.MaintainerCommand) error {
d.state.maintainer = c.Maintainer
return d.builder.commit(d.state, "MAINTAINER "+c.Maintainer)
return d.builder.commit(ctx, d.state, "MAINTAINER "+c.Maintainer)
}
// LABEL some json data describing the image
//
// Sets the Label variable foo to bar,
func dispatchLabel(d dispatchRequest, c *instructions.LabelCommand) error {
func dispatchLabel(ctx context.Context, d dispatchRequest, c *instructions.LabelCommand) error {
if d.state.runConfig.Labels == nil {
d.state.runConfig.Labels = make(map[string]string)
}
@@ -80,14 +80,14 @@ func dispatchLabel(d dispatchRequest, c *instructions.LabelCommand) error {
d.state.runConfig.Labels[v.Key] = v.Value
commitStr += " " + v.String()
}
return d.builder.commit(d.state, commitStr)
return d.builder.commit(ctx, d.state, commitStr)
}
// ADD foo /path
//
// Add the file 'foo' to '/path'. Tarball and Remote URL (http, https) handling
// exist here. If you do not wish to have this automatic handling, use COPY.
func dispatchAdd(d dispatchRequest, c *instructions.AddCommand) error {
func dispatchAdd(ctx context.Context, d dispatchRequest, c *instructions.AddCommand) error {
if c.Chmod != "" {
return errors.New("the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled")
}
@@ -102,20 +102,20 @@ func dispatchAdd(d dispatchRequest, c *instructions.AddCommand) error {
copyInstruction.chownStr = c.Chown
copyInstruction.allowLocalDecompression = true
return d.builder.performCopy(d, copyInstruction)
return d.builder.performCopy(ctx, d, copyInstruction)
}
// COPY foo /path
//
// Same as 'ADD' but without the tar and remote url handling.
func dispatchCopy(d dispatchRequest, c *instructions.CopyCommand) error {
func dispatchCopy(ctx context.Context, d dispatchRequest, c *instructions.CopyCommand) error {
if c.Chmod != "" {
return errors.New("the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled")
}
var im *imageMount
var err error
if c.From != "" {
im, err = d.getImageMount(c.From)
im, err = d.getImageMount(ctx, c.From)
if err != nil {
return errors.Wrapf(err, "invalid from flag value %s", c.From)
}
@@ -130,10 +130,10 @@ func dispatchCopy(d dispatchRequest, c *instructions.CopyCommand) error {
if c.From != "" && copyInstruction.chownStr == "" {
copyInstruction.preserveOwnership = true
}
return d.builder.performCopy(d, copyInstruction)
return d.builder.performCopy(ctx, d, copyInstruction)
}
func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error) {
func (d *dispatchRequest) getImageMount(ctx context.Context, imageRefOrID string) (*imageMount, error) {
if imageRefOrID == "" {
// TODO: this could return the source in the default case as well?
return nil, nil
@@ -148,14 +148,17 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error
imageRefOrID = stage.Image
localOnly = true
}
return d.builder.imageSources.Get(imageRefOrID, localOnly, d.builder.platform)
return d.builder.imageSources.Get(ctx, imageRefOrID, localOnly, d.builder.platform)
}
// FROM [--platform=platform] imagename[:tag | @digest] [AS build-stage-name]
func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
d.builder.imageProber.Reset()
func initializeStage(ctx context.Context, d dispatchRequest, cmd *instructions.Stage) error {
err := d.builder.imageProber.Reset(ctx)
if err != nil {
return err
}
var platform *specs.Platform
var platform *ocispec.Platform
if v := cmd.Platform; v != "" {
v, err := d.getExpandedString(d.shlex, v)
if err != nil {
@@ -169,7 +172,7 @@ func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
platform = &p
}
image, err := d.getFromImage(d.shlex, cmd.BaseName, platform)
image, err := d.getFromImage(ctx, d.shlex, cmd.BaseName, platform)
if err != nil {
return err
}
@@ -180,12 +183,12 @@ func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
if len(state.runConfig.OnBuild) > 0 {
triggers := state.runConfig.OnBuild
state.runConfig.OnBuild = nil
return dispatchTriggeredOnBuild(d, triggers)
return dispatchTriggeredOnBuild(ctx, d, triggers)
}
return nil
}
func dispatchTriggeredOnBuild(d dispatchRequest, triggers []string) error {
func dispatchTriggeredOnBuild(ctx context.Context, d dispatchRequest, triggers []string) error {
fmt.Fprintf(d.builder.Stdout, "# Executing %d build trigger", len(triggers))
if len(triggers) > 1 {
fmt.Fprint(d.builder.Stdout, "s")
@@ -208,7 +211,7 @@ func dispatchTriggeredOnBuild(d dispatchRequest, triggers []string) error {
}
return err
}
err = dispatch(d, cmd)
err = dispatch(ctx, d, cmd)
if err != nil {
return err
}
@@ -229,7 +232,7 @@ func (d *dispatchRequest) getExpandedString(shlex *shell.Lex, str string) (strin
return name, nil
}
func (d *dispatchRequest) getImageOrStage(name string, platform *specs.Platform) (builder.Image, error) {
func (d *dispatchRequest) getImageOrStage(ctx context.Context, name string, platform *ocispec.Platform) (builder.Image, error) {
var localOnly bool
if im, ok := d.stages.getByName(name); ok {
name = im.Image
@@ -256,13 +259,14 @@ func (d *dispatchRequest) getImageOrStage(name string, platform *specs.Platform)
}
return builder.Image(imageImage), nil
}
imageMount, err := d.builder.imageSources.Get(name, localOnly, platform)
imageMount, err := d.builder.imageSources.Get(ctx, name, localOnly, platform)
if err != nil {
return nil, err
}
return imageMount.Image(), nil
}
func (d *dispatchRequest) getFromImage(shlex *shell.Lex, basename string, platform *specs.Platform) (builder.Image, error) {
func (d *dispatchRequest) getFromImage(ctx context.Context, shlex *shell.Lex, basename string, platform *ocispec.Platform) (builder.Image, error) {
name, err := d.getExpandedString(shlex, basename)
if err != nil {
return nil, err
@@ -273,18 +277,18 @@ func (d *dispatchRequest) getFromImage(shlex *shell.Lex, basename string, platfo
return nil, errors.Errorf("base name (%s) should not be blank", basename)
}
return d.getImageOrStage(name, platform)
return d.getImageOrStage(ctx, name, platform)
}
func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error {
func dispatchOnbuild(ctx context.Context, d dispatchRequest, c *instructions.OnbuildCommand) error {
d.state.runConfig.OnBuild = append(d.state.runConfig.OnBuild, c.Expression)
return d.builder.commit(d.state, "ONBUILD "+c.Expression)
return d.builder.commit(ctx, d.state, "ONBUILD "+c.Expression)
}
// WORKDIR /tmp
//
// Set the working directory for future RUN/CMD/etc statements.
func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
func dispatchWorkdir(ctx context.Context, d dispatchRequest, c *instructions.WorkdirCommand) error {
runConfig := d.state.runConfig
var err error
runConfig.WorkingDir, err = normalizeWorkdir(d.state.operatingSystem, runConfig.WorkingDir, c.Path)
@@ -305,7 +309,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
comment := "WORKDIR " + runConfig.WorkingDir
runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.state.operatingSystem))
containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd)
containerID, err := d.builder.probeAndCreate(ctx, d.state, runConfigWithCommentCmd)
if err != nil || containerID == "" {
return err
}
@@ -314,7 +318,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
return err
}
return d.builder.commitContainer(d.state, containerID, runConfigWithCommentCmd)
return d.builder.commitContainer(ctx, d.state, containerID, runConfigWithCommentCmd)
}
// RUN some command yo
@@ -326,7 +330,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
// RUN echo hi # sh -c echo hi (Linux and LCOW)
// RUN echo hi # cmd /S /C echo hi (Windows)
// RUN [ "echo", "hi" ] # echo hi
func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
func dispatchRun(ctx context.Context, d dispatchRequest, c *instructions.RunCommand) error {
if !system.IsOSSupported(d.state.operatingSystem) {
return system.ErrNotSupportedOperatingSystem
}
@@ -360,12 +364,12 @@ func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
withEntrypointOverride(saveCmd, strslice.StrSlice{""}),
withoutHealthcheck())
cID, err := d.builder.create(runConfig)
cID, err := d.builder.create(ctx, runConfig)
if err != nil {
return err
}
if err := d.builder.containerManager.Run(d.builder.clientCtx, cID, d.builder.Stdout, d.builder.Stderr); err != nil {
if err := d.builder.containerManager.Run(ctx, cID, d.builder.Stdout, d.builder.Stderr); err != nil {
if err, ok := err.(*statusCodeError); ok {
// TODO: change error type, because jsonmessage.JSONError assumes HTTP
msg := fmt.Sprintf(
@@ -388,7 +392,7 @@ func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
runConfigForCacheProbe.ArgsEscaped = stateRunConfig.ArgsEscaped
}
return d.builder.commitContainer(d.state, cID, runConfigForCacheProbe)
return d.builder.commitContainer(ctx, d.state, cID, runConfigForCacheProbe)
}
// Derive the command to use for probeCache() and to commit in this container.
@@ -403,9 +407,9 @@ func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
// These args are transparent so resulting image should be the same regardless
// of the value.
func prependEnvOnCmd(buildArgs *BuildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice {
var tmpBuildEnv []string
tmpBuildEnv := make([]string, 0, len(buildArgVars))
for _, env := range buildArgVars {
key := strings.SplitN(env, "=", 2)[0]
key, _, _ := strings.Cut(env, "=")
if buildArgs.IsReferencedOrNotBuiltin(key) {
tmpBuildEnv = append(tmpBuildEnv, env)
}
@@ -413,14 +417,14 @@ func prependEnvOnCmd(buildArgs *BuildArgs, buildArgVars []string, cmd strslice.S
sort.Strings(tmpBuildEnv)
tmpEnv := append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...)
return strslice.StrSlice(append(tmpEnv, cmd...))
return append(tmpEnv, cmd...)
}
// CMD foo
//
// Set the default command to run in the container (which may be empty).
// Argument handling is the same as RUN.
func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
func dispatchCmd(ctx context.Context, d dispatchRequest, c *instructions.CmdCommand) error {
runConfig := d.state.runConfig
cmd, argsEscaped := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.state.operatingSystem, c.Name(), c.String())
@@ -436,7 +440,7 @@ func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
runConfig.Cmd = cmd
runConfig.ArgsEscaped = argsEscaped
if err := d.builder.commit(d.state, fmt.Sprintf("CMD %q", cmd)); err != nil {
if err := d.builder.commit(ctx, d.state, fmt.Sprintf("CMD %q", cmd)); err != nil {
return err
}
if len(c.ShellDependantCmdLine.CmdLine) != 0 {
@@ -450,7 +454,7 @@ func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
//
// Set the default healthcheck command to run in the container (which may be empty).
// Argument handling is the same as RUN.
func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand) error {
func dispatchHealthcheck(ctx context.Context, d dispatchRequest, c *instructions.HealthCheckCommand) error {
runConfig := d.state.runConfig
if runConfig.Healthcheck != nil {
oldCmd := runConfig.Healthcheck.Test
@@ -459,7 +463,7 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
}
}
runConfig.Healthcheck = c.Health
return d.builder.commit(d.state, fmt.Sprintf("HEALTHCHECK %q", runConfig.Healthcheck))
return d.builder.commit(ctx, d.state, fmt.Sprintf("HEALTHCHECK %q", runConfig.Healthcheck))
}
// ENTRYPOINT /usr/sbin/nginx
@@ -469,7 +473,7 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
//
// Handles command processing similar to CMD and RUN, only req.runConfig.Entrypoint
// is initialized at newBuilder time instead of through argument parsing.
func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
func dispatchEntrypoint(ctx context.Context, d dispatchRequest, c *instructions.EntrypointCommand) error {
runConfig := d.state.runConfig
cmd, argsEscaped := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.state.operatingSystem, c.Name(), c.String())
@@ -491,14 +495,14 @@ func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) er
runConfig.Cmd = nil
}
return d.builder.commit(d.state, fmt.Sprintf("ENTRYPOINT %q", runConfig.Entrypoint))
return d.builder.commit(ctx, d.state, fmt.Sprintf("ENTRYPOINT %q", runConfig.Entrypoint))
}
// EXPOSE 6667/tcp 7000/tcp
//
// Expose ports for links and port mappings. This all ends up in
// req.runConfig.ExposedPorts for runconfig.
func dispatchExpose(d dispatchRequest, c *instructions.ExposeCommand, envs []string) error {
func dispatchExpose(ctx context.Context, d dispatchRequest, c *instructions.ExposeCommand, envs []string) error {
// custom multi word expansion
// expose $FOO with FOO="80 443" is expanded as EXPOSE [80,443]. This is the only command supporting word to words expansion
// so the word processing has been de-generalized
@@ -524,22 +528,22 @@ func dispatchExpose(d dispatchRequest, c *instructions.ExposeCommand, envs []str
d.state.runConfig.ExposedPorts[p] = struct{}{}
}
return d.builder.commit(d.state, "EXPOSE "+strings.Join(c.Ports, " "))
return d.builder.commit(ctx, d.state, "EXPOSE "+strings.Join(c.Ports, " "))
}
// USER foo
//
// Set the user to 'foo' for future commands and when running the
// ENTRYPOINT/CMD at container run time.
func dispatchUser(d dispatchRequest, c *instructions.UserCommand) error {
func dispatchUser(ctx context.Context, d dispatchRequest, c *instructions.UserCommand) error {
d.state.runConfig.User = c.User
return d.builder.commit(d.state, fmt.Sprintf("USER %v", c.User))
return d.builder.commit(ctx, d.state, fmt.Sprintf("USER %v", c.User))
}
// VOLUME /foo
//
// Expose the volume /foo for use. Will also accept the JSON array form.
func dispatchVolume(d dispatchRequest, c *instructions.VolumeCommand) error {
func dispatchVolume(ctx context.Context, d dispatchRequest, c *instructions.VolumeCommand) error {
if d.state.runConfig.Volumes == nil {
d.state.runConfig.Volumes = map[string]struct{}{}
}
@@ -549,19 +553,19 @@ func dispatchVolume(d dispatchRequest, c *instructions.VolumeCommand) error {
}
d.state.runConfig.Volumes[v] = struct{}{}
}
return d.builder.commit(d.state, fmt.Sprintf("VOLUME %v", c.Volumes))
return d.builder.commit(ctx, d.state, fmt.Sprintf("VOLUME %v", c.Volumes))
}
// STOPSIGNAL signal
//
// Set the signal that will be used to kill the container.
func dispatchStopSignal(d dispatchRequest, c *instructions.StopSignalCommand) error {
func dispatchStopSignal(ctx context.Context, d dispatchRequest, c *instructions.StopSignalCommand) error {
_, err := signal.ParseSignal(c.Signal)
if err != nil {
return errdefs.InvalidParameter(err)
}
d.state.runConfig.StopSignal = c.Signal
return d.builder.commit(d.state, fmt.Sprintf("STOPSIGNAL %v", c.Signal))
return d.builder.commit(ctx, d.state, fmt.Sprintf("STOPSIGNAL %v", c.Signal))
}
// ARG name[=value]
@@ -569,7 +573,7 @@ func dispatchStopSignal(d dispatchRequest, c *instructions.StopSignalCommand) er
// Adds the variable foo to the trusted list of variables that can be passed
// to builder using the --build-arg flag for expansion/substitution or passing to 'run'.
// Dockerfile author may optionally set a default value of this variable.
func dispatchArg(d dispatchRequest, c *instructions.ArgCommand) error {
func dispatchArg(ctx context.Context, d dispatchRequest, c *instructions.ArgCommand) error {
var commitStr strings.Builder
commitStr.WriteString("ARG ")
for i, arg := range c.Args {
@@ -584,13 +588,13 @@ func dispatchArg(d dispatchRequest, c *instructions.ArgCommand) error {
d.state.buildArgs.AddArg(arg.Key, arg.Value)
}
return d.builder.commit(d.state, commitStr.String())
return d.builder.commit(ctx, d.state, commitStr.String())
}
// SHELL powershell -command
//
// Set the non-default shell to use.
func dispatchShell(d dispatchRequest, c *instructions.ShellCommand) error {
func dispatchShell(ctx context.Context, d dispatchRequest, c *instructions.ShellCommand) error {
d.state.runConfig.Shell = c.Shell
return d.builder.commit(d.state, fmt.Sprintf("SHELL %v", d.state.runConfig.Shell))
return d.builder.commit(ctx, d.state, fmt.Sprintf("SHELL %v", d.state.runConfig.Shell))
}
+22 -19
View File
@@ -20,6 +20,7 @@
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
import (
"context"
"reflect"
"strconv"
"strings"
@@ -27,6 +28,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/builder"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig/opts"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
@@ -34,7 +36,7 @@ import (
"github.com/pkg/errors"
)
func dispatch(d dispatchRequest, cmd instructions.Command) (err error) {
func dispatch(ctx context.Context, d dispatchRequest, cmd instructions.Command) (err error) {
if c, ok := cmd.(instructions.PlatformSpecific); ok {
err := c.CheckPlatform(d.state.operatingSystem)
if err != nil {
@@ -65,39 +67,39 @@ func dispatch(d dispatchRequest, cmd instructions.Command) (err error) {
}()
switch c := cmd.(type) {
case *instructions.EnvCommand:
return dispatchEnv(d, c)
return dispatchEnv(ctx, d, c)
case *instructions.MaintainerCommand:
return dispatchMaintainer(d, c)
return dispatchMaintainer(ctx, d, c)
case *instructions.LabelCommand:
return dispatchLabel(d, c)
return dispatchLabel(ctx, d, c)
case *instructions.AddCommand:
return dispatchAdd(d, c)
return dispatchAdd(ctx, d, c)
case *instructions.CopyCommand:
return dispatchCopy(d, c)
return dispatchCopy(ctx, d, c)
case *instructions.OnbuildCommand:
return dispatchOnbuild(d, c)
return dispatchOnbuild(ctx, d, c)
case *instructions.WorkdirCommand:
return dispatchWorkdir(d, c)
return dispatchWorkdir(ctx, d, c)
case *instructions.RunCommand:
return dispatchRun(d, c)
return dispatchRun(ctx, d, c)
case *instructions.CmdCommand:
return dispatchCmd(d, c)
return dispatchCmd(ctx, d, c)
case *instructions.HealthCheckCommand:
return dispatchHealthcheck(d, c)
return dispatchHealthcheck(ctx, d, c)
case *instructions.EntrypointCommand:
return dispatchEntrypoint(d, c)
return dispatchEntrypoint(ctx, d, c)
case *instructions.ExposeCommand:
return dispatchExpose(d, c, envs)
return dispatchExpose(ctx, d, c, envs)
case *instructions.UserCommand:
return dispatchUser(d, c)
return dispatchUser(ctx, d, c)
case *instructions.VolumeCommand:
return dispatchVolume(d, c)
return dispatchVolume(ctx, d, c)
case *instructions.StopSignalCommand:
return dispatchStopSignal(d, c)
return dispatchStopSignal(ctx, d, c)
case *instructions.ArgCommand:
return dispatchArg(d, c)
return dispatchArg(ctx, d, c)
case *instructions.ShellCommand:
return dispatchShell(d, c)
return dispatchShell(ctx, d, c)
}
return errors.Errorf("unsupported command type: %v", reflect.TypeOf(cmd))
}
@@ -235,7 +237,8 @@ func (s *dispatchState) beginStage(stageName string, image builder.Image) error
// Add the default PATH to runConfig.ENV if one exists for the operating system and there
// is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS
func (s *dispatchState) setDefaultPath() {
defaultPath := system.DefaultPathEnv(s.operatingSystem)
// TODO(thaJeztah): use github.com/moby/buildkit/util/system.DefaultPathEnv() once https://github.com/moby/buildkit/pull/3158 is resolved.
defaultPath := oci.DefaultPathEnv(s.operatingSystem)
if defaultPath == "" {
return
}
+7 -7
View File
@@ -8,12 +8,12 @@ import (
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/builder"
dockerimage "github.com/docker/docker/image"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type getAndMountFunc func(string, bool, *specs.Platform) (builder.Image, builder.ROLayer, error)
type getAndMountFunc func(context.Context, string, bool, *ocispec.Platform) (builder.Image, builder.ROLayer, error)
// imageSources mounts images and provides a cache for mounted images. It tracks
// all images so they can be unmounted at the end of the build.
@@ -23,8 +23,8 @@ type imageSources struct {
getImage getAndMountFunc
}
func newImageSources(ctx context.Context, options builderOptions) *imageSources {
getAndMount := func(idOrRef string, localOnly bool, platform *specs.Platform) (builder.Image, builder.ROLayer, error) {
func newImageSources(options builderOptions) *imageSources {
getAndMount := func(ctx context.Context, idOrRef string, localOnly bool, platform *ocispec.Platform) (builder.Image, builder.ROLayer, error) {
pullOption := backend.PullOptionNoPull
if !localOnly {
if options.Options.PullParent {
@@ -47,12 +47,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
}
}
func (m *imageSources) Get(idOrRef string, localOnly bool, platform *specs.Platform) (*imageMount, error) {
func (m *imageSources) Get(ctx context.Context, idOrRef string, localOnly bool, platform *ocispec.Platform) (*imageMount, error) {
if im, ok := m.byImageID[idOrRef]; ok {
return im, nil
}
image, layer, err := m.getImage(idOrRef, localOnly, platform)
image, layer, err := m.getImage(ctx, idOrRef, localOnly, platform)
if err != nil {
return nil, err
}
@@ -71,7 +71,7 @@ func (m *imageSources) Unmount() (retErr error) {
return
}
func (m *imageSources) Add(im *imageMount, platform *specs.Platform) {
func (m *imageSources) Add(im *imageMount, platform *ocispec.Platform) {
switch im.image {
case nil:
// Set the platform for scratch images
+26 -10
View File
@@ -1,6 +1,8 @@
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
import (
"context"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/builder"
"github.com/sirupsen/logrus"
@@ -9,30 +11,42 @@ import (
// ImageProber exposes an Image cache to the Builder. It supports resetting a
// cache.
type ImageProber interface {
Reset()
Reset(ctx context.Context) error
Probe(parentID string, runConfig *container.Config) (string, error)
}
type resetFunc func(context.Context) (builder.ImageCache, error)
type imageProber struct {
cache builder.ImageCache
reset func() builder.ImageCache
reset resetFunc
cacheBusted bool
}
func newImageProber(cacheBuilder builder.ImageCacheBuilder, cacheFrom []string, noCache bool) ImageProber {
func newImageProber(ctx context.Context, cacheBuilder builder.ImageCacheBuilder, cacheFrom []string, noCache bool) (ImageProber, error) {
if noCache {
return &nopProber{}
return &nopProber{}, nil
}
reset := func() builder.ImageCache {
return cacheBuilder.MakeImageCache(cacheFrom)
reset := func(ctx context.Context) (builder.ImageCache, error) {
return cacheBuilder.MakeImageCache(ctx, cacheFrom)
}
return &imageProber{cache: reset(), reset: reset}
cache, err := reset(ctx)
if err != nil {
return nil, err
}
return &imageProber{cache: cache, reset: reset}, nil
}
func (c *imageProber) Reset() {
c.cache = c.reset()
func (c *imageProber) Reset(ctx context.Context) error {
newCache, err := c.reset(ctx)
if err != nil {
return err
}
c.cache = newCache
c.cacheBusted = false
return nil
}
// Probe checks if cache match can be found for current build instruction.
@@ -56,7 +70,9 @@ func (c *imageProber) Probe(parentID string, runConfig *container.Config) (strin
type nopProber struct{}
func (c *nopProber) Reset() {}
func (c *nopProber) Reset(ctx context.Context) error {
return nil
}
func (c *nopProber) Probe(_ string, _ *container.Config) (string, error) {
return "", nil
+29 -65
View File
@@ -4,10 +4,10 @@ package dockerfile // import "github.com/docker/docker/builder/dockerfile"
// non-contiguous functionality. Please read the comments.
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"strings"
"github.com/docker/docker/api/types"
@@ -17,62 +17,18 @@ import (
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-connections/nat"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Archiver defines an interface for copying files from one destination to
// another using Tar/Untar.
type Archiver interface {
TarUntar(src, dst string) error
UntarPath(src, dst string) error
CopyWithTar(src, dst string) error
CopyFileWithTar(src, dst string) error
IdentityMapping() idtools.IdentityMapping
func (b *Builder) getArchiver() *archive.Archiver {
return chrootarchive.NewArchiver(b.idMapping)
}
// The builder will use the following interfaces if the container fs implements
// these for optimized copies to and from the container.
type extractor interface {
ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error
}
type archiver interface {
ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error)
}
// helper functions to get tar/untar func
func untarFunc(i interface{}) containerfs.UntarFunc {
if ea, ok := i.(extractor); ok {
return ea.ExtractArchive
}
return chrootarchive.Untar
}
func tarFunc(i interface{}) containerfs.TarFunc {
if ap, ok := i.(archiver); ok {
return ap.ArchivePath
}
return archive.TarWithOptions
}
func (b *Builder) getArchiver(src, dst containerfs.Driver) Archiver {
t, u := tarFunc(src), untarFunc(dst)
return &containerfs.Archiver{
SrcDriver: src,
DstDriver: dst,
Tar: t,
Untar: u,
IDMapping: b.idMapping,
}
}
func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
func (b *Builder) commit(ctx context.Context, dispatchState *dispatchState, comment string) error {
if b.disableCommit {
return nil
}
@@ -81,15 +37,15 @@ func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
}
runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, dispatchState.operatingSystem))
id, err := b.probeAndCreate(dispatchState, runConfigWithCommentCmd)
id, err := b.probeAndCreate(ctx, dispatchState, runConfigWithCommentCmd)
if err != nil || id == "" {
return err
}
return b.commitContainer(dispatchState, id, runConfigWithCommentCmd)
return b.commitContainer(ctx, dispatchState, id, runConfigWithCommentCmd)
}
func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
func (b *Builder) commitContainer(ctx context.Context, dispatchState *dispatchState, id string, containerConfig *container.Config) error {
if b.disableCommit {
return nil
}
@@ -102,12 +58,12 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta
ContainerID: id,
}
imageID, err := b.docker.CommitBuildStep(commitCfg)
imageID, err := b.docker.CommitBuildStep(ctx, commitCfg)
dispatchState.imageID = string(imageID)
return err
}
func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error {
func (b *Builder) exportImage(ctx context.Context, state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error {
newLayer, err := layer.Commit()
if err != nil {
return err
@@ -118,7 +74,7 @@ func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, paren
return errors.Errorf("unexpected image type")
}
platform := &specs.Platform{
platform := &ocispec.Platform{
OS: parentImage.OS,
Architecture: parentImage.Architecture,
Variant: parentImage.Variant,
@@ -142,7 +98,15 @@ func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, paren
return errors.Wrap(err, "failed to encode image config")
}
exportedImage, err := b.docker.CreateImage(config, state.imageID)
// when writing the new image's manifest, we now need to pass in the new layer's digest.
// before the containerd store work this was unnecessary since we get the layer id
// from the image's RootFS ChainID -- see:
// https://github.com/moby/moby/blob/8cf66ed7322fa885ef99c4c044fa23e1727301dc/image/store.go#L162
// however, with the containerd store we can't do this. An alternative implementation here
// without changing the signature would be to get the layer digest by walking the content store
// and filtering the objects to find the layer with the DiffID we want, but that has performance
// implications that should be called out/investigated
exportedImage, err := b.docker.CreateImage(ctx, config, state.imageID, newLayer.ContentStoreDigest())
if err != nil {
return errors.Wrapf(err, "failed to export image")
}
@@ -152,7 +116,7 @@ func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, paren
return nil
}
func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error {
func (b *Builder) performCopy(ctx context.Context, req dispatchRequest, inst copyInstruction) error {
state := req.state
srcHash := getSourceHashFromInfos(inst.infos)
@@ -171,7 +135,7 @@ func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error {
return err
}
imageMount, err := b.imageSources.Get(state.imageID, true, req.builder.platform)
imageMount, err := b.imageSources.Get(ctx, state.imageID, true, req.builder.platform)
if err != nil {
return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
}
@@ -192,7 +156,7 @@ func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error {
// translated (if necessary because of user namespaces), and replace
// the root pair with the chown pair for copy operations
if inst.chownStr != "" {
identity, err = parseChownFlag(b, state, inst.chownStr, destInfo.root.Path(), b.idMapping)
identity, err = parseChownFlag(ctx, b, state, inst.chownStr, destInfo.root, b.idMapping)
if err != nil {
if b.options.Platform != "windows" {
return errors.Wrapf(err, "unable to convert uid/gid chown string to host mapping")
@@ -205,7 +169,7 @@ func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error {
for _, info := range inst.infos {
opts := copyFileOptions{
decompress: inst.allowLocalDecompression,
archiver: b.getArchiver(info.root, destInfo.root),
archiver: b.getArchiver(),
}
if !inst.preserveOwnership {
opts.identity = &identity
@@ -214,7 +178,7 @@ func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error {
return errors.Wrapf(err, "failed to copy files")
}
}
return b.exportImage(state, rwLayer, imageMount.Image(), runConfigWithCommentCmd)
return b.exportImage(ctx, state, rwLayer, imageMount.Image(), runConfigWithCommentCmd)
}
func createDestInfo(workingDir string, inst copyInstruction, rwLayer builder.RWLayer, platform string) (copyInfo, error) {
@@ -376,18 +340,18 @@ func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.
var defaultLogConfig = container.LogConfig{Type: "none"}
func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *container.Config) (string, error) {
func (b *Builder) probeAndCreate(ctx context.Context, dispatchState *dispatchState, runConfig *container.Config) (string, error) {
if hit, err := b.probeCache(dispatchState, runConfig); err != nil || hit {
return "", err
}
return b.create(runConfig)
return b.create(ctx, runConfig)
}
func (b *Builder) create(runConfig *container.Config) (string, error) {
func (b *Builder) create(ctx context.Context, runConfig *container.Config) (string, error) {
logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd)
hostConfig := hostConfigFromOptions(b.options)
container, err := b.containerManager.Create(runConfig, hostConfig)
container, err := b.containerManager.Create(ctx, runConfig, hostConfig)
if err != nil {
return "", err
}
+2 -1
View File
@@ -1,6 +1,7 @@
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
import (
"context"
"path/filepath"
"strconv"
"strings"
@@ -11,7 +12,7 @@ import (
"github.com/pkg/errors"
)
func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping idtools.IdentityMapping) (idtools.Identity, error) {
func parseChownFlag(ctx context.Context, builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping idtools.IdentityMapping) (idtools.Identity, error) {
var userStr, grpStr string
parts := strings.Split(chown, ":")
if len(parts) > 2 {
+8 -7
View File
@@ -2,6 +2,7 @@ package dockerfile // import "github.com/docker/docker/builder/dockerfile"
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
@@ -14,15 +15,15 @@ import (
"golang.org/x/sys/windows"
)
func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping idtools.IdentityMapping) (idtools.Identity, error) {
func parseChownFlag(ctx context.Context, builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping idtools.IdentityMapping) (idtools.Identity, error) {
if builder.options.Platform == "windows" {
return getAccountIdentity(builder, chown, ctrRootPath, state)
return getAccountIdentity(ctx, builder, chown, ctrRootPath, state)
}
return identityMapping.RootPair(), nil
}
func getAccountIdentity(builder *Builder, accountName string, ctrRootPath string, state *dispatchState) (idtools.Identity, error) {
func getAccountIdentity(ctx context.Context, builder *Builder, accountName string, ctrRootPath string, state *dispatchState) (idtools.Identity, error) {
// If this is potentially a string SID then attempt to convert it to verify
// this, otherwise continue looking for the account.
if strings.HasPrefix(accountName, "S-") || strings.HasPrefix(accountName, "s-") {
@@ -51,10 +52,10 @@ func getAccountIdentity(builder *Builder, accountName string, ctrRootPath string
// All other lookups failed, so therefore determine if the account in
// question exists in the container and if so, obtain its SID.
return lookupNTAccount(builder, accountName, state)
return lookupNTAccount(ctx, builder, accountName, state)
}
func lookupNTAccount(builder *Builder, accountName string, state *dispatchState) (idtools.Identity, error) {
func lookupNTAccount(ctx context.Context, builder *Builder, accountName string, state *dispatchState) (idtools.Identity, error) {
source, _ := filepath.Split(os.Args[0])
@@ -81,7 +82,7 @@ func lookupNTAccount(builder *Builder, accountName string, state *dispatchState)
},
}
container, err := builder.containerManager.Create(runConfig, hostConfig)
container, err := builder.containerManager.Create(ctx, runConfig, hostConfig)
if err != nil {
return idtools.Identity{}, err
}
@@ -89,7 +90,7 @@ func lookupNTAccount(builder *Builder, accountName string, state *dispatchState)
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
if err := builder.containerManager.Run(builder.clientCtx, container.ID, stdout, stderr); err != nil {
if err := builder.containerManager.Run(ctx, container.ID, stdout, stderr); err != nil {
if err, ok := err.(*statusCodeError); ok {
return idtools.Identity{}, &jsonmessage.JSONError{
Message: stderr.String(),
+11 -11
View File
@@ -9,18 +9,18 @@ import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/longpath"
"github.com/docker/docker/pkg/tarsum"
"github.com/pkg/errors"
)
type archiveContext struct {
root containerfs.ContainerFS
root string
sums tarsum.FileInfoSums
}
func (c *archiveContext) Close() error {
return c.root.RemoveAll(c.root.Path())
return os.RemoveAll(c.root)
}
func convertPathError(err error, cleanpath string) error {
@@ -47,13 +47,13 @@ type modifiableContext interface {
//
// Closing tarStream has to be done by the caller.
func FromArchive(tarStream io.Reader) (builder.Source, error) {
root, err := ioutils.TempDir("", "docker-builder")
root, err := longpath.MkdirTemp("", "docker-builder")
if err != nil {
return nil, err
}
// Assume local file system. Since it's coming from a tar file.
tsc := &archiveContext{root: containerfs.NewLocalContainerFS(root)}
tsc := &archiveContext{root: root}
// Make sure we clean-up upon error. In the happy case the caller
// is expected to manage the clean-up
@@ -82,7 +82,7 @@ func FromArchive(tarStream io.Reader) (builder.Source, error) {
return tsc, nil
}
func (c *archiveContext) Root() containerfs.ContainerFS {
func (c *archiveContext) Root() string {
return c.root
}
@@ -91,7 +91,7 @@ func (c *archiveContext) Remove(path string) error {
if err != nil {
return err
}
return c.root.RemoveAll(fullpath)
return os.RemoveAll(fullpath)
}
func (c *archiveContext) Hash(path string) (string, error) {
@@ -100,7 +100,7 @@ func (c *archiveContext) Hash(path string) (string, error) {
return "", err
}
rel, err := c.root.Rel(c.root.Path(), fullpath)
rel, err := filepath.Rel(c.root, fullpath)
if err != nil {
return "", convertPathError(err, cleanpath)
}
@@ -115,9 +115,9 @@ func (c *archiveContext) Hash(path string) (string, error) {
return path, nil // backwards compat TODO: see if really needed
}
func normalize(path string, root containerfs.ContainerFS) (cleanPath, fullPath string, err error) {
cleanPath = root.Clean(string(root.Separator()) + path)[1:]
fullPath, err = root.ResolveScopedPath(path, true)
func normalize(path string, root string) (cleanPath, fullPath string, err error) {
cleanPath = filepath.Clean(string(filepath.Separator) + path)[1:]
fullPath, err = containerfs.ResolveScopedPath(root, path)
if err != nil {
return "", "", errors.Wrapf(err, "forbidden path outside the build context: %s (%s)", path, cleanPath)
}
+7 -6
View File
@@ -13,9 +13,10 @@ import (
"github.com/docker/docker/builder"
"github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/docker/docker/errdefs"
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
"github.com/docker/docker/pkg/containerfs"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/moby/patternmatcher"
"github.com/moby/patternmatcher/ignorefile"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -122,10 +123,10 @@ func removeDockerfile(c modifiableContext, filesToRemove ...string) error {
case err != nil:
return err
}
excludes, err := dockerignore.ReadAll(f)
excludes, err := ignorefile.ReadAll(f)
if err != nil {
f.Close()
return err
return errors.Wrap(err, "error reading .dockerignore")
}
f.Close()
filesToRemove = append([]string{".dockerignore"}, filesToRemove...)
@@ -161,7 +162,7 @@ func openAt(remote builder.Source, path string) (driver.File, error) {
if err != nil {
return nil, err
}
return remote.Root().Open(fullPath)
return os.Open(fullPath)
}
// StatAt is a helper for calling Stat on a path from a source
@@ -170,12 +171,12 @@ func StatAt(remote builder.Source, path string) (os.FileInfo, error) {
if err != nil {
return nil, err
}
return remote.Root().Stat(fullPath)
return os.Stat(fullPath)
}
// FullPath is a helper for getting a full path for a path from a source
func FullPath(remote builder.Source, path string) (string, error) {
fullPath, err := remote.Root().ResolveScopedPath(path, true)
fullPath, err := containerfs.ResolveScopedPath(remote.Root(), path)
if err != nil {
if runtime.GOOS == "windows" {
return "", fmt.Errorf("failed to resolve scoped path %s (%s): %s. Possible cause is a forbidden path outside the build context", path, fullPath, err)
+7 -16
View File
@@ -4,12 +4,12 @@ import (
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/moby/sys/symlink"
"github.com/pkg/errors"
exec "golang.org/x/sys/execabs"
)
type gitRepo struct {
@@ -97,15 +97,10 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) {
remoteURL = "https://" + remoteURL
}
var fragment string
if strings.HasPrefix(remoteURL, "git@") {
// git@.. is not an URL, so cannot be parsed as URL
parts := strings.SplitN(remoteURL, "#", 2)
repo.remote = parts[0]
if len(parts) == 2 {
fragment = parts[1]
}
var fragment string
repo.remote, fragment, _ = strings.Cut(remoteURL, "#")
repo.ref, repo.subdir = getRefAndSubdir(fragment)
} else {
u, err := url.Parse(remoteURL)
@@ -126,15 +121,11 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) {
}
func getRefAndSubdir(fragment string) (ref string, subdir string) {
refAndDir := strings.SplitN(fragment, ":", 2)
ref = "master"
if len(refAndDir[0]) != 0 {
ref = refAndDir[0]
ref, subdir, _ = strings.Cut(fragment, ":")
if ref == "" {
ref = "master"
}
if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
subdir = refAndDir[1]
}
return
return ref, subdir
}
func fetchArgs(remoteURL string, ref string) []string {
+12 -11
View File
@@ -3,10 +3,11 @@ package remotecontext // import "github.com/docker/docker/builder/remotecontext"
import (
"encoding/hex"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/docker/docker/builder"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/pools"
"github.com/pkg/errors"
)
@@ -14,7 +15,7 @@ import (
// NewLazySource creates a new LazyContext. LazyContext defines a hashed build
// context based on a root directory. Individual files are hashed first time
// they are asked. It is not safe to call methods of LazyContext concurrently.
func NewLazySource(root containerfs.ContainerFS) (builder.Source, error) {
func NewLazySource(root string) (builder.Source, error) {
return &lazySource{
root: root,
sums: make(map[string]string),
@@ -22,11 +23,11 @@ func NewLazySource(root containerfs.ContainerFS) (builder.Source, error) {
}
type lazySource struct {
root containerfs.ContainerFS
root string
sums map[string]string
}
func (c *lazySource) Root() containerfs.ContainerFS {
func (c *lazySource) Root() string {
return c.root
}
@@ -45,7 +46,7 @@ func (c *lazySource) Hash(path string) (string, error) {
return "", errors.WithStack(convertPathError(err, cleanPath))
}
fi, err := c.root.Lstat(fullPath)
fi, err := os.Lstat(fullPath)
if err != nil {
// Backwards compatibility: a missing file returns a path as hash.
// This is reached in the case of a broken symlink.
@@ -64,13 +65,13 @@ func (c *lazySource) Hash(path string) (string, error) {
}
func (c *lazySource) prepareHash(relPath string, fi os.FileInfo) (string, error) {
p := c.root.Join(c.root.Path(), relPath)
p := filepath.Join(c.root, relPath)
h, err := NewFileHash(p, relPath, fi)
if err != nil {
return "", errors.Wrapf(err, "failed to create hash for %s", relPath)
}
if fi.Mode().IsRegular() && fi.Size() > 0 {
f, err := c.root.Open(p)
f, err := os.Open(p)
if err != nil {
return "", errors.Wrapf(err, "failed to open %s", relPath)
}
@@ -86,10 +87,10 @@ func (c *lazySource) prepareHash(relPath string, fi os.FileInfo) (string, error)
// Rel makes a path relative to base path. Same as `filepath.Rel` but can also
// handle UUID paths in windows.
func Rel(basepath containerfs.ContainerFS, targpath string) (string, error) {
func Rel(basepath string, targpath string) (string, error) {
// filepath.Rel can't handle UUID paths in windows
if basepath.OS() == "windows" {
pfx := basepath.Path() + `\`
if runtime.GOOS == "windows" {
pfx := basepath + `\`
if strings.HasPrefix(targpath, pfx) {
p := strings.TrimPrefix(targpath, pfx)
if p == "" {
@@ -98,5 +99,5 @@ func Rel(basepath containerfs.ContainerFS, targpath string) (string, error) {
return p, nil
}
}
return basepath.Rel(basepath.Path(), targpath)
return filepath.Rel(basepath, targpath)
}
+11 -7
View File
@@ -2,9 +2,9 @@ package remotecontext // import "github.com/docker/docker/builder/remotecontext"
import (
"os"
"path/filepath"
"sync"
"github.com/docker/docker/pkg/containerfs"
iradix "github.com/hashicorp/go-immutable-radix"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
@@ -15,19 +15,23 @@ type hashed interface {
Digest() digest.Digest
}
// CachableSource is a source that contains cache records for its contents
// CachableSource is a source that contains cache records for its contents.
//
// Deprecated: this type was used for the experimental "stream" support for the classic builder, which is no longer supported.
type CachableSource struct {
mu sync.Mutex
root containerfs.ContainerFS
root string
tree *iradix.Tree
txn *iradix.Txn
}
// NewCachableSource creates new CachableSource
// NewCachableSource creates new CachableSource.
//
// Deprecated: this type was used for the experimental "stream" support for the classic builder, which is no longer supported.
func NewCachableSource(root string) *CachableSource {
ts := &CachableSource{
tree: iradix.New(),
root: containerfs.NewLocalContainerFS(root),
root: root,
}
return ts
}
@@ -66,7 +70,7 @@ func (cs *CachableSource) Scan() error {
return err
}
txn := iradix.New().Txn()
err = cs.root.Walk(cs.root.Path(), func(path string, info os.FileInfo, err error) error {
err = filepath.WalkDir(cs.root, func(path string, _ os.DirEntry, err error) error {
if err != nil {
return errors.Wrapf(err, "failed to walk %s", path)
}
@@ -144,7 +148,7 @@ func (cs *CachableSource) Hash(path string) (string, error) {
}
// Root returns a root directory for the source
func (cs *CachableSource) Root() containerfs.ContainerFS {
func (cs *CachableSource) Root() string {
return cs.root
}
+5 -5
View File
@@ -3,8 +3,8 @@ package client // import "github.com/docker/docker/client"
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
@@ -23,12 +23,12 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
if opts.All {
query.Set("all", "1")
}
query.Set("keep-storage", fmt.Sprintf("%d", opts.KeepStorage))
filters, err := filters.ToJSON(opts.Filters)
query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage)))
f, err := filters.ToJSON(opts.Filters)
if err != nil {
return nil, errors.Wrap(err, "prune could not marshal filters option")
}
query.Set("filters", filters)
query.Set("filters", f)
serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
defer ensureReaderClosed(serverResp)
@@ -38,7 +38,7 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
}
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
return nil, fmt.Errorf("Error retrieving disk usage: %v", err)
return nil, errors.Wrap(err, "error retrieving disk usage")
}
return &report, nil
+43 -10
View File
@@ -56,6 +56,36 @@ import (
"github.com/pkg/errors"
)
// DummyHost is a hostname used for local communication.
//
// It acts as a valid formatted hostname for local connections (such as "unix://"
// or "npipe://") which do not require a hostname. It should never be resolved,
// but uses the special-purpose ".localhost" TLD (as defined in [RFC 2606, Section 2]
// and [RFC 6761, Section 6.3]).
//
// [RFC 7230, Section 5.4] defines that an empty header must be used for such
// cases:
//
// If the authority component is missing or undefined for the target URI,
// then a client MUST send a Host header field with an empty field-value.
//
// However, [Go stdlib] enforces the semantics of HTTP(S) over TCP, does not
// allow an empty header to be used, and requires req.URL.Scheme to be either
// "http" or "https".
//
// For further details, refer to:
//
// - https://github.com/docker/engine-api/issues/189
// - https://github.com/golang/go/issues/13624
// - https://github.com/golang/go/issues/61076
// - https://github.com/moby/moby/issues/45935
//
// [RFC 2606, Section 2]: https://www.rfc-editor.org/rfc/rfc2606.html#section-2
// [RFC 6761, Section 6.3]: https://www.rfc-editor.org/rfc/rfc6761#section-6.3
// [RFC 7230, Section 5.4]: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
// [Go stdlib]: https://github.com/golang/go/blob/6244b1946bc2101b01955468f1be502dbadd6807/src/net/http/transport.go#L558-L569
const DummyHost = "api.moby.localhost"
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
var ErrRedirect = errors.New("unexpected redirect in response")
@@ -126,7 +156,12 @@ func CheckRedirect(req *http.Request, via []*http.Request) error {
// client.WithAPIVersionNegotiation(),
// )
func NewClientWithOpts(ops ...Opt) (*Client, error) {
client, err := defaultHTTPClient(DefaultDockerHost)
hostURL, err := ParseHostURL(DefaultDockerHost)
if err != nil {
return nil, err
}
client, err := defaultHTTPClient(hostURL)
if err != nil {
return nil, err
}
@@ -134,8 +169,8 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
host: DefaultDockerHost,
version: api.DefaultVersion,
client: client,
proto: defaultProto,
addr: defaultAddr,
proto: hostURL.Scheme,
addr: hostURL.Host,
}
for _, op := range ops {
@@ -161,13 +196,12 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
return c, nil
}
func defaultHTTPClient(host string) (*http.Client, error) {
hostURL, err := ParseHostURL(host)
func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) {
transport := &http.Transport{}
err := sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
if err != nil {
return nil, err
}
transport := &http.Transport{}
_ = sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
return &http.Client{
Transport: transport,
CheckRedirect: CheckRedirect,
@@ -283,13 +317,12 @@ func (cli *Client) HTTPClient() *http.Client {
// ParseHostURL parses a url string, validates the string is a host url, and
// returns the parsed URL
func ParseHostURL(host string) (*url.URL, error) {
protoAddrParts := strings.SplitN(host, "://", 2)
if len(protoAddrParts) == 1 {
proto, addr, ok := strings.Cut(host, "://")
if !ok || addr == "" {
return nil, errors.Errorf("unable to parse docker host `%s`", host)
}
var basePath string
proto, addr := protoAddrParts[0], protoAddrParts[1]
if proto == "tcp" {
parsed, err := url.Parse("tcp://" + addr)
if err != nil {
+2 -5
View File
@@ -1,11 +1,8 @@
//go:build linux || freebsd || openbsd || netbsd || darwin || solaris || illumos || dragonfly
// +build linux freebsd openbsd netbsd darwin solaris illumos dragonfly
//go:build !windows
// +build !windows
package client // import "github.com/docker/docker/client"
// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
// (EnvOverrideHost) environment variable is unset or empty.
const DefaultDockerHost = "unix:///var/run/docker.sock"
const defaultProto = "unix"
const defaultAddr = "/var/run/docker.sock"
-3
View File
@@ -3,6 +3,3 @@ package client // import "github.com/docker/docker/client"
// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
// (EnvOverrideHost) environment variable is unset or empty.
const DefaultDockerHost = "npipe:////./pipe/docker_engine"
const defaultProto = "npipe"
const defaultAddr = "//./pipe/docker_engine"
+3 -3
View File
@@ -9,7 +9,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type configWrapper struct {
@@ -20,7 +20,7 @@ type configWrapper struct {
// ContainerCreate creates a new container based on the given configuration.
// It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.CreateResponse, error) {
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
var response container.CreateResponse
if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
@@ -75,7 +75,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
// Similar to containerd's platforms.Format(), but does allow components to be
// omitted (e.g. pass "architecture" only, without "os":
// https://github.com/containerd/containerd/blob/v1.5.2/platforms/platforms.go#L243-L263
func formatPlatform(platform *specs.Platform) string {
func formatPlatform(platform *ocispec.Platform) string {
if platform == nil {
return ""
}
+2 -2
View File
@@ -9,8 +9,8 @@ import (
)
// ContainerDiff shows differences in a container filesystem since it was started.
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.ContainerChangeResponseItem, error) {
var changes []container.ContainerChangeResponseItem
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
var changes []container.FilesystemChange
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
defer ensureReaderClosed(serverResp)
+4 -4
View File
@@ -5,13 +5,13 @@ import (
"encoding/json"
"net/url"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/registry"
)
// DistributionInspect returns the image digest with the full manifest.
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) {
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error) {
// Contact the registry to retrieve digest and platform information
var distributionInspect registrytypes.DistributionInspect
var distributionInspect registry.DistributionInspect
if image == "" {
return distributionInspect, objectNotFoundError{object: "distribution", id: image}
}
@@ -23,7 +23,7 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
if encodedRegistryAuth != "" {
headers = map[string][]string{
"X-Registry-Auth": {encodedRegistryAuth},
registry.AuthHeader: {encodedRegistryAuth},
}
}
-25
View File
@@ -58,31 +58,6 @@ func (e objectNotFoundError) Error() string {
return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
}
// IsErrUnauthorized returns true if the error is caused
// when a remote registry authentication fails
//
// Deprecated: use errdefs.IsUnauthorized
func IsErrUnauthorized(err error) bool {
return errdefs.IsUnauthorized(err)
}
type pluginPermissionDenied struct {
name string
}
func (e pluginPermissionDenied) Error() string {
return "Permission denied while installing plugin " + e.name
}
// IsErrNotImplemented returns true if the error is a NotImplemented error.
// This is returned by the API when a requested feature has not been
// implemented.
//
// Deprecated: use errdefs.IsNotImplemented
func IsErrNotImplemented(err error) bool {
return errdefs.IsNotImplemented(err)
}
// NewVersionError returns an error if the APIVersion required
// if less than the current supported version
func (cli *Client) NewVersionError(APIrequired, feature string) error {
+4 -9
View File
@@ -23,14 +23,10 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
if err != nil {
return types.HijackedResponse{}, err
}
apiPath := cli.getAPIPath(ctx, path, query)
req, err := http.NewRequest(http.MethodPost, apiPath, bodyEncoded)
req, err := cli.buildRequest(http.MethodPost, cli.getAPIPath(ctx, path, query), bodyEncoded, headers)
if err != nil {
return types.HijackedResponse{}, err
}
req = cli.addHeaders(req, headers)
conn, mediaType, err := cli.setupHijackConn(ctx, req, "tcp")
if err != nil {
return types.HijackedResponse{}, err
@@ -64,7 +60,6 @@ func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
}
func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto string) (net.Conn, string, error) {
req.Host = cli.addr
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", proto)
@@ -80,8 +75,8 @@ func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
_ = tcpConn.SetKeepAlive(true)
_ = tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
clientconn := httputil.NewClientConn(conn, nil)
@@ -96,7 +91,7 @@ func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto
return nil, "", err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
resp.Body.Close()
_ = resp.Body.Close()
return nil, "", fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
)
// ImageCreate creates a new image based on the parent options.
@@ -32,6 +33,6 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
return cli.post(ctx, "/images/create", query, nil, headers)
}
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/errdefs"
)
@@ -49,6 +50,6 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options types.Im
}
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers)
}
+1 -1
View File
@@ -48,6 +48,6 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options types.I
}
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
return cli.get(ctx, "/images/search", query, headers)
}
+5 -5
View File
@@ -15,7 +15,7 @@ import (
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/volume"
specs "github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// CommonAPIClient is the common methods between stable and experimental versions of APIClient.
@@ -47,8 +47,8 @@ type CommonAPIClient interface {
type ContainerAPIClient interface {
ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.IDResponse, error)
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.CreateResponse, error)
ContainerDiff(ctx context.Context, container string) ([]container.ContainerChangeResponseItem, error)
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error)
ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error)
ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)
ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error)
@@ -166,7 +166,7 @@ type SwarmAPIClient interface {
type SystemAPIClient interface {
Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error)
Info(ctx context.Context) (types.Info, error)
RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error)
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error)
Ping(ctx context.Context) (types.Ping, error)
}
@@ -176,7 +176,7 @@ type VolumeAPIClient interface {
VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error)
VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
VolumeList(ctx context.Context, filter filters.Args) (volume.ListResponse, error)
VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error)
VolumeRemove(ctx context.Context, volumeID string, force bool) error
VolumesPrune(ctx context.Context, pruneFilter filters.Args) (types.VolumesPruneReport, error)
VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error
+1 -2
View File
@@ -5,13 +5,12 @@ import (
"encoding/json"
"net/url"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
)
// RegistryLogin authenticates the docker server with a given docker registry.
// It returns unauthorizedError when the authentication fails.
func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error) {
func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) {
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
defer ensureReaderClosed(resp)
+3 -3
View File
@@ -64,10 +64,10 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
ping.BuilderVersion = types.BuilderVersion(bv)
}
if si := resp.header.Get("Swarm"); si != "" {
parts := strings.SplitN(si, "/", 2)
state, role, _ := strings.Cut(si, "/")
ping.SwarmStatus = &swarm.Status{
NodeState: swarm.LocalNodeState(parts[0]),
ControlAvailable: len(parts) == 2 && parts[1] == "manager",
NodeState: swarm.LocalNodeState(state),
ControlAvailable: role == "manager",
}
}
err := cli.checkResponseErr(resp)
+4 -3
View File
@@ -8,6 +8,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
)
@@ -67,12 +68,12 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
}
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
return cli.get(ctx, "/plugins/privileges", query, headers)
}
func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
return cli.post(ctx, "/plugins/pull", query, privileges, headers)
}
@@ -106,7 +107,7 @@ func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values,
return nil, err
}
if !accept {
return nil, pluginPermissionDenied{options.RemoteRef}
return nil, errors.Errorf("permission denied while installing plugin %s", options.RemoteRef)
}
}
return privileges, nil
+3 -1
View File
@@ -3,11 +3,13 @@ package client // import "github.com/docker/docker/client"
import (
"context"
"io"
"github.com/docker/docker/api/types/registry"
)
// PluginPush pushes a plugin to a registry
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers)
if err != nil {
return nil, err
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/pkg/errors"
)
@@ -34,6 +35,6 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types
}
func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, headers)
}
+4 -6
View File
@@ -96,16 +96,14 @@ func (cli *Client) buildRequest(method, path string, body io.Reader, headers hea
return nil, err
}
req = cli.addHeaders(req, headers)
req.URL.Scheme = cli.scheme
req.URL.Host = cli.addr
if cli.proto == "unix" || cli.proto == "npipe" {
// For local communications, it doesn't matter what the host is. We just
// need a valid and meaningful host name. (See #189)
req.Host = "docker"
// Override host header for non-tcp connections.
req.Host = DummyHost
}
req.URL.Host = cli.addr
req.URL.Scheme = cli.scheme
if expectedPayload && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "text/plain")
}
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
@@ -21,7 +22,7 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
}
if options.EncodedRegistryAuth != "" {
headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
}
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"net/url"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
)
@@ -23,7 +24,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
}
if options.EncodedRegistryAuth != "" {
headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
}
if options.RegistryAuthFrom != "" {
+3 -3
View File
@@ -10,13 +10,13 @@ import (
)
// VolumeList returns the volumes configured in the docker host.
func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (volume.ListResponse, error) {
func (cli *Client) VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
var volumes volume.ListResponse
query := url.Values{}
if filter.Len() > 0 {
if options.Filters.Len() > 0 {
//nolint:staticcheck // ignore SA1019 for old code
filterJSON, err := filters.ToParamWithVersion(cli.version, filter)
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
if err != nil {
return volumes, err
}
@@ -2,10 +2,10 @@ package container // import "github.com/docker/docker/container"
import (
"os"
"path/filepath"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/system"
"github.com/pkg/errors"
)
@@ -14,24 +14,21 @@ import (
// the absolute path to the resource relative to the container's rootfs, and
// an error if the path points to outside the container's rootfs.
func (container *Container) ResolvePath(path string) (resolvedPath, absPath string, err error) {
if container.BaseFS == nil {
return "", "", errors.New("ResolvePath: BaseFS of container " + container.ID + " is unexpectedly nil")
if container.BaseFS == "" {
return "", "", errors.New("ResolvePath: BaseFS of container " + container.ID + " is unexpectedly empty")
}
// Check if a drive letter supplied, it must be the system drive. No-op except on Windows
path, err = system.CheckSystemDriveAndRemoveDriveLetter(path, container.BaseFS)
path, err = archive.CheckSystemDriveAndRemoveDriveLetter(path)
if err != nil {
return "", "", err
}
// Consider the given path as an absolute path in the container.
absPath = archive.PreserveTrailingDotOrSeparator(
container.BaseFS.Join(string(container.BaseFS.Separator()), path),
path,
container.BaseFS.Separator())
absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
// Split the absPath into its Directory and Base components. We will
// resolve the dir in the scope of the container then append the base.
dirPath, basePath := container.BaseFS.Split(absPath)
dirPath, basePath := filepath.Split(absPath)
resolvedDirPath, err := container.GetResourcePath(dirPath)
if err != nil {
@@ -40,7 +37,7 @@ func (container *Container) ResolvePath(path string) (resolvedPath, absPath stri
// resolvedDirPath will have been cleaned (no trailing path separators) so
// we can manually join it with the base path element.
resolvedPath = resolvedDirPath + string(container.BaseFS.Separator()) + basePath
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
return resolvedPath, absPath, nil
}
@@ -49,12 +46,11 @@ func (container *Container) ResolvePath(path string) (resolvedPath, absPath stri
// resolved to a path on the host corresponding to the given absolute path
// inside the container.
func (container *Container) StatPath(resolvedPath, absPath string) (stat *types.ContainerPathStat, err error) {
if container.BaseFS == nil {
return nil, errors.New("StatPath: BaseFS of container " + container.ID + " is unexpectedly nil")
if container.BaseFS == "" {
return nil, errors.New("StatPath: BaseFS of container " + container.ID + " is unexpectedly empty")
}
driver := container.BaseFS
lstat, err := driver.Lstat(resolvedPath)
lstat, err := os.Lstat(resolvedPath)
if err != nil {
return nil, err
}
@@ -67,17 +63,17 @@ func (container *Container) StatPath(resolvedPath, absPath string) (stat *types.
return nil, err
}
linkTarget, err = driver.Rel(driver.Path(), hostPath)
linkTarget, err = filepath.Rel(container.BaseFS, hostPath)
if err != nil {
return nil, err
}
// Make it an absolute path.
linkTarget = driver.Join(string(driver.Separator()), linkTarget)
linkTarget = filepath.Join(string(filepath.Separator), linkTarget)
}
return &types.ContainerPathStat{
Name: driver.Base(absPath),
Name: filepath.Base(absPath),
Size: lstat.Size(),
Mode: lstat.Mode(),
Mtime: lstat.ModTime(),
+75 -36
View File
@@ -19,7 +19,6 @@ import (
mounttypes "github.com/docker/docker/api/types/mount"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/container/stream"
"github.com/docker/docker/daemon/exec"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/docker/docker/daemon/logger/local"
@@ -28,10 +27,11 @@ import (
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/restartmanager"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
@@ -39,6 +39,7 @@ import (
agentexec "github.com/moby/swarmkit/v2/agent/exec"
"github.com/moby/sys/signal"
"github.com/moby/sys/symlink"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -53,9 +54,6 @@ type ExitStatus struct {
// The exit code with which the container exited.
ExitCode int
// Whether the container encountered an OOM.
OOMKilled bool
// Time at which the container died
ExitedAt time.Time
}
@@ -64,10 +62,10 @@ type ExitStatus struct {
type Container struct {
StreamConfig *stream.Config
// embed for Container to support states directly.
*State `json:"State"` // Needed for Engine API version <= 1.11
Root string `json:"-"` // Path to the "home" of the container, including metadata.
BaseFS containerfs.ContainerFS `json:"-"` // interface containing graphdriver mount
RWLayer layer.RWLayer `json:"-"`
*State `json:"State"` // Needed for Engine API version <= 1.11
Root string `json:"-"` // Path to the "home" of the container, including metadata.
BaseFS string `json:"-"` // Path to the graphdriver mountpoint
RWLayer layer.RWLayer `json:"-"`
ID string
Created time.Time
Managed bool
@@ -75,38 +73,35 @@ type Container struct {
Args []string
Config *containertypes.Config
ImageID image.ID `json:"Image"`
ImageManifest *ocispec.Descriptor
NetworkSettings *network.Settings
LogPath string
Name string
Driver string
OS string
// MountLabel contains the options for the 'mount' command
MountLabel string
ProcessLabel string
RestartCount int
HasBeenStartedBefore bool
HasBeenManuallyStopped bool // used for unless-stopped restart policy
HasBeenManuallyRestarted bool `json:"-"` // used to distinguish restart caused by restart policy from the manual one
MountPoints map[string]*volumemounts.MountPoint
HostConfig *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable
ExecCommands *exec.Store `json:"-"`
ExecCommands *ExecStore `json:"-"`
DependencyStore agentexec.DependencyGetter `json:"-"`
SecretReferences []*swarmtypes.SecretReference
ConfigReferences []*swarmtypes.ConfigReference
// logDriver for closing
LogDriver logger.Logger `json:"-"`
LogCopier *logger.Copier `json:"-"`
restartManager restartmanager.RestartManager
restartManager *restartmanager.RestartManager
attachContext *attachContext
// Fields here are specific to Unix platforms
AppArmorProfile string
HostnamePath string
HostsPath string
ShmPath string
ResolvConfPath string
SeccompProfile string
NoNewPrivileges bool
SecurityOptions
HostnamePath string
HostsPath string
ShmPath string
ResolvConfPath string
// Fields here are specific to Windows
NetworkSharedContainerID string `json:"-"`
@@ -114,6 +109,15 @@ type Container struct {
LocalLogCacheMeta localLogCacheMeta `json:",omitempty"`
}
type SecurityOptions struct {
// MountLabel contains the options for the "mount" command.
MountLabel string
ProcessLabel string
AppArmorProfile string
SeccompProfile string
NoNewPrivileges bool
}
type localLogCacheMeta struct {
HaveNotifyEnabled bool
}
@@ -124,7 +128,7 @@ func NewBaseContainer(id, root string) *Container {
return &Container{
ID: id,
State: NewState(),
ExecCommands: exec.NewStore(),
ExecCommands: NewExecStore(),
Root: root,
MountPoints: make(map[string]*volumemounts.MountPoint),
StreamConfig: stream.NewConfig(),
@@ -196,7 +200,7 @@ func (container *Container) toDisk() (*Container, error) {
// CheckpointTo makes the Container's current state visible to queries, and persists state.
// Callers must hold a Container lock.
func (container *Container) CheckpointTo(store ViewDB) error {
func (container *Container) CheckpointTo(store *ViewDB) error {
deepCopy, err := container.toDisk()
if err != nil {
return err
@@ -302,18 +306,18 @@ func (container *Container) SetupWorkingDirectory(rootIdentity idtools.Identity)
// symlinking to a different path) between using this method and using the
// path. See symlink.FollowSymlinkInScope for more details.
func (container *Container) GetResourcePath(path string) (string, error) {
if container.BaseFS == nil {
return "", errors.New("GetResourcePath: BaseFS of container " + container.ID + " is unexpectedly nil")
if container.BaseFS == "" {
return "", errors.New("GetResourcePath: BaseFS of container " + container.ID + " is unexpectedly empty")
}
// IMPORTANT - These are paths on the OS where the daemon is running, hence
// any filepath operations must be done in an OS agnostic way.
r, e := container.BaseFS.ResolveScopedPath(path, false)
r, e := containerfs.ResolveScopedPath(container.BaseFS, containerfs.CleanScopedPath(path))
// Log this here on the daemon side as there's otherwise no indication apart
// from the error being propagated all the way back to the client. This makes
// debugging significantly easier and clearly indicates the error comes from the daemon.
if e != nil {
logrus.Errorf("Failed to ResolveScopedPath BaseFS %s path %s %s\n", container.BaseFS.Path(), path, e)
logrus.Errorf("Failed to ResolveScopedPath BaseFS %s path %s %s\n", container.BaseFS, path, e)
}
return r, e
}
@@ -560,13 +564,7 @@ func (container *Container) InitDNSHostConfig() {
// UpdateMonitor updates monitor configure for running container
func (container *Container) UpdateMonitor(restartPolicy containertypes.RestartPolicy) {
type policySetter interface {
SetPolicy(containertypes.RestartPolicy)
}
if rm, ok := container.RestartManager().(policySetter); ok {
rm.SetPolicy(restartPolicy)
}
container.RestartManager().SetPolicy(restartPolicy)
}
// FullHostname returns hostname and optional domain appended to it.
@@ -579,7 +577,7 @@ func (container *Container) FullHostname() string {
}
// RestartManager returns the current restartmanager instance connected to container.
func (container *Container) RestartManager() restartmanager.RestartManager {
func (container *Container) RestartManager() *restartmanager.RestartManager {
if container.restartManager == nil {
container.restartManager = restartmanager.New(container.HostConfig.RestartPolicy, container.RestartCount)
}
@@ -740,7 +738,7 @@ func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string
env := make([]string, 0, envSize)
if runtime.GOOS != "windows" {
env = append(env, "PATH="+system.DefaultPathEnv(ctrOS))
env = append(env, "PATH="+oci.DefaultPathEnv(ctrOS))
env = append(env, "HOSTNAME="+container.Config.Hostname)
if tty {
env = append(env, "TERM=xterm")
@@ -755,6 +753,47 @@ func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string
return env
}
// RestoreTask restores the containerd container and task handles and reattaches
// the IO for the running task. Container state is not synced with containerd's
// state.
//
// An errdefs.NotFound error is returned if the container does not exist in
// containerd. However, a nil error is returned if the task does not exist in
// containerd.
func (container *Container) RestoreTask(ctx context.Context, client libcontainerdtypes.Client) error {
container.Lock()
defer container.Unlock()
var err error
container.ctr, err = client.LoadContainer(ctx, container.ID)
if err != nil {
return err
}
container.task, err = container.ctr.AttachTask(ctx, container.InitializeStdio)
if err != nil && !errdefs.IsNotFound(err) {
return err
}
return nil
}
// GetRunningTask asserts that the container is running and returns the Task for
// the container. An errdefs.Conflict error is returned if the container is not
// in the Running state.
//
// A system error is returned if container is in a bad state: Running is true
// but has a nil Task.
//
// The container lock must be held when calling this method.
func (container *Container) GetRunningTask() (libcontainerdtypes.Task, error) {
if !container.Running {
return nil, errdefs.Conflict(fmt.Errorf("container %s is not running", container.ID))
}
tsk, ok := container.Task()
if !ok {
return nil, errdefs.System(errors.WithStack(fmt.Errorf("container %s is in Running state but has no containerd Task set", container.ID)))
}
return tsk, nil
}
type rio struct {
cio.IO
@@ -1,20 +1,20 @@
package exec // import "github.com/docker/docker/daemon/exec"
package container // import "github.com/docker/docker/container"
import (
"context"
"runtime"
"sync"
"github.com/containerd/containerd/cio"
"github.com/docker/docker/container/stream"
"github.com/docker/docker/libcontainerd/types"
"github.com/docker/docker/pkg/stringid"
"github.com/sirupsen/logrus"
)
// Config holds the configurations for execs. The Daemon keeps
// ExecConfig holds the configurations for execs. The Daemon keeps
// track of both running and finished execs so that they can be
// examined both during and after completion.
type Config struct {
type ExecConfig struct {
sync.Mutex
Started chan struct{}
StreamConfig *stream.Config
@@ -25,7 +25,7 @@ type Config struct {
OpenStderr bool
OpenStdout bool
CanRemove bool
ContainerID string
Container *Container
DetachKeys []byte
Entrypoint string
Args []string
@@ -34,39 +34,22 @@ type Config struct {
User string
WorkingDir string
Env []string
Pid int
Process types.Process
ConsoleSize *[2]uint
}
// NewConfig initializes the a new exec configuration
func NewConfig() *Config {
return &Config{
// NewExecConfig initializes the a new exec configuration
func NewExecConfig(c *Container) *ExecConfig {
return &ExecConfig{
ID: stringid.GenerateRandomID(),
Container: c,
StreamConfig: stream.NewConfig(),
Started: make(chan struct{}),
}
}
type rio struct {
cio.IO
sc *stream.Config
}
func (i *rio) Close() error {
i.IO.Close()
return i.sc.CloseStreams()
}
func (i *rio) Wait() {
i.sc.Wait(context.Background())
i.IO.Wait()
}
// InitializeStdio is called by libcontainerd to connect the stdio.
func (c *Config) InitializeStdio(iop *cio.DirectIO) (cio.IO, error) {
func (c *ExecConfig) InitializeStdio(iop *cio.DirectIO) (cio.IO, error) {
c.StreamConfig.CopyToPipe(iop)
if c.StreamConfig.Stdin() == nil && !c.Tty && runtime.GOOS == "windows" {
@@ -81,68 +64,68 @@ func (c *Config) InitializeStdio(iop *cio.DirectIO) (cio.IO, error) {
}
// CloseStreams closes the stdio streams for the exec
func (c *Config) CloseStreams() error {
func (c *ExecConfig) CloseStreams() error {
return c.StreamConfig.CloseStreams()
}
// SetExitCode sets the exec config's exit code
func (c *Config) SetExitCode(code int) {
func (c *ExecConfig) SetExitCode(code int) {
c.ExitCode = &code
}
// Store keeps track of the exec configurations.
type Store struct {
byID map[string]*Config
sync.RWMutex
// ExecStore keeps track of the exec configurations.
type ExecStore struct {
byID map[string]*ExecConfig
mu sync.RWMutex
}
// NewStore initializes a new exec store.
func NewStore() *Store {
return &Store{
byID: make(map[string]*Config),
// NewExecStore initializes a new exec store.
func NewExecStore() *ExecStore {
return &ExecStore{
byID: make(map[string]*ExecConfig),
}
}
// Commands returns the exec configurations in the store.
func (e *Store) Commands() map[string]*Config {
e.RLock()
byID := make(map[string]*Config, len(e.byID))
func (e *ExecStore) Commands() map[string]*ExecConfig {
e.mu.RLock()
byID := make(map[string]*ExecConfig, len(e.byID))
for id, config := range e.byID {
byID[id] = config
}
e.RUnlock()
e.mu.RUnlock()
return byID
}
// Add adds a new exec configuration to the store.
func (e *Store) Add(id string, Config *Config) {
e.Lock()
func (e *ExecStore) Add(id string, Config *ExecConfig) {
e.mu.Lock()
e.byID[id] = Config
e.Unlock()
e.mu.Unlock()
}
// Get returns an exec configuration by its id.
func (e *Store) Get(id string) *Config {
e.RLock()
func (e *ExecStore) Get(id string) *ExecConfig {
e.mu.RLock()
res := e.byID[id]
e.RUnlock()
e.mu.RUnlock()
return res
}
// Delete removes an exec configuration from the store.
func (e *Store) Delete(id string, pid int) {
e.Lock()
func (e *ExecStore) Delete(id string) {
e.mu.Lock()
delete(e.byID, id)
e.Unlock()
e.mu.Unlock()
}
// List returns the list of exec ids in the store.
func (e *Store) List() []string {
func (e *ExecStore) List() []string {
var IDs []string
e.RLock()
e.mu.RLock()
for id := range e.byID {
IDs = append(IDs, id)
}
e.RUnlock()
e.mu.RUnlock()
return IDs
}
+36 -4
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/docker/docker/api/types"
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
units "github.com/docker/go-units"
)
@@ -36,6 +37,14 @@ type State struct {
stopWaiters []chan<- StateStatus
removeOnlyWaiters []chan<- StateStatus
// The libcontainerd reference fields are unexported to force consumers
// to access them through the getter methods with multi-valued returns
// so that they can't forget to nil-check: the code won't compile unless
// the nil-check result is explicitly consumed or discarded.
ctr libcontainerdtypes.Container
task libcontainerdtypes.Task
}
// StateStatus is used to return container wait results.
@@ -260,7 +269,7 @@ func (s *State) SetExitCode(ec int) {
}
// SetRunning sets the state of the container to "running".
func (s *State) SetRunning(pid int, initial bool) {
func (s *State) SetRunning(ctr libcontainerdtypes.Container, tsk libcontainerdtypes.Task, initial bool) {
s.ErrorMsg = ""
s.Paused = false
s.Running = true
@@ -269,7 +278,14 @@ func (s *State) SetRunning(pid int, initial bool) {
s.Paused = false
}
s.ExitCodeValue = 0
s.Pid = pid
s.ctr = ctr
s.task = tsk
if tsk != nil {
s.Pid = int(tsk.Pid())
} else {
s.Pid = 0
}
s.OOMKilled = false
if initial {
s.StartedAt = time.Now().UTC()
}
@@ -287,7 +303,6 @@ func (s *State) SetStopped(exitStatus *ExitStatus) {
s.FinishedAt = exitStatus.ExitedAt
}
s.ExitCodeValue = exitStatus.ExitCode
s.OOMKilled = exitStatus.OOMKilled
s.notifyAndClear(&s.stopWaiters)
}
@@ -303,7 +318,6 @@ func (s *State) SetRestarting(exitStatus *ExitStatus) {
s.Pid = 0
s.FinishedAt = time.Now().UTC()
s.ExitCodeValue = exitStatus.ExitCode
s.OOMKilled = exitStatus.OOMKilled
s.notifyAndClear(&s.stopWaiters)
}
@@ -405,3 +419,21 @@ func (s *State) notifyAndClear(waiters *[]chan<- StateStatus) {
}
*waiters = nil
}
// C8dContainer returns a reference to the libcontainerd Container object for
// the container and whether the reference is valid.
//
// The container lock must be held when calling this method.
func (s *State) C8dContainer() (_ libcontainerdtypes.Container, ok bool) {
return s.ctr, s.ctr != nil
}
// Task returns a reference to the libcontainerd Task object for the container
// and whether the reference is valid.
//
// The container lock must be held when calling this method.
//
// See also: (*Container).GetRunningTask().
func (s *State) Task() (_ libcontainerdtypes.Task, ok bool) {
return s.task, s.task != nil
}
+82 -68
View File
@@ -1,6 +1,7 @@
package container // import "github.com/docker/docker/container"
import (
"bytes"
"errors"
"fmt"
"strings"
@@ -8,6 +9,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/errdefs"
"github.com/docker/go-connections/nat"
memdb "github.com/hashicorp/go-memdb"
"github.com/sirupsen/logrus"
@@ -17,6 +19,7 @@ const (
memdbContainersTable = "containers"
memdbNamesTable = "names"
memdbIDIndex = "id"
memdbIDIndexPrefix = "id_prefix"
memdbContainerIDIndex = "containerid"
)
@@ -58,25 +61,6 @@ type nameAssociation struct {
containerID string
}
// ViewDB provides an in-memory transactional (ACID) container Store
type ViewDB interface {
Snapshot() View
Save(*Container) error
Delete(*Container) error
ReserveName(name, containerID string) error
ReleaseName(name string) error
}
// View can be used by readers to avoid locking
type View interface {
All() ([]Snapshot, error)
Get(id string) (*Snapshot, error)
GetID(name string) (string, error)
GetAllNames() map[string][]string
}
var schema = &memdb.DBSchema{
Tables: map[string]*memdb.TableSchema{
memdbContainersTable: {
@@ -107,43 +91,63 @@ var schema = &memdb.DBSchema{
},
}
type memDB struct {
// ViewDB provides an in-memory transactional (ACID) container store.
type ViewDB struct {
store *memdb.MemDB
}
// NoSuchContainerError indicates that the container wasn't found in the
// database.
type NoSuchContainerError struct {
id string
}
// Error satisfies the error interface.
func (e NoSuchContainerError) Error() string {
return "no such container " + e.id
}
// NewViewDB provides the default implementation, with the default schema
func NewViewDB() (ViewDB, error) {
func NewViewDB() (*ViewDB, error) {
store, err := memdb.NewMemDB(schema)
if err != nil {
return nil, err
return nil, errdefs.System(err)
}
return &memDB{store: store}, nil
return &ViewDB{store: store}, nil
}
// Snapshot provides a consistent read-only View of the database
func (db *memDB) Snapshot() View {
return &memdbView{
// GetByPrefix returns a container with the given ID prefix. It returns an
// error if an empty prefix was given or if multiple containers match the prefix.
func (db *ViewDB) GetByPrefix(s string) (string, error) {
if s == "" {
return "", errdefs.InvalidParameter(errors.New("prefix can't be empty"))
}
iter, err := db.store.Txn(false).Get(memdbContainersTable, memdbIDIndexPrefix, s)
if err != nil {
return "", errdefs.System(err)
}
var id string
for {
item := iter.Next()
if item == nil {
break
}
if id != "" {
return "", errdefs.InvalidParameter(errors.New("multiple IDs found with provided prefix: " + s))
}
id = item.(*Container).ID
}
if id != "" {
return id, nil
}
return "", errdefs.NotFound(errors.New("No such container: " + s))
}
// Snapshot provides a consistent read-only view of the database.
func (db *ViewDB) Snapshot() *View {
return &View{
txn: db.store.Txn(false),
}
}
func (db *memDB) withTxn(cb func(*memdb.Txn) error) error {
func (db *ViewDB) withTxn(cb func(*memdb.Txn) error) error {
txn := db.store.Txn(true)
err := cb(txn)
if err != nil {
txn.Abort()
return err
return errdefs.System(err)
}
txn.Commit()
return nil
@@ -151,16 +155,16 @@ func (db *memDB) withTxn(cb func(*memdb.Txn) error) error {
// Save atomically updates the in-memory store state for a Container.
// Only read only (deep) copies of containers may be passed in.
func (db *memDB) Save(c *Container) error {
func (db *ViewDB) Save(c *Container) error {
return db.withTxn(func(txn *memdb.Txn) error {
return txn.Insert(memdbContainersTable, c)
})
}
// Delete removes an item by ID
func (db *memDB) Delete(c *Container) error {
func (db *ViewDB) Delete(c *Container) error {
return db.withTxn(func(txn *memdb.Txn) error {
view := &memdbView{txn: txn}
view := &View{txn: txn}
names := view.getNames(c.ID)
for _, name := range names {
@@ -178,11 +182,11 @@ func (db *memDB) Delete(c *Container) error {
// ReserveName is idempotent
// Attempting to reserve a container ID to a name that already exists results in an `ErrNameReserved`
// A name reservation is globally unique
func (db *memDB) ReserveName(name, containerID string) error {
func (db *ViewDB) ReserveName(name, containerID string) error {
return db.withTxn(func(txn *memdb.Txn) error {
s, err := txn.First(memdbNamesTable, memdbIDIndex, name)
if err != nil {
return err
return errdefs.System(err)
}
if s != nil {
if s.(nameAssociation).containerID != containerID {
@@ -196,22 +200,23 @@ func (db *memDB) ReserveName(name, containerID string) error {
// ReleaseName releases the reserved name
// Once released, a name can be reserved again
func (db *memDB) ReleaseName(name string) error {
func (db *ViewDB) ReleaseName(name string) error {
return db.withTxn(func(txn *memdb.Txn) error {
return txn.Delete(memdbNamesTable, nameAssociation{name: name})
})
}
type memdbView struct {
// View provides a consistent read-only view of the database.
type View struct {
txn *memdb.Txn
}
// All returns a all items in this snapshot. Returned objects must never be modified.
func (v *memdbView) All() ([]Snapshot, error) {
func (v *View) All() ([]Snapshot, error) {
var all []Snapshot
iter, err := v.txn.Get(memdbContainersTable, memdbIDIndex)
if err != nil {
return nil, err
return nil, errdefs.System(err)
}
for {
item := iter.Next()
@@ -225,19 +230,19 @@ func (v *memdbView) All() ([]Snapshot, error) {
}
// Get returns an item by id. Returned objects must never be modified.
func (v *memdbView) Get(id string) (*Snapshot, error) {
func (v *View) Get(id string) (*Snapshot, error) {
s, err := v.txn.First(memdbContainersTable, memdbIDIndex, id)
if err != nil {
return nil, err
return nil, errdefs.System(err)
}
if s == nil {
return nil, NoSuchContainerError{id: id}
return nil, errdefs.NotFound(errors.New("No such container: " + id))
}
return v.transform(s.(*Container)), nil
}
// getNames lists all the reserved names for the given container ID.
func (v *memdbView) getNames(containerID string) []string {
func (v *View) getNames(containerID string) []string {
iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex, containerID)
if err != nil {
return nil
@@ -256,10 +261,10 @@ func (v *memdbView) getNames(containerID string) []string {
}
// GetID returns the container ID that the passed in name is reserved to.
func (v *memdbView) GetID(name string) (string, error) {
func (v *View) GetID(name string) (string, error) {
s, err := v.txn.First(memdbNamesTable, memdbIDIndex, name)
if err != nil {
return "", err
return "", errdefs.System(err)
}
if s == nil {
return "", ErrNameNotReserved
@@ -268,7 +273,7 @@ func (v *memdbView) GetID(name string) (string, error) {
}
// GetAllNames returns all registered names.
func (v *memdbView) GetAllNames() map[string][]string {
func (v *View) GetAllNames() map[string][]string {
iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex)
if err != nil {
return nil
@@ -289,7 +294,7 @@ func (v *memdbView) GetAllNames() map[string][]string {
// transform maps a (deep) copied Container object to what queries need.
// A lock on the Container is not held because these are immutable deep copies.
func (v *memdbView) transform(container *Container) *Snapshot {
func (v *View) transform(container *Container) *Snapshot {
health := types.NoHealthcheck
if container.Health != nil {
health = container.Health.Status()
@@ -382,7 +387,7 @@ func (v *memdbView) transform(container *Container) *Snapshot {
for port, bindings := range container.NetworkSettings.Ports {
p, err := nat.ParsePort(port.Port())
if err != nil {
logrus.Warnf("invalid port map %+v", err)
logrus.WithError(err).Warn("invalid port map")
continue
}
if len(bindings) == 0 {
@@ -395,7 +400,7 @@ func (v *memdbView) transform(container *Container) *Snapshot {
for _, binding := range bindings {
h, err := nat.ParsePort(binding.HostPort)
if err != nil {
logrus.Warnf("invalid host port map %+v", err)
logrus.WithError(err).Warn("invalid host port map")
continue
}
snapshot.Ports = append(snapshot.Ports, types.Port{
@@ -416,6 +421,9 @@ func (v *memdbView) transform(container *Container) *Snapshot {
// memdb.StringFieldIndex can not be used since ID is a field from an embedded struct.
type containerByIDIndexer struct{}
// terminator is the null character, used as a terminator.
const terminator = "\x00"
// FromObject implements the memdb.SingleIndexer interface for Container objects
func (e *containerByIDIndexer) FromObject(obj interface{}) (bool, []byte, error) {
c, ok := obj.(*Container)
@@ -423,8 +431,7 @@ func (e *containerByIDIndexer) FromObject(obj interface{}) (bool, []byte, error)
return false, nil, fmt.Errorf("%T is not a Container", obj)
}
// Add the null character as a terminator
v := c.ID + "\x00"
return true, []byte(v), nil
return true, []byte(c.ID + terminator), nil
}
// FromArgs implements the memdb.Indexer interface
@@ -437,8 +444,17 @@ func (e *containerByIDIndexer) FromArgs(args ...interface{}) ([]byte, error) {
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
}
// Add the null character as a terminator
arg += "\x00"
return []byte(arg), nil
return []byte(arg + terminator), nil
}
func (e *containerByIDIndexer) PrefixFromArgs(args ...interface{}) ([]byte, error) {
val, err := e.FromArgs(args...)
if err != nil {
return nil, err
}
// Strip the null terminator, the rest is a prefix
return bytes.TrimSuffix(val, []byte(terminator)), nil
}
// namesByNameIndexer is used to index container name associations by name.
@@ -451,7 +467,7 @@ func (e *namesByNameIndexer) FromObject(obj interface{}) (bool, []byte, error) {
}
// Add the null character as a terminator
return true, []byte(n.name + "\x00"), nil
return true, []byte(n.name + terminator), nil
}
func (e *namesByNameIndexer) FromArgs(args ...interface{}) ([]byte, error) {
@@ -463,8 +479,7 @@ func (e *namesByNameIndexer) FromArgs(args ...interface{}) ([]byte, error) {
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
}
// Add the null character as a terminator
arg += "\x00"
return []byte(arg), nil
return []byte(arg + terminator), nil
}
// namesByContainerIDIndexer is used to index container names by container ID.
@@ -477,7 +492,7 @@ func (e *namesByContainerIDIndexer) FromObject(obj interface{}) (bool, []byte, e
}
// Add the null character as a terminator
return true, []byte(n.containerID + "\x00"), nil
return true, []byte(n.containerID + terminator), nil
}
func (e *namesByContainerIDIndexer) FromArgs(args ...interface{}) ([]byte, error) {
@@ -489,6 +504,5 @@ func (e *namesByContainerIDIndexer) FromArgs(args ...interface{}) ([]byte, error
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
}
// Add the null character as a terminator
arg += "\x00"
return []byte(arg), nil
return []byte(arg + terminator), nil
}
+15 -3
View File
@@ -1,13 +1,13 @@
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/plugingetter"
"github.com/pkg/errors"
@@ -60,7 +60,7 @@ type ProtoDriver interface {
// Get returns the mountpoint for the layered filesystem referred
// to by this id. You can optionally specify a mountLabel or "".
// Returns the absolute path to the mounted layered filesystem.
Get(id, mountLabel string) (fs containerfs.ContainerFS, err error)
Get(id, mountLabel string) (fs string, err error)
// Put releases the system resources for the specified id,
// e.g, unmounting layered filesystem.
Put(id string) error
@@ -193,6 +193,9 @@ type Options struct {
func New(name string, pg plugingetter.PluginGetter, config Options) (Driver, error) {
if name != "" {
logrus.Infof("[graphdriver] trying configured driver: %s", name)
if err := checkRemoved(name); err != nil {
return nil, err
}
if isDeprecated(name) {
logrus.Warnf("[graphdriver] WARNING: the %s storage-driver is deprecated and will be removed in a future release; visit https://docs.docker.com/go/storage-driver/ for more information", name)
}
@@ -315,8 +318,17 @@ func isEmptyDir(name string) bool {
func isDeprecated(name string) bool {
switch name {
// NOTE: when deprecating a driver, update daemon.fillDriverInfo() accordingly
case "aufs", "devicemapper", "overlay":
case "devicemapper":
return true
}
return false
}
// checkRemoved checks if a storage-driver has been deprecated (and removed)
func checkRemoved(name string) error {
switch name {
case "aufs", "overlay":
return NotSupportedError(fmt.Sprintf("[graphdriver] ERROR: the %s storage-driver has been deprecated and removed; visit https://docs.docker.com/go/storage-driver/ for more information", name))
}
return nil
}
+1 -1
View File
@@ -50,7 +50,7 @@ const (
var (
// List of drivers that should be used in an order
priority = "overlay2,fuse-overlayfs,btrfs,zfs,aufs,overlay,devicemapper,vfs"
priority = "overlay2,fuse-overlayfs,btrfs,zfs,devicemapper,vfs"
// FsNames maps filesystem id to name of the filesystem.
FsNames = map[FsMagic]string{
+6 -11
View File
@@ -21,7 +21,6 @@ var (
// capability of the Diffing methods on the local file system,
// which it may or may not support on its own. See the comment
// on the exported NewNaiveDiffDriver function below.
// Notably, the AUFS driver doesn't need to be wrapped like this.
type NaiveDiffDriver struct {
ProtoDriver
idMap idtools.IdentityMapping
@@ -50,7 +49,7 @@ func (gdw *NaiveDiffDriver) Diff(id, parent string) (arch io.ReadCloser, err err
if err != nil {
return nil, err
}
layerFs := layerRootFs.Path()
layerFs := layerRootFs
defer func() {
if err != nil {
@@ -70,14 +69,12 @@ func (gdw *NaiveDiffDriver) Diff(id, parent string) (arch io.ReadCloser, err err
}), nil
}
parentRootFs, err := driver.Get(parent, "")
parentFs, err := driver.Get(parent, "")
if err != nil {
return nil, err
}
defer driver.Put(parent)
parentFs := parentRootFs.Path()
changes, err := archive.ChangesDirs(layerFs, parentFs)
if err != nil {
return nil, err
@@ -106,22 +103,20 @@ func (gdw *NaiveDiffDriver) Diff(id, parent string) (arch io.ReadCloser, err err
func (gdw *NaiveDiffDriver) Changes(id, parent string) ([]archive.Change, error) {
driver := gdw.ProtoDriver
layerRootFs, err := driver.Get(id, "")
layerFs, err := driver.Get(id, "")
if err != nil {
return nil, err
}
defer driver.Put(id)
layerFs := layerRootFs.Path()
parentFs := ""
if parent != "" {
parentRootFs, err := driver.Get(parent, "")
parentFs, err = driver.Get(parent, "")
if err != nil {
return nil, err
}
defer driver.Put(parent)
parentFs = parentRootFs.Path()
}
return archive.ChangesDirs(layerFs, parentFs)
@@ -140,7 +135,7 @@ func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff io.Reader) (size i
}
defer driver.Put(id)
layerFs := layerRootFs.Path()
layerFs := layerRootFs
options := &archive.TarOptions{IDMap: gdw.idMap}
start := time.Now().UTC()
logrus.WithField("id", id).Debug("Start untar layer")
@@ -169,5 +164,5 @@ func (gdw *NaiveDiffDriver) DiffSize(id, parent string) (size int64, err error)
}
defer driver.Put(id)
return archive.ChangesSize(layerFs.Path(), changes), nil
return archive.ChangesSize(layerFs, changes), nil
}
+3 -4
View File
@@ -6,7 +6,6 @@ import (
"io"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/plugins"
@@ -128,20 +127,20 @@ func (d *graphDriverProxy) Remove(id string) error {
return nil
}
func (d *graphDriverProxy) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
func (d *graphDriverProxy) Get(id, mountLabel string) (string, error) {
args := &graphDriverRequest{
ID: id,
MountLabel: mountLabel,
}
var ret graphDriverResponse
if err := d.client.Call("GraphDriver.Get", args, &ret); err != nil {
return nil, err
return "", err
}
var err error
if ret.Err != "" {
err = errors.New(ret.Err)
}
return containerfs.NewLocalContainerFS(d.p.ScopedPath(ret.Dir)), err
return d.p.ScopedPath(ret.Dir), err
}
func (d *graphDriverProxy) Put(id string) error {
+1 -1
View File
@@ -87,7 +87,7 @@ type ReadConfig struct {
// LogReader is the interface for reading log messages for loggers that support reading.
type LogReader interface {
// Read logs from underlying logging backend
// ReadLogs reads logs from underlying logging backend.
ReadLogs(ReadConfig) *LogWatcher
}
+10 -35
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"io"
"os"
"time"
"github.com/docker/docker/daemon/logger"
"github.com/pkg/errors"
@@ -12,10 +11,10 @@ import (
)
type follow struct {
LogFile *LogFile
Watcher *logger.LogWatcher
Decoder Decoder
Since, Until time.Time
LogFile *LogFile
Watcher *logger.LogWatcher
Decoder Decoder
Forwarder *forwarder
log *logrus.Entry
c chan logPos
@@ -49,7 +48,7 @@ func (fl *follow) Do(f *os.File, read logPos) {
fl.Watcher.Err <- err
return
}
if fl.decode(f) {
if !fl.forward(f) {
return
}
@@ -91,7 +90,7 @@ func (fl *follow) Do(f *os.File, read logPos) {
read.size = 0
}
if fl.decode(io.NewSectionReader(f, read.size, wrote.size-read.size)) {
if !fl.forward(io.NewSectionReader(f, read.size, wrote.size-read.size)) {
return
}
read = wrote
@@ -132,34 +131,10 @@ func (fl *follow) nextPos(current logPos) (next logPos, ok bool) {
return next, true
}
// decode decodes log messages from r and sends messages with timestamps between
// Since and Until to the log watcher.
// forward decodes log messages from r and forwards them to the log watcher.
//
// The return value, done, signals whether following should end due to a
// condition encountered during decode.
func (fl *follow) decode(r io.Reader) (done bool) {
// The return value, cont, signals whether following should continue.
func (fl *follow) forward(r io.Reader) (cont bool) {
fl.Decoder.Reset(r)
for {
msg, err := fl.Decoder.Decode()
if err != nil {
if errors.Is(err, io.EOF) {
return false
}
fl.Watcher.Err <- err
return true
}
if !fl.Since.IsZero() && msg.Timestamp.Before(fl.Since) {
continue
}
if !fl.Until.IsZero() && msg.Timestamp.After(fl.Until) {
return true
}
// send the message, unless the consumer is gone
select {
case fl.Watcher.Msg <- msg:
case <-fl.Watcher.WatchConsumerGone():
return true
}
}
return fl.Forwarder.Do(fl.Watcher, fl.Decoder)
}
+41 -20
View File
@@ -411,6 +411,7 @@ func (w *LogFile) readLogsLocked(currentPos logPos, config logger.ReadConfig, wa
defer dec.Close()
currentChunk := io.NewSectionReader(currentFile, 0, currentPos.size)
fwd := newForwarder(config)
if config.Tail != 0 {
// TODO(@cpuguy83): Instead of opening every file, only get the files which
@@ -449,7 +450,7 @@ func (w *LogFile) readLogsLocked(currentPos logPos, config logger.ReadConfig, wa
readers = append(readers, currentChunk)
}
ok := tailFiles(readers, watcher, dec, w.getTailReader, config)
ok := tailFiles(readers, watcher, dec, w.getTailReader, config.Tail, fwd)
closeFiles()
if !ok {
return
@@ -463,11 +464,10 @@ func (w *LogFile) readLogsLocked(currentPos logPos, config logger.ReadConfig, wa
}
(&follow{
LogFile: w,
Watcher: watcher,
Decoder: dec,
Since: config.Since,
Until: config.Until,
LogFile: w,
Watcher: watcher,
Decoder: dec,
Forwarder: fwd,
}).Do(currentFile, currentPos)
}
@@ -573,7 +573,7 @@ func decompress(dst io.WriteSeeker, src io.ReadSeeker) error {
return rc.Close()
}
func tailFiles(files []SizeReaderAt, watcher *logger.LogWatcher, dec Decoder, getTailReader GetTailReaderFunc, config logger.ReadConfig) (cont bool) {
func tailFiles(files []SizeReaderAt, watcher *logger.LogWatcher, dec Decoder, getTailReader GetTailReaderFunc, nLines int, fwd *forwarder) (cont bool) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -583,20 +583,18 @@ func tailFiles(files []SizeReaderAt, watcher *logger.LogWatcher, dec Decoder, ge
select {
case <-ctx.Done():
case <-watcher.WatchConsumerGone():
cont = false
cancel()
}
}()
readers := make([]io.Reader, 0, len(files))
if config.Tail > 0 {
nLines := config.Tail
if nLines > 0 {
for i := len(files) - 1; i >= 0 && nLines > 0; i-- {
tail, n, err := getTailReader(ctx, files[i], nLines)
if err != nil {
watcher.Err <- errors.Wrap(err, "error finding file position to start log tailing")
return
return false
}
nLines -= n
readers = append([]io.Reader{tail}, readers...)
@@ -609,24 +607,47 @@ func tailFiles(files []SizeReaderAt, watcher *logger.LogWatcher, dec Decoder, ge
rdr := io.MultiReader(readers...)
dec.Reset(rdr)
return fwd.Do(watcher, dec)
}
type forwarder struct {
since, until time.Time
}
func newForwarder(config logger.ReadConfig) *forwarder {
return &forwarder{since: config.Since, until: config.Until}
}
// Do reads log messages from dec and sends the messages matching the filter
// conditions to watcher. Do returns cont=true iff it has read all messages from
// dec without encountering a message with a timestamp which is after the
// configured until time.
func (fwd *forwarder) Do(watcher *logger.LogWatcher, dec Decoder) (cont bool) {
for {
msg, err := dec.Decode()
if err != nil {
if !errors.Is(err, io.EOF) {
watcher.Err <- err
if errors.Is(err, io.EOF) {
return true
}
return
watcher.Err <- err
return false
}
if !config.Since.IsZero() && msg.Timestamp.Before(config.Since) {
continue
if !fwd.since.IsZero() {
if msg.Timestamp.Before(fwd.since) {
continue
}
// We've found our first message with a timestamp >= since. As message
// timestamps might not be monotonic, we need to skip the since check for all
// subsequent messages so we do not filter out later messages which happen to
// have timestamps before since.
fwd.since = time.Time{}
}
if !config.Until.IsZero() && msg.Timestamp.After(config.Until) {
return
if !fwd.until.IsZero() && msg.Timestamp.After(fwd.until) {
return false
}
select {
case <-ctx.Done():
return
case <-watcher.WatchConsumerGone():
return false
case watcher.Msg <- msg:
}
}
+2 -2
View File
@@ -59,8 +59,8 @@ func (info *Info) ExtraAttributes(keyMod func(string) string) (map[string]string
envMapping := make(map[string]string)
for _, e := range info.ContainerEnv {
if kv := strings.SplitN(e, "=", 2); len(kv) == 2 {
envMapping[kv[0]] = kv[1]
if k, v, ok := strings.Cut(e, "="); ok {
envMapping[k] = v
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ func RegisterPluginGetter(plugingetter getter.PluginGetter) {
pluginGetter = plugingetter
}
// GetDriver returns a logging driver by its name.
// getPlugin returns a logging driver by its name.
// If the driver is empty, it looks for the local driver.
func getPlugin(name string, mode int) (Creator, error) {
p, err := pluginGetter.Get(name, extName, mode)
+53 -33
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"runtime"
"sync"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/useragent"
@@ -16,40 +17,69 @@ type UAStringKey struct{}
// In accordance with RFC 7231 (5.5.3) is of the form:
//
// [docker client's UA] UpstreamClient([upstream client's UA])
func DockerUserAgent(ctx context.Context) string {
httpVersion := make([]useragent.VersionInfo, 0, 6)
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
func DockerUserAgent(ctx context.Context, extraVersions ...useragent.VersionInfo) string {
ua := useragent.AppendVersions(getDaemonUserAgent(), extraVersions...)
if upstreamUA := getUpstreamUserAgent(ctx); upstreamUA != "" {
ua += " " + upstreamUA
}
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
dockerUA := useragent.AppendVersions("", httpVersion...)
upstreamUA := getUserAgentFromContext(ctx)
if len(upstreamUA) > 0 {
ret := insertUpstreamUserAgent(upstreamUA, dockerUA)
return ret
}
return dockerUA
return ua
}
// getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
func getUserAgentFromContext(ctx context.Context) string {
var (
daemonUAOnce sync.Once
daemonUA string
)
// getDaemonUserAgent returns the user-agent to use for requests made by
// the daemon.
//
// It includes;
//
// - the docker version
// - go version
// - git-commit
// - kernel version
// - os
// - architecture
func getDaemonUserAgent() string {
daemonUAOnce.Do(func() {
httpVersion := make([]useragent.VersionInfo, 0, 6)
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
}
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
daemonUA = useragent.AppendVersions("", httpVersion...)
})
return daemonUA
}
// getUpstreamUserAgent returns the previously saved user-agent context stored
// in ctx, if one exists, and formats it as:
//
// UpstreamClient(<upstream user agent string>)
//
// It returns an empty string if no user-agent is present in the context.
func getUpstreamUserAgent(ctx context.Context) string {
var upstreamUA string
if ctx != nil {
var ki interface{} = ctx.Value(UAStringKey{})
if ki != nil {
if ki := ctx.Value(UAStringKey{}); ki != nil {
upstreamUA = ctx.Value(UAStringKey{}).(string)
}
}
return upstreamUA
if upstreamUA == "" {
return ""
}
return fmt.Sprintf("UpstreamClient(%s)", escapeStr(upstreamUA))
}
const charsToEscape = `();\`
// escapeStr returns s with every rune in charsToEscape escaped by a backslash
func escapeStr(s string, charsToEscape string) string {
func escapeStr(s string) string {
var ret string
for _, currRune := range s {
appended := false
@@ -66,13 +96,3 @@ func escapeStr(s string, charsToEscape string) string {
}
return ret
}
// insertUpstreamUserAgent adds the upstream client useragent to create a user-agent
// string of the form:
//
// $dockerUA UpstreamClient($upstreamUA)
func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string {
charsToEscape := `();\`
upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape)
return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped)
}
+31
View File
@@ -9,6 +9,7 @@ import (
"strings"
"time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/layer"
@@ -28,6 +29,8 @@ func (id ID) Digest() digest.Digest {
}
// IDFromDigest creates an ID from a digest
//
// Deprecated: cast to an ID using ID(digest).
func IDFromDigest(digest digest.Digest) ID {
return ID(digest)
}
@@ -112,6 +115,18 @@ type Image struct {
// computedID is the ID computed from the hash of the image config.
// Not to be confused with the legacy V1 ID in V1Image.
computedID ID
// Details holds additional details about image
Details *Details `json:"-"`
}
// Details provides additional image data
type Details struct {
References []reference.Named
Size int64
Metadata map[string]string
Driver string
LastUpdated time.Time
}
// RawJSON returns the immutable JSON associated with the image.
@@ -188,6 +203,13 @@ type ChildConfig struct {
Config *container.Config
}
// NewImage creates a new image with the given ID
func NewImage(id ID) *Image {
return &Image{
computedID: id,
}
}
// NewChildImage creates a new Image as a child of this image.
func NewChildImage(img *Image, child ChildConfig, os string) *Image {
isEmptyLayer := layer.IsEmpty(child.DiffID)
@@ -226,6 +248,15 @@ func NewChildImage(img *Image, child ChildConfig, os string) *Image {
}
}
// Clone clones an image and changes ID.
func Clone(base *Image, id ID) *Image {
img := *base
img.RootFS = img.RootFS.Clone()
img.V1Image.ID = id.String()
img.computedID = id
return &img
}
// History stores build commands that were used to create an image
type History struct {
// Created is the timestamp at which the image was created
+28 -24
View File
@@ -67,22 +67,29 @@ func NewImageStore(fs StoreBackend, lss LayerGetReleaser) (Store, error) {
}
func (is *store) restore() error {
// As the code below is run when restoring all images (which can be "many"),
// constructing the "logrus.WithFields" is deliberately not "DRY", as the
// logger is only used for error-cases, and we don't want to do allocations
// if we don't need it. The "f" type alias is here is just for convenience,
// and to make the code _slightly_ more DRY. See the discussion on GitHub;
// https://github.com/moby/moby/pull/44426#discussion_r1059519071
type f = logrus.Fields
err := is.fs.Walk(func(dgst digest.Digest) error {
img, err := is.Get(IDFromDigest(dgst))
img, err := is.Get(ID(dgst))
if err != nil {
logrus.Errorf("invalid image %v, %v", dgst, err)
logrus.WithFields(f{"digest": dgst, "err": err}).Error("invalid image")
return nil
}
var l layer.Layer
if chainID := img.RootFS.ChainID(); chainID != "" {
if !system.IsOSSupported(img.OperatingSystem()) {
logrus.Errorf("not restoring image with unsupported operating system %v, %v, %s", dgst, chainID, img.OperatingSystem())
logrus.WithFields(f{"chainID": chainID, "os": img.OperatingSystem()}).Error("not restoring image with unsupported operating system")
return nil
}
l, err = is.lss.Get(chainID)
if err != nil {
if err == layer.ErrLayerDoesNotExist {
logrus.Errorf("layer does not exist, not restoring image %v, %v, %s", dgst, chainID, img.OperatingSystem())
if errors.Is(err, layer.ErrLayerDoesNotExist) {
logrus.WithFields(f{"chainID": chainID, "os": img.OperatingSystem(), "err": err}).Error("not restoring image")
return nil
}
return err
@@ -92,13 +99,11 @@ func (is *store) restore() error {
return err
}
imageMeta := &imageMeta{
is.images[ID(dgst)] = &imageMeta{
layer: l,
children: make(map[ID]struct{}),
}
is.images[IDFromDigest(dgst)] = imageMeta
return nil
})
if err != nil {
@@ -141,15 +146,15 @@ func (is *store) Create(config []byte) (ID, error) {
return "", errdefs.InvalidParameter(errors.New("too many non-empty layers in History section"))
}
dgst, err := is.fs.Set(config)
imageDigest, err := is.fs.Set(config)
if err != nil {
return "", errdefs.InvalidParameter(err)
}
imageID := IDFromDigest(dgst)
is.Lock()
defer is.Unlock()
imageID := ID(imageDigest)
if _, exists := is.images[imageID]; exists {
return imageID, nil
}
@@ -167,13 +172,12 @@ func (is *store) Create(config []byte) (ID, error) {
}
}
imageMeta := &imageMeta{
is.images[imageID] = &imageMeta{
layer: l,
children: make(map[ID]struct{}),
}
is.images[imageID] = imageMeta
if err := is.digestSet.Add(imageID.Digest()); err != nil {
if err = is.digestSet.Add(imageDigest); err != nil {
delete(is.images, imageID)
return "", errdefs.InvalidParameter(err)
}
@@ -197,7 +201,7 @@ func (is *store) Search(term string) (ID, error) {
}
return "", errors.WithStack(err)
}
return IDFromDigest(dgst), nil
return ID(dgst), nil
}
func (is *store) Get(id ID) (*Image, error) {
@@ -226,16 +230,16 @@ func (is *store) Delete(id ID) ([]layer.Metadata, error) {
is.Lock()
defer is.Unlock()
imageMeta := is.images[id]
if imageMeta == nil {
imgMeta := is.images[id]
if imgMeta == nil {
return nil, errdefs.NotFound(fmt.Errorf("unrecognized image ID %s", id.String()))
}
_, err := is.Get(id)
if err != nil {
return nil, errdefs.NotFound(fmt.Errorf("unrecognized image %s, %v", id.String(), err))
}
for id := range imageMeta.children {
is.fs.DeleteMetadata(id.Digest(), "parent")
for cID := range imgMeta.children {
is.fs.DeleteMetadata(cID.Digest(), "parent")
}
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
delete(is.images[parent].children, id)
@@ -247,24 +251,24 @@ func (is *store) Delete(id ID) ([]layer.Metadata, error) {
delete(is.images, id)
is.fs.Delete(id.Digest())
if imageMeta.layer != nil {
return is.lss.Release(imageMeta.layer)
if imgMeta.layer != nil {
return is.lss.Release(imgMeta.layer)
}
return nil, nil
}
func (is *store) SetParent(id, parent ID) error {
func (is *store) SetParent(id, parentID ID) error {
is.Lock()
defer is.Unlock()
parentMeta := is.images[parent]
parentMeta := is.images[parentID]
if parentMeta == nil {
return errdefs.NotFound(fmt.Errorf("unknown parent image ID %s", parent.String()))
return errdefs.NotFound(fmt.Errorf("unknown parent image ID %s", parentID.String()))
}
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
delete(is.images[parent].children, id)
}
parentMeta.children[id] = struct{}{}
return is.fs.SetMetadata(id.Digest(), "parent", []byte(parent))
return is.fs.SetMetadata(id.Digest(), "parent", []byte(parentID))
}
func (is *store) GetParent(id ID) (ID, error) {
+94
View File
@@ -0,0 +1,94 @@
package mounttree // import "github.com/docker/docker/internal/mounttree"
import (
"fmt"
"os"
"path/filepath"
"github.com/moby/sys/mount"
"github.com/moby/sys/mountinfo"
"golang.org/x/sys/unix"
)
// SwitchRoot changes path to be the root of the mount tree and changes the
// current working directory to the new root.
//
// This function bind-mounts onto path; it is the caller's responsibility to set
// the desired propagation mode of path's parent mount beforehand to prevent
// unwanted propagation into different mount namespaces.
func SwitchRoot(path string) error {
if mounted, _ := mountinfo.Mounted(path); !mounted {
if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil {
return realChroot(path)
}
}
// setup oldRoot for pivot_root
pivotDir, err := os.MkdirTemp(path, ".pivot_root")
if err != nil {
return fmt.Errorf("Error setting up pivot dir: %v", err)
}
var mounted bool
defer func() {
if mounted {
// make sure pivotDir is not mounted before we try to remove it
if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil {
if err == nil {
err = errCleanup
}
return
}
}
errCleanup := os.Remove(pivotDir)
// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
// because we already cleaned it up on failed pivot_root
if errCleanup != nil && !os.IsNotExist(errCleanup) {
errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
if err == nil {
err = errCleanup
}
}
}()
if err := unix.PivotRoot(path, pivotDir); err != nil {
// If pivot fails, fall back to the normal chroot after cleaning up temp dir
if err := os.Remove(pivotDir); err != nil {
return fmt.Errorf("Error cleaning up after failed pivot: %v", err)
}
return realChroot(path)
}
mounted = true
// This is the new path for where the old root (prior to the pivot) has been moved to
// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
pivotDir = filepath.Join("/", filepath.Base(pivotDir))
if err := unix.Chdir("/"); err != nil {
return fmt.Errorf("Error changing to new root: %v", err)
}
// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil {
return fmt.Errorf("Error making old root private after pivot: %v", err)
}
// Now unmount the old root so it's no longer visible from the new root
if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil {
return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
}
mounted = false
return nil
}
func realChroot(path string) error {
if err := unix.Chroot(path); err != nil {
return fmt.Errorf("Error after fallback to chroot: %v", err)
}
if err := unix.Chdir("/"); err != nil {
return fmt.Errorf("Error changing to new root after chroot: %v", err)
}
return nil
}
+176
View File
@@ -0,0 +1,176 @@
//go:build go1.10
// +build go1.10
package unshare // import "github.com/docker/docker/internal/unshare"
import (
"fmt"
"os"
"runtime"
"golang.org/x/sys/unix"
)
func init() {
// The startup thread of a process is special in a few different ways.
// Most pertinent to the discussion at hand, any per-thread kernel state
// reflected in the /proc/[pid]/ directory for a process is taken from
// the state of the startup thread. Same goes for /proc/self/; it shows
// the state of the current process' startup thread, no matter which
// thread the files are being opened from. For most programs this is a
// distinction without a difference as the kernel state, such as the
// mount namespace and current working directory, is shared among (and
// kept synchronized across) all threads of a process. But things start
// to break down once threads start unsharing and modifying parts of
// their kernel state.
//
// The Go runtime schedules goroutines to execute on the startup thread,
// same as any other. How this could be problematic is best illustrated
// with a concrete example. Consider what happens if a call to
// Go(unix.CLONE_NEWNS, ...) spawned a goroutine which gets scheduled
// onto the startup thread. The thread's mount namespace will be
// unshared and modified. The contents of the /proc/[pid]/mountinfo file
// will then describe the mount tree of the unshared namespace, not the
// namespace of any other thread. It will remain this way until the
// process exits. (The startup thread is special in another way: exiting
// it puts the process into a "non-waitable zombie" state. To avoid this
// fate, the Go runtime parks the thread instead of exiting if a
// goroutine returns while locked to the startup thread. More
// information can be found in the Go runtime sources:
// `go doc -u -src runtime.mexit`.) The github.com/moby/sys/mountinfo
// package reads from /proc/self/mountinfo, so will read the mount tree
// for the wrong namespace if the startup thread has had its mount
// namespace unshared! The /proc/thread-self/ directory, introduced in
// Linux 3.17, is one potential solution to this problem, but every
// package which opens files in /proc/self/ would need to be updated,
// and fallbacks to /proc/self/task/[tid]/ would be required to support
// older kernels. Overlooking any reference to /proc/self/ would
// manifest as stochastically-reproducible bugs, so this is far from an
// ideal solution.
//
// Reading from /proc/self/ would not be a problem if we could prevent
// the per-thread state of the startup thread from being modified
// nondeterministically in the first place. We can accomplish this
// simply by locking the main() function to the startup thread! Doing so
// excludes any other goroutine from being scheduled on the thread.
runtime.LockOSThread()
}
// reversibleSetnsFlags maps the unshare(2) flags whose effects can be fully
// reversed using setns(2). The values are the basenames of the corresponding
// /proc/self/task/[tid]/ns/ magic symlinks to use to save and restore the
// state.
var reversibleSetnsFlags = map[int]string{
unix.CLONE_NEWCGROUP: "cgroup",
unix.CLONE_NEWNET: "net",
unix.CLONE_NEWUTS: "uts",
unix.CLONE_NEWPID: "pid",
unix.CLONE_NEWTIME: "time",
// The following CLONE_NEW* flags are not included because they imply
// another, irreversible flag when used with unshare(2).
// - unix.CLONE_NEWIPC: implies CLONE_SYSVMEM
// - unix.CLONE_NEWNS: implies CLONE_FS
// - unix.CLONE_NEWUSER: implies CLONE_FS since Linux 3.9
}
// Go calls the given functions in a new goroutine, locked to an OS thread,
// which has had the parts of its execution state disassociated from the rest of
// the current process using [unshare(2)]. It blocks until the new goroutine has
// started and setupfn has returned. fn is only called if setupfn returns nil. A
// nil setupfn or fn is equivalent to passing a no-op function.
//
// The disassociated execution state and any changes made to it are only visible
// to the goroutine which the functions are called in. Any other goroutines,
// including ones started from the function, will see the same execution state
// as the rest of the process.
//
// The acceptable flags are documented in the [unshare(2)] Linux man-page.
// The corresponding CLONE_* constants are defined in package [unix].
//
// # Warning
//
// This function may terminate the thread which the new goroutine executed on
// after fn returns, which could cause subprocesses started with the
// [syscall.SysProcAttr] Pdeathsig field set to be signaled before process
// termination. Any subprocess started before this function is called may be
// affected, in addition to any subprocesses started inside setupfn or fn.
// There are more details at https://go.dev/issue/27505.
//
// [unshare(2)]: https://man7.org/linux/man-pages/man2/unshare.2.html
func Go(flags int, setupfn func() error, fn func()) error {
started := make(chan error)
maskedFlags := flags
for f := range reversibleSetnsFlags {
maskedFlags &^= f
}
isReversible := maskedFlags == 0
go func() {
// Prepare to manipulate per-thread kernel state.
runtime.LockOSThread()
// Not all changes to the execution state can be reverted.
// If an irreversible change to the execution state is made, our
// only recourse is to have the tampered thread terminated by
// returning from this function while the goroutine remains
// wired to the thread. The Go runtime will terminate the thread
// and replace it with a fresh one as needed.
if isReversible {
defer func() {
if isReversible {
// All execution state has been restored without error.
// The thread is once again fungible.
runtime.UnlockOSThread()
}
}()
tid := unix.Gettid()
for f, ns := range reversibleSetnsFlags {
if flags&f != f {
continue
}
// The /proc/thread-self directory was added in Linux 3.17.
// We are not using it to maximize compatibility.
pth := fmt.Sprintf("/proc/self/task/%d/ns/%s", tid, ns)
fd, err := unix.Open(pth, unix.O_RDONLY|unix.O_CLOEXEC, 0)
if err != nil {
started <- &os.PathError{Op: "open", Path: pth, Err: err}
return
}
defer func() {
if isReversible {
if err := unix.Setns(fd, 0); err != nil {
isReversible = false
}
}
_ = unix.Close(fd)
}()
}
}
// Threads are implemented under Linux as processes which share
// a virtual memory space. Therefore in a multithreaded process
// unshare(2) disassociates parts of the calling thread's
// context from the thread it was clone(2)'d from.
if err := unix.Unshare(flags); err != nil {
started <- os.NewSyscallError("unshare", err)
return
}
if setupfn != nil {
if err := setupfn(); err != nil {
started <- err
return
}
}
close(started)
if fn != nil {
fn()
}
}()
return <-started
}
+15 -17
View File
@@ -3,7 +3,6 @@ package layer // import "github.com/docker/docker/layer"
import (
"compress/gzip"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
@@ -40,7 +39,7 @@ type fileMetadataTransaction struct {
// which is backed by files on disk using the provided root
// as the root of metadata files.
func newFSMetadataStore(root string) (*fileMetadataStore, error) {
if err := os.MkdirAll(root, 0700); err != nil {
if err := os.MkdirAll(root, 0o700); err != nil {
return nil, err
}
return &fileMetadataStore{
@@ -67,7 +66,7 @@ func (fms *fileMetadataStore) getMountFilename(mount, filename string) string {
func (fms *fileMetadataStore) StartTransaction() (*fileMetadataTransaction, error) {
tmpDir := filepath.Join(fms.root, "tmp")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
return nil, err
}
ws, err := ioutils.NewAtomicWriteSet(tmpDir)
@@ -82,20 +81,19 @@ func (fms *fileMetadataStore) StartTransaction() (*fileMetadataTransaction, erro
}
func (fm *fileMetadataTransaction) SetSize(size int64) error {
content := fmt.Sprintf("%d", size)
return fm.ws.WriteFile("size", []byte(content), 0644)
return fm.ws.WriteFile("size", []byte(strconv.FormatInt(size, 10)), 0o644)
}
func (fm *fileMetadataTransaction) SetParent(parent ChainID) error {
return fm.ws.WriteFile("parent", []byte(digest.Digest(parent).String()), 0644)
return fm.ws.WriteFile("parent", []byte(digest.Digest(parent).String()), 0o644)
}
func (fm *fileMetadataTransaction) SetDiffID(diff DiffID) error {
return fm.ws.WriteFile("diff", []byte(digest.Digest(diff).String()), 0644)
return fm.ws.WriteFile("diff", []byte(digest.Digest(diff).String()), 0o644)
}
func (fm *fileMetadataTransaction) SetCacheID(cacheID string) error {
return fm.ws.WriteFile("cache-id", []byte(cacheID), 0644)
return fm.ws.WriteFile("cache-id", []byte(cacheID), 0o644)
}
func (fm *fileMetadataTransaction) SetDescriptor(ref distribution.Descriptor) error {
@@ -103,11 +101,11 @@ func (fm *fileMetadataTransaction) SetDescriptor(ref distribution.Descriptor) er
if err != nil {
return err
}
return fm.ws.WriteFile("descriptor.json", jsonRef, 0644)
return fm.ws.WriteFile("descriptor.json", jsonRef, 0o644)
}
func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) {
f, err := fm.ws.FileWriter("tar-split.json.gz", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
f, err := fm.ws.FileWriter("tar-split.json.gz", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
@@ -126,7 +124,7 @@ func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteC
func (fm *fileMetadataTransaction) Commit(layer ChainID) error {
finalDir := fm.store.getLayerDirectory(layer)
if err := os.MkdirAll(filepath.Dir(finalDir), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(finalDir), 0o755); err != nil {
return err
}
@@ -236,24 +234,24 @@ func (fms *fileMetadataStore) TarSplitReader(layer ChainID) (io.ReadCloser, erro
}
func (fms *fileMetadataStore) SetMountID(mount string, mountID string) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0o755); err != nil {
return err
}
return os.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0644)
return os.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0o644)
}
func (fms *fileMetadataStore) SetInitID(mount string, init string) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0o755); err != nil {
return err
}
return os.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0644)
return os.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0o644)
}
func (fms *fileMetadataStore) SetMountParent(mount string, parent ChainID) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0o755); err != nil {
return err
}
return os.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0644)
return os.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0o644)
}
func (fms *fileMetadataStore) GetMountID(mount string) (string, error) {
+2 -3
View File
@@ -15,7 +15,6 @@ import (
"github.com/docker/distribution"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/containerfs"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)
@@ -114,7 +113,7 @@ type RWLayer interface {
// Mount mounts the RWLayer and returns the filesystem path
// to the writable layer.
Mount(mountLabel string) (containerfs.ContainerFS, error)
Mount(mountLabel string) (string, error)
// Unmount unmounts the RWLayer. This should be called
// for every mount. If there are multiple mount calls
@@ -158,7 +157,7 @@ type Metadata struct {
// writable mount. Changes made here will
// not be included in the Tar stream of the
// RWLayer.
type MountInit func(root containerfs.ContainerFS) error
type MountInit func(root string) error
// CreateRWLayerOpts contains optional arguments to be passed to CreateRWLayer
type CreateRWLayerOpts struct {
+23 -20
View File
@@ -62,6 +62,9 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) {
ExperimentalEnabled: options.ExperimentalEnabled,
})
if err != nil {
if options.GraphDriver != "" {
return nil, fmt.Errorf("error initializing graphdriver: %v: %s", err, options.GraphDriver)
}
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
}
logrus.Debugf("Initialized graph driver %s", driver)
@@ -265,10 +268,10 @@ func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
}
func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
// err is used to hold the error which will always trigger
// cErr is used to hold the error which will always trigger
// cleanup of creates sources but may not be an error returned
// to the caller (already exists).
var err error
var cErr error
var pid string
var p *roLayer
@@ -282,15 +285,15 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descr
pid = p.cacheID
// Release parent chain if error
defer func() {
if err != nil {
if cErr != nil {
ls.layerL.Lock()
ls.releaseLayer(p)
ls.layerL.Unlock()
}
}()
if p.depth() >= maxLayerDepth {
err = ErrMaxDepthExceeded
return nil, err
cErr = ErrMaxDepthExceeded
return nil, cErr
}
}
@@ -304,18 +307,18 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descr
descriptor: descriptor,
}
if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {
return nil, err
if cErr = ls.driver.Create(layer.cacheID, pid, nil); cErr != nil {
return nil, cErr
}
tx, err := ls.store.StartTransaction()
if err != nil {
return nil, err
tx, cErr := ls.store.StartTransaction()
if cErr != nil {
return nil, cErr
}
defer func() {
if err != nil {
logrus.Debugf("Cleaning up layer %s: %v", layer.cacheID, err)
if cErr != nil {
logrus.Debugf("Cleaning up layer %s: %v", layer.cacheID, cErr)
if err := ls.driver.Remove(layer.cacheID); err != nil {
logrus.Errorf("Error cleaning up cache layer %s: %v", layer.cacheID, err)
}
@@ -325,8 +328,8 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descr
}
}()
if err = ls.applyTar(tx, ts, pid, layer); err != nil {
return nil, err
if cErr = ls.applyTar(tx, ts, pid, layer); cErr != nil {
return nil, cErr
}
if layer.parent == nil {
@@ -335,8 +338,8 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descr
layer.chainID = createChainIDFromParent(layer.parent.chainID, layer.diffID)
}
if err = storeLayer(tx, layer); err != nil {
return nil, err
if cErr = storeLayer(tx, layer); cErr != nil {
return nil, cErr
}
ls.layerL.Lock()
@@ -344,12 +347,12 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descr
if existingLayer := ls.get(layer.chainID); existingLayer != nil {
// Set error for cleanup, but do not return the error
err = errors.New("layer already exists")
cErr = errors.New("layer already exists")
return existingLayer.getReference(), nil
}
if err = tx.Commit(layer.chainID); err != nil {
return nil, err
if cErr = tx.Commit(layer.chainID); cErr != nil {
return nil, cErr
}
ls.layerMap[layer.chainID] = layer
@@ -786,5 +789,5 @@ func (n *naiveDiffPathDriver) DiffGetter(id string) (graphdriver.FileGetCloser,
if err != nil {
return nil, err
}
return &fileGetPutter{storage.NewPathFileGetter(p.Path()), n.Driver, id}, nil
return &fileGetPutter{storage.NewPathFileGetter(p), n.Driver, id}, nil
}
+1 -1
View File
@@ -37,7 +37,7 @@ func GetLayerPath(s Store, layer ChainID) (string, error) {
return "", err
}
return path.Path(), nil
return path, nil
}
func (ls *layerStore) mountID(name string) string {
+1 -2
View File
@@ -5,7 +5,6 @@ import (
"sync"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/containerfs"
)
type mountedLayer struct {
@@ -100,7 +99,7 @@ type referencedRWLayer struct {
*mountedLayer
}
func (rl *referencedRWLayer) Mount(mountLabel string) (containerfs.ContainerFS, error) {
func (rl *referencedRWLayer) Mount(mountLabel string) (string, error) {
return rl.layerStore.driver.Get(rl.mountedLayer.mountID, mountLabel)
}
+1
View File
@@ -175,6 +175,7 @@ func (vrc *verifiedReadCloser) Read(p []byte) (n int, err error) {
}
return
}
func (vrc *verifiedReadCloser) Close() error {
return vrc.rc.Close()
}
+101
View File
@@ -0,0 +1,101 @@
package types // import "github.com/docker/docker/libcontainerd/types"
import (
"context"
"syscall"
"time"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// EventType represents a possible event from libcontainerd
type EventType string
// Event constants used when reporting events
const (
EventUnknown EventType = "unknown"
EventExit EventType = "exit"
EventOOM EventType = "oom"
EventCreate EventType = "create"
EventStart EventType = "start"
EventExecAdded EventType = "exec-added"
EventExecStarted EventType = "exec-started"
EventPaused EventType = "paused"
EventResumed EventType = "resumed"
)
// EventInfo contains the event info
type EventInfo struct {
ContainerID string
ProcessID string
Pid uint32
ExitCode uint32
ExitedAt time.Time
Error error
}
// Backend defines callbacks that the client of the library needs to implement.
type Backend interface {
ProcessEvent(containerID string, event EventType, ei EventInfo) error
}
// Process of a container
type Process interface {
// Pid is the system specific process id
Pid() uint32
// Kill sends the provided signal to the process
Kill(ctx context.Context, signal syscall.Signal) error
// Resize changes the width and height of the process's terminal
Resize(ctx context.Context, width, height uint32) error
// Delete removes the process and any resources allocated returning the exit status
Delete(context.Context) (*containerd.ExitStatus, error)
}
// Client provides access to containerd features.
type Client interface {
Version(ctx context.Context) (containerd.Version, error)
// LoadContainer loads the metadata for a container from containerd.
LoadContainer(ctx context.Context, containerID string) (Container, error)
// NewContainer creates a new containerd container.
NewContainer(ctx context.Context, containerID string, spec *specs.Spec, shim string, runtimeOptions interface{}, opts ...containerd.NewContainerOpts) (Container, error)
}
// Container provides access to a containerd container.
type Container interface {
Start(ctx context.Context, checkpointDir string, withStdin bool, attachStdio StdioCallback) (Task, error)
Task(ctx context.Context) (Task, error)
// AttachTask returns the current task for the container and reattaches
// to the IO for the running task. If no task exists for the container
// a NotFound error is returned.
//
// Clients must make sure that only one reader is attached to the task.
AttachTask(ctx context.Context, attachStdio StdioCallback) (Task, error)
// Delete removes the container and associated resources
Delete(context.Context) error
}
// Task provides access to a running containerd container.
type Task interface {
Process
// Pause suspends the execution of the task
Pause(context.Context) error
// Resume the execution of the task
Resume(context.Context) error
Stats(ctx context.Context) (*Stats, error)
// Pids returns a list of system specific process ids inside the task
Pids(context.Context) ([]containerd.ProcessInfo, error)
Summary(ctx context.Context) ([]Summary, error)
// ForceDelete forcefully kills the task's processes and deletes the task
ForceDelete(context.Context) error
// Status returns the executing status of the task
Status(ctx context.Context) (containerd.Status, error)
// Exec creates and starts a new process inside the task
Exec(ctx context.Context, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (Process, error)
UpdateResources(ctx context.Context, resources *Resources) error
CreateCheckpoint(ctx context.Context, checkpointDir string, exit bool) error
}
// StdioCallback is called to connect a container or process stdio.
type StdioCallback func(io *cio.DirectIO) (cio.IO, error)
+33
View File
@@ -0,0 +1,33 @@
package types // import "github.com/docker/docker/libcontainerd/types"
import (
"time"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// Summary is not used on linux
type Summary struct{}
// Stats holds metrics properties as returned by containerd
type Stats struct {
Read time.Time
// Metrics is expected to be either one of:
// * github.com/containerd/cgroups/v3/cgroup1/stats.Metrics
// * github.com/containerd/cgroups/v3/cgroup2/stats.Metrics
Metrics interface{}
}
// InterfaceToStats returns a stats object from the platform-specific interface.
func InterfaceToStats(read time.Time, v interface{}) *Stats {
return &Stats{
Metrics: v,
Read: read,
}
}
// Resources defines updatable container resource values. TODO: it must match containerd upcoming API
type Resources = specs.LinuxResources
// Checkpoints contains the details of a checkpoint
type Checkpoints struct{}
+37
View File
@@ -0,0 +1,37 @@
package types // import "github.com/docker/docker/libcontainerd/types"
import (
"time"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
)
type Summary options.ProcessDetails
// Stats contains statistics from HCS
type Stats struct {
Read time.Time
HCSStats *hcsshim.Statistics
}
// InterfaceToStats returns a stats object from the platform-specific interface.
func InterfaceToStats(read time.Time, v interface{}) *Stats {
return &Stats{
HCSStats: v.(*hcsshim.Statistics),
Read: read,
}
}
// Resources defines updatable container resource values.
type Resources struct{}
// Checkpoint holds the details of a checkpoint (not supported in windows)
type Checkpoint struct {
Name string
}
// Checkpoints contains the details of a checkpoint
type Checkpoints struct {
Checkpoints []*Checkpoint
}
+20 -4
View File
@@ -1,16 +1,31 @@
package oci // import "github.com/docker/docker/oci"
import (
"os"
"runtime"
"github.com/docker/docker/oci/caps"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func iPtr(i int64) *int64 { return &i }
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
func iPtr(i int64) *int64 { return &i }
const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
// DefaultPathEnv is unix style list of directories to search for
// executables. Each directory is separated from the next by a colon
// ':' character .
// For Windows containers, an empty string is returned as the default
// path will be set by the container, and Docker has no context of what the
// default path should be.
//
// TODO(thaJeztah) align Windows default with BuildKit; see https://github.com/moby/buildkit/pull/1747
// TODO(thaJeztah) use defaults from containerd (but align it with BuildKit; see https://github.com/moby/buildkit/pull/1747)
func DefaultPathEnv(os string) string {
if os == "windows" {
return ""
}
return defaultUnixPathEnv
}
// DefaultSpec returns the default spec used by docker for the current Platform
func DefaultSpec() specs.Spec {
@@ -98,6 +113,7 @@ func DefaultLinuxSpec() specs.Spec {
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
"/sys/devices/virtual/powercap",
},
ReadonlyPaths: []string{
"/proc/bus",
+11 -25
View File
@@ -6,31 +6,17 @@ import (
"path/filepath"
"strings"
"github.com/opencontainers/runc/libcontainer/devices"
coci "github.com/containerd/containerd/oci"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
// Device transforms a libcontainer devices.Device to a specs.LinuxDevice object.
func Device(d *devices.Device) specs.LinuxDevice {
return specs.LinuxDevice{
Type: string(d.Type),
Path: d.Path,
Major: d.Major,
Minor: d.Minor,
FileMode: fmPtr(int64(d.FileMode &^ unix.S_IFMT)), // strip file type, as OCI spec only expects file-mode to be included
UID: u32Ptr(int64(d.Uid)),
GID: u32Ptr(int64(d.Gid)),
}
}
func deviceCgroup(d *devices.Device) specs.LinuxDeviceCgroup {
func deviceCgroup(d *specs.LinuxDevice, permissions string) specs.LinuxDeviceCgroup {
return specs.LinuxDeviceCgroup{
Allow: true,
Type: string(d.Type),
Type: d.Type,
Major: &d.Major,
Minor: &d.Minor,
Access: string(d.Permissions),
Access: permissions,
}
}
@@ -45,22 +31,22 @@ func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (dev
}
}
device, err := devices.DeviceFromPath(resolvedPathOnHost, cgroupPermissions)
device, err := coci.DeviceFromPath(resolvedPathOnHost)
// if there was no error, return the device
if err == nil {
device.Path = pathInContainer
return append(devs, Device(device)), append(devPermissions, deviceCgroup(device)), nil
return append(devs, *device), append(devPermissions, deviceCgroup(device, cgroupPermissions)), nil
}
// if the device is not a device node
// try to see if it's a directory holding many devices
if err == devices.ErrNotADevice {
if err == coci.ErrNotADevice {
// check if it is a directory
if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() {
// mount the internal devices recursively
// TODO check if additional errors should be handled or logged
_ = filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, _ error) error {
childDevice, e := devices.DeviceFromPath(dpath, cgroupPermissions)
_ = filepath.WalkDir(resolvedPathOnHost, func(dpath string, f os.DirEntry, _ error) error {
childDevice, e := coci.DeviceFromPath(dpath)
if e != nil {
// ignore the device
return nil
@@ -68,8 +54,8 @@ func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (dev
// add the device to userSpecified devices
childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, pathInContainer, 1)
devs = append(devs, Device(childDevice))
devPermissions = append(devPermissions, deviceCgroup(childDevice))
devs = append(devs, *childDevice)
devPermissions = append(devPermissions, deviceCgroup(childDevice, cgroupPermissions))
return nil
})
+3
View File
@@ -4,6 +4,9 @@ import specs "github.com/opencontainers/runtime-spec/specs-go"
// RemoveNamespace removes the `nsType` namespace from OCI spec `s`
func RemoveNamespace(s *specs.Spec, nsType specs.LinuxNamespaceType) {
if s.Linux == nil {
return
}
for i, n := range s.Linux.Namespaces {
if n.Type == nsType {
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
+7 -13
View File
@@ -20,19 +20,13 @@ var deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\
// SetCapabilities sets the provided capabilities on the spec
// All capabilities are added if privileged is true.
func SetCapabilities(s *specs.Spec, caplist []string) error {
// setUser has already been executed here
if s.Process.User.UID == 0 {
s.Process.Capabilities = &specs.LinuxCapabilities{
Effective: caplist,
Bounding: caplist,
Permitted: caplist,
}
} else {
// Do not set Effective and Permitted capabilities for non-root users,
// to match what execve does.
s.Process.Capabilities = &specs.LinuxCapabilities{
Bounding: caplist,
}
if s.Process == nil {
s.Process = &specs.Process{}
}
s.Process.Capabilities = &specs.LinuxCapabilities{
Effective: caplist,
Bounding: caplist,
Permitted: caplist,
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More