vendor: github.com/docker/docker v28.0.2

full diff: https://github.com/docker/docker/compare/v28.0.1...v28.0.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2025-03-19 17:48:22 +01:00
parent 967fc2a696
commit 882ef0db91
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
26 changed files with 363 additions and 170 deletions

2
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/distribution/reference v0.6.0
github.com/docker/cli v28.0.1+incompatible
github.com/docker/cli-docs-tool v0.9.0
github.com/docker/docker v28.0.1+incompatible
github.com/docker/docker v28.0.2+incompatible
github.com/docker/go-units v0.5.0
github.com/gofrs/flock v0.12.1
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510

4
go.sum
View File

@ -129,8 +129,8 @@ github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3T
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8=
github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=

View File

@ -5508,8 +5508,11 @@ definitions:
com.example.some-other-label: "some-other-value"
Data:
description: |
Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5))
data to store as secret.
Data is the data to store as a secret, formatted as a Base64-url-safe-encoded
([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) string.
It must be empty if the Driver field is set, in which case the data is
loaded from an external secret store. The maximum allowed size is 500KB,
as defined in [MaxSecretSize](https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/api/validation#MaxSecretSize).
This field is only used to _create_ a secret, and is not returned by
other endpoints.
@ -5560,8 +5563,9 @@ definitions:
type: "string"
Data:
description: |
Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5))
config data.
Data is the data to store as a config, formatted as a Base64-url-safe-encoded
([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) string.
The maximum allowed size is 1000KB, as defined in [MaxConfigSize](https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/manager/controlapi#MaxConfigSize).
type: "string"
Templating:
description: |

View File

@ -49,15 +49,17 @@ func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) {
}
// UnmarshalJSON sets the IPNet from a byte array of JSON
func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) {
func (ipnet *NetIPNet) UnmarshalJSON(b []byte) error {
var ipnetStr string
if err = json.Unmarshal(b, &ipnetStr); err == nil {
var cidr *net.IPNet
if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil {
if err := json.Unmarshal(b, &ipnetStr); err != nil {
return err
}
_, cidr, err := net.ParseCIDR(ipnetStr)
if err != nil {
return err
}
*ipnet = NetIPNet(*cidr)
}
}
return
return nil
}
// IndexInfo contains information about a registry

View File

@ -12,6 +12,12 @@ type Config struct {
// ConfigSpec represents a config specification from a config in swarm
type ConfigSpec struct {
Annotations
// Data is the data to store as a config.
//
// The maximum allowed size is 1000KB, as defined in [MaxConfigSize].
//
// [MaxConfigSize]: https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/manager/controlapi#MaxConfigSize
Data []byte `json:",omitempty"`
// Templating controls whether and how to evaluate the config payload as

View File

@ -12,8 +12,22 @@ type Secret struct {
// SecretSpec represents a secret specification from a secret in swarm
type SecretSpec struct {
Annotations
// Data is the data to store as a secret. It must be empty if a
// [Driver] is used, in which case the data is loaded from an external
// secret store. The maximum allowed size is 500KB, as defined in
// [MaxSecretSize].
//
// This field is only used to create the secret, and is not returned
// by other endpoints.
//
// [MaxSecretSize]: https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/api/validation#MaxSecretSize
Data []byte `json:",omitempty"`
Driver *Driver `json:",omitempty"` // name of the secrets driver used to fetch the secret's value from an external secret store
// Driver is the name of the secrets driver used to fetch the secret's
// value from an external secret store. If not set, the default built-in
// store is used.
Driver *Driver `json:",omitempty"`
// Templating controls whether and how to evaluate the secret payload as
// a template. If it is not set, no templating is used.

View File

@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client"
import (
"context"
"encoding/json"
"errors"
"net/url"
"path"
"sort"
@ -54,6 +55,19 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
// When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize
hostConfig.ConsoleSize = [2]uint{0, 0}
}
if versions.LessThan(cli.ClientVersion(), "1.44") {
for _, m := range hostConfig.Mounts {
if m.BindOptions != nil {
// ReadOnlyNonRecursive can be safely ignored when API < 1.44
if m.BindOptions.ReadOnlyForceRecursive {
return response, errors.New("bind-recursive=readonly requires API v1.44 or later")
}
if m.BindOptions.NonRecursive && versions.LessThan(cli.ClientVersion(), "1.40") {
return response, errors.New("bind-recursive=disabled requires API v1.40 or later")
}
}
}
}
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)

View File

@ -37,6 +37,11 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
if err := validateServiceSpec(service); err != nil {
return response, err
}
if versions.LessThan(cli.version, "1.30") {
if err := validateAPIVersion(service, cli.version); err != nil {
return response, err
}
}
// ensure that the image is tagged
var resolveWarning string
@ -191,3 +196,18 @@ func validateServiceSpec(s swarm.ServiceSpec) error {
}
return nil
}
func validateAPIVersion(c swarm.ServiceSpec, apiVersion string) error {
for _, m := range c.TaskTemplate.ContainerSpec.Mounts {
if m.BindOptions != nil {
if m.BindOptions.NonRecursive && versions.LessThan(apiVersion, "1.40") {
return errors.Errorf("bind-recursive=disabled requires API v1.40 or later")
}
// ReadOnlyNonRecursive can be safely ignored when API < 1.44
if m.BindOptions.ReadOnlyForceRecursive && versions.LessThan(apiVersion, "1.44") {
return errors.Errorf("bind-recursive=readonly requires API v1.44 or later")
}
}
}
return nil
}

View File

@ -236,11 +236,9 @@ func (r *readCloserWrapper) Close() error {
return nil
}
var (
bufioReader32KPool = &sync.Pool{
var bufioReader32KPool = &sync.Pool{
New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) },
}
)
}
type bufferedReader struct {
buf *bufio.Reader
@ -252,17 +250,17 @@ func newBufferedReader(r io.Reader) *bufferedReader {
return &bufferedReader{buf}
}
func (r *bufferedReader) Read(p []byte) (n int, err error) {
func (r *bufferedReader) Read(p []byte) (int, error) {
if r.buf == nil {
return 0, io.EOF
}
n, err = r.buf.Read(p)
n, err := r.buf.Read(p)
if err == io.EOF {
r.buf.Reset(nil)
bufioReader32KPool.Put(r.buf)
r.buf = nil
}
return
return n, err
}
func (r *bufferedReader) Peek(n int) ([]byte, error) {
@ -428,7 +426,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
pipeWriter.CloseWithError(err)
return
}
if _, err := copyWithBuffer(tarWriter, tarReader); err != nil {
if err := copyWithBuffer(tarWriter, tarReader); err != nil {
pipeWriter.CloseWithError(err)
return
}
@ -731,7 +729,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
return err
}
_, err = copyWithBuffer(ta.TarWriter, file)
err = copyWithBuffer(ta.TarWriter, file)
file.Close()
if err != nil {
return err
@ -778,11 +776,11 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
if err != nil {
return err
}
if _, err := copyWithBuffer(file, reader); err != nil {
file.Close()
if err := copyWithBuffer(file, reader); err != nil {
_ = file.Close()
return err
}
file.Close()
_ = file.Close()
case tar.TypeBlock, tar.TypeChar:
if inUserns { // cannot create devices in a userns
@ -1438,7 +1436,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err := copyWithBuffer(tw, srcF); err != nil {
if err := copyWithBuffer(tw, srcF); err != nil {
return err
}
return nil

View File

@ -20,7 +20,7 @@ func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
type overlayWhiteoutConverter struct{}
func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) {
func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) {
// convert whiteouts to AUFS format
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
// we just rename the file and make it normal
@ -31,7 +31,11 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
hdr.Size = 0
}
if fi.Mode()&os.ModeDir != 0 {
if fi.Mode()&os.ModeDir == 0 {
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
return nil, nil
}
opaqueXattrName := "trusted.overlay.opaque"
if userns.RunningInUserNS() {
opaqueXattrName = "user.overlay.opaque"
@ -42,12 +46,15 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
if err != nil {
return nil, err
}
if len(opaque) == 1 && opaque[0] == 'y' {
if len(opaque) != 1 || opaque[0] != 'y' {
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
return nil, nil
}
delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName)
// create a header for the whiteout file
// it should inherit some properties from the parent, but be a regular file
wo = &tar.Header{
return &tar.Header{
Typeflag: tar.TypeReg,
Mode: hdr.Mode & int64(os.ModePerm),
Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
@ -58,11 +65,7 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
Gname: hdr.Gname,
AccessTime: hdr.AccessTime,
ChangeTime: hdr.ChangeTime,
}
}
}
return
}, nil
}
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) {

View File

@ -73,14 +73,13 @@ func statUnix(fi os.FileInfo, hdr *tar.Header) error {
return nil
}
func getInodeFromStat(stat interface{}) (inode uint64, err error) {
func getInodeFromStat(stat interface{}) (uint64, error) {
s, ok := stat.(*syscall.Stat_t)
if ok {
inode = s.Ino
if !ok {
// FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897
return 0, nil
}
return
return s.Ino, nil
}
func getFileUIDGID(stat interface{}) (idtools.Identity, error) {

View File

@ -48,9 +48,9 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (
return
}
func getInodeFromStat(stat interface{}) (inode uint64, err error) {
func getInodeFromStat(stat interface{}) (uint64, error) {
// do nothing. no notion of Inode in stat on Windows
return
return 0, nil
}
// handleTarTypeBlockCharFifo is an OS-specific helper function used by

View File

@ -83,7 +83,7 @@ func aufsMetadataSkip(path string) (skip bool, err error) {
if err != nil {
skip = true
}
return
return skip, err
}
func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) {

View File

@ -25,11 +25,11 @@ var copyPool = sync.Pool{
New: func() interface{} { s := make([]byte, 32*1024); return &s },
}
func copyWithBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
func copyWithBuffer(dst io.Writer, src io.Reader) error {
buf := copyPool.Get().(*[]byte)
written, err = io.CopyBuffer(dst, src, *buf)
_, err := io.CopyBuffer(dst, src, *buf)
copyPool.Put(buf)
return
return err
}
// PreserveTrailingDotOrSeparator returns the given cleaned path (after
@ -105,13 +105,13 @@ func TarResource(sourceInfo CopyInfo) (content io.ReadCloser, err error) {
// TarResourceRebase is like TarResource but renames the first path element of
// items in the resulting tar archive to match the given rebaseName if not "".
func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, err error) {
func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, _ error) {
sourcePath = normalizePath(sourcePath)
if _, err = os.Lstat(sourcePath); err != nil {
if _, err := os.Lstat(sourcePath); err != nil {
// Catches the case where the source does not exist or is not a
// directory if asserted to be a directory, as this also causes an
// error.
return
return nil, err
}
// Separate the source path between its directory and
@ -442,11 +442,12 @@ func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error {
// whether to follow symbol link or not, if followLink is true, resolvedPath will return
// link target of any symbol link file, else it will only resolve symlink of directory
// but return symbol link file itself without resolving.
func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, err error) {
func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, _ error) {
if followLink {
var err error
resolvedPath, err = filepath.EvalSymlinks(path)
if err != nil {
return
return "", "", err
}
resolvedPath, rebaseName = GetRebaseName(path, resolvedPath)
@ -454,10 +455,9 @@ func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseNa
dirPath, basePath := filepath.Split(path)
// if not follow symbol link, then resolve symbol link of parent dir
var resolvedDirPath string
resolvedDirPath, err = filepath.EvalSymlinks(dirPath)
resolvedDirPath, err := filepath.EvalSymlinks(dirPath)
if err != nil {
return
return "", "", err
}
// resolvedDirPath will have been cleaned (no trailing path separators) so
// we can manually join it with the base path element.

View File

@ -17,12 +17,13 @@ func chtimes(name string, atime time.Time, mtime time.Time) error {
return os.Chtimes(name, atime, mtime)
}
func timeToTimespec(time time.Time) (ts unix.Timespec) {
func timeToTimespec(time time.Time) unix.Timespec {
if time.IsZero() {
// Return UTIME_OMIT special value
ts.Sec = 0
ts.Nsec = (1 << 30) - 2
return
return unix.Timespec{
Sec: 0,
Nsec: (1 << 30) - 2,
}
}
return unix.NsecToTimespec(time.UnixNano())
}

View File

@ -45,8 +45,8 @@ func Generate(input ...string) (io.Reader, error) {
return buf, nil
}
func parseStringPairs(input ...string) (output [][2]string) {
output = make([][2]string, 0, len(input)/2+1)
func parseStringPairs(input ...string) [][2]string {
output := make([][2]string, 0, len(input)/2+1)
for i := 0; i < len(input); i += 2 {
var pair [2]string
pair[0] = input[i]
@ -55,5 +55,5 @@ func parseStringPairs(input ...string) (output [][2]string) {
}
output = append(output, pair)
}
return
return output
}

View File

@ -11,12 +11,12 @@ import (
// destination path. Writing and closing concurrently is not allowed.
// NOTE: umask is not considered for the file's permissions.
func New(filename string, perm os.FileMode) (io.WriteCloser, error) {
f, err := os.CreateTemp(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
abspath, err := filepath.Abs(filename)
if err != nil {
return nil, err
}
abspath, err := filepath.Abs(filename)
f, err := os.CreateTemp(filepath.Dir(abspath), ".tmp-"+filepath.Base(filename))
if err != nil {
return nil, err
}

View File

@ -43,9 +43,9 @@ type stdWriter struct {
// It inserts the prefix header before the buffer,
// so stdcopy.StdCopy knows where to multiplex the output.
// It makes stdWriter to implement io.Writer.
func (w *stdWriter) Write(p []byte) (n int, err error) {
func (w *stdWriter) Write(p []byte) (int, error) {
if w == nil || w.Writer == nil {
return 0, errors.New("Writer not instantiated")
return 0, errors.New("writer not instantiated")
}
if p == nil {
return 0, nil
@ -57,7 +57,7 @@ func (w *stdWriter) Write(p []byte) (n int, err error) {
buf.Write(header[:])
buf.Write(p)
n, err = w.Writer.Write(buf.Bytes())
n, err := w.Writer.Write(buf.Bytes())
n -= stdWriterPrefixLen
if n < 0 {
n = 0
@ -65,7 +65,7 @@ func (w *stdWriter) Write(p []byte) (n int, err error) {
buf.Reset()
bufPool.Put(buf)
return
return n, err
}
// NewStdWriter instantiates a new Writer.

View File

@ -66,13 +66,13 @@ func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
// loginV2 tries to login to the v2 registry server. The given registry
// endpoint will be pinged to get authorization challenges. These challenges
// will be used to authenticate against the registry to validate credentials.
func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (status string, token string, _ error) {
func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (token string, _ error) {
endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
log.G(context.TODO()).Debugf("attempting v2 login to registry endpoint %s", endpointStr)
req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
if err != nil {
return "", "", err
return "", err
}
var (
@ -84,22 +84,22 @@ func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent st
loginClient, err := v2AuthHTTPClient(endpoint.URL, authTrans, modifiers, creds, nil)
if err != nil {
return "", "", err
return "", err
}
resp, err := loginClient.Do(req)
if err != nil {
err = translateV2AuthError(err)
return "", "", err
return "", err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
if resp.StatusCode != http.StatusOK {
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
return "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
}
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
return credentialAuthConfig.IdentityToken, nil
}
func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {

View File

@ -4,13 +4,17 @@ import (
"context"
"net"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/containerd/log"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/internal/lazyregexp"
"github.com/docker/docker/pkg/homedir"
)
// ServiceOptions holds command line options.
@ -56,26 +60,52 @@ var (
Host: DefaultRegistryHost,
}
emptyServiceConfig, _ = newServiceConfig(ServiceOptions{})
validHostPortRegex = lazyregexp.New(`^` + reference.DomainRegexp.String() + `$`)
// certsDir is used to override defaultCertsDir.
// certsDir is used to override defaultCertsDir when running with rootlessKit.
//
// TODO(thaJeztah): change to a sync.OnceValue once we remove [SetCertsDir]
// TODO(thaJeztah): certsDir should not be a package variable, but stored in our config, and passed when needed.
setCertsDirOnce sync.Once
certsDir string
)
func setCertsDir(dir string) string {
setCertsDirOnce.Do(func() {
if dir != "" {
certsDir = dir
return
}
if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" {
// Configure registry.CertsDir() when running in rootless-mode
// This is the equivalent of [rootless.RunningWithRootlessKit],
// but inlining it to prevent adding that as a dependency
// for docker/cli.
//
// [rootless.RunningWithRootlessKit]: https://github.com/moby/moby/blob/b4bdf12daec84caaf809a639f923f7370d4926ad/pkg/rootless/rootless.go#L5-L8
if configHome, _ := homedir.GetConfigHome(); configHome != "" {
certsDir = filepath.Join(configHome, "docker/certs.d")
return
}
}
certsDir = defaultCertsDir
})
return certsDir
}
// SetCertsDir allows the default certs directory to be changed. This function
// is used at daemon startup to set the correct location when running in
// rootless mode.
//
// Deprecated: the cert-directory is now automatically selected when running with rootlessKit, and should no longer be set manually.
func SetCertsDir(path string) {
certsDir = path
setCertsDir(path)
}
// CertsDir is the directory where certificates are stored.
func CertsDir() string {
if certsDir != "" {
return certsDir
}
return defaultCertsDir
// call setCertsDir with an empty path to synchronise with [SetCertsDir]
return setCertsDir("")
}
// newServiceConfig returns a new instance of ServiceConfig
@ -181,7 +211,7 @@ skip:
// Assume `host:port` if not CIDR.
indexConfigs[r] = &registry.IndexInfo{
Name: r,
Mirrors: make([]string, 0),
Mirrors: []string{},
Secure: false,
Official: false,
}
@ -288,16 +318,22 @@ func ValidateMirror(val string) (string, error) {
// ValidateIndexName validates an index name. It is used by the daemon to
// validate the daemon configuration.
func ValidateIndexName(val string) (string, error) {
// TODO: upstream this to check to reference package
if val == "index.docker.io" {
val = "docker.io"
}
val = normalizeIndexName(val)
if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
return "", invalidParamf("invalid index name (%s). Cannot begin or end with a hyphen", val)
}
return val, nil
}
func normalizeIndexName(val string) string {
// TODO(thaJeztah): consider normalizing other known options, such as "(https://)registry-1.docker.io", "https://index.docker.io/v1/".
// TODO: upstream this to check to reference package
if val == "index.docker.io" {
return "docker.io"
}
return val
}
func hasScheme(reposName string) bool {
return strings.Contains(reposName, "://")
}
@ -327,25 +363,20 @@ func validateHostPort(s string) error {
}
// newIndexInfo returns IndexInfo configuration from indexName
func newIndexInfo(config *serviceConfig, indexName string) (*registry.IndexInfo, error) {
var err error
indexName, err = ValidateIndexName(indexName)
if err != nil {
return nil, err
}
func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo {
indexName = normalizeIndexName(indexName)
// Return any configured index info, first.
if index, ok := config.IndexConfigs[indexName]; ok {
return index, nil
return index
}
// Construct a non-configured index info.
return &registry.IndexInfo{
Name: indexName,
Mirrors: make([]string, 0),
Mirrors: []string{},
Secure: config.isSecureIndex(indexName),
Official: false,
}, nil
}
}
// GetAuthConfigKey special-cases using the full index address of the official
@ -358,18 +389,22 @@ func GetAuthConfigKey(index *registry.IndexInfo) string {
}
// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) {
index, err := newIndexInfo(config, reference.Domain(name))
if err != nil {
return nil, err
func newRepositoryInfo(config *serviceConfig, name reference.Named) *RepositoryInfo {
index := newIndexInfo(config, reference.Domain(name))
var officialRepo bool
if index.Official {
// RepositoryInfo.Official indicates whether the image repository
// is an official (docker library official images) repository.
//
// We only need to check this if the image-repository is on Docker Hub.
officialRepo = !strings.ContainsRune(reference.FamiliarName(name), '/')
}
official := !strings.ContainsRune(reference.FamiliarName(name), '/')
return &RepositoryInfo{
Name: reference.TrimNamed(name),
Index: index,
Official: official,
}, nil
Official: officialRepo,
}
}
// ParseRepositoryInfo performs the breakdown of a repository name into a
@ -377,5 +412,70 @@ func newRepositoryInfo(config *serviceConfig, name reference.Named) (*Repository
//
// It is used by the Docker cli to interact with registry-related endpoints.
func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
return newRepositoryInfo(emptyServiceConfig, reposName)
indexName := normalizeIndexName(reference.Domain(reposName))
if indexName == IndexName {
officialRepo := !strings.ContainsRune(reference.FamiliarName(reposName), '/')
return &RepositoryInfo{
Name: reference.TrimNamed(reposName),
Index: &registry.IndexInfo{
Name: IndexName,
Mirrors: []string{},
Secure: true,
Official: true,
},
Official: officialRepo,
}, nil
}
insecure := false
if isInsecure(indexName) {
insecure = true
}
return &RepositoryInfo{
Name: reference.TrimNamed(reposName),
Index: &registry.IndexInfo{
Name: indexName,
Mirrors: []string{},
Secure: !insecure,
},
}, nil
}
// isInsecure is used to detect whether a registry domain or IP-address is allowed
// to use an insecure (non-TLS, or self-signed cert) connection according to the
// defaults, which allows for insecure connections with registries running on a
// loopback address ("localhost", "::1/128", "127.0.0.0/8").
//
// It is used in situations where we don't have access to the daemon's configuration,
// for example, when used from the client / CLI.
func isInsecure(hostNameOrIP string) bool {
// Attempt to strip port if present; this also strips brackets for
// IPv6 addresses with a port (e.g. "[::1]:5000").
//
// This is best-effort; we'll continue using the address as-is if it fails.
if host, _, err := net.SplitHostPort(hostNameOrIP); err == nil {
hostNameOrIP = host
}
if hostNameOrIP == "127.0.0.1" || hostNameOrIP == "::1" || strings.EqualFold(hostNameOrIP, "localhost") {
// Fast path; no need to resolve these, assuming nobody overrides
// "localhost" for anything else than a loopback address (sorry, not sorry).
return true
}
var addresses []net.IP
if ip := net.ParseIP(hostNameOrIP); ip != nil {
addresses = append(addresses, ip)
} else {
// Try to resolve the host's IP-addresses.
addrs, _ := lookupIP(hostNameOrIP)
addresses = append(addresses, addrs...)
}
for _, addr := range addresses {
if addr.IsLoopback() {
return true
}
}
return false
}

View File

@ -8,7 +8,6 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/containerd/log"
@ -18,7 +17,14 @@ import (
)
// HostCertsDir returns the config directory for a specific host.
//
// Deprecated: this function was only used internally, and will be removed in a future release.
func HostCertsDir(hostname string) string {
return hostCertsDir(hostname)
}
// hostCertsDir returns the config directory for a specific host.
func hostCertsDir(hostname string) string {
return filepath.Join(CertsDir(), cleanPath(hostname))
}
@ -26,11 +32,10 @@ func HostCertsDir(hostname string) string {
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
// PreferredServerCipherSuites should have no effect
tlsConfig := tlsconfig.ServerDefault()
tlsConfig.InsecureSkipVerify = !isSecure
if isSecure && CertsDir() != "" {
hostDir := HostCertsDir(hostname)
if isSecure {
hostDir := hostCertsDir(hostname)
log.G(context.TODO()).Debugf("hostDir: %s", hostDir)
if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
return nil, err
@ -59,7 +64,8 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
}
for _, f := range fs {
if strings.HasSuffix(f.Name(), ".crt") {
switch filepath.Ext(f.Name()) {
case ".crt":
if tlsConfig.RootCAs == nil {
systemPool, err := tlsconfig.SystemCertPool()
if err != nil {
@ -67,17 +73,17 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
}
tlsConfig.RootCAs = systemPool
}
log.G(context.TODO()).Debugf("crt: %s", filepath.Join(directory, f.Name()))
data, err := os.ReadFile(filepath.Join(directory, f.Name()))
fileName := filepath.Join(directory, f.Name())
log.G(context.TODO()).Debugf("crt: %s", fileName)
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
tlsConfig.RootCAs.AppendCertsFromPEM(data)
}
if strings.HasSuffix(f.Name(), ".cert") {
case ".cert":
certName := f.Name()
keyName := certName[:len(certName)-5] + ".key"
log.G(context.TODO()).Debugf("cert: %s", filepath.Join(directory, f.Name()))
log.G(context.TODO()).Debugf("cert: %s", filepath.Join(directory, certName))
if !hasFile(fs, keyName) {
return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
}
@ -86,11 +92,10 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
return err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
}
if strings.HasSuffix(f.Name(), ".key") {
case ".key":
keyName := f.Name()
certName := keyName[:len(keyName)-4] + ".cert"
log.G(context.TODO()).Debugf("key: %s", filepath.Join(directory, f.Name()))
log.G(context.TODO()).Debugf("key: %s", filepath.Join(directory, keyName))
if !hasFile(fs, certName) {
return invalidParamf("missing client certificate %s for key %s", certName, keyName)
}

View File

@ -93,12 +93,8 @@ func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int,
// Search is a long-running operation, just lock s.config to avoid block others.
s.mu.RLock()
index, err := newIndexInfo(s.config, indexName)
index := newIndexInfo(s.config, indexName)
s.mu.RUnlock()
if err != nil {
return nil, err
}
if index.Official {
// If pull "library/foo", it's stored locally under "foo"
remoteName = strings.TrimPrefix(remoteName, "library/")
@ -158,5 +154,24 @@ func splitReposSearchTerm(reposName string) (string, string) {
// for that.
func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) {
indexName, _ := splitReposSearchTerm(reposName)
return newIndexInfo(emptyServiceConfig, indexName)
indexName = normalizeIndexName(indexName)
if indexName == IndexName {
return &registry.IndexInfo{
Name: IndexName,
Mirrors: []string{},
Secure: true,
Official: true,
}, nil
}
insecure := false
if isInsecure(indexName) {
insecure = true
}
return &registry.IndexInfo{
Name: indexName,
Mirrors: []string{},
Secure: !insecure,
}, nil
}

View File

@ -83,12 +83,12 @@ type onEOFReader struct {
Fn func()
}
func (r *onEOFReader) Read(p []byte) (n int, err error) {
n, err = r.Rc.Read(p)
func (r *onEOFReader) Read(p []byte) (int, error) {
n, err := r.Rc.Read(p)
if err == io.EOF {
r.runFunc()
}
return
return n, err
}
// Close closes the file and run the function.

View File

@ -52,7 +52,7 @@ func (s *Service) ReplaceConfig(options ServiceOptions) (commit func(), err erro
// Auth contacts the public registry with the provided credentials,
// and returns OK if authentication was successful.
// It can be used to verify the validity of a client's credentials.
func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error) {
func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (statusMessage, token string, _ error) {
// TODO Use ctx when searching for repositories
registryHostName := IndexHostname
@ -77,19 +77,28 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use
return "", "", invalidParam(err)
}
var lastErr error
for _, endpoint := range endpoints {
status, token, err = loginV2(authConfig, endpoint, userAgent)
if err == nil {
return
}
authToken, err := loginV2(authConfig, endpoint, userAgent)
if err != nil {
if errdefs.IsUnauthorized(err) {
// Failed to authenticate; don't continue with (non-TLS) endpoints.
return status, token, err
return "", "", err
}
log.G(ctx).WithError(err).Infof("Error logging in to endpoint, trying next endpoint")
// Try next endpoint
log.G(ctx).WithFields(log.Fields{
"error": err,
"endpoint": endpoint,
}).Infof("Error logging in to endpoint, trying next endpoint")
lastErr = err
continue
}
return "", "", err
// TODO(thaJeztah): move the statusMessage to the API endpoint; we don't need to produce that here?
return "Login Succeeded", authToken, nil
}
return "", "", lastErr
}
// ResolveRepository splits a repository name into its components
@ -97,7 +106,8 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use
func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return newRepositoryInfo(s.config, name)
// TODO(thaJeztah): remove error return as it's no longer used.
return newRepositoryInfo(s.config, name), nil
}
// APIEndpoint represents a remote API endpoint

View File

@ -13,6 +13,8 @@ type RepositoryInfo struct {
// Official indicates whether the repository is considered official.
// If the registry is official, and the normalized name does not
// contain a '/' (e.g. "foo"), then it is considered an official repo.
//
// Deprecated: this field is no longer used and will be removed in the next release. The information captured in this field can be obtained from the [Name] field instead.
Official bool
// Class represents the class of the repository, such as "plugin"
// or "image".

2
vendor/modules.txt vendored
View File

@ -283,7 +283,7 @@ github.com/docker/distribution/registry/client/transport
github.com/docker/distribution/registry/storage/cache
github.com/docker/distribution/registry/storage/cache/memory
github.com/docker/distribution/uuid
# github.com/docker/docker v28.0.1+incompatible
# github.com/docker/docker v28.0.2+incompatible
## explicit
github.com/docker/docker/api
github.com/docker/docker/api/types