mirror of https://github.com/docker/cli.git
Merge pull request #3486 from thaJeztah/update_engine
vendor: github.com/docker/docker 4a26fdda76d996708aa8100dd23ea90215984451
This commit is contained in:
commit
174e51cc94
10
cli/cobra.go
10
cli/cobra.go
|
@ -3,12 +3,15 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
pluginmanager "github.com/docker/cli/cli-plugins/manager"
|
pluginmanager "github.com/docker/cli/cli-plugins/manager"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
"github.com/moby/term"
|
"github.com/moby/term"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -52,6 +55,13 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *p
|
||||||
|
|
||||||
rootCmd.Annotations = map[string]string{"additionalHelp": "To get more help with docker, check out our guides at https://docs.docker.com/go/guides/"}
|
rootCmd.Annotations = map[string]string{"additionalHelp": "To get more help with docker, check out our guides at https://docs.docker.com/go/guides/"}
|
||||||
|
|
||||||
|
// Configure registry.CertsDir() when running in rootless-mode
|
||||||
|
if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" {
|
||||||
|
if configHome, err := homedir.GetConfigHome(); err == nil {
|
||||||
|
registry.SetCertsDir(filepath.Join(configHome, "docker/certs.d"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return opts, flags, helpCommand
|
return opts, flags, helpCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@ func NewSearchCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
|
||||||
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
flags.IntVar(&options.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
|
// TODO(thaJeztah) remove default from client as the daemon already has a default
|
||||||
|
flags.IntVar(&options.limit, "limit", 25, "Max number of search results")
|
||||||
flags.StringVar(&options.format, "format", "", "Pretty-print search using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print search using a Go template")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}}
|
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled","WARNING: bridge-nf-call-iptables is disabled","WARNING: bridge-nf-call-ip6tables is disabled"],"ClientInfo":{"Debug":true,"Context":"default","Plugins":[],"Warnings":null}}
|
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled","WARNING: bridge-nf-call-iptables is disabled","WARNING: bridge-nf-call-ip6tables is disabled"],"ClientInfo":{"Debug":true,"Context":"default","Plugins":[],"Warnings":null}}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":false,"SwapLimit":false,"KernelMemory":false,"KernelMemoryTCP":false,"CpuCfsPeriod":false,"CpuCfsQuota":false,"CPUShares":false,"CPUSet":false,"PidsLimit":false,"IPv4Forwarding":false,"BridgeNfIptables":false,"BridgeNfIp6tables":false,"Debug":true,"NFd":33,"OomKillDisable":false,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":true,"Context":"default","Plugins":[],"Warnings":null}}
|
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":false,"SwapLimit":false,"CpuCfsPeriod":false,"CpuCfsQuota":false,"CPUShares":false,"CPUSet":false,"PidsLimit":false,"IPv4Forwarding":false,"BridgeNfIptables":false,"BridgeNfIp6tables":false,"Debug":true,"NFd":33,"OomKillDisable":false,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":true,"Context":"default","Plugins":[],"Warnings":null}}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":true,"Context":"default","Plugins":[],"Warnings":null}}
|
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":true,"Context":"default","Plugins":[],"Warnings":null}}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}}
|
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}}
|
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"aufs","DriverStatus":[["Root Dir","/var/lib/docker/aufs"],["Backing Filesystem","extfs"],["Dirs","0"],["Dirperm1 Supported","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}}
|
||||||
|
|
|
@ -10,7 +10,7 @@ require (
|
||||||
github.com/containerd/containerd v1.6.2
|
github.com/containerd/containerd v1.6.2
|
||||||
github.com/creack/pty v1.1.11
|
github.com/creack/pty v1.1.11
|
||||||
github.com/docker/distribution v2.8.1+incompatible
|
github.com/docker/distribution v2.8.1+incompatible
|
||||||
github.com/docker/docker v20.10.7+incompatible // see "replace" for the actual version
|
github.com/docker/docker v20.10.14+incompatible // see "replace" for the actual version
|
||||||
github.com/docker/docker-credential-helpers v0.6.4
|
github.com/docker/docker-credential-helpers v0.6.4
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/docker/go-units v0.4.0
|
github.com/docker/go-units v0.4.0
|
||||||
|
@ -74,6 +74,6 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible // master (v21.xx-dev)
|
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220326171151-8941dcfcc5db+incompatible // master (v21.xx-dev)
|
||||||
github.com/gogo/googleapis => github.com/gogo/googleapis v1.3.2
|
github.com/gogo/googleapis => github.com/gogo/googleapis v1.3.2
|
||||||
)
|
)
|
||||||
|
|
|
@ -105,8 +105,8 @@ github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xb
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible h1:bL4hLpxukr5Ls3bzYrn3LCYIwML+XXCktZHaGBIN3og=
|
github.com/docker/docker v20.10.3-0.20220326171151-8941dcfcc5db+incompatible h1:5DYFLB020CbxyjsxBle60QaEUb4krFjr30O0eLXsNp0=
|
||||||
github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v20.10.3-0.20220326171151-8941dcfcc5db+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
|
github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
|
||||||
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
|
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
|
|
|
@ -577,19 +577,13 @@ definitions:
|
||||||
type: "array"
|
type: "array"
|
||||||
items:
|
items:
|
||||||
$ref: "#/definitions/DeviceRequest"
|
$ref: "#/definitions/DeviceRequest"
|
||||||
KernelMemory:
|
|
||||||
description: |
|
|
||||||
Kernel memory limit in bytes.
|
|
||||||
|
|
||||||
<p><br /></p>
|
|
||||||
|
|
||||||
> **Deprecated**: This field is deprecated as the kernel 5.4 deprecated
|
|
||||||
> `kmem.limit_in_bytes`.
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
example: 209715200
|
|
||||||
KernelMemoryTCP:
|
KernelMemoryTCP:
|
||||||
description: "Hard limit for kernel TCP buffer memory (in bytes)."
|
description: |
|
||||||
|
Hard limit for kernel TCP buffer memory (in bytes). Depending on the
|
||||||
|
OCI runtime in use, this option may be ignored. It is no longer supported
|
||||||
|
by the default (runc) runtime.
|
||||||
|
|
||||||
|
This field is omitted when empty.
|
||||||
type: "integer"
|
type: "integer"
|
||||||
format: "int64"
|
format: "int64"
|
||||||
MemoryReservation:
|
MemoryReservation:
|
||||||
|
@ -1075,8 +1069,9 @@ definitions:
|
||||||
description: "Mount the container's root filesystem as read only."
|
description: "Mount the container's root filesystem as read only."
|
||||||
SecurityOpt:
|
SecurityOpt:
|
||||||
type: "array"
|
type: "array"
|
||||||
description: "A list of string values to customize labels for MLS
|
description: |
|
||||||
systems, such as SELinux."
|
A list of string values to customize labels for MLS systems, such
|
||||||
|
as SELinux.
|
||||||
items:
|
items:
|
||||||
type: "string"
|
type: "string"
|
||||||
StorageOpt:
|
StorageOpt:
|
||||||
|
@ -1869,18 +1864,22 @@ definitions:
|
||||||
type: "string"
|
type: "string"
|
||||||
description: "Name of the volume."
|
description: "Name of the volume."
|
||||||
x-nullable: false
|
x-nullable: false
|
||||||
|
example: "tardis"
|
||||||
Driver:
|
Driver:
|
||||||
type: "string"
|
type: "string"
|
||||||
description: "Name of the volume driver used by the volume."
|
description: "Name of the volume driver used by the volume."
|
||||||
x-nullable: false
|
x-nullable: false
|
||||||
|
example: "custom"
|
||||||
Mountpoint:
|
Mountpoint:
|
||||||
type: "string"
|
type: "string"
|
||||||
description: "Mount path of the volume on the host."
|
description: "Mount path of the volume on the host."
|
||||||
x-nullable: false
|
x-nullable: false
|
||||||
|
example: "/var/lib/docker/volumes/tardis"
|
||||||
CreatedAt:
|
CreatedAt:
|
||||||
type: "string"
|
type: "string"
|
||||||
format: "dateTime"
|
format: "dateTime"
|
||||||
description: "Date/Time the volume was created."
|
description: "Date/Time the volume was created."
|
||||||
|
example: "2016-06-07T20:31:11.853781916Z"
|
||||||
Status:
|
Status:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: |
|
description: |
|
||||||
|
@ -1892,12 +1891,17 @@ definitions:
|
||||||
does not support this feature.
|
does not support this feature.
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: "object"
|
type: "object"
|
||||||
|
example:
|
||||||
|
hello: "world"
|
||||||
Labels:
|
Labels:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: "User-defined key/value metadata."
|
description: "User-defined key/value metadata."
|
||||||
x-nullable: false
|
x-nullable: false
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: "string"
|
type: "string"
|
||||||
|
example:
|
||||||
|
com.example.some-label: "some-value"
|
||||||
|
com.example.some-other-label: "some-other-value"
|
||||||
Scope:
|
Scope:
|
||||||
type: "string"
|
type: "string"
|
||||||
description: |
|
description: |
|
||||||
|
@ -1906,12 +1910,17 @@ definitions:
|
||||||
default: "local"
|
default: "local"
|
||||||
x-nullable: false
|
x-nullable: false
|
||||||
enum: ["local", "global"]
|
enum: ["local", "global"]
|
||||||
|
example: "local"
|
||||||
Options:
|
Options:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: |
|
description: |
|
||||||
The driver specific options used when creating the volume.
|
The driver specific options used when creating the volume.
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: "string"
|
type: "string"
|
||||||
|
example:
|
||||||
|
device: "tmpfs"
|
||||||
|
o: "size=100m,uid=1000"
|
||||||
|
type: "tmpfs"
|
||||||
UsageData:
|
UsageData:
|
||||||
type: "object"
|
type: "object"
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
@ -1937,17 +1946,43 @@ definitions:
|
||||||
is set to `-1` if the reference-count is not available.
|
is set to `-1` if the reference-count is not available.
|
||||||
x-nullable: false
|
x-nullable: false
|
||||||
|
|
||||||
|
VolumeCreateOptions:
|
||||||
|
description: "Volume configuration"
|
||||||
|
type: "object"
|
||||||
|
title: "VolumeConfig"
|
||||||
|
x-go-name: "VolumeCreateBody"
|
||||||
|
properties:
|
||||||
|
Name:
|
||||||
|
description: |
|
||||||
|
The new volume's name. If not specified, Docker generates a name.
|
||||||
|
type: "string"
|
||||||
|
x-nullable: false
|
||||||
|
example: "tardis"
|
||||||
|
Driver:
|
||||||
|
description: "Name of the volume driver to use."
|
||||||
|
type: "string"
|
||||||
|
default: "local"
|
||||||
|
x-nullable: false
|
||||||
|
example: "custom"
|
||||||
|
DriverOpts:
|
||||||
|
description: |
|
||||||
|
A mapping of driver options and values. These options are
|
||||||
|
passed directly to the driver and are driver specific.
|
||||||
|
type: "object"
|
||||||
|
additionalProperties:
|
||||||
|
type: "string"
|
||||||
example:
|
example:
|
||||||
Name: "tardis"
|
device: "tmpfs"
|
||||||
Driver: "custom"
|
o: "size=100m,uid=1000"
|
||||||
Mountpoint: "/var/lib/docker/volumes/tardis"
|
type: "tmpfs"
|
||||||
Status:
|
|
||||||
hello: "world"
|
|
||||||
Labels:
|
Labels:
|
||||||
|
description: "User-defined key/value metadata."
|
||||||
|
type: "object"
|
||||||
|
additionalProperties:
|
||||||
|
type: "string"
|
||||||
|
example:
|
||||||
com.example.some-label: "some-value"
|
com.example.some-label: "some-value"
|
||||||
com.example.some-other-label: "some-other-value"
|
com.example.some-other-label: "some-other-value"
|
||||||
Scope: "local"
|
|
||||||
CreatedAt: "2016-06-07T20:31:11.853781916Z"
|
|
||||||
|
|
||||||
Network:
|
Network:
|
||||||
type: "object"
|
type: "object"
|
||||||
|
@ -2035,11 +2070,23 @@ definitions:
|
||||||
```
|
```
|
||||||
type: "array"
|
type: "array"
|
||||||
items:
|
items:
|
||||||
|
$ref: "#/definitions/IPAMConfig"
|
||||||
|
Options:
|
||||||
|
description: "Driver-specific options, specified as a map."
|
||||||
type: "object"
|
type: "object"
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: "string"
|
type: "string"
|
||||||
Options:
|
|
||||||
description: "Driver-specific options, specified as a map."
|
IPAMConfig:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
Subnet:
|
||||||
|
type: "string"
|
||||||
|
IPRange:
|
||||||
|
type: "string"
|
||||||
|
Gateway:
|
||||||
|
type: "string"
|
||||||
|
AuxiliaryAddresses:
|
||||||
type: "object"
|
type: "object"
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: "string"
|
type: "string"
|
||||||
|
@ -3827,6 +3874,7 @@ definitions:
|
||||||
|
|
||||||
ServiceSpec:
|
ServiceSpec:
|
||||||
description: "User modifiable configuration for a service."
|
description: "User modifiable configuration for a service."
|
||||||
|
type: object
|
||||||
properties:
|
properties:
|
||||||
Name:
|
Name:
|
||||||
description: "Name of the service."
|
description: "Name of the service."
|
||||||
|
@ -4464,6 +4512,29 @@ definitions:
|
||||||
Health:
|
Health:
|
||||||
$ref: "#/definitions/Health"
|
$ref: "#/definitions/Health"
|
||||||
|
|
||||||
|
ContainerWaitResponse:
|
||||||
|
description: "OK response to ContainerWait operation"
|
||||||
|
type: "object"
|
||||||
|
x-go-name: "ContainerWaitOKBody"
|
||||||
|
title: "ContainerWaitResponse"
|
||||||
|
required: [StatusCode, Error]
|
||||||
|
properties:
|
||||||
|
StatusCode:
|
||||||
|
description: "Exit code of the container"
|
||||||
|
type: "integer"
|
||||||
|
x-nullable: false
|
||||||
|
Error:
|
||||||
|
$ref: "#/definitions/ContainerWaitExitError"
|
||||||
|
|
||||||
|
ContainerWaitExitError:
|
||||||
|
description: "container waiting error, if any"
|
||||||
|
type: "object"
|
||||||
|
x-go-name: "ContainerWaitOKBodyError"
|
||||||
|
properties:
|
||||||
|
Message:
|
||||||
|
description: "Details of an error"
|
||||||
|
type: "string"
|
||||||
|
|
||||||
SystemVersion:
|
SystemVersion:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: |
|
description: |
|
||||||
|
@ -4644,19 +4715,10 @@ definitions:
|
||||||
description: "Indicates if the host has memory swap limit support enabled."
|
description: "Indicates if the host has memory swap limit support enabled."
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: true
|
example: true
|
||||||
KernelMemory:
|
|
||||||
description: |
|
|
||||||
Indicates if the host has kernel memory limit support enabled.
|
|
||||||
|
|
||||||
<p><br /></p>
|
|
||||||
|
|
||||||
> **Deprecated**: This field is deprecated as the kernel 5.4 deprecated
|
|
||||||
> `kmem.limit_in_bytes`.
|
|
||||||
type: "boolean"
|
|
||||||
example: true
|
|
||||||
KernelMemoryTCP:
|
KernelMemoryTCP:
|
||||||
description: |
|
description: |
|
||||||
Indicates if the host has kernel memory TCP limit support enabled.
|
Indicates if the host has kernel memory TCP limit support enabled. This
|
||||||
|
field is omitted if not supported.
|
||||||
|
|
||||||
Kernel memory TCP limits are not supported when using cgroups v2, which
|
Kernel memory TCP limits are not supported when using cgroups v2, which
|
||||||
does not support the corresponding `memory.kmem.tcp.limit_in_bytes` cgroup.
|
does not support the corresponding `memory.kmem.tcp.limit_in_bytes` cgroup.
|
||||||
|
@ -5369,6 +5431,7 @@ definitions:
|
||||||
|
|
||||||
PeerNode:
|
PeerNode:
|
||||||
description: "Represents a peer-node in the swarm"
|
description: "Represents a peer-node in the swarm"
|
||||||
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
NodeID:
|
NodeID:
|
||||||
description: "Unique identifier of for this node in the swarm."
|
description: "Unique identifier of for this node in the swarm."
|
||||||
|
@ -5820,7 +5883,6 @@ paths:
|
||||||
Memory: 0
|
Memory: 0
|
||||||
MemorySwap: 0
|
MemorySwap: 0
|
||||||
MemoryReservation: 0
|
MemoryReservation: 0
|
||||||
KernelMemory: 0
|
|
||||||
NanoCpus: 500000
|
NanoCpus: 500000
|
||||||
CpuPercent: 80
|
CpuPercent: 80
|
||||||
CpuShares: 512
|
CpuShares: 512
|
||||||
|
@ -6112,7 +6174,6 @@ paths:
|
||||||
Memory: 0
|
Memory: 0
|
||||||
MemorySwap: 0
|
MemorySwap: 0
|
||||||
MemoryReservation: 0
|
MemoryReservation: 0
|
||||||
KernelMemory: 0
|
|
||||||
OomKillDisable: false
|
OomKillDisable: false
|
||||||
OomScoreAdj: 500
|
OomScoreAdj: 500
|
||||||
NetworkMode: "bridge"
|
NetworkMode: "bridge"
|
||||||
|
@ -6857,7 +6918,6 @@ paths:
|
||||||
Memory: 314572800
|
Memory: 314572800
|
||||||
MemorySwap: 514288000
|
MemorySwap: 514288000
|
||||||
MemoryReservation: 209715200
|
MemoryReservation: 209715200
|
||||||
KernelMemory: 52428800
|
|
||||||
RestartPolicy:
|
RestartPolicy:
|
||||||
MaximumRetryCount: 4
|
MaximumRetryCount: 4
|
||||||
Name: "on-failure"
|
Name: "on-failure"
|
||||||
|
@ -7196,22 +7256,7 @@ paths:
|
||||||
200:
|
200:
|
||||||
description: "The container has exit."
|
description: "The container has exit."
|
||||||
schema:
|
schema:
|
||||||
type: "object"
|
$ref: "#/definitions/ContainerWaitResponse"
|
||||||
title: "ContainerWaitResponse"
|
|
||||||
description: "OK response to ContainerWait operation"
|
|
||||||
required: [StatusCode]
|
|
||||||
properties:
|
|
||||||
StatusCode:
|
|
||||||
description: "Exit code of the container"
|
|
||||||
type: "integer"
|
|
||||||
x-nullable: false
|
|
||||||
Error:
|
|
||||||
description: "container waiting error, if any"
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
Message:
|
|
||||||
description: "Details of an error"
|
|
||||||
type: "string"
|
|
||||||
400:
|
400:
|
||||||
description: "bad parameter"
|
description: "bad parameter"
|
||||||
schema:
|
schema:
|
||||||
|
@ -7319,17 +7364,7 @@ paths:
|
||||||
400:
|
400:
|
||||||
description: "Bad parameter"
|
description: "Bad parameter"
|
||||||
schema:
|
schema:
|
||||||
allOf:
|
$ref: "#/definitions/ErrorResponse"
|
||||||
- $ref: "#/definitions/ErrorResponse"
|
|
||||||
- type: "object"
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
description: |
|
|
||||||
The error message. Either "must specify path parameter"
|
|
||||||
(path cannot be empty) or "not a directory" (path was
|
|
||||||
asserted to be a directory but exists as a file).
|
|
||||||
type: "string"
|
|
||||||
x-nullable: false
|
|
||||||
404:
|
404:
|
||||||
description: "Container or path does not exist"
|
description: "Container or path does not exist"
|
||||||
schema:
|
schema:
|
||||||
|
@ -7364,17 +7399,7 @@ paths:
|
||||||
400:
|
400:
|
||||||
description: "Bad parameter"
|
description: "Bad parameter"
|
||||||
schema:
|
schema:
|
||||||
allOf:
|
$ref: "#/definitions/ErrorResponse"
|
||||||
- $ref: "#/definitions/ErrorResponse"
|
|
||||||
- type: "object"
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
description: |
|
|
||||||
The error message. Either "must specify path parameter"
|
|
||||||
(path cannot be empty) or "not a directory" (path was
|
|
||||||
asserted to be a directory but exists as a file).
|
|
||||||
type: "string"
|
|
||||||
x-nullable: false
|
|
||||||
404:
|
404:
|
||||||
description: "Container or path does not exist"
|
description: "Container or path does not exist"
|
||||||
schema:
|
schema:
|
||||||
|
@ -7400,7 +7425,10 @@ paths:
|
||||||
tags: ["Container"]
|
tags: ["Container"]
|
||||||
put:
|
put:
|
||||||
summary: "Extract an archive of files or folders to a directory in a container"
|
summary: "Extract an archive of files or folders to a directory in a container"
|
||||||
description: "Upload a tar archive to be extracted to a path in the filesystem of container id."
|
description: |
|
||||||
|
Upload a tar archive to be extracted to a path in the filesystem of container id.
|
||||||
|
`path` parameter is asserted to be a directory. If it exists as a file, 400 error
|
||||||
|
will be returned with message "not a directory".
|
||||||
operationId: "PutContainerArchive"
|
operationId: "PutContainerArchive"
|
||||||
consumes: ["application/x-tar", "application/octet-stream"]
|
consumes: ["application/x-tar", "application/octet-stream"]
|
||||||
responses:
|
responses:
|
||||||
|
@ -7410,6 +7438,9 @@ paths:
|
||||||
description: "Bad parameter"
|
description: "Bad parameter"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/ErrorResponse"
|
$ref: "#/definitions/ErrorResponse"
|
||||||
|
examples:
|
||||||
|
application/json:
|
||||||
|
message: "not a directory"
|
||||||
403:
|
403:
|
||||||
description: "Permission denied, the volume or container rootfs is marked as read-only."
|
description: "Permission denied, the volume or container rootfs is marked as read-only."
|
||||||
schema:
|
schema:
|
||||||
|
@ -8346,6 +8377,13 @@ paths:
|
||||||
Docker-Experimental:
|
Docker-Experimental:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
description: "If the server is running with experimental mode enabled"
|
description: "If the server is running with experimental mode enabled"
|
||||||
|
Swarm:
|
||||||
|
type: "string"
|
||||||
|
enum: ["inactive", "pending", "error", "locked", "active/worker", "active/manager"]
|
||||||
|
description: |
|
||||||
|
Contains information about Swarm status of the daemon,
|
||||||
|
and if the daemon is acting as a manager or worker node.
|
||||||
|
default: "inactive"
|
||||||
Cache-Control:
|
Cache-Control:
|
||||||
type: "string"
|
type: "string"
|
||||||
default: "no-cache, no-store, must-revalidate"
|
default: "no-cache, no-store, must-revalidate"
|
||||||
|
@ -8385,6 +8423,13 @@ paths:
|
||||||
Docker-Experimental:
|
Docker-Experimental:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
description: "If the server is running with experimental mode enabled"
|
description: "If the server is running with experimental mode enabled"
|
||||||
|
Swarm:
|
||||||
|
type: "string"
|
||||||
|
enum: ["inactive", "pending", "error", "locked", "active/worker", "active/manager"]
|
||||||
|
description: |
|
||||||
|
Contains information about Swarm status of the daemon,
|
||||||
|
and if the daemon is acting as a manager or worker node.
|
||||||
|
default: "inactive"
|
||||||
Cache-Control:
|
Cache-Control:
|
||||||
type: "string"
|
type: "string"
|
||||||
default: "no-cache, no-store, must-revalidate"
|
default: "no-cache, no-store, must-revalidate"
|
||||||
|
@ -9051,23 +9096,6 @@ paths:
|
||||||
Warnings that occurred when fetching the list of volumes.
|
Warnings that occurred when fetching the list of volumes.
|
||||||
items:
|
items:
|
||||||
type: "string"
|
type: "string"
|
||||||
|
|
||||||
examples:
|
|
||||||
application/json:
|
|
||||||
Volumes:
|
|
||||||
- CreatedAt: "2017-07-19T12:00:26Z"
|
|
||||||
Name: "tardis"
|
|
||||||
Driver: "local"
|
|
||||||
Mountpoint: "/var/lib/docker/volumes/tardis"
|
|
||||||
Labels:
|
|
||||||
com.example.some-label: "some-value"
|
|
||||||
com.example.some-other-label: "some-other-value"
|
|
||||||
Scope: "local"
|
|
||||||
Options:
|
|
||||||
device: "tmpfs"
|
|
||||||
o: "size=100m,uid=1000"
|
|
||||||
type: "tmpfs"
|
|
||||||
Warnings: []
|
|
||||||
500:
|
500:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
|
@ -9112,38 +9140,7 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
description: "Volume configuration"
|
description: "Volume configuration"
|
||||||
schema:
|
schema:
|
||||||
type: "object"
|
$ref: "#/definitions/VolumeCreateOptions"
|
||||||
description: "Volume configuration"
|
|
||||||
title: "VolumeConfig"
|
|
||||||
properties:
|
|
||||||
Name:
|
|
||||||
description: |
|
|
||||||
The new volume's name. If not specified, Docker generates a name.
|
|
||||||
type: "string"
|
|
||||||
x-nullable: false
|
|
||||||
Driver:
|
|
||||||
description: "Name of the volume driver to use."
|
|
||||||
type: "string"
|
|
||||||
default: "local"
|
|
||||||
x-nullable: false
|
|
||||||
DriverOpts:
|
|
||||||
description: |
|
|
||||||
A mapping of driver options and values. These options are
|
|
||||||
passed directly to the driver and are driver specific.
|
|
||||||
type: "object"
|
|
||||||
additionalProperties:
|
|
||||||
type: "string"
|
|
||||||
Labels:
|
|
||||||
description: "User-defined key/value metadata."
|
|
||||||
type: "object"
|
|
||||||
additionalProperties:
|
|
||||||
type: "string"
|
|
||||||
example:
|
|
||||||
Name: "tardis"
|
|
||||||
Labels:
|
|
||||||
com.example.some-label: "some-value"
|
|
||||||
com.example.some-other-label: "some-other-value"
|
|
||||||
Driver: "custom"
|
|
||||||
tags: ["Volume"]
|
tags: ["Volume"]
|
||||||
|
|
||||||
/volumes/{name}:
|
/volumes/{name}:
|
||||||
|
|
|
@ -1,28 +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
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// ContainerWaitOKBodyError container waiting error, if any
|
|
||||||
// swagger:model ContainerWaitOKBodyError
|
|
||||||
type ContainerWaitOKBodyError struct {
|
|
||||||
|
|
||||||
// Details of an error
|
|
||||||
Message string `json:"Message,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerWaitOKBody OK response to ContainerWait operation
|
|
||||||
// swagger:model ContainerWaitOKBody
|
|
||||||
type ContainerWaitOKBody struct {
|
|
||||||
|
|
||||||
// error
|
|
||||||
// Required: true
|
|
||||||
Error *ContainerWaitOKBodyError `json:"Error"`
|
|
||||||
|
|
||||||
// Exit code of the container
|
|
||||||
// Required: true
|
|
||||||
StatusCode int64 `json:"StatusCode"`
|
|
||||||
}
|
|
19
vendor/github.com/docker/docker/api/types/container/container_wait_o_k_body.go
generated
vendored
Normal file
19
vendor/github.com/docker/docker/api/types/container/container_wait_o_k_body.go
generated
vendored
Normal file
|
@ -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
|
||||||
|
|
||||||
|
// ContainerWaitOKBody ContainerWaitResponse
|
||||||
|
//
|
||||||
|
// OK response to ContainerWait operation
|
||||||
|
// swagger:model ContainerWaitOKBody
|
||||||
|
type ContainerWaitOKBody struct {
|
||||||
|
|
||||||
|
// error
|
||||||
|
// Required: true
|
||||||
|
Error *ContainerWaitOKBodyError `json:"Error"`
|
||||||
|
|
||||||
|
// Exit code of the container
|
||||||
|
// Required: true
|
||||||
|
StatusCode int64 `json:"StatusCode"`
|
||||||
|
}
|
12
vendor/github.com/docker/docker/api/types/container/container_wait_o_k_body_error.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/api/types/container/container_wait_o_k_body_error.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
// ContainerWaitOKBodyError container waiting error, if any
|
||||||
|
// swagger:model ContainerWaitOKBodyError
|
||||||
|
type ContainerWaitOKBodyError struct {
|
||||||
|
|
||||||
|
// Details of an error
|
||||||
|
Message string `json:"Message,omitempty"`
|
||||||
|
}
|
|
@ -376,8 +376,11 @@ type Resources struct {
|
||||||
Devices []DeviceMapping // List of devices to map inside the container
|
Devices []DeviceMapping // List of devices to map inside the container
|
||||||
DeviceCgroupRules []string // List of rule to be added to the device cgroup
|
DeviceCgroupRules []string // List of rule to be added to the device cgroup
|
||||||
DeviceRequests []DeviceRequest // List of device requests for device drivers
|
DeviceRequests []DeviceRequest // List of device requests for device drivers
|
||||||
KernelMemory int64 // Kernel memory limit (in bytes), Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
|
|
||||||
KernelMemoryTCP int64 // Hard limit for kernel TCP buffer memory (in bytes)
|
// KernelMemory specifies the kernel memory limit (in bytes) for the container.
|
||||||
|
// Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes.
|
||||||
|
KernelMemory int64 `json:",omitempty"`
|
||||||
|
KernelMemoryTCP int64 `json:",omitempty"` // Hard limit for kernel TCP buffer memory (in bytes)
|
||||||
MemoryReservation int64 // Memory soft limit (in bytes)
|
MemoryReservation int64 // Memory soft limit (in bytes)
|
||||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
|
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
|
||||||
MemorySwappiness *int64 // Tuning container memory swappiness behaviour
|
MemorySwappiness *int64 // Tuning container memory swappiness behaviour
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Args stores a mapping of keys to a set of multiple values.
|
// Args stores a mapping of keys to a set of multiple values.
|
||||||
|
@ -97,7 +98,7 @@ func FromJSON(p string) (Args, error) {
|
||||||
// Fallback to parsing arguments in the legacy slice format
|
// Fallback to parsing arguments in the legacy slice format
|
||||||
deprecated := map[string][]string{}
|
deprecated := map[string][]string{}
|
||||||
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
||||||
return args, err
|
return args, invalidFilter{errors.Wrap(err, "invalid filter")}
|
||||||
}
|
}
|
||||||
|
|
||||||
args.fields = deprecatedArgs(deprecated)
|
args.fields = deprecatedArgs(deprecated)
|
||||||
|
@ -247,10 +248,10 @@ func (args Args) Contains(field string) bool {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
type invalidFilter string
|
type invalidFilter struct{ error }
|
||||||
|
|
||||||
func (e invalidFilter) Error() string {
|
func (e invalidFilter) Error() string {
|
||||||
return "Invalid filter '" + string(e) + "'"
|
return e.error.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (invalidFilter) InvalidParameter() {}
|
func (invalidFilter) InvalidParameter() {}
|
||||||
|
@ -260,7 +261,7 @@ func (invalidFilter) InvalidParameter() {}
|
||||||
func (args Args) Validate(accepted map[string]bool) error {
|
func (args Args) Validate(accepted map[string]bool) error {
|
||||||
for name := range args.fields {
|
for name := range args.fields {
|
||||||
if !accepted[name] {
|
if !accepted[name] {
|
||||||
return invalidFilter(name)
|
return invalidFilter{errors.New("invalid filter '" + name + "'")}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -213,6 +213,16 @@ type Info struct {
|
||||||
Warnings []string `json:",omitempty"`
|
Warnings []string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status provides information about the current swarm status and role,
|
||||||
|
// obtained from the "Swarm" header in the API response.
|
||||||
|
type Status struct {
|
||||||
|
// NodeState represents the state of the node.
|
||||||
|
NodeState LocalNodeState
|
||||||
|
|
||||||
|
// ControlAvailable indicates if the node is a swarm manager.
|
||||||
|
ControlAvailable bool
|
||||||
|
}
|
||||||
|
|
||||||
// Peer represents a peer.
|
// Peer represents a peer.
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
NodeID string
|
NodeID string
|
||||||
|
|
|
@ -188,6 +188,15 @@ type Ping struct {
|
||||||
OSType string
|
OSType string
|
||||||
Experimental bool
|
Experimental bool
|
||||||
BuilderVersion BuilderVersion
|
BuilderVersion BuilderVersion
|
||||||
|
|
||||||
|
// SwarmStatus provides information about the current swarm status of the
|
||||||
|
// engine, obtained from the "Swarm" header in the API response.
|
||||||
|
//
|
||||||
|
// It can be a nil struct if the API version does not provide this header
|
||||||
|
// in the ping response, or if an error occurred, in which case the client
|
||||||
|
// should use other ways to get the current swarm status, such as the /swarm
|
||||||
|
// endpoint.
|
||||||
|
SwarmStatus *swarm.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComponentVersion describes the version information for a specific component.
|
// ComponentVersion describes the version information for a specific component.
|
||||||
|
@ -239,8 +248,8 @@ type Info struct {
|
||||||
Plugins PluginsInfo
|
Plugins PluginsInfo
|
||||||
MemoryLimit bool
|
MemoryLimit bool
|
||||||
SwapLimit bool
|
SwapLimit bool
|
||||||
KernelMemory bool // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
|
KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
|
||||||
KernelMemoryTCP bool
|
KernelMemoryTCP bool `json:",omitempty"` // KernelMemoryTCP is not supported on cgroups v2.
|
||||||
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
|
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
|
||||||
CPUCfsQuota bool `json:"CpuCfsQuota"`
|
CPUCfsQuota bool `json:"CpuCfsQuota"`
|
||||||
CPUShares bool
|
CPUShares bool
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
package volume // import "github.com/docker/docker/api/types/volume"
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Code generated by `swagger generate operation`. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
// See hack/generate-swagger-api.sh
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// VolumeCreateBody Volume configuration
|
|
||||||
// swagger:model VolumeCreateBody
|
|
||||||
type VolumeCreateBody struct {
|
|
||||||
|
|
||||||
// Name of the volume driver to use.
|
|
||||||
// Required: true
|
|
||||||
Driver string `json:"Driver"`
|
|
||||||
|
|
||||||
// A mapping of driver options and values. These options are
|
|
||||||
// passed directly to the driver and are driver specific.
|
|
||||||
//
|
|
||||||
// Required: true
|
|
||||||
DriverOpts map[string]string `json:"DriverOpts"`
|
|
||||||
|
|
||||||
// User-defined key/value metadata.
|
|
||||||
// Required: true
|
|
||||||
Labels map[string]string `json:"Labels"`
|
|
||||||
|
|
||||||
// The new volume's name. If not specified, Docker generates a name.
|
|
||||||
//
|
|
||||||
// Required: true
|
|
||||||
Name string `json:"Name"`
|
|
||||||
}
|
|
26
vendor/github.com/docker/docker/api/types/volume/volume_create_body.go
generated
vendored
Normal file
26
vendor/github.com/docker/docker/api/types/volume/volume_create_body.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package volume
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
// VolumeCreateBody VolumeConfig
|
||||||
|
//
|
||||||
|
// Volume configuration
|
||||||
|
// swagger:model VolumeCreateBody
|
||||||
|
type VolumeCreateBody struct {
|
||||||
|
|
||||||
|
// Name of the volume driver to use.
|
||||||
|
Driver string `json:"Driver,omitempty"`
|
||||||
|
|
||||||
|
// A mapping of driver options and values. These options are
|
||||||
|
// passed directly to the driver and are driver specific.
|
||||||
|
//
|
||||||
|
DriverOpts map[string]string `json:"DriverOpts,omitempty"`
|
||||||
|
|
||||||
|
// User-defined key/value metadata.
|
||||||
|
Labels map[string]string `json:"Labels,omitempty"`
|
||||||
|
|
||||||
|
// The new volume's name. If not specified, Docker generates a name.
|
||||||
|
//
|
||||||
|
Name string `json:"Name,omitempty"`
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ func (cli *Client) CheckpointList(ctx context.Context, container string, options
|
||||||
resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil)
|
resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return checkpoints, wrapResponseError(err, resp, "container", container)
|
return checkpoints, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(resp.body).Decode(&checkpoints)
|
err = json.NewDecoder(resp.body).Decode(&checkpoints)
|
||||||
|
|
|
@ -43,7 +43,6 @@ package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -93,15 +92,18 @@ type Client struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRedirect specifies the policy for dealing with redirect responses:
|
// CheckRedirect specifies the policy for dealing with redirect responses:
|
||||||
// If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
|
// If the request is non-GET return ErrRedirect, otherwise use the last response.
|
||||||
//
|
//
|
||||||
// Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client .
|
// Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308)
|
||||||
// The Docker client (and by extension docker API client) can be made to send a request
|
// in the client. The Docker client (and by extension docker API client) can be
|
||||||
// like POST /containers//start where what would normally be in the name section of the URL is empty.
|
// made to send a request like POST /containers//start where what would normally
|
||||||
// This triggers an HTTP 301 from the daemon.
|
// be in the name section of the URL is empty. This triggers an HTTP 301 from
|
||||||
// In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
|
// the daemon.
|
||||||
// This behavior change manifests in the client in that before the 301 was not followed and
|
//
|
||||||
// the client did not generate an error, but now results in a message like Error response from daemon: page not found.
|
// In go 1.8 this 301 will be converted to a GET request, and ends up getting
|
||||||
|
// a 404 from the daemon. This behavior change manifests in the client in that
|
||||||
|
// before, the 301 was not followed and the client did not generate an error,
|
||||||
|
// but now results in a message like Error response from daemon: page not found.
|
||||||
func CheckRedirect(req *http.Request, via []*http.Request) error {
|
func CheckRedirect(req *http.Request, via []*http.Request) error {
|
||||||
if via[0].Method == http.MethodGet {
|
if via[0].Method == http.MethodGet {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
|
@ -109,13 +111,22 @@ func CheckRedirect(req *http.Request, via []*http.Request) error {
|
||||||
return ErrRedirect
|
return ErrRedirect
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithOpts initializes a new API client with default values. It takes functors
|
// NewClientWithOpts initializes a new API client with a default HTTPClient, and
|
||||||
// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
|
// default API host and version. It also initializes the custom HTTP headers to
|
||||||
// It also initializes the custom http headers to add to each request.
|
// add to each request.
|
||||||
|
//
|
||||||
|
// It takes an optional list of Opt functional arguments, which are applied in
|
||||||
|
// the order they're provided, which allows modifying the defaults when creating
|
||||||
|
// the client. For example, the following initializes a client that configures
|
||||||
|
// itself with values from environment variables (client.FromEnv), and has
|
||||||
|
// automatic API version negotiation enabled (client.WithAPIVersionNegotiation()).
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// cli, err := client.NewClientWithOpts(
|
||||||
|
// client.FromEnv,
|
||||||
|
// client.WithAPIVersionNegotiation(),
|
||||||
|
// )
|
||||||
//
|
//
|
||||||
// It won't send any version information if the version number is empty. It is
|
|
||||||
// highly recommended that you set a version or your client may break if the
|
|
||||||
// server is upgraded.
|
|
||||||
func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
||||||
client, err := defaultHTTPClient(DefaultDockerHost)
|
client, err := defaultHTTPClient(DefaultDockerHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -153,12 +164,12 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultHTTPClient(host string) (*http.Client, error) {
|
func defaultHTTPClient(host string) (*http.Client, error) {
|
||||||
url, err := ParseHostURL(host)
|
hostURL, err := ParseHostURL(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
transport := new(http.Transport)
|
transport := &http.Transport{}
|
||||||
sockets.ConfigureTransport(transport, url.Scheme, url.Host)
|
_ = sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
CheckRedirect: CheckRedirect,
|
CheckRedirect: CheckRedirect,
|
||||||
|
@ -194,11 +205,21 @@ func (cli *Client) ClientVersion() string {
|
||||||
return cli.version
|
return cli.version
|
||||||
}
|
}
|
||||||
|
|
||||||
// NegotiateAPIVersion queries the API and updates the version to match the
|
// NegotiateAPIVersion queries the API and updates the version to match the API
|
||||||
// API version. Any errors are silently ignored. If a manual override is in place,
|
// version. NegotiateAPIVersion downgrades the client's API version to match the
|
||||||
// either through the `DOCKER_API_VERSION` environment variable, or if the client
|
// APIVersion if the ping version is lower than the default version. If the API
|
||||||
// was initialized with a fixed version (`opts.WithVersion(xx)`), no negotiation
|
// version reported by the server is higher than the maximum version supported
|
||||||
// will be performed.
|
// by the client, it uses the client's maximum version.
|
||||||
|
//
|
||||||
|
// If a manual override is in place, either through the "DOCKER_API_VERSION"
|
||||||
|
// (EnvOverrideAPIVersion) environment variable, or if the client is initialized
|
||||||
|
// with a fixed version (WithVersion(xx)), no negotiation is performed.
|
||||||
|
//
|
||||||
|
// If the API server's ping response does not contain an API version, or if the
|
||||||
|
// client did not get a successful ping response, it assumes it is connected with
|
||||||
|
// an old daemon that does not support API version negotiation, in which case it
|
||||||
|
// downgrades to the latest version of the API before version negotiation was
|
||||||
|
// added (1.24).
|
||||||
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
||||||
if !cli.manualOverride {
|
if !cli.manualOverride {
|
||||||
ping, _ := cli.Ping(ctx)
|
ping, _ := cli.Ping(ctx)
|
||||||
|
@ -206,23 +227,31 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
|
// NegotiateAPIVersionPing downgrades the client's API version to match the
|
||||||
// if the ping version is less than the default version. If a manual override is
|
// APIVersion in the ping response. If the API version in pingResponse is higher
|
||||||
// in place, either through the `DOCKER_API_VERSION` environment variable, or if
|
// than the maximum version supported by the client, it uses the client's maximum
|
||||||
// the client was initialized with a fixed version (`opts.WithVersion(xx)`), no
|
// version.
|
||||||
// negotiation is performed.
|
//
|
||||||
func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
|
// If a manual override is in place, either through the "DOCKER_API_VERSION"
|
||||||
|
// (EnvOverrideAPIVersion) environment variable, or if the client is initialized
|
||||||
|
// with a fixed version (WithVersion(xx)), no negotiation is performed.
|
||||||
|
//
|
||||||
|
// If the API server's ping response does not contain an API version, we assume
|
||||||
|
// we are connected with an old daemon without API version negotiation support,
|
||||||
|
// and downgrade to the latest version of the API before version negotiation was
|
||||||
|
// added (1.24).
|
||||||
|
func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
|
||||||
if !cli.manualOverride {
|
if !cli.manualOverride {
|
||||||
cli.negotiateAPIVersionPing(p)
|
cli.negotiateAPIVersionPing(pingResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// negotiateAPIVersionPing queries the API and updates the version to match the
|
// negotiateAPIVersionPing queries the API and updates the version to match the
|
||||||
// API version. Any errors are silently ignored.
|
// API version from the ping response.
|
||||||
func (cli *Client) negotiateAPIVersionPing(p types.Ping) {
|
func (cli *Client) negotiateAPIVersionPing(pingResponse types.Ping) {
|
||||||
// try the latest version before versioning headers existed
|
// default to the latest version before versioning headers existed
|
||||||
if p.APIVersion == "" {
|
if pingResponse.APIVersion == "" {
|
||||||
p.APIVersion = "1.24"
|
pingResponse.APIVersion = "1.24"
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the client is not initialized with a version, start with the latest supported version
|
// if the client is not initialized with a version, start with the latest supported version
|
||||||
|
@ -231,8 +260,8 @@ func (cli *Client) negotiateAPIVersionPing(p types.Ping) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if server version is lower than the client version, downgrade
|
// if server version is lower than the client version, downgrade
|
||||||
if versions.LessThan(p.APIVersion, cli.version) {
|
if versions.LessThan(pingResponse.APIVersion, cli.version) {
|
||||||
cli.version = p.APIVersion
|
cli.version = pingResponse.APIVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the results, so that automatic API version negotiation (if enabled)
|
// Store the results, so that automatic API version negotiation (if enabled)
|
||||||
|
@ -258,7 +287,7 @@ func (cli *Client) HTTPClient() *http.Client {
|
||||||
func ParseHostURL(host string) (*url.URL, error) {
|
func ParseHostURL(host string) (*url.URL, error) {
|
||||||
protoAddrParts := strings.SplitN(host, "://", 2)
|
protoAddrParts := strings.SplitN(host, "://", 2)
|
||||||
if len(protoAddrParts) == 1 {
|
if len(protoAddrParts) == 1 {
|
||||||
return nil, fmt.Errorf("unable to parse docker host `%s`", host)
|
return nil, errors.Errorf("unable to parse docker host `%s`", host)
|
||||||
}
|
}
|
||||||
|
|
||||||
var basePath string
|
var basePath string
|
||||||
|
@ -278,7 +307,9 @@ func ParseHostURL(host string) (*url.URL, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
|
// Dialer returns a dialer for a raw stream connection, with an HTTP/1.1 header,
|
||||||
|
// that can be used for proxying the daemon connection.
|
||||||
|
//
|
||||||
// Used by `docker dial-stdio` (docker/cli#889).
|
// Used by `docker dial-stdio` (docker/cli#889).
|
||||||
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
|
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
|
||||||
return func(ctx context.Context) (net.Conn, error) {
|
return func(ctx context.Context) (net.Conn, error) {
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
package client // import "github.com/docker/docker/client"
|
package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
// 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 DefaultDockerHost = "unix:///var/run/docker.sock"
|
||||||
|
|
||||||
const defaultProto = "unix"
|
const defaultProto = "unix"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package client // import "github.com/docker/docker/client"
|
package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
|
||||||
|
// (EnvOverrideHost) environment variable is unset or empty.
|
||||||
const DefaultDockerHost = "npipe:////./pipe/docker_engine"
|
const DefaultDockerHost = "npipe:////./pipe/docker_engine"
|
||||||
|
|
||||||
const defaultProto = "npipe"
|
const defaultProto = "npipe"
|
||||||
|
|
|
@ -20,7 +20,7 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
|
||||||
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
|
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return swarm.Config{}, nil, wrapResponseError(err, resp, "config", id)
|
return swarm.Config{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.body)
|
body, err := io.ReadAll(resp.body)
|
||||||
|
|
|
@ -9,5 +9,5 @@ func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
|
||||||
}
|
}
|
||||||
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
|
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "config", id)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri
|
||||||
response, err := cli.head(ctx, urlStr, query, nil)
|
response, err := cli.head(ctx, urlStr, query, nil)
|
||||||
defer ensureReaderClosed(response)
|
defer ensureReaderClosed(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+path)
|
return types.ContainerPathStat{}, err
|
||||||
}
|
}
|
||||||
return getContainerPathStatFromHeader(response.header)
|
return getContainerPathStatFromHeader(response.header)
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
|
||||||
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
|
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
|
||||||
defer ensureReaderClosed(response)
|
defer ensureReaderClosed(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapResponseError(err, response, "container:path", containerID+":"+dstPath)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
||||||
|
@ -67,7 +67,7 @@ func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath s
|
||||||
apiPath := "/containers/" + containerID + "/archive"
|
apiPath := "/containers/" + containerID + "/archive"
|
||||||
response, err := cli.get(ctx, apiPath, query, nil)
|
response, err := cli.get(ctx, apiPath, query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+srcPath)
|
return nil, types.ContainerPathStat{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
||||||
|
|
|
@ -18,7 +18,7 @@ func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (ty
|
||||||
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
|
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
|
||||||
defer ensureReaderClosed(serverResp)
|
defer ensureReaderClosed(serverResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ContainerJSON{}, wrapResponseError(err, serverResp, "container", containerID)
|
return types.ContainerJSON{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var response types.ContainerJSON
|
var response types.ContainerJSON
|
||||||
|
@ -38,7 +38,7 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri
|
||||||
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
|
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
|
||||||
defer ensureReaderClosed(serverResp)
|
defer ensureReaderClosed(serverResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ContainerJSON{}, nil, wrapResponseError(err, serverResp, "container", containerID)
|
return types.ContainerJSON{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(serverResp.body)
|
body, err := io.ReadAll(serverResp.body)
|
||||||
|
|
|
@ -74,7 +74,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options
|
||||||
|
|
||||||
resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil)
|
resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapResponseError(err, resp, "container", container)
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp.body, nil
|
return resp.body, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,5 +23,5 @@ func (cli *Client) ContainerRemove(ctx context.Context, containerID string, opti
|
||||||
|
|
||||||
resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
|
resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "container", containerID)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EnvOverrideHost is the name of the environment variable that can be used
|
||||||
|
// to override the default host to connect to (DefaultDockerHost).
|
||||||
|
//
|
||||||
|
// This env-var is read by FromEnv and WithHostFromEnv and when set to a
|
||||||
|
// non-empty value, takes precedence over the default host (which is platform
|
||||||
|
// specific), or any host already set.
|
||||||
|
EnvOverrideHost = "DOCKER_HOST"
|
||||||
|
|
||||||
|
// EnvOverrideAPIVersion is the name of the environment variable that can
|
||||||
|
// be used to override the API version to use. Value should be
|
||||||
|
// formatted as MAJOR.MINOR, for example, "1.19".
|
||||||
|
//
|
||||||
|
// This env-var is read by FromEnv and WithVersionFromEnv and when set to a
|
||||||
|
// non-empty value, takes precedence over API version negotiation.
|
||||||
|
//
|
||||||
|
// This environment variable should be used for debugging purposes only, as
|
||||||
|
// it can set the client to use an incompatible (or invalid) API version.
|
||||||
|
EnvOverrideAPIVersion = "DOCKER_API_VERSION"
|
||||||
|
|
||||||
|
// EnvOverrideCertPath is the name of the environment variable that can be
|
||||||
|
// used to specify the directory from which to load the TLS certificates
|
||||||
|
// (ca.pem, cert.pem, key.pem) from. These certificates are used to configure
|
||||||
|
// the Client for a TCP connection protected by TLS client authentication.
|
||||||
|
//
|
||||||
|
// TLS certificate verification is enabled by default if the Client is configured
|
||||||
|
// to use a TLS connection. Refer to EnvTLSVerify below to learn how to
|
||||||
|
// disable verification for testing purposes.
|
||||||
|
//
|
||||||
|
// WARNING: Access to the remote API is equivalent to root access to the
|
||||||
|
// host where the daemon runs. Do not expose the API without protection,
|
||||||
|
// and only if needed. Make sure you are familiar with the "daemon attack
|
||||||
|
// surface" (https://docs.docker.com/go/attack-surface/).
|
||||||
|
//
|
||||||
|
// For local access to the API, it is recommended to connect with the daemon
|
||||||
|
// using the default local socket connection (on Linux), or the named pipe
|
||||||
|
// (on Windows).
|
||||||
|
//
|
||||||
|
// If you need to access the API of a remote daemon, consider using an SSH
|
||||||
|
// (ssh://) connection, which is easier to set up, and requires no additional
|
||||||
|
// configuration if the host is accessible using ssh.
|
||||||
|
//
|
||||||
|
// If you cannot use the alternatives above, and you must expose the API over
|
||||||
|
// a TCP connection, refer to https://docs.docker.com/engine/security/protect-access/
|
||||||
|
// to learn how to configure the daemon and client to use a TCP connection
|
||||||
|
// with TLS client authentication. Make sure you know the differences between
|
||||||
|
// a regular TLS connection and a TLS connection protected by TLS client
|
||||||
|
// authentication, and verify that the API cannot be accessed by other clients.
|
||||||
|
EnvOverrideCertPath = "DOCKER_CERT_PATH"
|
||||||
|
|
||||||
|
// EnvTLSVerify is the name of the environment variable that can be used to
|
||||||
|
// enable or disable TLS certificate verification. When set to a non-empty
|
||||||
|
// value, TLS certificate verification is enabled, and the client is configured
|
||||||
|
// to use a TLS connection, using certificates from the default directories
|
||||||
|
// (within `~/.docker`); refer to EnvOverrideCertPath above for additional
|
||||||
|
// details.
|
||||||
|
//
|
||||||
|
// WARNING: Access to the remote API is equivalent to root access to the
|
||||||
|
// host where the daemon runs. Do not expose the API without protection,
|
||||||
|
// and only if needed. Make sure you are familiar with the "daemon attack
|
||||||
|
// surface" (https://docs.docker.com/go/attack-surface/).
|
||||||
|
//
|
||||||
|
// Before setting up your client and daemon to use a TCP connection with TLS
|
||||||
|
// client authentication, consider using one of the alternatives mentioned
|
||||||
|
// in EnvOverrideCertPath above.
|
||||||
|
//
|
||||||
|
// Disabling TLS certificate verification (for testing purposes)
|
||||||
|
//
|
||||||
|
// TLS certificate verification is enabled by default if the Client is configured
|
||||||
|
// to use a TLS connection, and it is highly recommended to keep verification
|
||||||
|
// enabled to prevent machine-in-the-middle attacks. Refer to the documentation
|
||||||
|
// at https://docs.docker.com/engine/security/protect-access/ and pages linked
|
||||||
|
// from that page to learn how to configure the daemon and client to use a
|
||||||
|
// TCP connection with TLS client authentication enabled.
|
||||||
|
//
|
||||||
|
// Set the "DOCKER_TLS_VERIFY" environment to an empty string ("") to
|
||||||
|
// disable TLS certificate verification. Disabling verification is insecure,
|
||||||
|
// so should only be done for testing purposes. From the Go documentation
|
||||||
|
// (https://pkg.go.dev/crypto/tls#Config):
|
||||||
|
//
|
||||||
|
// InsecureSkipVerify controls whether a client verifies the server's
|
||||||
|
// certificate chain and host name. If InsecureSkipVerify is true, crypto/tls
|
||||||
|
// accepts any certificate presented by the server and any host name in that
|
||||||
|
// certificate. In this mode, TLS is susceptible to machine-in-the-middle
|
||||||
|
// attacks unless custom verification is used. This should be used only for
|
||||||
|
// testing or in combination with VerifyConnection or VerifyPeerCertificate.
|
||||||
|
EnvTLSVerify = "DOCKER_TLS_VERIFY"
|
||||||
|
)
|
|
@ -2,7 +2,6 @@ package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
|
@ -59,19 +58,6 @@ func (e objectNotFoundError) Error() string {
|
||||||
return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
|
return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapResponseError(err error, resp serverResponse, object, id string) error {
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
return nil
|
|
||||||
case resp.statusCode == http.StatusNotFound:
|
|
||||||
return objectNotFoundError{object: object, id: id}
|
|
||||||
case resp.statusCode == http.StatusNotImplemented:
|
|
||||||
return errdefs.NotImplemented(err)
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unauthorizedError represents an authorization error in a remote registry.
|
// unauthorizedError represents an authorization error in a remote registry.
|
||||||
type unauthorizedError struct {
|
type unauthorizedError struct {
|
||||||
cause error
|
cause error
|
||||||
|
|
|
@ -17,7 +17,7 @@ func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (typ
|
||||||
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
|
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
|
||||||
defer ensureReaderClosed(serverResp)
|
defer ensureReaderClosed(serverResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ImageInspect{}, nil, wrapResponseError(err, serverResp, "image", imageID)
|
return types.ImageInspect{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(serverResp.body)
|
body, err := io.ReadAll(serverResp.body)
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options type
|
||||||
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
|
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dels, wrapResponseError(err, resp, "image", imageID)
|
return dels, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(resp.body).Decode(&dels)
|
err = json.NewDecoder(resp.body).Decode(&dels)
|
||||||
|
|
|
@ -3,8 +3,8 @@ package client // import "github.com/docker/docker/client"
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
@ -18,7 +18,9 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options types.I
|
||||||
var results []registry.SearchResult
|
var results []registry.SearchResult
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Set("term", term)
|
query.Set("term", term)
|
||||||
query.Set("limit", fmt.Sprintf("%d", options.Limit))
|
if options.Limit > 0 {
|
||||||
|
query.Set("limit", strconv.Itoa(options.Limit))
|
||||||
|
}
|
||||||
|
|
||||||
if options.Filters.Len() > 0 {
|
if options.Filters.Len() > 0 {
|
||||||
filterJSON, err := filters.ToJSON(options.Filters)
|
filterJSON, err := filters.ToJSON(options.Filters)
|
||||||
|
|
|
@ -36,7 +36,7 @@ func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string,
|
||||||
resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
|
resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return networkResource, nil, wrapResponseError(err, resp, "network", networkID)
|
return networkResource, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.body)
|
body, err := io.ReadAll(resp.body)
|
||||||
|
|
|
@ -6,5 +6,5 @@ import "context"
|
||||||
func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
|
func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
|
||||||
resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
|
resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "network", networkID)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm
|
||||||
serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
|
serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
|
||||||
defer ensureReaderClosed(serverResp)
|
defer ensureReaderClosed(serverResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return swarm.Node{}, nil, wrapResponseError(err, serverResp, "node", nodeID)
|
return swarm.Node{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(serverResp.body)
|
body, err := io.ReadAll(serverResp.body)
|
||||||
|
|
|
@ -16,5 +16,5 @@ func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.
|
||||||
|
|
||||||
resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
|
resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "node", nodeID)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,18 @@ type Opt func(*Client) error
|
||||||
|
|
||||||
// FromEnv configures the client with values from environment variables.
|
// FromEnv configures the client with values from environment variables.
|
||||||
//
|
//
|
||||||
// Supported environment variables:
|
// FromEnv uses the following environment variables:
|
||||||
// DOCKER_HOST to set the url to the docker server.
|
//
|
||||||
// DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
|
// DOCKER_HOST (EnvOverrideHost) to set the URL to the docker server.
|
||||||
// DOCKER_CERT_PATH to load the TLS certificates from.
|
//
|
||||||
// DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
|
// DOCKER_API_VERSION (EnvOverrideAPIVersion) to set the version of the API to
|
||||||
|
// use, leave empty for latest.
|
||||||
|
//
|
||||||
|
// DOCKER_CERT_PATH (EnvOverrideCertPath) to specify the directory from which to
|
||||||
|
// load the TLS certificates (ca.pem, cert.pem, key.pem).
|
||||||
|
//
|
||||||
|
// DOCKER_TLS_VERIFY (EnvTLSVerify) to enable or disable TLS verification (off by
|
||||||
|
// default).
|
||||||
func FromEnv(c *Client) error {
|
func FromEnv(c *Client) error {
|
||||||
ops := []Opt{
|
ops := []Opt{
|
||||||
WithTLSClientConfigFromEnv(),
|
WithTLSClientConfigFromEnv(),
|
||||||
|
@ -75,11 +82,11 @@ func WithHost(host string) Opt {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHostFromEnv overrides the client host with the host specified in the
|
// WithHostFromEnv overrides the client host with the host specified in the
|
||||||
// DOCKER_HOST environment variable. If DOCKER_HOST is not set, the host is
|
// DOCKER_HOST (EnvOverrideHost) environment variable. If DOCKER_HOST is not set,
|
||||||
// not modified.
|
// or set to an empty value, the host is not modified.
|
||||||
func WithHostFromEnv() Opt {
|
func WithHostFromEnv() Opt {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if host := os.Getenv("DOCKER_HOST"); host != "" {
|
if host := os.Getenv(EnvOverrideHost); host != "" {
|
||||||
return WithHost(host)(c)
|
return WithHost(host)(c)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -145,12 +152,16 @@ func WithTLSClientConfig(cacertPath, certPath, keyPath string) Opt {
|
||||||
// settings in the DOCKER_CERT_PATH and DOCKER_TLS_VERIFY environment variables.
|
// settings in the DOCKER_CERT_PATH and DOCKER_TLS_VERIFY environment variables.
|
||||||
// If DOCKER_CERT_PATH is not set or empty, TLS configuration is not modified.
|
// If DOCKER_CERT_PATH is not set or empty, TLS configuration is not modified.
|
||||||
//
|
//
|
||||||
// Supported environment variables:
|
// WithTLSClientConfigFromEnv uses the following environment variables:
|
||||||
// DOCKER_CERT_PATH directory to load the TLS certificates (ca.pem, cert.pem, key.pem) from.
|
//
|
||||||
// DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
|
// DOCKER_CERT_PATH (EnvOverrideCertPath) to specify the directory from which to
|
||||||
|
// load the TLS certificates (ca.pem, cert.pem, key.pem).
|
||||||
|
//
|
||||||
|
// DOCKER_TLS_VERIFY (EnvTLSVerify) to enable or disable TLS verification (off by
|
||||||
|
// default).
|
||||||
func WithTLSClientConfigFromEnv() Opt {
|
func WithTLSClientConfigFromEnv() Opt {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
dockerCertPath := os.Getenv("DOCKER_CERT_PATH")
|
dockerCertPath := os.Getenv(EnvOverrideCertPath)
|
||||||
if dockerCertPath == "" {
|
if dockerCertPath == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -158,7 +169,7 @@ func WithTLSClientConfigFromEnv() Opt {
|
||||||
CAFile: filepath.Join(dockerCertPath, "ca.pem"),
|
CAFile: filepath.Join(dockerCertPath, "ca.pem"),
|
||||||
CertFile: filepath.Join(dockerCertPath, "cert.pem"),
|
CertFile: filepath.Join(dockerCertPath, "cert.pem"),
|
||||||
KeyFile: filepath.Join(dockerCertPath, "key.pem"),
|
KeyFile: filepath.Join(dockerCertPath, "key.pem"),
|
||||||
InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
|
InsecureSkipVerify: os.Getenv(EnvTLSVerify) == "",
|
||||||
}
|
}
|
||||||
tlsc, err := tlsconfig.Client(options)
|
tlsc, err := tlsconfig.Client(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -190,10 +201,7 @@ func WithVersion(version string) Opt {
|
||||||
// the version is not modified.
|
// the version is not modified.
|
||||||
func WithVersionFromEnv() Opt {
|
func WithVersionFromEnv() Opt {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if version := os.Getenv("DOCKER_API_VERSION"); version != "" {
|
return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c)
|
||||||
return WithVersion(version)(c)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,6 +63,13 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
|
||||||
if bv := resp.header.Get("Builder-Version"); bv != "" {
|
if bv := resp.header.Get("Builder-Version"); bv != "" {
|
||||||
ping.BuilderVersion = types.BuilderVersion(bv)
|
ping.BuilderVersion = types.BuilderVersion(bv)
|
||||||
}
|
}
|
||||||
|
if si := resp.header.Get("Swarm"); si != "" {
|
||||||
|
parts := strings.SplitN(si, "/", 2)
|
||||||
|
ping.SwarmStatus = &swarm.Status{
|
||||||
|
NodeState: swarm.LocalNodeState(parts[0]),
|
||||||
|
ControlAvailable: len(parts) == 2 && parts[1] == "manager",
|
||||||
|
}
|
||||||
|
}
|
||||||
err := cli.checkResponseErr(resp)
|
err := cli.checkResponseErr(resp)
|
||||||
return ping, errdefs.FromStatusCode(err, resp.statusCode)
|
return ping, errdefs.FromStatusCode(err, resp.statusCode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*type
|
||||||
resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
|
resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, wrapResponseError(err, resp, "plugin", name)
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.body)
|
body, err := io.ReadAll(resp.body)
|
||||||
|
|
|
@ -25,7 +25,7 @@ func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (types.P
|
||||||
resp, err := cli.get(ctx, "/plugins", query, nil)
|
resp, err := cli.get(ctx, "/plugins", query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugins, wrapResponseError(err, resp, "plugin", "")
|
return plugins, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(resp.body).Decode(&plugins)
|
err = json.NewDecoder(resp.body).Decode(&plugins)
|
||||||
|
|
|
@ -16,5 +16,5 @@ func (cli *Client) PluginRemove(ctx context.Context, name string, options types.
|
||||||
|
|
||||||
resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
|
resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "plugin", name)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S
|
||||||
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
|
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return swarm.Secret{}, nil, wrapResponseError(err, resp, "secret", id)
|
return swarm.Secret{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.body)
|
body, err := io.ReadAll(resp.body)
|
||||||
|
|
|
@ -9,5 +9,5 @@ func (cli *Client) SecretRemove(ctx context.Context, id string) error {
|
||||||
}
|
}
|
||||||
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
|
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "secret", id)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string,
|
||||||
serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
|
serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
|
||||||
defer ensureReaderClosed(serverResp)
|
defer ensureReaderClosed(serverResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return swarm.Service{}, nil, wrapResponseError(err, serverResp, "service", serviceID)
|
return swarm.Service{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(serverResp.body)
|
body, err := io.ReadAll(serverResp.body)
|
||||||
|
|
|
@ -6,5 +6,5 @@ import "context"
|
||||||
func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
|
func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
|
||||||
resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
|
resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "service", serviceID)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm
|
||||||
serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
|
serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
|
||||||
defer ensureReaderClosed(serverResp)
|
defer ensureReaderClosed(serverResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return swarm.Task{}, nil, wrapResponseError(err, serverResp, "task", taskID)
|
return swarm.Task{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(serverResp.body)
|
body, err := io.ReadAll(serverResp.body)
|
||||||
|
|
|
@ -25,7 +25,7 @@ func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (t
|
||||||
resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
|
resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return volume, nil, wrapResponseError(err, resp, "volume", volumeID)
|
return volume, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.body)
|
body, err := io.ReadAll(resp.body)
|
||||||
|
|
|
@ -17,5 +17,5 @@ func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool
|
||||||
}
|
}
|
||||||
resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
|
resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
return wrapResponseError(err, resp, "volume", volumeID)
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +1,11 @@
|
||||||
package errdefs // import "github.com/docker/docker/errdefs"
|
package errdefs // import "github.com/docker/docker/errdefs"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
containerderrors "github.com/containerd/containerd/errdefs"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetHTTPErrorStatusCode retrieves status code from error message.
|
|
||||||
func GetHTTPErrorStatusCode(err error) int {
|
|
||||||
if err == nil {
|
|
||||||
logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
|
|
||||||
return http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusCode int
|
|
||||||
|
|
||||||
// Stop right there
|
|
||||||
// Are you sure you should be adding a new error class here? Do one of the existing ones work?
|
|
||||||
|
|
||||||
// Note that the below functions are already checking the error causal chain for matches.
|
|
||||||
switch {
|
|
||||||
case IsNotFound(err):
|
|
||||||
statusCode = http.StatusNotFound
|
|
||||||
case IsInvalidParameter(err):
|
|
||||||
statusCode = http.StatusBadRequest
|
|
||||||
case IsConflict(err):
|
|
||||||
statusCode = http.StatusConflict
|
|
||||||
case IsUnauthorized(err):
|
|
||||||
statusCode = http.StatusUnauthorized
|
|
||||||
case IsUnavailable(err):
|
|
||||||
statusCode = http.StatusServiceUnavailable
|
|
||||||
case IsForbidden(err):
|
|
||||||
statusCode = http.StatusForbidden
|
|
||||||
case IsNotModified(err):
|
|
||||||
statusCode = http.StatusNotModified
|
|
||||||
case IsNotImplemented(err):
|
|
||||||
statusCode = http.StatusNotImplemented
|
|
||||||
case IsSystem(err) || IsUnknown(err) || IsDataLoss(err) || IsDeadline(err) || IsCancelled(err):
|
|
||||||
statusCode = http.StatusInternalServerError
|
|
||||||
default:
|
|
||||||
statusCode = statusCodeFromGRPCError(err)
|
|
||||||
if statusCode != http.StatusInternalServerError {
|
|
||||||
return statusCode
|
|
||||||
}
|
|
||||||
statusCode = statusCodeFromContainerdError(err)
|
|
||||||
if statusCode != http.StatusInternalServerError {
|
|
||||||
return statusCode
|
|
||||||
}
|
|
||||||
statusCode = statusCodeFromDistributionError(err)
|
|
||||||
if statusCode != http.StatusInternalServerError {
|
|
||||||
return statusCode
|
|
||||||
}
|
|
||||||
if e, ok := err.(causer); ok {
|
|
||||||
return GetHTTPErrorStatusCode(e.Cause())
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
|
||||||
"module": "api",
|
|
||||||
"error_type": fmt.Sprintf("%T", err),
|
|
||||||
}).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusCode == 0 {
|
|
||||||
statusCode = http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
return statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromStatusCode creates an errdef error, based on the provided HTTP status-code
|
// FromStatusCode creates an errdef error, based on the provided HTTP status-code
|
||||||
func FromStatusCode(err error, statusCode int) error {
|
func FromStatusCode(err error, statusCode int) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -118,74 +51,3 @@ func FromStatusCode(err error, statusCode int) error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// statusCodeFromGRPCError returns status code according to gRPC error
|
|
||||||
func statusCodeFromGRPCError(err error) int {
|
|
||||||
switch status.Code(err) {
|
|
||||||
case codes.InvalidArgument: // code 3
|
|
||||||
return http.StatusBadRequest
|
|
||||||
case codes.NotFound: // code 5
|
|
||||||
return http.StatusNotFound
|
|
||||||
case codes.AlreadyExists: // code 6
|
|
||||||
return http.StatusConflict
|
|
||||||
case codes.PermissionDenied: // code 7
|
|
||||||
return http.StatusForbidden
|
|
||||||
case codes.FailedPrecondition: // code 9
|
|
||||||
return http.StatusBadRequest
|
|
||||||
case codes.Unauthenticated: // code 16
|
|
||||||
return http.StatusUnauthorized
|
|
||||||
case codes.OutOfRange: // code 11
|
|
||||||
return http.StatusBadRequest
|
|
||||||
case codes.Unimplemented: // code 12
|
|
||||||
return http.StatusNotImplemented
|
|
||||||
case codes.Unavailable: // code 14
|
|
||||||
return http.StatusServiceUnavailable
|
|
||||||
default:
|
|
||||||
// codes.Canceled(1)
|
|
||||||
// codes.Unknown(2)
|
|
||||||
// codes.DeadlineExceeded(4)
|
|
||||||
// codes.ResourceExhausted(8)
|
|
||||||
// codes.Aborted(10)
|
|
||||||
// codes.Internal(13)
|
|
||||||
// codes.DataLoss(15)
|
|
||||||
return http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// statusCodeFromDistributionError returns status code according to registry errcode
|
|
||||||
// code is loosely based on errcode.ServeJSON() in docker/distribution
|
|
||||||
func statusCodeFromDistributionError(err error) int {
|
|
||||||
switch errs := err.(type) {
|
|
||||||
case errcode.Errors:
|
|
||||||
if len(errs) < 1 {
|
|
||||||
return http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
if _, ok := errs[0].(errcode.ErrorCoder); ok {
|
|
||||||
return statusCodeFromDistributionError(errs[0])
|
|
||||||
}
|
|
||||||
case errcode.ErrorCoder:
|
|
||||||
return errs.ErrorCode().Descriptor().HTTPStatusCode
|
|
||||||
}
|
|
||||||
return http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
// statusCodeFromContainerdError returns status code for containerd errors when
|
|
||||||
// consumed directly (not through gRPC)
|
|
||||||
func statusCodeFromContainerdError(err error) int {
|
|
||||||
switch {
|
|
||||||
case containerderrors.IsInvalidArgument(err):
|
|
||||||
return http.StatusBadRequest
|
|
||||||
case containerderrors.IsNotFound(err):
|
|
||||||
return http.StatusNotFound
|
|
||||||
case containerderrors.IsAlreadyExists(err):
|
|
||||||
return http.StatusConflict
|
|
||||||
case containerderrors.IsFailedPrecondition(err):
|
|
||||||
return http.StatusPreconditionFailed
|
|
||||||
case containerderrors.IsUnavailable(err):
|
|
||||||
return http.StatusServiceUnavailable
|
|
||||||
case containerderrors.IsNotImplemented(err):
|
|
||||||
return http.StatusNotImplemented
|
|
||||||
default:
|
|
||||||
return http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -40,8 +40,7 @@ type (
|
||||||
ExcludePatterns []string
|
ExcludePatterns []string
|
||||||
Compression Compression
|
Compression Compression
|
||||||
NoLchown bool
|
NoLchown bool
|
||||||
UIDMaps []idtools.IDMap
|
IDMap idtools.IdentityMapping
|
||||||
GIDMaps []idtools.IDMap
|
|
||||||
ChownOpts *idtools.Identity
|
ChownOpts *idtools.Identity
|
||||||
IncludeSourceDir bool
|
IncludeSourceDir bool
|
||||||
// WhiteoutFormat is the expected on disk format for whiteout files.
|
// WhiteoutFormat is the expected on disk format for whiteout files.
|
||||||
|
@ -63,12 +62,12 @@ type (
|
||||||
// mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations.
|
// mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations.
|
||||||
type Archiver struct {
|
type Archiver struct {
|
||||||
Untar func(io.Reader, string, *TarOptions) error
|
Untar func(io.Reader, string, *TarOptions) error
|
||||||
IDMapping *idtools.IdentityMapping
|
IDMapping idtools.IdentityMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultArchiver returns a new Archiver without any IdentityMapping
|
// NewDefaultArchiver returns a new Archiver without any IdentityMapping
|
||||||
func NewDefaultArchiver() *Archiver {
|
func NewDefaultArchiver() *Archiver {
|
||||||
return &Archiver{Untar: Untar, IDMapping: &idtools.IdentityMapping{}}
|
return &Archiver{Untar: Untar}
|
||||||
}
|
}
|
||||||
|
|
||||||
// breakoutError is used to differentiate errors related to breaking out
|
// breakoutError is used to differentiate errors related to breaking out
|
||||||
|
@ -534,7 +533,7 @@ type tarAppender struct {
|
||||||
|
|
||||||
// for hardlink mapping
|
// for hardlink mapping
|
||||||
SeenFiles map[uint64]string
|
SeenFiles map[uint64]string
|
||||||
IdentityMapping *idtools.IdentityMapping
|
IdentityMapping idtools.IdentityMapping
|
||||||
ChownOpts *idtools.Identity
|
ChownOpts *idtools.Identity
|
||||||
|
|
||||||
// For packing and unpacking whiteout files in the
|
// For packing and unpacking whiteout files in the
|
||||||
|
@ -544,7 +543,7 @@ type tarAppender struct {
|
||||||
WhiteoutConverter tarWhiteoutConverter
|
WhiteoutConverter tarWhiteoutConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTarAppender(idMapping *idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender {
|
func newTarAppender(idMapping idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender {
|
||||||
return &tarAppender{
|
return &tarAppender{
|
||||||
SeenFiles: make(map[uint64]string),
|
SeenFiles: make(map[uint64]string),
|
||||||
TarWriter: tar.NewWriter(writer),
|
TarWriter: tar.NewWriter(writer),
|
||||||
|
@ -860,7 +859,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
ta := newTarAppender(
|
ta := newTarAppender(
|
||||||
idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps),
|
options.IDMap,
|
||||||
compressWriter,
|
compressWriter,
|
||||||
options.ChownOpts,
|
options.ChownOpts,
|
||||||
)
|
)
|
||||||
|
@ -1044,8 +1043,7 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err
|
||||||
defer pools.BufioReader32KPool.Put(trBuf)
|
defer pools.BufioReader32KPool.Put(trBuf)
|
||||||
|
|
||||||
var dirs []*tar.Header
|
var dirs []*tar.Header
|
||||||
idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
|
rootIDs := options.IDMap.RootPair()
|
||||||
rootIDs := idMapping.RootPair()
|
|
||||||
whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
|
whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1134,7 +1132,7 @@ loop:
|
||||||
}
|
}
|
||||||
trBuf.Reset(tr)
|
trBuf.Reset(tr)
|
||||||
|
|
||||||
if err := remapIDs(idMapping, hdr); err != nil {
|
if err := remapIDs(options.IDMap, hdr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1221,8 +1219,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
|
||||||
}
|
}
|
||||||
defer archive.Close()
|
defer archive.Close()
|
||||||
options := &TarOptions{
|
options := &TarOptions{
|
||||||
UIDMaps: archiver.IDMapping.UIDs(),
|
IDMap: archiver.IDMapping,
|
||||||
GIDMaps: archiver.IDMapping.GIDs(),
|
|
||||||
}
|
}
|
||||||
return archiver.Untar(archive, dst, options)
|
return archiver.Untar(archive, dst, options)
|
||||||
}
|
}
|
||||||
|
@ -1235,8 +1232,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
|
||||||
}
|
}
|
||||||
defer archive.Close()
|
defer archive.Close()
|
||||||
options := &TarOptions{
|
options := &TarOptions{
|
||||||
UIDMaps: archiver.IDMapping.UIDs(),
|
IDMap: archiver.IDMapping,
|
||||||
GIDMaps: archiver.IDMapping.GIDs(),
|
|
||||||
}
|
}
|
||||||
return archiver.Untar(archive, dst, options)
|
return archiver.Untar(archive, dst, options)
|
||||||
}
|
}
|
||||||
|
@ -1343,11 +1339,11 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IdentityMapping returns the IdentityMapping of the archiver.
|
// IdentityMapping returns the IdentityMapping of the archiver.
|
||||||
func (archiver *Archiver) IdentityMapping() *idtools.IdentityMapping {
|
func (archiver *Archiver) IdentityMapping() idtools.IdentityMapping {
|
||||||
return archiver.IDMapping
|
return archiver.IDMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
func remapIDs(idMapping *idtools.IdentityMapping, hdr *tar.Header) error {
|
func remapIDs(idMapping idtools.IdentityMapping, hdr *tar.Header) error {
|
||||||
ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
|
ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
|
||||||
hdr.Uid, hdr.Gid = ids.UID, ids.GID
|
hdr.Uid, hdr.Gid = ids.UID, ids.GID
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -394,10 +394,10 @@ func ChangesSize(newDir string, changes []Change) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportChanges produces an Archive from the provided changes, relative to dir.
|
// ExportChanges produces an Archive from the provided changes, relative to dir.
|
||||||
func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (io.ReadCloser, error) {
|
func ExportChanges(dir string, changes []Change, idMap idtools.IdentityMapping) (io.ReadCloser, error) {
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
ta := newTarAppender(idtools.NewIDMappingsFromMaps(uidMaps, gidMaps), writer, nil)
|
ta := newTarAppender(idMap, writer, nil)
|
||||||
|
|
||||||
// this buffer is needed for the duration of this piped stream
|
// this buffer is needed for the duration of this piped stream
|
||||||
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/idtools"
|
|
||||||
"github.com/docker/docker/pkg/pools"
|
"github.com/docker/docker/pkg/pools"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -32,7 +31,6 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||||
if options.ExcludePatterns == nil {
|
if options.ExcludePatterns == nil {
|
||||||
options.ExcludePatterns = []string{}
|
options.ExcludePatterns = []string{}
|
||||||
}
|
}
|
||||||
idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
|
|
||||||
|
|
||||||
aufsTempdir := ""
|
aufsTempdir := ""
|
||||||
aufsHardlinks := make(map[string]*tar.Header)
|
aufsHardlinks := make(map[string]*tar.Header)
|
||||||
|
@ -192,7 +190,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||||
srcData = tmpFile
|
srcData = tmpFile
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := remapIDs(idMapping, srcHdr); err != nil {
|
if err := remapIDs(options.IDMap, srcHdr); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,70 +108,72 @@ type Identity struct {
|
||||||
SID string
|
SID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IdentityMapping contains a mappings of UIDs and GIDs
|
// Chown changes the numeric uid and gid of the named file to id.UID and id.GID.
|
||||||
type IdentityMapping struct {
|
func (id Identity) Chown(name string) error {
|
||||||
uids []IDMap
|
return os.Chown(name, id.UID, id.GID)
|
||||||
gids []IDMap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIDMappingsFromMaps creates a new mapping from two slices
|
// IdentityMapping contains a mappings of UIDs and GIDs.
|
||||||
// Deprecated: this is a temporary shim while transitioning to IDMapping
|
// The zero value represents an empty mapping.
|
||||||
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IdentityMapping {
|
type IdentityMapping struct {
|
||||||
return &IdentityMapping{uids: uids, gids: gids}
|
UIDMaps []IDMap `json:"UIDMaps"`
|
||||||
|
GIDMaps []IDMap `json:"GIDMaps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RootPair returns a uid and gid pair for the root user. The error is ignored
|
// RootPair returns a uid and gid pair for the root user. The error is ignored
|
||||||
// because a root user always exists, and the defaults are correct when the uid
|
// because a root user always exists, and the defaults are correct when the uid
|
||||||
// and gid maps are empty.
|
// and gid maps are empty.
|
||||||
func (i *IdentityMapping) RootPair() Identity {
|
func (i IdentityMapping) RootPair() Identity {
|
||||||
uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
|
uid, gid, _ := GetRootUIDGID(i.UIDMaps, i.GIDMaps)
|
||||||
return Identity{UID: uid, GID: gid}
|
return Identity{UID: uid, GID: gid}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToHost returns the host UID and GID for the container uid, gid.
|
// ToHost returns the host UID and GID for the container uid, gid.
|
||||||
// Remapping is only performed if the ids aren't already the remapped root ids
|
// Remapping is only performed if the ids aren't already the remapped root ids
|
||||||
func (i *IdentityMapping) ToHost(pair Identity) (Identity, error) {
|
func (i IdentityMapping) ToHost(pair Identity) (Identity, error) {
|
||||||
var err error
|
var err error
|
||||||
target := i.RootPair()
|
target := i.RootPair()
|
||||||
|
|
||||||
if pair.UID != target.UID {
|
if pair.UID != target.UID {
|
||||||
target.UID, err = toHost(pair.UID, i.uids)
|
target.UID, err = toHost(pair.UID, i.UIDMaps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return target, err
|
return target, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pair.GID != target.GID {
|
if pair.GID != target.GID {
|
||||||
target.GID, err = toHost(pair.GID, i.gids)
|
target.GID, err = toHost(pair.GID, i.GIDMaps)
|
||||||
}
|
}
|
||||||
return target, err
|
return target, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToContainer returns the container UID and GID for the host uid and gid
|
// ToContainer returns the container UID and GID for the host uid and gid
|
||||||
func (i *IdentityMapping) ToContainer(pair Identity) (int, int, error) {
|
func (i IdentityMapping) ToContainer(pair Identity) (int, int, error) {
|
||||||
uid, err := toContainer(pair.UID, i.uids)
|
uid, err := toContainer(pair.UID, i.UIDMaps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1, err
|
return -1, -1, err
|
||||||
}
|
}
|
||||||
gid, err := toContainer(pair.GID, i.gids)
|
gid, err := toContainer(pair.GID, i.GIDMaps)
|
||||||
return uid, gid, err
|
return uid, gid, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if there are no id mappings
|
// Empty returns true if there are no id mappings
|
||||||
func (i *IdentityMapping) Empty() bool {
|
func (i IdentityMapping) Empty() bool {
|
||||||
return len(i.uids) == 0 && len(i.gids) == 0
|
return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIDs return the UID mapping
|
// UIDs returns the mapping for UID.
|
||||||
// TODO: remove this once everything has been refactored to use pairs
|
//
|
||||||
func (i *IdentityMapping) UIDs() []IDMap {
|
// Deprecated: reference the UIDMaps field directly.
|
||||||
return i.uids
|
func (i IdentityMapping) UIDs() []IDMap {
|
||||||
|
return i.UIDMaps
|
||||||
}
|
}
|
||||||
|
|
||||||
// GIDs return the UID mapping
|
// GIDs returns the mapping for GID.
|
||||||
// TODO: remove this once everything has been refactored to use pairs
|
//
|
||||||
func (i *IdentityMapping) GIDs() []IDMap {
|
// Deprecated: reference the GIDMaps field directly.
|
||||||
return i.gids
|
func (i IdentityMapping) GIDs() []IDMap {
|
||||||
|
return i.GIDMaps
|
||||||
}
|
}
|
||||||
|
|
||||||
func createIDMap(subidRanges ranges) []IDMap {
|
func createIDMap(subidRanges ranges) []IDMap {
|
||||||
|
|
|
@ -240,24 +240,37 @@ func setPermissions(p string, mode os.FileMode, uid, gid int, stat *system.StatT
|
||||||
// NewIdentityMapping takes a requested username and
|
// NewIdentityMapping takes a requested username and
|
||||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||||
// proper uid and gid remapping ranges for that user/group pair
|
// proper uid and gid remapping ranges for that user/group pair
|
||||||
|
//
|
||||||
|
// Deprecated: Use LoadIdentityMapping.
|
||||||
func NewIdentityMapping(name string) (*IdentityMapping, error) {
|
func NewIdentityMapping(name string) (*IdentityMapping, error) {
|
||||||
|
m, err := LoadIdentityMapping(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadIdentityMapping takes a requested username and
|
||||||
|
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||||
|
// proper uid and gid remapping ranges for that user/group pair
|
||||||
|
func LoadIdentityMapping(name string) (IdentityMapping, error) {
|
||||||
usr, err := LookupUser(name)
|
usr, err := LookupUser(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not get user for username %s: %v", name, err)
|
return IdentityMapping{}, fmt.Errorf("Could not get user for username %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subuidRanges, err := lookupSubUIDRanges(usr)
|
subuidRanges, err := lookupSubUIDRanges(usr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return IdentityMapping{}, err
|
||||||
}
|
}
|
||||||
subgidRanges, err := lookupSubGIDRanges(usr)
|
subgidRanges, err := lookupSubGIDRanges(usr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return IdentityMapping{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &IdentityMapping{
|
return IdentityMapping{
|
||||||
uids: subuidRanges,
|
UIDMaps: subuidRanges,
|
||||||
gids: subgidRanges,
|
GIDMaps: subgidRanges,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
import "golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Deprecated: use github.com/docker/pkg/idtools.SeTakeOwnershipPrivilege
|
|
||||||
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
|
||||||
// Deprecated: use github.com/docker/pkg/idtools.ContainerAdministratorSidString
|
|
||||||
ContainerAdministratorSidString = "S-1-5-93-2-1"
|
|
||||||
// Deprecated: use github.com/docker/pkg/idtools.ContainerUserSidString
|
|
||||||
ContainerUserSidString = "S-1-5-93-2-2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VER_NT_WORKSTATION, see https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa
|
|
||||||
const verNTWorkstation = 0x00000001 // VER_NT_WORKSTATION
|
|
||||||
|
|
||||||
// IsWindowsClient returns true if the SKU is client. It returns false on
|
|
||||||
// Windows server, or if an error occurred when making the GetVersionExW
|
|
||||||
// syscall.
|
|
||||||
func IsWindowsClient() bool {
|
|
||||||
ver := windows.RtlGetVersion()
|
|
||||||
return ver != nil && ver.ProductType == verNTWorkstation
|
|
||||||
}
|
|
|
@ -10,15 +10,13 @@ import (
|
||||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// AuthClientID is used the ClientID used for the token server
|
// AuthClientID is used the ClientID used for the token server
|
||||||
AuthClientID = "docker"
|
const AuthClientID = "docker"
|
||||||
)
|
|
||||||
|
|
||||||
type loginCredentialStore struct {
|
type loginCredentialStore struct {
|
||||||
authConfig *types.AuthConfig
|
authConfig *types.AuthConfig
|
||||||
|
@ -65,14 +63,6 @@ func (scs staticCredentialStore) RefreshToken(*url.URL, string) string {
|
||||||
func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
|
func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type fallbackError struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err fallbackError) Error() string {
|
|
||||||
return err.err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// loginV2 tries to login to the v2 registry server. The given registry
|
// loginV2 tries to login to the v2 registry server. The given registry
|
||||||
// endpoint will be pinged to get authorization challenges. These challenges
|
// endpoint will be pinged to get authorization challenges. These challenges
|
||||||
// will be used to authenticate against the registry to validate credentials.
|
// will be used to authenticate against the registry to validate credentials.
|
||||||
|
@ -80,7 +70,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
|
||||||
var (
|
var (
|
||||||
endpointStr = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
endpointStr = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
||||||
modifiers = Headers(userAgent, nil)
|
modifiers = Headers(userAgent, nil)
|
||||||
authTransport = transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
|
authTransport = transport.NewTransport(newTransport(endpoint.TLSConfig), modifiers...)
|
||||||
credentialAuthConfig = *authConfig
|
credentialAuthConfig = *authConfig
|
||||||
creds = loginCredentialStore{authConfig: &credentialAuthConfig}
|
creds = loginCredentialStore{authConfig: &credentialAuthConfig}
|
||||||
)
|
)
|
||||||
|
@ -109,8 +99,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
|
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
|
||||||
err = errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
|
return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||||
return "", "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
|
func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
|
||||||
|
@ -129,10 +118,9 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi
|
||||||
tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
|
tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
|
||||||
basicHandler := auth.NewBasicHandler(creds)
|
basicHandler := auth.NewBasicHandler(creds)
|
||||||
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
|
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
|
||||||
tr := transport.NewTransport(authTransport, modifiers...)
|
|
||||||
|
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: tr,
|
Transport: transport.NewTransport(authTransport, modifiers...),
|
||||||
Timeout: 15 * time.Second,
|
Timeout: 15 * time.Second,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -146,14 +134,11 @@ func ConvertToHostname(url string) string {
|
||||||
} else if strings.HasPrefix(url, "https://") {
|
} else if strings.HasPrefix(url, "https://") {
|
||||||
stripped = strings.TrimPrefix(url, "https://")
|
stripped = strings.TrimPrefix(url, "https://")
|
||||||
}
|
}
|
||||||
|
return strings.SplitN(stripped, "/", 2)[0]
|
||||||
nameParts := strings.SplitN(stripped, "/", 2)
|
|
||||||
|
|
||||||
return nameParts[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveAuthConfig matches an auth configuration to a server address or a URL
|
// ResolveAuthConfig matches an auth configuration to a server address or a URL
|
||||||
func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
|
func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registry.IndexInfo) types.AuthConfig {
|
||||||
configKey := GetAuthConfigKey(index)
|
configKey := GetAuthConfigKey(index)
|
||||||
// First try the happy case
|
// First try the happy case
|
||||||
if c, found := authConfigs[configKey]; found || index.Official {
|
if c, found := authConfigs[configKey]; found || index.Official {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package registry // import "github.com/docker/docker/registry"
|
package registry // import "github.com/docker/docker/registry"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -9,8 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,9 +20,7 @@ type ServiceOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// serviceConfig holds daemon configuration for the registry service.
|
// serviceConfig holds daemon configuration for the registry service.
|
||||||
type serviceConfig struct {
|
type serviceConfig registry.ServiceConfig
|
||||||
registrytypes.ServiceConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(thaJeztah) both the "index.docker.io" and "registry-1.docker.io" domains
|
// TODO(thaJeztah) both the "index.docker.io" and "registry-1.docker.io" domains
|
||||||
// are here for historic reasons and backward-compatibility. These domains
|
// are here for historic reasons and backward-compatibility. These domains
|
||||||
|
@ -58,70 +54,92 @@ var (
|
||||||
Host: DefaultRegistryHost,
|
Host: DefaultRegistryHost,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrInvalidRepositoryName is an error returned if the repository name did
|
|
||||||
// not have the correct form
|
|
||||||
ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
|
|
||||||
|
|
||||||
emptyServiceConfig, _ = newServiceConfig(ServiceOptions{})
|
emptyServiceConfig, _ = newServiceConfig(ServiceOptions{})
|
||||||
validHostPortRegex = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
|
validHostPortRegex = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
|
||||||
|
|
||||||
// for mocking in unit tests
|
// for mocking in unit tests
|
||||||
lookupIP = net.LookupIP
|
lookupIP = net.LookupIP
|
||||||
|
|
||||||
|
// certsDir is used to override defaultCertsDir.
|
||||||
|
certsDir string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func SetCertsDir(path string) {
|
||||||
|
certsDir = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertsDir is the directory where certificates are stored.
|
||||||
|
func CertsDir() string {
|
||||||
|
if certsDir != "" {
|
||||||
|
return certsDir
|
||||||
|
}
|
||||||
|
return defaultCertsDir
|
||||||
|
}
|
||||||
|
|
||||||
// newServiceConfig returns a new instance of ServiceConfig
|
// newServiceConfig returns a new instance of ServiceConfig
|
||||||
func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
|
func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
|
||||||
config := &serviceConfig{
|
config := &serviceConfig{}
|
||||||
ServiceConfig: registrytypes.ServiceConfig{
|
if err := config.loadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts); err != nil {
|
||||||
InsecureRegistryCIDRs: make([]*registrytypes.NetIPNet, 0),
|
|
||||||
IndexConfigs: make(map[string]*registrytypes.IndexInfo),
|
|
||||||
// Hack: Bypass setting the mirrors to IndexConfigs since they are going away
|
|
||||||
// and Mirrors are only for the official registry anyways.
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := config.LoadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := config.LoadMirrors(options.Mirrors); err != nil {
|
if err := config.loadMirrors(options.Mirrors); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := config.LoadInsecureRegistries(options.InsecureRegistries); err != nil {
|
if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
|
// copy constructs a new ServiceConfig with a copy of the configuration in config.
|
||||||
func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
|
func (config *serviceConfig) copy() *registry.ServiceConfig {
|
||||||
cidrs := map[string]*registrytypes.NetIPNet{}
|
ic := make(map[string]*registry.IndexInfo)
|
||||||
|
for key, value := range config.IndexConfigs {
|
||||||
|
ic[key] = value
|
||||||
|
}
|
||||||
|
return ®istry.ServiceConfig{
|
||||||
|
AllowNondistributableArtifactsCIDRs: append([]*registry.NetIPNet(nil), config.AllowNondistributableArtifactsCIDRs...),
|
||||||
|
AllowNondistributableArtifactsHostnames: append([]string(nil), config.AllowNondistributableArtifactsHostnames...),
|
||||||
|
InsecureRegistryCIDRs: append([]*registry.NetIPNet(nil), config.InsecureRegistryCIDRs...),
|
||||||
|
IndexConfigs: ic,
|
||||||
|
Mirrors: append([]string(nil), config.Mirrors...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
|
||||||
|
func (config *serviceConfig) loadAllowNondistributableArtifacts(registries []string) error {
|
||||||
|
cidrs := map[string]*registry.NetIPNet{}
|
||||||
hostnames := map[string]bool{}
|
hostnames := map[string]bool{}
|
||||||
|
|
||||||
for _, r := range registries {
|
for _, r := range registries {
|
||||||
if _, err := ValidateIndexName(r); err != nil {
|
if _, err := ValidateIndexName(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if validateNoScheme(r) != nil {
|
if hasScheme(r) {
|
||||||
return fmt.Errorf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
|
return invalidParamf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ipnet, err := net.ParseCIDR(r); err == nil {
|
if _, ipnet, err := net.ParseCIDR(r); err == nil {
|
||||||
// Valid CIDR.
|
// Valid CIDR.
|
||||||
cidrs[ipnet.String()] = (*registrytypes.NetIPNet)(ipnet)
|
cidrs[ipnet.String()] = (*registry.NetIPNet)(ipnet)
|
||||||
} else if err := validateHostPort(r); err == nil {
|
} else if err = validateHostPort(r); err == nil {
|
||||||
// Must be `host:port` if not CIDR.
|
// Must be `host:port` if not CIDR.
|
||||||
hostnames[r] = true
|
hostnames[r] = true
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("allow-nondistributable-artifacts registry %s is not valid: %v", r, err)
|
return invalidParamWrapf(err, "allow-nondistributable-artifacts registry %s is not valid", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.AllowNondistributableArtifactsCIDRs = make([]*(registrytypes.NetIPNet), 0)
|
config.AllowNondistributableArtifactsCIDRs = make([]*registry.NetIPNet, 0, len(cidrs))
|
||||||
for _, c := range cidrs {
|
for _, c := range cidrs {
|
||||||
config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
|
config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.AllowNondistributableArtifactsHostnames = make([]string, 0)
|
config.AllowNondistributableArtifactsHostnames = make([]string, 0, len(hostnames))
|
||||||
for h := range hostnames {
|
for h := range hostnames {
|
||||||
config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
|
config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
|
||||||
}
|
}
|
||||||
|
@ -129,9 +147,9 @@ func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []str
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadMirrors loads mirrors to config, after removing duplicates.
|
// loadMirrors loads mirrors to config, after removing duplicates.
|
||||||
// Returns an error if mirrors contains an invalid mirror.
|
// Returns an error if mirrors contains an invalid mirror.
|
||||||
func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
func (config *serviceConfig) loadMirrors(mirrors []string) error {
|
||||||
mMap := map[string]struct{}{}
|
mMap := map[string]struct{}{}
|
||||||
unique := []string{}
|
unique := []string{}
|
||||||
|
|
||||||
|
@ -149,40 +167,33 @@ func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
||||||
config.Mirrors = unique
|
config.Mirrors = unique
|
||||||
|
|
||||||
// Configure public registry since mirrors may have changed.
|
// Configure public registry since mirrors may have changed.
|
||||||
config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{
|
config.IndexConfigs = map[string]*registry.IndexInfo{
|
||||||
|
IndexName: {
|
||||||
Name: IndexName,
|
Name: IndexName,
|
||||||
Mirrors: config.Mirrors,
|
Mirrors: unique,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
Official: true,
|
Official: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadInsecureRegistries loads insecure registries to config
|
// loadInsecureRegistries loads insecure registries to config
|
||||||
func (config *serviceConfig) LoadInsecureRegistries(registries []string) error {
|
func (config *serviceConfig) loadInsecureRegistries(registries []string) error {
|
||||||
// Localhost is by default considered as an insecure registry
|
// Localhost is by default considered as an insecure registry. This is a
|
||||||
// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
|
// stop-gap for people who are running a private registry on localhost.
|
||||||
//
|
|
||||||
// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
|
|
||||||
// daemon flags on boot2docker?
|
|
||||||
registries = append(registries, "127.0.0.0/8")
|
registries = append(registries, "127.0.0.0/8")
|
||||||
|
|
||||||
// Store original InsecureRegistryCIDRs and IndexConfigs
|
var (
|
||||||
// Clean InsecureRegistryCIDRs and IndexConfigs in config, as passed registries has all insecure registry info.
|
insecureRegistryCIDRs = make([]*registry.NetIPNet, 0)
|
||||||
originalCIDRs := config.ServiceConfig.InsecureRegistryCIDRs
|
indexConfigs = make(map[string]*registry.IndexInfo)
|
||||||
originalIndexInfos := config.ServiceConfig.IndexConfigs
|
)
|
||||||
|
|
||||||
config.ServiceConfig.InsecureRegistryCIDRs = make([]*registrytypes.NetIPNet, 0)
|
|
||||||
config.ServiceConfig.IndexConfigs = make(map[string]*registrytypes.IndexInfo)
|
|
||||||
|
|
||||||
skip:
|
skip:
|
||||||
for _, r := range registries {
|
for _, r := range registries {
|
||||||
// validate insecure registry
|
// validate insecure registry
|
||||||
if _, err := ValidateIndexName(r); err != nil {
|
if _, err := ValidateIndexName(r); err != nil {
|
||||||
// before returning err, roll back to original data
|
|
||||||
config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
|
|
||||||
config.ServiceConfig.IndexConfigs = originalIndexInfos
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(strings.ToLower(r), "http://") {
|
if strings.HasPrefix(strings.ToLower(r), "http://") {
|
||||||
|
@ -191,35 +202,27 @@ skip:
|
||||||
} else if strings.HasPrefix(strings.ToLower(r), "https://") {
|
} else if strings.HasPrefix(strings.ToLower(r), "https://") {
|
||||||
logrus.Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r)
|
logrus.Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r)
|
||||||
r = r[8:]
|
r = r[8:]
|
||||||
} else if validateNoScheme(r) != nil {
|
} else if hasScheme(r) {
|
||||||
// Insecure registry should not contain '://'
|
return invalidParamf("insecure registry %s should not contain '://'", r)
|
||||||
// before returning err, roll back to original data
|
|
||||||
config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
|
|
||||||
config.ServiceConfig.IndexConfigs = originalIndexInfos
|
|
||||||
return fmt.Errorf("insecure registry %s should not contain '://'", r)
|
|
||||||
}
|
}
|
||||||
// Check if CIDR was passed to --insecure-registry
|
// Check if CIDR was passed to --insecure-registry
|
||||||
_, ipnet, err := net.ParseCIDR(r)
|
_, ipnet, err := net.ParseCIDR(r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
|
// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
|
||||||
data := (*registrytypes.NetIPNet)(ipnet)
|
data := (*registry.NetIPNet)(ipnet)
|
||||||
for _, value := range config.InsecureRegistryCIDRs {
|
for _, value := range insecureRegistryCIDRs {
|
||||||
if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
|
if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
|
||||||
continue skip
|
continue skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ipnet is not found, add it in config.InsecureRegistryCIDRs
|
// ipnet is not found, add it in config.InsecureRegistryCIDRs
|
||||||
config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, data)
|
insecureRegistryCIDRs = append(insecureRegistryCIDRs, data)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if err := validateHostPort(r); err != nil {
|
if err := validateHostPort(r); err != nil {
|
||||||
config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
|
return invalidParamWrapf(err, "insecure registry %s is not valid", r)
|
||||||
config.ServiceConfig.IndexConfigs = originalIndexInfos
|
|
||||||
return fmt.Errorf("insecure registry %s is not valid: %v", r, err)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// Assume `host:port` if not CIDR.
|
// Assume `host:port` if not CIDR.
|
||||||
config.IndexConfigs[r] = ®istrytypes.IndexInfo{
|
indexConfigs[r] = ®istry.IndexInfo{
|
||||||
Name: r,
|
Name: r,
|
||||||
Mirrors: make([]string, 0),
|
Mirrors: make([]string, 0),
|
||||||
Secure: false,
|
Secure: false,
|
||||||
|
@ -229,12 +232,14 @@ skip:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure public registry.
|
// Configure public registry.
|
||||||
config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{
|
indexConfigs[IndexName] = ®istry.IndexInfo{
|
||||||
Name: IndexName,
|
Name: IndexName,
|
||||||
Mirrors: config.Mirrors,
|
Mirrors: config.Mirrors,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
Official: true,
|
Official: true,
|
||||||
}
|
}
|
||||||
|
config.InsecureRegistryCIDRs = insecureRegistryCIDRs
|
||||||
|
config.IndexConfigs = indexConfigs
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -248,7 +253,7 @@ skip:
|
||||||
// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
|
// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
|
||||||
// or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
|
// or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
|
||||||
// resolution fails, CIDR matching is not performed.
|
// resolution fails, CIDR matching is not performed.
|
||||||
func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool {
|
func (config *serviceConfig) allowNondistributableArtifacts(hostname string) bool {
|
||||||
for _, h := range config.AllowNondistributableArtifactsHostnames {
|
for _, h := range config.AllowNondistributableArtifactsHostnames {
|
||||||
if h == hostname {
|
if h == hostname {
|
||||||
return true
|
return true
|
||||||
|
@ -269,7 +274,7 @@ func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool
|
||||||
// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
|
// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
|
||||||
// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
|
// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
|
||||||
// of insecureRegistries.
|
// of insecureRegistries.
|
||||||
func isSecureIndex(config *serviceConfig, indexName string) bool {
|
func (config *serviceConfig) isSecureIndex(indexName string) bool {
|
||||||
// Check for configured index, first. This is needed in case isSecureIndex
|
// Check for configured index, first. This is needed in case isSecureIndex
|
||||||
// is called from anything besides newIndexInfo, in order to honor per-index configurations.
|
// is called from anything besides newIndexInfo, in order to honor per-index configurations.
|
||||||
if index, ok := config.IndexConfigs[indexName]; ok {
|
if index, ok := config.IndexConfigs[indexName]; ok {
|
||||||
|
@ -282,7 +287,7 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
|
||||||
// isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
|
// isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
|
||||||
// where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
|
// where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
|
||||||
// resolved to IP addresses for matching. If resolution fails, false is returned.
|
// resolved to IP addresses for matching. If resolution fails, false is returned.
|
||||||
func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
|
func isCIDRMatch(cidrs []*registry.NetIPNet, URLHost string) bool {
|
||||||
host, _, err := net.SplitHostPort(URLHost)
|
host, _, err := net.SplitHostPort(URLHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Assume URLHost is of the form `host` without the port and go on.
|
// Assume URLHost is of the form `host` without the port and go on.
|
||||||
|
@ -318,18 +323,18 @@ func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
|
||||||
func ValidateMirror(val string) (string, error) {
|
func ValidateMirror(val string) (string, error) {
|
||||||
uri, err := url.Parse(val)
|
uri, err := url.Parse(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid mirror: %q is not a valid URI", val)
|
return "", invalidParamWrapf(err, "invalid mirror: %q is not a valid URI", val)
|
||||||
}
|
}
|
||||||
if uri.Scheme != "http" && uri.Scheme != "https" {
|
if uri.Scheme != "http" && uri.Scheme != "https" {
|
||||||
return "", fmt.Errorf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri)
|
return "", invalidParamf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri)
|
||||||
}
|
}
|
||||||
if (uri.Path != "" && uri.Path != "/") || uri.RawQuery != "" || uri.Fragment != "" {
|
if (uri.Path != "" && uri.Path != "/") || uri.RawQuery != "" || uri.Fragment != "" {
|
||||||
return "", fmt.Errorf("invalid mirror: path, query, or fragment at end of the URI %q", uri)
|
return "", invalidParamf("invalid mirror: path, query, or fragment at end of the URI %q", uri)
|
||||||
}
|
}
|
||||||
if uri.User != nil {
|
if uri.User != nil {
|
||||||
// strip password from output
|
// strip password from output
|
||||||
uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
|
uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
|
||||||
return "", fmt.Errorf("invalid mirror: username/password not allowed in URI %q", uri)
|
return "", invalidParamf("invalid mirror: username/password not allowed in URI %q", uri)
|
||||||
}
|
}
|
||||||
return strings.TrimSuffix(val, "/") + "/", nil
|
return strings.TrimSuffix(val, "/") + "/", nil
|
||||||
}
|
}
|
||||||
|
@ -341,17 +346,13 @@ func ValidateIndexName(val string) (string, error) {
|
||||||
val = "docker.io"
|
val = "docker.io"
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
|
if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
|
||||||
return "", fmt.Errorf("invalid index name (%s). Cannot begin or end with a hyphen", val)
|
return "", invalidParamf("invalid index name (%s). Cannot begin or end with a hyphen", val)
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateNoScheme(reposName string) error {
|
func hasScheme(reposName string) bool {
|
||||||
if strings.Contains(reposName, "://") {
|
return strings.Contains(reposName, "://")
|
||||||
// It cannot contain a scheme!
|
|
||||||
return ErrInvalidRepositoryName
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateHostPort(s string) error {
|
func validateHostPort(s string) error {
|
||||||
|
@ -364,7 +365,7 @@ func validateHostPort(s string) error {
|
||||||
// If match against the `host:port` pattern fails,
|
// If match against the `host:port` pattern fails,
|
||||||
// it might be `IPv6:port`, which will be captured by net.ParseIP(host)
|
// it might be `IPv6:port`, which will be captured by net.ParseIP(host)
|
||||||
if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
|
if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
|
||||||
return fmt.Errorf("invalid host %q", host)
|
return invalidParamf("invalid host %q", host)
|
||||||
}
|
}
|
||||||
if port != "" {
|
if port != "" {
|
||||||
v, err := strconv.Atoi(port)
|
v, err := strconv.Atoi(port)
|
||||||
|
@ -372,14 +373,14 @@ func validateHostPort(s string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if v < 0 || v > 65535 {
|
if v < 0 || v > 65535 {
|
||||||
return fmt.Errorf("invalid port %q", port)
|
return invalidParamf("invalid port %q", port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newIndexInfo returns IndexInfo configuration from indexName
|
// newIndexInfo returns IndexInfo configuration from indexName
|
||||||
func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.IndexInfo, error) {
|
func newIndexInfo(config *serviceConfig, indexName string) (*registry.IndexInfo, error) {
|
||||||
var err error
|
var err error
|
||||||
indexName, err = ValidateIndexName(indexName)
|
indexName, err = ValidateIndexName(indexName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -392,18 +393,17 @@ func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a non-configured index info.
|
// Construct a non-configured index info.
|
||||||
index := ®istrytypes.IndexInfo{
|
return ®istry.IndexInfo{
|
||||||
Name: indexName,
|
Name: indexName,
|
||||||
Mirrors: make([]string, 0),
|
Mirrors: make([]string, 0),
|
||||||
|
Secure: config.isSecureIndex(indexName),
|
||||||
Official: false,
|
Official: false,
|
||||||
}
|
}, nil
|
||||||
index.Secure = isSecureIndex(config, indexName)
|
|
||||||
return index, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthConfigKey special-cases using the full index address of the official
|
// GetAuthConfigKey special-cases using the full index address of the official
|
||||||
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
||||||
func GetAuthConfigKey(index *registrytypes.IndexInfo) string {
|
func GetAuthConfigKey(index *registry.IndexInfo) string {
|
||||||
if index.Official {
|
if index.Official {
|
||||||
return IndexServer
|
return IndexServer
|
||||||
}
|
}
|
||||||
|
@ -432,7 +432,12 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSearchIndexInfo will use repository name to get back an indexInfo.
|
// ParseSearchIndexInfo will use repository name to get back an indexInfo.
|
||||||
func ParseSearchIndexInfo(reposName string) (*registrytypes.IndexInfo, error) {
|
//
|
||||||
|
// TODO(thaJeztah) this function is only used by the CLI, and used to get
|
||||||
|
// information of the registry (to provide credentials if needed). We should
|
||||||
|
// move this function (or equivalent) to the CLI, as it's doing too much just
|
||||||
|
// for that.
|
||||||
|
func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) {
|
||||||
indexName, _ := splitReposSearchTerm(reposName)
|
indexName, _ := splitReposSearchTerm(reposName)
|
||||||
|
|
||||||
indexInfo, err := newIndexInfo(emptyServiceConfig, indexName)
|
indexInfo, err := newIndexInfo(emptyServiceConfig, indexName)
|
||||||
|
|
|
@ -3,25 +3,10 @@
|
||||||
|
|
||||||
package registry // import "github.com/docker/docker/registry"
|
package registry // import "github.com/docker/docker/registry"
|
||||||
|
|
||||||
import (
|
// defaultCertsDir is the platform-specific default directory where certificates
|
||||||
"path/filepath"
|
// are stored. On Linux, it may be overridden through certsDir, for example, when
|
||||||
|
// running in rootless mode.
|
||||||
"github.com/docker/docker/pkg/homedir"
|
const defaultCertsDir = "/etc/docker/certs.d"
|
||||||
"github.com/docker/docker/rootless"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CertsDir is the directory where certificates are stored
|
|
||||||
func CertsDir() string {
|
|
||||||
d := "/etc/docker/certs.d"
|
|
||||||
|
|
||||||
if rootless.RunningWithRootlessKit() {
|
|
||||||
configHome, err := homedir.GetConfigHome()
|
|
||||||
if err == nil {
|
|
||||||
d = filepath.Join(configHome, "docker/certs.d")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanPath is used to ensure that a directory name is valid on the target
|
// cleanPath is used to ensure that a directory name is valid on the target
|
||||||
// platform. It will be passed in something *similar* to a URL such as
|
// platform. It will be passed in something *similar* to a URL such as
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertsDir is the directory where certificates are stored
|
// defaultCertsDir is the platform-specific default directory where certificates
|
||||||
func CertsDir() string {
|
// are stored. On Linux, it may be overridden through certsDir, for example, when
|
||||||
return os.Getenv("programdata") + `\docker\certs.d`
|
// running in rootless mode.
|
||||||
}
|
var defaultCertsDir = os.Getenv("programdata") + `\docker\certs.d`
|
||||||
|
|
||||||
// cleanPath is used to ensure that a directory name is valid on the target
|
// cleanPath is used to ensure that a directory name is valid on the target
|
||||||
// platform. It will be passed in something *similar* to a URL such as
|
// platform. It will be passed in something *similar* to a URL such as
|
||||||
|
|
|
@ -3,27 +3,39 @@ package registry // import "github.com/docker/docker/registry"
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// V1Endpoint stores basic information about a V1 registry endpoint.
|
// v1PingResult contains the information returned when pinging a registry. It
|
||||||
type V1Endpoint struct {
|
// indicates the registry's version and whether the registry claims to be a
|
||||||
|
// standalone registry.
|
||||||
|
type v1PingResult struct {
|
||||||
|
// Version is the registry version supplied by the registry in an HTTP
|
||||||
|
// header
|
||||||
|
Version string `json:"version"`
|
||||||
|
// Standalone is set to true if the registry indicates it is a
|
||||||
|
// standalone registry in the X-Docker-Registry-Standalone
|
||||||
|
// header
|
||||||
|
Standalone bool `json:"standalone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1Endpoint stores basic information about a V1 registry endpoint.
|
||||||
|
type v1Endpoint struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
IsSecure bool
|
IsSecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewV1Endpoint parses the given address to return a registry endpoint.
|
// newV1Endpoint parses the given address to return a registry endpoint.
|
||||||
// TODO: remove. This is only used by search.
|
// TODO: remove. This is only used by search.
|
||||||
func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
|
func newV1Endpoint(index *registry.IndexInfo, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
|
||||||
tlsConfig, err := newTLSConfig(index.Name, index.Secure)
|
tlsConfig, err := newTLSConfig(index.Name, index.Secure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -42,28 +54,28 @@ func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders
|
||||||
return endpoint, nil
|
return endpoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateEndpoint(endpoint *V1Endpoint) error {
|
func validateEndpoint(endpoint *v1Endpoint) error {
|
||||||
logrus.Debugf("pinging registry endpoint %s", endpoint)
|
logrus.Debugf("pinging registry endpoint %s", endpoint)
|
||||||
|
|
||||||
// Try HTTPS ping to registry
|
// Try HTTPS ping to registry
|
||||||
endpoint.URL.Scheme = "https"
|
endpoint.URL.Scheme = "https"
|
||||||
if _, err := endpoint.Ping(); err != nil {
|
if _, err := endpoint.ping(); err != nil {
|
||||||
if endpoint.IsSecure {
|
if endpoint.IsSecure {
|
||||||
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
|
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
|
||||||
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
|
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
|
||||||
return fmt.Errorf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
|
return invalidParamf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If registry is insecure and HTTPS failed, fallback to HTTP.
|
// If registry is insecure and HTTPS failed, fallback to HTTP.
|
||||||
logrus.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
|
logrus.WithError(err).Debugf("error from registry %q marked as insecure - insecurely falling back to HTTP", endpoint)
|
||||||
endpoint.URL.Scheme = "http"
|
endpoint.URL.Scheme = "http"
|
||||||
|
|
||||||
var err2 error
|
var err2 error
|
||||||
if _, err2 = endpoint.Ping(); err2 == nil {
|
if _, err2 = endpoint.ping(); err2 == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
|
return invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -72,28 +84,23 @@ func validateEndpoint(endpoint *V1Endpoint) error {
|
||||||
// trimV1Address trims the version off the address and returns the
|
// trimV1Address trims the version off the address and returns the
|
||||||
// trimmed address or an error if there is a non-V1 version.
|
// trimmed address or an error if there is a non-V1 version.
|
||||||
func trimV1Address(address string) (string, error) {
|
func trimV1Address(address string) (string, error) {
|
||||||
var (
|
|
||||||
chunks []string
|
|
||||||
apiVersionStr string
|
|
||||||
)
|
|
||||||
|
|
||||||
address = strings.TrimSuffix(address, "/")
|
address = strings.TrimSuffix(address, "/")
|
||||||
chunks = strings.Split(address, "/")
|
chunks := strings.Split(address, "/")
|
||||||
apiVersionStr = chunks[len(chunks)-1]
|
apiVersionStr := chunks[len(chunks)-1]
|
||||||
if apiVersionStr == "v1" {
|
if apiVersionStr == "v1" {
|
||||||
return strings.Join(chunks[:len(chunks)-1], "/"), nil
|
return strings.Join(chunks[:len(chunks)-1], "/"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range apiVersions {
|
for k, v := range apiVersions {
|
||||||
if k != APIVersion1 && apiVersionStr == v {
|
if k != APIVersion1 && apiVersionStr == v {
|
||||||
return "", fmt.Errorf("unsupported V1 version path %s", apiVersionStr)
|
return "", invalidParamf("unsupported V1 version path %s", apiVersionStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
|
func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
|
||||||
if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
|
if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
|
||||||
address = "https://" + address
|
address = "https://" + address
|
||||||
}
|
}
|
||||||
|
@ -105,69 +112,64 @@ func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent strin
|
||||||
|
|
||||||
uri, err := url.Parse(address)
|
uri, err := url.Parse(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, invalidParam(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(tiborvass): make sure a ConnectTimeout transport is used
|
// TODO(tiborvass): make sure a ConnectTimeout transport is used
|
||||||
tr := NewTransport(tlsConfig)
|
tr := newTransport(tlsConfig)
|
||||||
|
|
||||||
return &V1Endpoint{
|
return &v1Endpoint{
|
||||||
IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
|
IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
|
||||||
URL: uri,
|
URL: uri,
|
||||||
client: HTTPClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
|
client: httpClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the formatted URL for the root of this registry Endpoint
|
// Get the formatted URL for the root of this registry Endpoint
|
||||||
func (e *V1Endpoint) String() string {
|
func (e *v1Endpoint) String() string {
|
||||||
return e.URL.String() + "/v1/"
|
return e.URL.String() + "/v1/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns a formatted string for the URL
|
// ping returns a v1PingResult which indicates whether the registry is standalone or not.
|
||||||
// of this endpoint with the given path appended.
|
func (e *v1Endpoint) ping() (v1PingResult, error) {
|
||||||
func (e *V1Endpoint) Path(path string) string {
|
|
||||||
return e.URL.String() + "/v1/" + path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping returns a PingResult which indicates whether the registry is standalone or not.
|
|
||||||
func (e *V1Endpoint) Ping() (PingResult, error) {
|
|
||||||
if e.String() == IndexServer {
|
if e.String() == IndexServer {
|
||||||
// Skip the check, we know this one is valid
|
// Skip the check, we know this one is valid
|
||||||
// (and we never want to fallback to http in case of error)
|
// (and we never want to fallback to http in case of error)
|
||||||
return PingResult{}, nil
|
return v1PingResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
|
logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
|
||||||
req, err := http.NewRequest(http.MethodGet, e.Path("_ping"), nil)
|
pingURL := e.String() + "_ping"
|
||||||
|
req, err := http.NewRequest(http.MethodGet, pingURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PingResult{}, err
|
return v1PingResult{}, invalidParam(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := e.client.Do(req)
|
resp, err := e.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PingResult{}, err
|
return v1PingResult{}, invalidParam(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
jsonString, err := io.ReadAll(resp.Body)
|
jsonString, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PingResult{}, fmt.Errorf("error while reading the http response: %s", err)
|
return v1PingResult{}, invalidParamWrapf(err, "error while reading response from %s", pingURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the header is absent, we assume true for compatibility with earlier
|
// If the header is absent, we assume true for compatibility with earlier
|
||||||
// versions of the registry. default to true
|
// versions of the registry. default to true
|
||||||
info := PingResult{
|
info := v1PingResult{
|
||||||
Standalone: true,
|
Standalone: true,
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(jsonString, &info); err != nil {
|
if err := json.Unmarshal(jsonString, &info); err != nil {
|
||||||
logrus.Debugf("Error unmarshaling the _ping PingResult: %s", err)
|
logrus.WithError(err).Debug("error unmarshaling _ping response")
|
||||||
// don't stop here. Just assume sane defaults
|
// don't stop here. Just assume sane defaults
|
||||||
}
|
}
|
||||||
if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
|
if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
|
||||||
info.Version = hdr
|
info.Version = hdr
|
||||||
}
|
}
|
||||||
logrus.Debugf("PingResult.Version: %q", info.Version)
|
logrus.Debugf("v1PingResult.Version: %q", info.Version)
|
||||||
|
|
||||||
standalone := resp.Header.Get("X-Docker-Registry-Standalone")
|
standalone := resp.Header.Get("X-Docker-Registry-Standalone")
|
||||||
|
|
||||||
|
@ -178,6 +180,6 @@ func (e *V1Endpoint) Ping() (PingResult, error) {
|
||||||
// there is a header set, and it is not "true" or "1", so assume fails
|
// there is a header set, and it is not "true" or "1", so assume fails
|
||||||
info.Standalone = false
|
info.Standalone = false
|
||||||
}
|
}
|
||||||
logrus.Debugf("PingResult.Standalone: %t", info.Standalone)
|
logrus.Debugf("v1PingResult.Standalone: %t", info.Standalone)
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func translateV2AuthError(err error) error {
|
func translateV2AuthError(err error) error {
|
||||||
|
@ -21,3 +22,15 @@ func translateV2AuthError(err error) error {
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invalidParam(err error) error {
|
||||||
|
return errdefs.InvalidParameter(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidParamf(format string, args ...interface{}) error {
|
||||||
|
return errdefs.InvalidParameter(errors.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidParamWrapf(err error, format string, args ...interface{}) error {
|
||||||
|
return errdefs.InvalidParameter(errors.Wrapf(err, format, args...))
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package registry // import "github.com/docker/docker/registry"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -16,15 +15,12 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HostCertsDir returns the config directory for a specific host
|
// HostCertsDir returns the config directory for a specific host.
|
||||||
func HostCertsDir(hostname string) (string, error) {
|
func HostCertsDir(hostname string) string {
|
||||||
certsDir := CertsDir()
|
return filepath.Join(CertsDir(), cleanPath(hostname))
|
||||||
|
|
||||||
hostDir := filepath.Join(certsDir, cleanPath(hostname))
|
|
||||||
|
|
||||||
return hostDir, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newTLSConfig constructs a client TLS configuration based on server defaults
|
||||||
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
|
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
|
||||||
// PreferredServerCipherSuites should have no effect
|
// PreferredServerCipherSuites should have no effect
|
||||||
tlsConfig := tlsconfig.ServerDefault()
|
tlsConfig := tlsconfig.ServerDefault()
|
||||||
|
@ -32,11 +28,7 @@ func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
|
||||||
tlsConfig.InsecureSkipVerify = !isSecure
|
tlsConfig.InsecureSkipVerify = !isSecure
|
||||||
|
|
||||||
if isSecure && CertsDir() != "" {
|
if isSecure && CertsDir() != "" {
|
||||||
hostDir, err := HostCertsDir(hostname)
|
hostDir := HostCertsDir(hostname)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Debugf("hostDir: %s", hostDir)
|
logrus.Debugf("hostDir: %s", hostDir)
|
||||||
if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
|
if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -61,7 +53,7 @@ func hasFile(files []os.DirEntry, name string) bool {
|
||||||
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
||||||
fs, err := os.ReadDir(directory)
|
fs, err := os.ReadDir(directory)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return invalidParam(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range fs {
|
for _, f := range fs {
|
||||||
|
@ -69,7 +61,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
||||||
if tlsConfig.RootCAs == nil {
|
if tlsConfig.RootCAs == nil {
|
||||||
systemPool, err := tlsconfig.SystemCertPool()
|
systemPool, err := tlsconfig.SystemCertPool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to get system cert pool: %v", err)
|
return invalidParamWrapf(err, "unable to get system cert pool")
|
||||||
}
|
}
|
||||||
tlsConfig.RootCAs = systemPool
|
tlsConfig.RootCAs = systemPool
|
||||||
}
|
}
|
||||||
|
@ -85,7 +77,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
||||||
keyName := certName[:len(certName)-5] + ".key"
|
keyName := certName[:len(certName)-5] + ".key"
|
||||||
logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
|
logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
|
||||||
if !hasFile(fs, keyName) {
|
if !hasFile(fs, keyName) {
|
||||||
return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
|
return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
|
||||||
}
|
}
|
||||||
cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
|
cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -98,7 +90,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
||||||
certName := keyName[:len(keyName)-4] + ".cert"
|
certName := keyName[:len(keyName)-4] + ".cert"
|
||||||
logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
|
logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
|
||||||
if !hasFile(fs, certName) {
|
if !hasFile(fs, certName) {
|
||||||
return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName)
|
return invalidParamf("missing client certificate %s for key %s", certName, keyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,9 +112,9 @@ func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModif
|
||||||
return modifiers
|
return modifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPClient returns an HTTP client structure which uses the given transport
|
// httpClient returns an HTTP client structure which uses the given transport
|
||||||
// and contains the necessary headers for redirected requests
|
// and contains the necessary headers for redirected requests
|
||||||
func HTTPClient(transport http.RoundTripper) *http.Client {
|
func httpClient(transport http.RoundTripper) *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
CheckRedirect: addRequiredHeadersToRedirectedRequests,
|
CheckRedirect: addRequiredHeadersToRedirectedRequests,
|
||||||
|
@ -165,9 +157,9 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
|
// newTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
|
||||||
// default TLS configuration.
|
// default TLS configuration.
|
||||||
func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
func newTransport(tlsConfig *tls.Config) *http.Transport {
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
tlsConfig = tlsconfig.ServerDefault()
|
tlsConfig = tlsconfig.ServerDefault()
|
||||||
}
|
}
|
||||||
|
@ -177,7 +169,7 @@ func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
||||||
KeepAlive: 30 * time.Second,
|
KeepAlive: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
base := &http.Transport{
|
return &http.Transport{
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
DialContext: direct.DialContext,
|
DialContext: direct.DialContext,
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
@ -185,6 +177,4 @@ func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
||||||
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
|
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return base
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,102 +11,74 @@ import (
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultSearchLimit is the default value for maximum number of returned search results.
|
|
||||||
DefaultSearchLimit = 25
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service is the interface defining what a registry service should implement.
|
// Service is the interface defining what a registry service should implement.
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
|
Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
|
||||||
LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
||||||
LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
||||||
ResolveRepository(name reference.Named) (*RepositoryInfo, error)
|
ResolveRepository(name reference.Named) (*RepositoryInfo, error)
|
||||||
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
|
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error)
|
||||||
ServiceConfig() *registrytypes.ServiceConfig
|
ServiceConfig() *registry.ServiceConfig
|
||||||
TLSConfig(hostname string) (*tls.Config, error)
|
|
||||||
LoadAllowNondistributableArtifacts([]string) error
|
LoadAllowNondistributableArtifacts([]string) error
|
||||||
LoadMirrors([]string) error
|
LoadMirrors([]string) error
|
||||||
LoadInsecureRegistries([]string) error
|
LoadInsecureRegistries([]string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultService is a registry service. It tracks configuration data such as a list
|
// defaultService is a registry service. It tracks configuration data such as a list
|
||||||
// of mirrors.
|
// of mirrors.
|
||||||
type DefaultService struct {
|
type defaultService struct {
|
||||||
config *serviceConfig
|
config *serviceConfig
|
||||||
mu sync.Mutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new instance of DefaultService ready to be
|
// NewService returns a new instance of defaultService ready to be
|
||||||
// installed into an engine.
|
// installed into an engine.
|
||||||
func NewService(options ServiceOptions) (*DefaultService, error) {
|
func NewService(options ServiceOptions) (Service, error) {
|
||||||
config, err := newServiceConfig(options)
|
config, err := newServiceConfig(options)
|
||||||
|
|
||||||
return &DefaultService{config: config}, err
|
return &defaultService{config: config}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceConfig returns the public registry service configuration.
|
// ServiceConfig returns a copy of the public registry service's configuration.
|
||||||
func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
|
func (s *defaultService) ServiceConfig() *registry.ServiceConfig {
|
||||||
s.mu.Lock()
|
s.mu.RLock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.RUnlock()
|
||||||
|
return s.config.copy()
|
||||||
servConfig := registrytypes.ServiceConfig{
|
|
||||||
AllowNondistributableArtifactsCIDRs: make([]*(registrytypes.NetIPNet), 0),
|
|
||||||
AllowNondistributableArtifactsHostnames: make([]string, 0),
|
|
||||||
InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
|
|
||||||
IndexConfigs: make(map[string]*(registrytypes.IndexInfo)),
|
|
||||||
Mirrors: make([]string, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct a new ServiceConfig which will not retrieve s.Config directly,
|
|
||||||
// and look up items in s.config with mu locked
|
|
||||||
servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...)
|
|
||||||
servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...)
|
|
||||||
servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
|
|
||||||
|
|
||||||
for key, value := range s.config.ServiceConfig.IndexConfigs {
|
|
||||||
servConfig.IndexConfigs[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
|
|
||||||
|
|
||||||
return &servConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
|
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
|
||||||
func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error {
|
func (s *defaultService) LoadAllowNondistributableArtifacts(registries []string) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
return s.config.LoadAllowNondistributableArtifacts(registries)
|
return s.config.loadAllowNondistributableArtifacts(registries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadMirrors loads registry mirrors for Service
|
// LoadMirrors loads registry mirrors for Service
|
||||||
func (s *DefaultService) LoadMirrors(mirrors []string) error {
|
func (s *defaultService) LoadMirrors(mirrors []string) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
return s.config.LoadMirrors(mirrors)
|
return s.config.loadMirrors(mirrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadInsecureRegistries loads insecure registries for Service
|
// LoadInsecureRegistries loads insecure registries for Service
|
||||||
func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
|
func (s *defaultService) LoadInsecureRegistries(registries []string) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
return s.config.LoadInsecureRegistries(registries)
|
return s.config.loadInsecureRegistries(registries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth contacts the public registry with the provided credentials,
|
// Auth contacts the public registry with the provided credentials,
|
||||||
// and returns OK if authentication was successful.
|
// and returns OK if authentication was successful.
|
||||||
// It can be used to verify the validity of a client's credentials.
|
// It can be used to verify the validity of a client's credentials.
|
||||||
func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
|
func (s *defaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
|
||||||
// TODO Use ctx when searching for repositories
|
// TODO Use ctx when searching for repositories
|
||||||
var registryHostName = IndexHostname
|
var registryHostName = IndexHostname
|
||||||
|
|
||||||
|
@ -117,7 +89,7 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
|
||||||
}
|
}
|
||||||
u, err := url.Parse(serverAddress)
|
u, err := url.Parse(serverAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errdefs.InvalidParameter(errors.Errorf("unable to parse server address: %v", err))
|
return "", "", invalidParamWrapf(err, "unable to parse server address")
|
||||||
}
|
}
|
||||||
registryHostName = u.Host
|
registryHostName = u.Host
|
||||||
}
|
}
|
||||||
|
@ -127,7 +99,7 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
|
||||||
// to a mirror.
|
// to a mirror.
|
||||||
endpoints, err := s.LookupPushEndpoints(registryHostName)
|
endpoints, err := s.LookupPushEndpoints(registryHostName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errdefs.InvalidParameter(err)
|
return "", "", invalidParam(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
|
@ -159,25 +131,28 @@ func splitReposSearchTerm(reposName string) (string, string) {
|
||||||
|
|
||||||
// Search queries the public registry for images matching the specified
|
// Search queries the public registry for images matching the specified
|
||||||
// search terms, and returns the results.
|
// search terms, and returns the results.
|
||||||
func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
func (s *defaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error) {
|
||||||
// TODO Use ctx when searching for repositories
|
// TODO Use ctx when searching for repositories
|
||||||
if err := validateNoScheme(term); err != nil {
|
if hasScheme(term) {
|
||||||
return nil, err
|
return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
|
||||||
}
|
}
|
||||||
|
|
||||||
indexName, remoteName := splitReposSearchTerm(term)
|
indexName, remoteName := splitReposSearchTerm(term)
|
||||||
|
|
||||||
// Search is a long-running operation, just lock s.config to avoid block others.
|
// Search is a long-running operation, just lock s.config to avoid block others.
|
||||||
s.mu.Lock()
|
s.mu.RLock()
|
||||||
index, err := newIndexInfo(s.config, indexName)
|
index, err := newIndexInfo(s.config, indexName)
|
||||||
s.mu.Unlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if index.Official {
|
||||||
|
// If pull "library/foo", it's stored locally under "foo"
|
||||||
|
remoteName = strings.TrimPrefix(remoteName, "library/")
|
||||||
|
}
|
||||||
|
|
||||||
// *TODO: Search multiple indexes.
|
endpoint, err := newV1Endpoint(index, userAgent, headers)
|
||||||
endpoint, err := NewV1Endpoint(index, userAgent, headers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -195,12 +170,8 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
|
||||||
modifiers := Headers(userAgent, nil)
|
modifiers := Headers(userAgent, nil)
|
||||||
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
|
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if fErr, ok := err.(fallbackError); ok {
|
|
||||||
logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
|
|
||||||
} else {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Copy non transport http client features
|
// Copy non transport http client features
|
||||||
v2Client.Timeout = endpoint.client.Timeout
|
v2Client.Timeout = endpoint.client.Timeout
|
||||||
v2Client.CheckRedirect = endpoint.client.CheckRedirect
|
v2Client.CheckRedirect = endpoint.client.CheckRedirect
|
||||||
|
@ -208,30 +179,21 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
|
||||||
|
|
||||||
logrus.Debugf("using v2 client for search to %s", endpoint.URL)
|
logrus.Debugf("using v2 client for search to %s", endpoint.URL)
|
||||||
client = v2Client
|
client = v2Client
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
if client == nil {
|
|
||||||
client = endpoint.client
|
client = endpoint.client
|
||||||
if err := authorizeClient(client, authConfig, endpoint); err != nil {
|
if err := authorizeClient(client, authConfig, endpoint); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r := newSession(client, authConfig, endpoint)
|
return newSession(client, endpoint).searchRepositories(remoteName, limit)
|
||||||
|
|
||||||
if index.Official {
|
|
||||||
// If pull "library/foo", it's stored locally under "foo"
|
|
||||||
remoteName = strings.TrimPrefix(remoteName, "library/")
|
|
||||||
}
|
|
||||||
return r.SearchRepositories(remoteName, limit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveRepository splits a repository name into its components
|
// ResolveRepository splits a repository name into its components
|
||||||
// and configuration of the associated registry.
|
// and configuration of the associated registry.
|
||||||
func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
func (s *defaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
||||||
s.mu.Lock()
|
s.mu.RLock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.RUnlock()
|
||||||
return newRepositoryInfo(s.config, name)
|
return newRepositoryInfo(s.config, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,33 +208,20 @@ type APIEndpoint struct {
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSConfig constructs a client TLS configuration based on server defaults
|
|
||||||
func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
return s.tlsConfig(hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tlsConfig constructs a client TLS configuration based on server defaults
|
|
||||||
func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) {
|
|
||||||
return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
|
// LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
|
||||||
// It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
|
// It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
|
||||||
func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||||
s.mu.Lock()
|
s.mu.RLock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
return s.lookupV2Endpoints(hostname)
|
return s.lookupV2Endpoints(hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
|
// LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
|
||||||
// It gives preference to HTTPS over plain HTTP. Mirrors are not included.
|
// It gives preference to HTTPS over plain HTTP. Mirrors are not included.
|
||||||
func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||||
s.mu.Lock()
|
s.mu.RLock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
allEndpoints, err := s.lookupV2Endpoints(hostname)
|
allEndpoints, err := s.lookupV2Endpoints(hostname)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -7,8 +7,7 @@ import (
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
func (s *defaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||||
tlsConfig := tlsconfig.ServerDefault()
|
|
||||||
if hostname == DefaultNamespace || hostname == IndexHostname {
|
if hostname == DefaultNamespace || hostname == IndexHostname {
|
||||||
for _, mirror := range s.config.Mirrors {
|
for _, mirror := range s.config.Mirrors {
|
||||||
if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
|
if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
|
||||||
|
@ -16,9 +15,9 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
||||||
}
|
}
|
||||||
mirrorURL, err := url.Parse(mirror)
|
mirrorURL, err := url.Parse(mirror)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, invalidParam(err)
|
||||||
}
|
}
|
||||||
mirrorTLSConfig, err := s.tlsConfig(mirrorURL.Host)
|
mirrorTLSConfig, err := newTLSConfig(mirrorURL.Host, s.config.isSecureIndex(mirrorURL.Host))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -35,19 +34,18 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
||||||
Version: APIVersion2,
|
Version: APIVersion2,
|
||||||
Official: true,
|
Official: true,
|
||||||
TrimHostname: true,
|
TrimHostname: true,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsconfig.ServerDefault(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return endpoints, nil
|
return endpoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ana := allowNondistributableArtifacts(s.config, hostname)
|
tlsConfig, err := newTLSConfig(hostname, s.config.isSecureIndex(hostname))
|
||||||
|
|
||||||
tlsConfig, err = s.tlsConfig(hostname)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ana := s.config.allowNondistributableArtifacts(hostname)
|
||||||
endpoints = []APIEndpoint{
|
endpoints = []APIEndpoint{
|
||||||
{
|
{
|
||||||
URL: &url.URL{
|
URL: &url.URL{
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
@ -21,12 +21,10 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Session is used to communicate with a V1 registry
|
// A session is used to communicate with a V1 registry
|
||||||
type Session struct {
|
type session struct {
|
||||||
indexEndpoint *V1Endpoint
|
indexEndpoint *v1Endpoint
|
||||||
client *http.Client
|
client *http.Client
|
||||||
// TODO(tiborvass): remove authConfig
|
|
||||||
authConfig *types.AuthConfig
|
|
||||||
id string
|
id string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +39,7 @@ type authTransport struct {
|
||||||
modReq map[*http.Request]*http.Request // original -> modified
|
modReq map[*http.Request]*http.Request // original -> modified
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthTransport handles the auth layer when communicating with a v1 registry (private or official)
|
// newAuthTransport handles the auth layer when communicating with a v1 registry (private or official)
|
||||||
//
|
//
|
||||||
// For private v1 registries, set alwaysSetBasicAuth to true.
|
// For private v1 registries, set alwaysSetBasicAuth to true.
|
||||||
//
|
//
|
||||||
|
@ -54,7 +52,7 @@ type authTransport struct {
|
||||||
// If the server sends a token without the client having requested it, it is ignored.
|
// If the server sends a token without the client having requested it, it is ignored.
|
||||||
//
|
//
|
||||||
// This RoundTripper also has a CancelRequest method important for correct timeout handling.
|
// This RoundTripper also has a CancelRequest method important for correct timeout handling.
|
||||||
func AuthTransport(base http.RoundTripper, authConfig *types.AuthConfig, alwaysSetBasicAuth bool) http.RoundTripper {
|
func newAuthTransport(base http.RoundTripper, authConfig *types.AuthConfig, alwaysSetBasicAuth bool) *authTransport {
|
||||||
if base == nil {
|
if base == nil {
|
||||||
base = http.DefaultTransport
|
base = http.DefaultTransport
|
||||||
}
|
}
|
||||||
|
@ -149,13 +147,13 @@ func (tr *authTransport) CancelRequest(req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) error {
|
func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *v1Endpoint) error {
|
||||||
var alwaysSetBasicAuth bool
|
var alwaysSetBasicAuth bool
|
||||||
|
|
||||||
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
||||||
// alongside all our requests.
|
// alongside all our requests.
|
||||||
if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" {
|
if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" {
|
||||||
info, err := endpoint.Ping()
|
info, err := endpoint.ping()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -167,47 +165,42 @@ func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint
|
||||||
|
|
||||||
// Annotate the transport unconditionally so that v2 can
|
// Annotate the transport unconditionally so that v2 can
|
||||||
// properly fallback on v1 when an image is not found.
|
// properly fallback on v1 when an image is not found.
|
||||||
client.Transport = AuthTransport(client.Transport, authConfig, alwaysSetBasicAuth)
|
client.Transport = newAuthTransport(client.Transport, authConfig, alwaysSetBasicAuth)
|
||||||
|
|
||||||
jar, err := cookiejar.New(nil)
|
jar, err := cookiejar.New(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("cookiejar.New is not supposed to return an error")
|
return errdefs.System(errors.New("cookiejar.New is not supposed to return an error"))
|
||||||
}
|
}
|
||||||
client.Jar = jar
|
client.Jar = jar
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) *Session {
|
func newSession(client *http.Client, endpoint *v1Endpoint) *session {
|
||||||
return &Session{
|
return &session{
|
||||||
authConfig: authConfig,
|
|
||||||
client: client,
|
client: client,
|
||||||
indexEndpoint: endpoint,
|
indexEndpoint: endpoint,
|
||||||
id: stringid.GenerateRandomID(),
|
id: stringid.GenerateRandomID(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSession creates a new session
|
// defaultSearchLimit is the default value for maximum number of returned search results.
|
||||||
// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
|
const defaultSearchLimit = 25
|
||||||
func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) (*Session, error) {
|
|
||||||
if err := authorizeClient(client, authConfig, endpoint); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newSession(client, authConfig, endpoint), nil
|
// searchRepositories performs a search against the remote repository
|
||||||
|
func (r *session) searchRepositories(term string, limit int) (*registry.SearchResults, error) {
|
||||||
|
if limit == 0 {
|
||||||
|
limit = defaultSearchLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchRepositories performs a search against the remote repository
|
|
||||||
func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) {
|
|
||||||
if limit < 1 || limit > 100 {
|
if limit < 1 || limit > 100 {
|
||||||
return nil, errdefs.InvalidParameter(errors.Errorf("Limit %d is outside the range of [1, 100]", limit))
|
return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit)
|
||||||
}
|
}
|
||||||
logrus.Debugf("Index server: %s", r.indexEndpoint)
|
logrus.Debugf("Index server: %s", r.indexEndpoint)
|
||||||
u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit))
|
u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, u, nil)
|
req, err := http.NewRequest(http.MethodGet, u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(errdefs.InvalidParameter(err), "Error building request")
|
return nil, invalidParamWrapf(err, "error building request")
|
||||||
}
|
}
|
||||||
// Have the AuthTransport send authentication, when logged in.
|
// Have the AuthTransport send authentication, when logged in.
|
||||||
req.Header.Set("X-Docker-Token", "true")
|
req.Header.Set("X-Docker-Token", "true")
|
||||||
|
@ -222,6 +215,6 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea
|
||||||
Code: res.StatusCode,
|
Code: res.StatusCode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result := new(registrytypes.SearchResults)
|
result := new(registry.SearchResults)
|
||||||
return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")
|
return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,39 +2,9 @@ package registry // import "github.com/docker/docker/registry"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepositoryData tracks the image list, list of endpoints for a repository
|
|
||||||
type RepositoryData struct {
|
|
||||||
// ImgList is a list of images in the repository
|
|
||||||
ImgList map[string]*ImgData
|
|
||||||
// Endpoints is a list of endpoints returned in X-Docker-Endpoints
|
|
||||||
Endpoints []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImgData is used to transfer image checksums to and from the registry
|
|
||||||
type ImgData struct {
|
|
||||||
// ID is an opaque string that identifies the image
|
|
||||||
ID string `json:"id"`
|
|
||||||
Checksum string `json:"checksum,omitempty"`
|
|
||||||
ChecksumPayload string `json:"-"`
|
|
||||||
Tag string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PingResult contains the information returned when pinging a registry. It
|
|
||||||
// indicates the registry's version and whether the registry claims to be a
|
|
||||||
// standalone registry.
|
|
||||||
type PingResult struct {
|
|
||||||
// Version is the registry version supplied by the registry in an HTTP
|
|
||||||
// header
|
|
||||||
Version string `json:"version"`
|
|
||||||
// Standalone is set to true if the registry indicates it is a
|
|
||||||
// standalone registry in the X-Docker-Registry-Standalone
|
|
||||||
// header
|
|
||||||
Standalone bool `json:"standalone"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIVersion is an integral representation of an API version (presently
|
// APIVersion is an integral representation of an API version (presently
|
||||||
// either 1 or 2)
|
// either 1 or 2)
|
||||||
type APIVersion int
|
type APIVersion int
|
||||||
|
@ -58,7 +28,7 @@ var apiVersions = map[APIVersion]string{
|
||||||
type RepositoryInfo struct {
|
type RepositoryInfo struct {
|
||||||
Name reference.Named
|
Name reference.Named
|
||||||
// Index points to registry information
|
// Index points to registry information
|
||||||
Index *registrytypes.IndexInfo
|
Index *registry.IndexInfo
|
||||||
// Official indicates whether the repository is considered official.
|
// Official indicates whether the repository is considered official.
|
||||||
// If the registry is official, and the normalized name does not
|
// If the registry is official, and the normalized name does not
|
||||||
// contain a '/' (e.g. "foo"), then it is considered an official repo.
|
// contain a '/' (e.g. "foo"), then it is considered an official repo.
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
package rootless // import "github.com/docker/docker/rootless"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// RootlessKitDockerProxyBinary is the binary name of rootlesskit-docker-proxy
|
|
||||||
RootlessKitDockerProxyBinary = "rootlesskit-docker-proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
runningWithRootlessKit bool
|
|
||||||
runningWithRootlessKitOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// RunningWithRootlessKit returns true if running under RootlessKit namespaces.
|
|
||||||
func RunningWithRootlessKit() bool {
|
|
||||||
runningWithRootlessKitOnce.Do(func() {
|
|
||||||
u := os.Getenv("ROOTLESSKIT_STATE_DIR")
|
|
||||||
runningWithRootlessKit = u != ""
|
|
||||||
})
|
|
||||||
return runningWithRootlessKit
|
|
||||||
}
|
|
|
@ -39,7 +39,7 @@ github.com/docker/distribution/registry/client/transport
|
||||||
github.com/docker/distribution/registry/storage/cache
|
github.com/docker/distribution/registry/storage/cache
|
||||||
github.com/docker/distribution/registry/storage/cache/memory
|
github.com/docker/distribution/registry/storage/cache/memory
|
||||||
github.com/docker/distribution/uuid
|
github.com/docker/distribution/uuid
|
||||||
# github.com/docker/docker v20.10.7+incompatible => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
|
# github.com/docker/docker v20.10.14+incompatible => github.com/docker/docker v20.10.3-0.20220326171151-8941dcfcc5db+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
github.com/docker/docker/api
|
github.com/docker/docker/api
|
||||||
github.com/docker/docker/api/types
|
github.com/docker/docker/api/types
|
||||||
|
@ -75,7 +75,6 @@ github.com/docker/docker/pkg/stringid
|
||||||
github.com/docker/docker/pkg/system
|
github.com/docker/docker/pkg/system
|
||||||
github.com/docker/docker/pkg/urlutil
|
github.com/docker/docker/pkg/urlutil
|
||||||
github.com/docker/docker/registry
|
github.com/docker/docker/registry
|
||||||
github.com/docker/docker/rootless
|
|
||||||
# github.com/docker/docker-credential-helpers v0.6.4
|
# github.com/docker/docker-credential-helpers v0.6.4
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/docker/docker-credential-helpers/client
|
github.com/docker/docker-credential-helpers/client
|
||||||
|
@ -385,5 +384,5 @@ gotest.tools/v3/internal/format
|
||||||
gotest.tools/v3/internal/source
|
gotest.tools/v3/internal/source
|
||||||
gotest.tools/v3/poll
|
gotest.tools/v3/poll
|
||||||
gotest.tools/v3/skip
|
gotest.tools/v3/skip
|
||||||
# github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
|
# github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220326171151-8941dcfcc5db+incompatible
|
||||||
# github.com/gogo/googleapis => github.com/gogo/googleapis v1.3.2
|
# github.com/gogo/googleapis => github.com/gogo/googleapis v1.3.2
|
||||||
|
|
Loading…
Reference in New Issue