Merge pull request #43298 from thaJeztah/cleanup_registry
registry: remove v1 leftovers, and refactor to reduce public api/interface
This commit is contained in:
commit
7cba4ffa30
16 changed files with 357 additions and 768 deletions
|
@ -183,10 +183,8 @@ func (daemon *Daemon) RegistryHosts() docker.RegistryHosts {
|
|||
}
|
||||
|
||||
for k, v := range m {
|
||||
if d, err := registry.HostCertsDir(k); err == nil {
|
||||
v.TLSConfigDir = []string{d}
|
||||
m[k] = v
|
||||
}
|
||||
v.TLSConfigDir = []string{registry.HostCertsDir(k)}
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
certsDir := registry.CertsDir()
|
||||
|
|
|
@ -11,16 +11,15 @@ import (
|
|||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
type FakeService struct {
|
||||
registry.DefaultService
|
||||
|
||||
type fakeService struct {
|
||||
registry.Service
|
||||
shouldReturnError bool
|
||||
|
||||
term string
|
||||
results []registrytypes.SearchResult
|
||||
}
|
||||
|
||||
func (s *FakeService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||
func (s *fakeService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||
if s.shouldReturnError {
|
||||
return nil, errors.New("Search unknown error")
|
||||
}
|
||||
|
@ -76,7 +75,7 @@ func TestSearchRegistryForImagesErrors(t *testing.T) {
|
|||
}
|
||||
for index, e := range errorCases {
|
||||
daemon := &ImageService{
|
||||
registryService: &FakeService{
|
||||
registryService: &fakeService{
|
||||
shouldReturnError: e.shouldReturnError,
|
||||
},
|
||||
}
|
||||
|
@ -322,7 +321,7 @@ func TestSearchRegistryForImages(t *testing.T) {
|
|||
}
|
||||
for index, s := range successCases {
|
||||
daemon := &ImageService{
|
||||
registryService: &FakeService{
|
||||
registryService: &fakeService{
|
||||
term: term,
|
||||
results: s.registryResults,
|
||||
},
|
||||
|
|
|
@ -10,15 +10,13 @@ import (
|
|||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"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/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// AuthClientID is used the ClientID used for the token server
|
||||
AuthClientID = "docker"
|
||||
)
|
||||
// AuthClientID is used the ClientID used for the token server
|
||||
const AuthClientID = "docker"
|
||||
|
||||
type loginCredentialStore struct {
|
||||
authConfig *types.AuthConfig
|
||||
|
@ -80,7 +78,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
|
|||
var (
|
||||
endpointStr = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
||||
modifiers = Headers(userAgent, nil)
|
||||
authTransport = transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
|
||||
authTransport = transport.NewTransport(newTransport(endpoint.TLSConfig), modifiers...)
|
||||
credentialAuthConfig = *authConfig
|
||||
creds = loginCredentialStore{authConfig: &credentialAuthConfig}
|
||||
)
|
||||
|
@ -109,8 +107,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
|
|||
}
|
||||
|
||||
// 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 "", "", err
|
||||
return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
|
||||
func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
|
||||
|
@ -129,10 +126,9 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi
|
|||
tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
|
||||
basicHandler := auth.NewBasicHandler(creds)
|
||||
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
|
||||
tr := transport.NewTransport(authTransport, modifiers...)
|
||||
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
Transport: transport.NewTransport(authTransport, modifiers...),
|
||||
Timeout: 15 * time.Second,
|
||||
}, nil
|
||||
}
|
||||
|
@ -146,14 +142,11 @@ func ConvertToHostname(url string) string {
|
|||
} else if strings.HasPrefix(url, "https://") {
|
||||
stripped = strings.TrimPrefix(url, "https://")
|
||||
}
|
||||
|
||||
nameParts := strings.SplitN(stripped, "/", 2)
|
||||
|
||||
return nameParts[0]
|
||||
return strings.SplitN(stripped, "/", 2)[0]
|
||||
}
|
||||
|
||||
// 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)
|
||||
// First try the happy case
|
||||
if c, found := authConfigs[configKey]; found || index.Official {
|
||||
|
|
|
@ -4,14 +4,15 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func buildAuthConfigs() map[string]types.AuthConfig {
|
||||
authConfigs := map[string]types.AuthConfig{}
|
||||
|
||||
for _, registry := range []string{"testIndex", IndexServer} {
|
||||
authConfigs[registry] = types.AuthConfig{
|
||||
for _, reg := range []string{"testIndex", IndexServer} {
|
||||
authConfigs[reg] = types.AuthConfig{
|
||||
Username: "docker-user",
|
||||
Password: "docker-pass",
|
||||
}
|
||||
|
@ -24,18 +25,18 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
|
|||
authConfigs := buildAuthConfigs()
|
||||
indexConfig := authConfigs[IndexServer]
|
||||
|
||||
officialIndex := ®istrytypes.IndexInfo{
|
||||
officialIndex := ®istry.IndexInfo{
|
||||
Official: true,
|
||||
}
|
||||
privateIndex := ®istrytypes.IndexInfo{
|
||||
privateIndex := ®istry.IndexInfo{
|
||||
Official: false,
|
||||
}
|
||||
|
||||
resolved := ResolveAuthConfig(authConfigs, officialIndex)
|
||||
assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer")
|
||||
assert.Equal(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer")
|
||||
|
||||
resolved = ResolveAuthConfig(authConfigs, privateIndex)
|
||||
assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServer")
|
||||
assert.Check(t, resolved != indexConfig, "Expected ResolveAuthConfig to not return IndexServer")
|
||||
}
|
||||
|
||||
func TestResolveAuthConfigFullURL(t *testing.T) {
|
||||
|
@ -87,19 +88,19 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
|||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
index := ®istrytypes.IndexInfo{
|
||||
index := ®istry.IndexInfo{
|
||||
Name: configKey,
|
||||
}
|
||||
for _, registry := range registries {
|
||||
authConfigs[registry] = configured
|
||||
for _, reg := range registries {
|
||||
authConfigs[reg] = configured
|
||||
resolved := ResolveAuthConfig(authConfigs, index)
|
||||
if resolved.Username != configured.Username || resolved.Password != configured.Password {
|
||||
t.Errorf("%s -> %v != %v\n", registry, resolved, configured)
|
||||
t.Errorf("%s -> %v != %v\n", reg, resolved, configured)
|
||||
}
|
||||
delete(authConfigs, registry)
|
||||
delete(authConfigs, reg)
|
||||
resolved = ResolveAuthConfig(authConfigs, index)
|
||||
if resolved.Username == configured.Username || resolved.Password == configured.Password {
|
||||
t.Errorf("%s -> %v == %v\n", registry, resolved, configured)
|
||||
t.Errorf("%s -> %v == %v\n", reg, resolved, configured)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package registry // import "github.com/docker/docker/registry"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
@ -9,8 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -22,9 +20,7 @@ type ServiceOptions struct {
|
|||
}
|
||||
|
||||
// serviceConfig holds daemon configuration for the registry service.
|
||||
type serviceConfig struct {
|
||||
registrytypes.ServiceConfig
|
||||
}
|
||||
type serviceConfig registry.ServiceConfig
|
||||
|
||||
// TODO(thaJeztah) both the "index.docker.io" and "registry-1.docker.io" domains
|
||||
// are here for historic reasons and backward-compatibility. These domains
|
||||
|
@ -58,10 +54,6 @@ var (
|
|||
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{})
|
||||
validHostPortRegex = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
|
||||
|
||||
|
@ -71,57 +63,65 @@ var (
|
|||
|
||||
// newServiceConfig returns a new instance of ServiceConfig
|
||||
func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
|
||||
config := &serviceConfig{
|
||||
ServiceConfig: registrytypes.ServiceConfig{
|
||||
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 {
|
||||
config := &serviceConfig{}
|
||||
if err := config.loadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.LoadMirrors(options.Mirrors); err != nil {
|
||||
if err := config.loadMirrors(options.Mirrors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.LoadInsecureRegistries(options.InsecureRegistries); err != nil {
|
||||
if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
|
||||
func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
|
||||
cidrs := map[string]*registrytypes.NetIPNet{}
|
||||
// copy constructs a new ServiceConfig with a copy of the configuration in config.
|
||||
func (config *serviceConfig) copy() *registry.ServiceConfig {
|
||||
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{}
|
||||
|
||||
for _, r := range registries {
|
||||
if _, err := ValidateIndexName(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if validateNoScheme(r) != nil {
|
||||
return fmt.Errorf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
|
||||
if hasScheme(r) {
|
||||
return invalidParamf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
|
||||
}
|
||||
|
||||
if _, ipnet, err := net.ParseCIDR(r); err == nil {
|
||||
// Valid CIDR.
|
||||
cidrs[ipnet.String()] = (*registrytypes.NetIPNet)(ipnet)
|
||||
} else if err := validateHostPort(r); err == nil {
|
||||
cidrs[ipnet.String()] = (*registry.NetIPNet)(ipnet)
|
||||
} else if err = validateHostPort(r); err == nil {
|
||||
// Must be `host:port` if not CIDR.
|
||||
hostnames[r] = true
|
||||
} 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 {
|
||||
config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
|
||||
}
|
||||
|
||||
config.AllowNondistributableArtifactsHostnames = make([]string, 0)
|
||||
config.AllowNondistributableArtifactsHostnames = make([]string, 0, len(hostnames))
|
||||
for h := range hostnames {
|
||||
config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
|
||||
}
|
||||
|
@ -129,9 +129,9 @@ func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []str
|
|||
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.
|
||||
func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
||||
func (config *serviceConfig) loadMirrors(mirrors []string) error {
|
||||
mMap := map[string]struct{}{}
|
||||
unique := []string{}
|
||||
|
||||
|
@ -149,40 +149,33 @@ func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
|||
config.Mirrors = unique
|
||||
|
||||
// Configure public registry since mirrors may have changed.
|
||||
config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{
|
||||
Name: IndexName,
|
||||
Mirrors: config.Mirrors,
|
||||
Secure: true,
|
||||
Official: true,
|
||||
config.IndexConfigs = map[string]*registry.IndexInfo{
|
||||
IndexName: {
|
||||
Name: IndexName,
|
||||
Mirrors: unique,
|
||||
Secure: true,
|
||||
Official: true,
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadInsecureRegistries loads insecure registries to config
|
||||
func (config *serviceConfig) LoadInsecureRegistries(registries []string) error {
|
||||
// Localhost is by default considered as an insecure registry
|
||||
// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
|
||||
//
|
||||
// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
|
||||
// daemon flags on boot2docker?
|
||||
// loadInsecureRegistries loads insecure registries to config
|
||||
func (config *serviceConfig) loadInsecureRegistries(registries []string) error {
|
||||
// Localhost is by default considered as an insecure registry. This is a
|
||||
// stop-gap for people who are running a private registry on localhost.
|
||||
registries = append(registries, "127.0.0.0/8")
|
||||
|
||||
// Store original InsecureRegistryCIDRs and IndexConfigs
|
||||
// Clean InsecureRegistryCIDRs and IndexConfigs in config, as passed registries has all insecure registry info.
|
||||
originalCIDRs := config.ServiceConfig.InsecureRegistryCIDRs
|
||||
originalIndexInfos := config.ServiceConfig.IndexConfigs
|
||||
|
||||
config.ServiceConfig.InsecureRegistryCIDRs = make([]*registrytypes.NetIPNet, 0)
|
||||
config.ServiceConfig.IndexConfigs = make(map[string]*registrytypes.IndexInfo)
|
||||
var (
|
||||
insecureRegistryCIDRs = make([]*registry.NetIPNet, 0)
|
||||
indexConfigs = make(map[string]*registry.IndexInfo)
|
||||
)
|
||||
|
||||
skip:
|
||||
for _, r := range registries {
|
||||
// validate insecure registry
|
||||
if _, err := ValidateIndexName(r); err != nil {
|
||||
// before returning err, roll back to original data
|
||||
config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
|
||||
config.ServiceConfig.IndexConfigs = originalIndexInfos
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(r), "http://") {
|
||||
|
@ -191,35 +184,27 @@ skip:
|
|||
} 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)
|
||||
r = r[8:]
|
||||
} else if validateNoScheme(r) != nil {
|
||||
// Insecure registry should not contain '://'
|
||||
// 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)
|
||||
} else if hasScheme(r) {
|
||||
return invalidParamf("insecure registry %s should not contain '://'", r)
|
||||
}
|
||||
// Check if CIDR was passed to --insecure-registry
|
||||
_, ipnet, err := net.ParseCIDR(r)
|
||||
if err == nil {
|
||||
// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
|
||||
data := (*registrytypes.NetIPNet)(ipnet)
|
||||
for _, value := range config.InsecureRegistryCIDRs {
|
||||
data := (*registry.NetIPNet)(ipnet)
|
||||
for _, value := range insecureRegistryCIDRs {
|
||||
if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
|
||||
continue skip
|
||||
}
|
||||
}
|
||||
// ipnet is not found, add it in config.InsecureRegistryCIDRs
|
||||
config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, data)
|
||||
|
||||
insecureRegistryCIDRs = append(insecureRegistryCIDRs, data)
|
||||
} else {
|
||||
if err := validateHostPort(r); err != nil {
|
||||
config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
|
||||
config.ServiceConfig.IndexConfigs = originalIndexInfos
|
||||
return fmt.Errorf("insecure registry %s is not valid: %v", r, err)
|
||||
|
||||
return invalidParamWrapf(err, "insecure registry %s is not valid", r)
|
||||
}
|
||||
// Assume `host:port` if not CIDR.
|
||||
config.IndexConfigs[r] = ®istrytypes.IndexInfo{
|
||||
indexConfigs[r] = ®istry.IndexInfo{
|
||||
Name: r,
|
||||
Mirrors: make([]string, 0),
|
||||
Secure: false,
|
||||
|
@ -229,12 +214,14 @@ skip:
|
|||
}
|
||||
|
||||
// Configure public registry.
|
||||
config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{
|
||||
indexConfigs[IndexName] = ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Mirrors: config.Mirrors,
|
||||
Secure: true,
|
||||
Official: true,
|
||||
}
|
||||
config.InsecureRegistryCIDRs = insecureRegistryCIDRs
|
||||
config.IndexConfigs = indexConfigs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -248,7 +235,7 @@ skip:
|
|||
// 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
|
||||
// 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 {
|
||||
if h == hostname {
|
||||
return true
|
||||
|
@ -269,7 +256,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
|
||||
// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
|
||||
// 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
|
||||
// is called from anything besides newIndexInfo, in order to honor per-index configurations.
|
||||
if index, ok := config.IndexConfigs[indexName]; ok {
|
||||
|
@ -282,7 +269,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`)
|
||||
// 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.
|
||||
func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
|
||||
func isCIDRMatch(cidrs []*registry.NetIPNet, URLHost string) bool {
|
||||
host, _, err := net.SplitHostPort(URLHost)
|
||||
if err != nil {
|
||||
// Assume URLHost is of the form `host` without the port and go on.
|
||||
|
@ -318,18 +305,18 @@ func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
|
|||
func ValidateMirror(val string) (string, error) {
|
||||
uri, err := url.Parse(val)
|
||||
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" {
|
||||
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 != "" {
|
||||
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 {
|
||||
// strip password from output
|
||||
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
|
||||
}
|
||||
|
@ -341,17 +328,13 @@ func ValidateIndexName(val string) (string, error) {
|
|||
val = "docker.io"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func validateNoScheme(reposName string) error {
|
||||
if strings.Contains(reposName, "://") {
|
||||
// It cannot contain a scheme!
|
||||
return ErrInvalidRepositoryName
|
||||
}
|
||||
return nil
|
||||
func hasScheme(reposName string) bool {
|
||||
return strings.Contains(reposName, "://")
|
||||
}
|
||||
|
||||
func validateHostPort(s string) error {
|
||||
|
@ -364,7 +347,7 @@ func validateHostPort(s string) error {
|
|||
// If match against the `host:port` pattern fails,
|
||||
// it might be `IPv6:port`, which will be captured by net.ParseIP(host)
|
||||
if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
|
||||
return fmt.Errorf("invalid host %q", host)
|
||||
return invalidParamf("invalid host %q", host)
|
||||
}
|
||||
if port != "" {
|
||||
v, err := strconv.Atoi(port)
|
||||
|
@ -372,14 +355,14 @@ func validateHostPort(s string) error {
|
|||
return err
|
||||
}
|
||||
if v < 0 || v > 65535 {
|
||||
return fmt.Errorf("invalid port %q", port)
|
||||
return invalidParamf("invalid port %q", port)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
indexName, err = ValidateIndexName(indexName)
|
||||
if err != nil {
|
||||
|
@ -392,18 +375,17 @@ func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.Index
|
|||
}
|
||||
|
||||
// Construct a non-configured index info.
|
||||
index := ®istrytypes.IndexInfo{
|
||||
return ®istry.IndexInfo{
|
||||
Name: indexName,
|
||||
Mirrors: make([]string, 0),
|
||||
Secure: config.isSecureIndex(indexName),
|
||||
Official: false,
|
||||
}
|
||||
index.Secure = isSecureIndex(config, indexName)
|
||||
return index, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func GetAuthConfigKey(index *registrytypes.IndexInfo) string {
|
||||
func GetAuthConfigKey(index *registry.IndexInfo) string {
|
||||
if index.Official {
|
||||
return IndexServer
|
||||
}
|
||||
|
@ -432,7 +414,12 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
indexInfo, err := newIndexInfo(emptyServiceConfig, indexName)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
@ -94,7 +95,7 @@ func TestLoadAllowNondistributableArtifacts(t *testing.T) {
|
|||
}
|
||||
for _, testCase := range testCases {
|
||||
config := emptyServiceConfig
|
||||
err := config.LoadAllowNondistributableArtifacts(testCase.registries)
|
||||
err := config.loadAllowNondistributableArtifacts(testCase.registries)
|
||||
if testCase.err == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got '%s'", err)
|
||||
|
@ -237,7 +238,7 @@ func TestLoadInsecureRegistries(t *testing.T) {
|
|||
}
|
||||
for _, testCase := range testCases {
|
||||
config := emptyServiceConfig
|
||||
err := config.LoadInsecureRegistries(testCase.registries)
|
||||
err := config.loadInsecureRegistries(testCase.registries)
|
||||
if testCase.err == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got '%s'", err)
|
||||
|
@ -255,9 +256,8 @@ func TestLoadInsecureRegistries(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatalf("expect error '%s', got no error", testCase.err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), testCase.err) {
|
||||
t.Fatalf("expect error '%s', got '%s'", testCase.err, err)
|
||||
}
|
||||
assert.ErrorContains(t, err, testCase.err)
|
||||
assert.Check(t, errdefs.IsInvalidParameter(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,6 +313,7 @@ func TestNewServiceConfig(t *testing.T) {
|
|||
_, err := newServiceConfig(testCase.opts)
|
||||
if testCase.errStr != "" {
|
||||
assert.Check(t, is.Error(err, testCase.errStr))
|
||||
assert.Check(t, errdefs.IsInvalidParameter(err))
|
||||
} else {
|
||||
assert.Check(t, err)
|
||||
}
|
||||
|
@ -377,5 +378,6 @@ func TestValidateIndexNameWithError(t *testing.T) {
|
|||
for _, testCase := range invalid {
|
||||
_, err := ValidateIndexName(testCase.index)
|
||||
assert.Check(t, is.Error(err, testCase.err))
|
||||
assert.Check(t, errdefs.IsInvalidParameter(err))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,9 +63,9 @@ func TestValidateEndpoint(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testEndpoint := V1Endpoint{
|
||||
testEndpoint := v1Endpoint{
|
||||
URL: testServerURL,
|
||||
client: HTTPClient(NewTransport(nil)),
|
||||
client: httpClient(newTransport(nil)),
|
||||
}
|
||||
|
||||
if err = validateEndpoint(&testEndpoint); err != nil {
|
||||
|
|
|
@ -3,27 +3,39 @@ package registry // import "github.com/docker/docker/registry"
|
|||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// V1Endpoint stores basic information about a V1 registry endpoint.
|
||||
type V1Endpoint struct {
|
||||
// v1PingResult 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 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
|
||||
URL *url.URL
|
||||
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.
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -42,28 +54,28 @@ func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders
|
|||
return endpoint, nil
|
||||
}
|
||||
|
||||
func validateEndpoint(endpoint *V1Endpoint) error {
|
||||
func validateEndpoint(endpoint *v1Endpoint) error {
|
||||
logrus.Debugf("pinging registry endpoint %s", endpoint)
|
||||
|
||||
// Try HTTPS ping to registry
|
||||
endpoint.URL.Scheme = "https"
|
||||
if _, err := endpoint.Ping(); err != nil {
|
||||
if _, err := endpoint.ping(); err != nil {
|
||||
if endpoint.IsSecure {
|
||||
// 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.
|
||||
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.
|
||||
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"
|
||||
|
||||
var err2 error
|
||||
if _, err2 = endpoint.Ping(); err2 == nil {
|
||||
if _, err2 = endpoint.ping(); err2 == 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
|
||||
|
@ -72,28 +84,23 @@ func validateEndpoint(endpoint *V1Endpoint) error {
|
|||
// trimV1Address trims the version off the address and returns the
|
||||
// trimmed address or an error if there is a non-V1 version.
|
||||
func trimV1Address(address string) (string, error) {
|
||||
var (
|
||||
chunks []string
|
||||
apiVersionStr string
|
||||
)
|
||||
|
||||
address = strings.TrimSuffix(address, "/")
|
||||
chunks = strings.Split(address, "/")
|
||||
apiVersionStr = chunks[len(chunks)-1]
|
||||
chunks := strings.Split(address, "/")
|
||||
apiVersionStr := chunks[len(chunks)-1]
|
||||
if apiVersionStr == "v1" {
|
||||
return strings.Join(chunks[:len(chunks)-1], "/"), nil
|
||||
}
|
||||
|
||||
for k, v := range apiVersions {
|
||||
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
|
||||
}
|
||||
|
||||
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://") {
|
||||
address = "https://" + address
|
||||
}
|
||||
|
@ -105,69 +112,64 @@ func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent strin
|
|||
|
||||
uri, err := url.Parse(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, invalidParam(err)
|
||||
}
|
||||
|
||||
// TODO(tiborvass): make sure a ConnectTimeout transport is used
|
||||
tr := NewTransport(tlsConfig)
|
||||
tr := newTransport(tlsConfig)
|
||||
|
||||
return &V1Endpoint{
|
||||
return &v1Endpoint{
|
||||
IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
|
||||
URL: uri,
|
||||
client: HTTPClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
|
||||
client: httpClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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/"
|
||||
}
|
||||
|
||||
// Path returns a formatted string for the URL
|
||||
// of this endpoint with the given path appended.
|
||||
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) {
|
||||
// ping returns a v1PingResult which indicates whether the registry is standalone or not.
|
||||
func (e *v1Endpoint) ping() (v1PingResult, error) {
|
||||
if e.String() == IndexServer {
|
||||
// Skip the check, we know this one is valid
|
||||
// (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)
|
||||
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 {
|
||||
return PingResult{}, err
|
||||
return v1PingResult{}, invalidParam(err)
|
||||
}
|
||||
|
||||
resp, err := e.client.Do(req)
|
||||
if err != nil {
|
||||
return PingResult{}, err
|
||||
return v1PingResult{}, invalidParam(err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
jsonString, err := io.ReadAll(resp.Body)
|
||||
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
|
||||
// versions of the registry. default to true
|
||||
info := PingResult{
|
||||
info := v1PingResult{
|
||||
Standalone: true,
|
||||
}
|
||||
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
|
||||
}
|
||||
if hdr := resp.Header.Get("X-Docker-Registry-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")
|
||||
|
||||
|
@ -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
|
||||
info.Standalone = false
|
||||
}
|
||||
logrus.Debugf("PingResult.Standalone: %t", info.Standalone)
|
||||
logrus.Debugf("v1PingResult.Standalone: %t", info.Standalone)
|
||||
return info, nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func translateV2AuthError(err error) error {
|
||||
|
@ -21,3 +22,15 @@ func translateV2AuthError(err error) error {
|
|||
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -16,15 +15,12 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HostCertsDir returns the config directory for a specific host
|
||||
func HostCertsDir(hostname string) (string, error) {
|
||||
certsDir := CertsDir()
|
||||
|
||||
hostDir := filepath.Join(certsDir, cleanPath(hostname))
|
||||
|
||||
return hostDir, nil
|
||||
// HostCertsDir returns the config directory for a specific host.
|
||||
func HostCertsDir(hostname string) string {
|
||||
return filepath.Join(CertsDir(), cleanPath(hostname))
|
||||
}
|
||||
|
||||
// newTLSConfig constructs a client TLS configuration based on server defaults
|
||||
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
|
||||
// PreferredServerCipherSuites should have no effect
|
||||
tlsConfig := tlsconfig.ServerDefault()
|
||||
|
@ -32,11 +28,7 @@ func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
|
|||
tlsConfig.InsecureSkipVerify = !isSecure
|
||||
|
||||
if isSecure && CertsDir() != "" {
|
||||
hostDir, err := HostCertsDir(hostname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostDir := HostCertsDir(hostname)
|
||||
logrus.Debugf("hostDir: %s", hostDir)
|
||||
if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
|
||||
return nil, err
|
||||
|
@ -61,7 +53,7 @@ func hasFile(files []os.DirEntry, name string) bool {
|
|||
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
||||
fs, err := os.ReadDir(directory)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
return invalidParam(err)
|
||||
}
|
||||
|
||||
for _, f := range fs {
|
||||
|
@ -69,7 +61,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
|||
if tlsConfig.RootCAs == nil {
|
||||
systemPool, err := tlsconfig.SystemCertPool()
|
||||
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
|
||||
}
|
||||
|
@ -85,7 +77,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
|||
keyName := certName[:len(certName)-5] + ".key"
|
||||
logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
|
||||
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))
|
||||
if err != nil {
|
||||
|
@ -98,7 +90,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
|||
certName := keyName[:len(keyName)-4] + ".cert"
|
||||
logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func HTTPClient(transport http.RoundTripper) *http.Client {
|
||||
func httpClient(transport http.RoundTripper) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
CheckRedirect: addRequiredHeadersToRedirectedRequests,
|
||||
|
@ -165,9 +157,9 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
|
|||
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.
|
||||
func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
||||
func newTransport(tlsConfig *tls.Config) *http.Transport {
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = tlsconfig.ServerDefault()
|
||||
}
|
||||
|
@ -177,7 +169,7 @@ func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
|||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
base := &http.Transport{
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: direct.DialContext,
|
||||
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
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
|
|
@ -3,93 +3,21 @@ package registry // import "github.com/docker/docker/registry"
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
testHTTPServer *httptest.Server
|
||||
testHTTPSServer *httptest.Server
|
||||
testLayers = map[string]map[string]string{
|
||||
"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
|
||||
"json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
|
||||
"comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00",
|
||||
"container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
|
||||
"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
|
||||
"Tty":false,"OpenStdin":false,"StdinOnce":false,
|
||||
"Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
|
||||
"VolumesFrom":"","Entrypoint":null},"Size":424242}`,
|
||||
"checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
|
||||
"checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c",
|
||||
"ancestry": `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
|
||||
"layer": string([]byte{
|
||||
0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
|
||||
0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
|
||||
0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66,
|
||||
0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78,
|
||||
0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31,
|
||||
0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8,
|
||||
0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1,
|
||||
0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6,
|
||||
0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb,
|
||||
0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce,
|
||||
0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00,
|
||||
}),
|
||||
},
|
||||
"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": {
|
||||
"json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
|
||||
"parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
|
||||
"comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00",
|
||||
"container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
|
||||
"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
|
||||
"Tty":false,"OpenStdin":false,"StdinOnce":false,
|
||||
"Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
|
||||
"VolumesFrom":"","Entrypoint":null},"Size":424242}`,
|
||||
"checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
|
||||
"checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2",
|
||||
"ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
|
||||
"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
|
||||
"layer": string([]byte{
|
||||
0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
|
||||
0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
|
||||
0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56,
|
||||
0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5,
|
||||
0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e,
|
||||
0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93,
|
||||
0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee,
|
||||
0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9,
|
||||
0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55,
|
||||
0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17,
|
||||
0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00,
|
||||
}),
|
||||
},
|
||||
}
|
||||
testRepositories = map[string]map[string]string{
|
||||
"foo42/bar": {
|
||||
"latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
|
||||
"test": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
|
||||
},
|
||||
}
|
||||
mockHosts = map[string][]net.IP{
|
||||
"": {net.ParseIP("0.0.0.0")},
|
||||
"localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
|
||||
"example.com": {net.ParseIP("42.42.42.42")},
|
||||
"other.com": {net.ParseIP("43.43.43.43")},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -97,14 +25,6 @@ func init() {
|
|||
|
||||
// /v1/
|
||||
r.HandleFunc("/v1/_ping", handlerGetPing).Methods(http.MethodGet)
|
||||
r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods(http.MethodGet)
|
||||
r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods(http.MethodPut)
|
||||
r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods(http.MethodGet, http.MethodDelete)
|
||||
r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods(http.MethodGet)
|
||||
r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods(http.MethodPut)
|
||||
r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods(http.MethodGet, http.MethodPost, http.MethodPut)
|
||||
r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods(http.MethodGet, http.MethodPut, http.MethodDelete)
|
||||
r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods(http.MethodPut)
|
||||
r.HandleFunc("/v1/search", handlerSearch).Methods(http.MethodGet)
|
||||
|
||||
// /v2/
|
||||
|
@ -119,6 +39,12 @@ func init() {
|
|||
// I believe in future Go versions this will fail, so let's fix it later
|
||||
return net.LookupIP(host)
|
||||
}
|
||||
mockHosts := map[string][]net.IP{
|
||||
"": {net.ParseIP("0.0.0.0")},
|
||||
"localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
|
||||
"example.com": {net.ParseIP("42.42.42.42")},
|
||||
"other.com": {net.ParseIP("43.43.43.43")},
|
||||
}
|
||||
for h, addrs := range mockHosts {
|
||||
if host == h {
|
||||
return addrs, nil
|
||||
|
@ -135,7 +61,7 @@ func init() {
|
|||
|
||||
func handlerAccessLog(handler http.Handler) http.Handler {
|
||||
logHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL)
|
||||
logrus.Debugf(`%s "%s %s"`, r.RemoteAddr, r.Method, r.URL)
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(logHandler)
|
||||
|
@ -149,22 +75,22 @@ func makeHTTPSURL(req string) string {
|
|||
return testHTTPSServer.URL + req
|
||||
}
|
||||
|
||||
func makeIndex(req string) *registrytypes.IndexInfo {
|
||||
index := ®istrytypes.IndexInfo{
|
||||
func makeIndex(req string) *registry.IndexInfo {
|
||||
index := ®istry.IndexInfo{
|
||||
Name: makeURL(req),
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func makeHTTPSIndex(req string) *registrytypes.IndexInfo {
|
||||
index := ®istrytypes.IndexInfo{
|
||||
func makeHTTPSIndex(req string) *registry.IndexInfo {
|
||||
index := ®istry.IndexInfo{
|
||||
Name: makeHTTPSURL(req),
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func makePublicIndex() *registrytypes.IndexInfo {
|
||||
index := ®istrytypes.IndexInfo{
|
||||
func makePublicIndex() *registry.IndexInfo {
|
||||
index := ®istry.IndexInfo{
|
||||
Name: IndexServer,
|
||||
Secure: true,
|
||||
Official: true,
|
||||
|
@ -203,252 +129,15 @@ func writeResponse(w http.ResponseWriter, message interface{}, code int) {
|
|||
w.Write(body)
|
||||
}
|
||||
|
||||
func readJSON(r *http.Request, dest interface{}) error {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(body, dest)
|
||||
}
|
||||
|
||||
func apiError(w http.ResponseWriter, message string, code int) {
|
||||
body := map[string]string{
|
||||
"error": message,
|
||||
}
|
||||
writeResponse(w, body, code)
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
||||
if a == b {
|
||||
return
|
||||
}
|
||||
if len(message) == 0 {
|
||||
message = fmt.Sprintf("%v != %v", a, b)
|
||||
}
|
||||
t.Fatal(message)
|
||||
}
|
||||
|
||||
func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
||||
if a != b {
|
||||
return
|
||||
}
|
||||
if len(message) == 0 {
|
||||
message = fmt.Sprintf("%v == %v", a, b)
|
||||
}
|
||||
t.Fatal(message)
|
||||
}
|
||||
|
||||
// Similar to assertEqual, but does not stop test
|
||||
func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
|
||||
if a == b {
|
||||
return
|
||||
}
|
||||
message := fmt.Sprintf("%v != %v", a, b)
|
||||
if len(messagePrefix) != 0 {
|
||||
message = messagePrefix + ": " + message
|
||||
}
|
||||
t.Error(message)
|
||||
}
|
||||
|
||||
// Similar to assertNotEqual, but does not stop test
|
||||
func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
|
||||
if a != b {
|
||||
return
|
||||
}
|
||||
message := fmt.Sprintf("%v == %v", a, b)
|
||||
if len(messagePrefix) != 0 {
|
||||
message = messagePrefix + ": " + message
|
||||
}
|
||||
t.Error(message)
|
||||
}
|
||||
|
||||
func requiresAuth(w http.ResponseWriter, r *http.Request) bool {
|
||||
writeCookie := func() {
|
||||
value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
|
||||
cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600}
|
||||
http.SetCookie(w, cookie)
|
||||
// FIXME(sam): this should be sent only on Index routes
|
||||
value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano())
|
||||
w.Header().Add("X-Docker-Token", value)
|
||||
}
|
||||
if len(r.Cookies()) > 0 {
|
||||
writeCookie()
|
||||
return true
|
||||
}
|
||||
if len(r.Header.Get("Authorization")) > 0 {
|
||||
writeCookie()
|
||||
return true
|
||||
}
|
||||
w.Header().Add("WWW-Authenticate", "token")
|
||||
apiError(w, "Wrong auth", http.StatusUnauthorized)
|
||||
return false
|
||||
}
|
||||
|
||||
func handlerGetPing(w http.ResponseWriter, r *http.Request) {
|
||||
writeResponse(w, true, http.StatusOK)
|
||||
}
|
||||
|
||||
func handlerGetImage(w http.ResponseWriter, r *http.Request) {
|
||||
if !requiresAuth(w, r) {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
layer, exists := testLayers[vars["image_id"]]
|
||||
if !exists {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
writeHeaders(w)
|
||||
layerSize := len(layer["layer"])
|
||||
w.Header().Add("X-Docker-Size", strconv.Itoa(layerSize))
|
||||
io.WriteString(w, layer[vars["action"]])
|
||||
}
|
||||
|
||||
func handlerPutImage(w http.ResponseWriter, r *http.Request) {
|
||||
if !requiresAuth(w, r) {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
imageID := vars["image_id"]
|
||||
action := vars["action"]
|
||||
layer, exists := testLayers[imageID]
|
||||
if !exists {
|
||||
if action != "json" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
layer = make(map[string]string)
|
||||
testLayers[imageID] = layer
|
||||
}
|
||||
if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" {
|
||||
if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] {
|
||||
apiError(w, "Wrong checksum", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
apiError(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
layer[action] = string(body)
|
||||
writeResponse(w, true, http.StatusOK)
|
||||
}
|
||||
|
||||
func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
|
||||
if !requiresAuth(w, r) {
|
||||
return
|
||||
}
|
||||
repositoryName, err := reference.WithName(mux.Vars(r)["repository"])
|
||||
if err != nil {
|
||||
apiError(w, "Could not parse repository", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tags, exists := testRepositories[repositoryName.String()]
|
||||
if !exists {
|
||||
apiError(w, "Repository not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodDelete {
|
||||
delete(testRepositories, repositoryName.String())
|
||||
writeResponse(w, true, http.StatusOK)
|
||||
return
|
||||
}
|
||||
writeResponse(w, tags, http.StatusOK)
|
||||
}
|
||||
|
||||
func handlerGetTag(w http.ResponseWriter, r *http.Request) {
|
||||
if !requiresAuth(w, r) {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
repositoryName, err := reference.WithName(vars["repository"])
|
||||
if err != nil {
|
||||
apiError(w, "Could not parse repository", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tagName := vars["tag"]
|
||||
tags, exists := testRepositories[repositoryName.String()]
|
||||
if !exists {
|
||||
apiError(w, "Repository not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
tag, exists := tags[tagName]
|
||||
if !exists {
|
||||
apiError(w, "Tag not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeResponse(w, tag, http.StatusOK)
|
||||
}
|
||||
|
||||
func handlerPutTag(w http.ResponseWriter, r *http.Request) {
|
||||
if !requiresAuth(w, r) {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
repositoryName, err := reference.WithName(vars["repository"])
|
||||
if err != nil {
|
||||
apiError(w, "Could not parse repository", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tagName := vars["tag"]
|
||||
tags, exists := testRepositories[repositoryName.String()]
|
||||
if !exists {
|
||||
tags = make(map[string]string)
|
||||
testRepositories[repositoryName.String()] = tags
|
||||
}
|
||||
tagValue := ""
|
||||
readJSON(r, tagValue)
|
||||
tags[tagName] = tagValue
|
||||
writeResponse(w, true, http.StatusOK)
|
||||
}
|
||||
|
||||
func handlerUsers(w http.ResponseWriter, r *http.Request) {
|
||||
code := http.StatusOK
|
||||
if r.Method == http.MethodPost {
|
||||
code = http.StatusCreated
|
||||
} else if r.Method == http.MethodPut {
|
||||
code = http.StatusNoContent
|
||||
}
|
||||
writeResponse(w, "", code)
|
||||
}
|
||||
|
||||
func handlerImages(w http.ResponseWriter, r *http.Request) {
|
||||
u, _ := url.Parse(testHTTPServer.URL)
|
||||
w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s , %s ", u.Host, "test.example.com"))
|
||||
w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()))
|
||||
if r.Method == http.MethodPut {
|
||||
if strings.HasSuffix(r.URL.Path, "images") {
|
||||
writeResponse(w, "", http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
writeResponse(w, "", http.StatusOK)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodDelete {
|
||||
writeResponse(w, "", http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
var images []map[string]string
|
||||
for imageID, layer := range testLayers {
|
||||
image := make(map[string]string)
|
||||
image["id"] = imageID
|
||||
image["checksum"] = layer["checksum_tarsum"]
|
||||
image["Tag"] = "latest"
|
||||
images = append(images, image)
|
||||
}
|
||||
writeResponse(w, images, http.StatusOK)
|
||||
}
|
||||
|
||||
func handlerAuth(w http.ResponseWriter, r *http.Request) {
|
||||
writeResponse(w, "OK", http.StatusOK)
|
||||
}
|
||||
|
||||
func handlerSearch(w http.ResponseWriter, r *http.Request) {
|
||||
result := ®istrytypes.SearchResults{
|
||||
result := ®istry.SearchResults{
|
||||
Query: "fakequery",
|
||||
NumResults: 1,
|
||||
Results: []registrytypes.SearchResult{{Name: "fakeimage", StarCount: 42}},
|
||||
Results: []registry.SearchResult{{Name: "fakeimage", StarCount: 42}},
|
||||
}
|
||||
writeResponse(w, result, http.StatusOK)
|
||||
}
|
||||
|
@ -458,18 +147,6 @@ func TestPing(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqual(t, res.StatusCode, http.StatusOK, "")
|
||||
assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock",
|
||||
"This is not a Mocked Registry")
|
||||
assert.Equal(t, res.StatusCode, http.StatusOK, "")
|
||||
assert.Equal(t, res.Header.Get("X-Docker-Registry-Config"), "mock", "This is not a Mocked Registry")
|
||||
}
|
||||
|
||||
/* Uncomment this to test Mocked Registry locally with curl
|
||||
* WARNING: Don't push on the repos uncommented, it'll block the tests
|
||||
*
|
||||
func TestWait(t *testing.T) {
|
||||
logrus.Println("Test HTTP server ready and waiting:", testHTTPServer.URL)
|
||||
c := make(chan int)
|
||||
<-c
|
||||
}
|
||||
|
||||
//*/
|
||||
|
|
|
@ -10,25 +10,28 @@ import (
|
|||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
func spawnTestRegistrySession(t *testing.T) *Session {
|
||||
func spawnTestRegistrySession(t *testing.T) *session {
|
||||
authConfig := &types.AuthConfig{}
|
||||
endpoint, err := NewV1Endpoint(makeIndex("/v1/"), "", nil)
|
||||
endpoint, err := newV1Endpoint(makeIndex("/v1/"), "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userAgent := "docker test client"
|
||||
var tr http.RoundTripper = debugTransport{NewTransport(nil), t.Log}
|
||||
var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log}
|
||||
tr = transport.NewTransport(AuthTransport(tr, authConfig, false), Headers(userAgent, nil)...)
|
||||
client := HTTPClient(tr)
|
||||
r, err := NewSession(client, authConfig, endpoint)
|
||||
if err != nil {
|
||||
client := httpClient(tr)
|
||||
|
||||
if err := authorizeClient(client, authConfig, endpoint); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := newSession(client, endpoint)
|
||||
|
||||
// In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
|
||||
// header while authenticating, in order to retrieve a token that can be later used to
|
||||
// perform authenticated actions.
|
||||
|
@ -45,17 +48,17 @@ func spawnTestRegistrySession(t *testing.T) *Session {
|
|||
|
||||
func TestPingRegistryEndpoint(t *testing.T) {
|
||||
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
|
||||
testPing := func(index *registrytypes.IndexInfo, expectedStandalone bool, assertMessage string) {
|
||||
ep, err := NewV1Endpoint(index, "", nil)
|
||||
testPing := func(index *registry.IndexInfo, expectedStandalone bool, assertMessage string) {
|
||||
ep, err := newV1Endpoint(index, "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
regInfo, err := ep.Ping()
|
||||
regInfo, err := ep.ping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage)
|
||||
assert.Equal(t, regInfo.Standalone, expectedStandalone, assertMessage)
|
||||
}
|
||||
|
||||
testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
|
||||
|
@ -66,61 +69,59 @@ func TestPingRegistryEndpoint(t *testing.T) {
|
|||
func TestEndpoint(t *testing.T) {
|
||||
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
|
||||
// Simple wrapper to fail test if err != nil
|
||||
expandEndpoint := func(index *registrytypes.IndexInfo) *V1Endpoint {
|
||||
endpoint, err := NewV1Endpoint(index, "", nil)
|
||||
expandEndpoint := func(index *registry.IndexInfo) *v1Endpoint {
|
||||
endpoint, err := newV1Endpoint(index, "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
||||
assertInsecureIndex := func(index *registrytypes.IndexInfo) {
|
||||
assertInsecureIndex := func(index *registry.IndexInfo) {
|
||||
index.Secure = true
|
||||
_, err := NewV1Endpoint(index, "", nil)
|
||||
assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
|
||||
assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index")
|
||||
_, err := newV1Endpoint(index, "", nil)
|
||||
assert.ErrorContains(t, err, "insecure-registry", index.Name+": Expected insecure-registry error for insecure index")
|
||||
index.Secure = false
|
||||
}
|
||||
|
||||
assertSecureIndex := func(index *registrytypes.IndexInfo) {
|
||||
assertSecureIndex := func(index *registry.IndexInfo) {
|
||||
index.Secure = true
|
||||
_, err := NewV1Endpoint(index, "", nil)
|
||||
assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
|
||||
assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
|
||||
_, err := newV1Endpoint(index, "", nil)
|
||||
assert.ErrorContains(t, err, "certificate signed by unknown authority", index.Name+": Expected cert error for secure index")
|
||||
index.Secure = false
|
||||
}
|
||||
|
||||
index := ®istrytypes.IndexInfo{}
|
||||
index := ®istry.IndexInfo{}
|
||||
index.Name = makeURL("/v1/")
|
||||
endpoint := expandEndpoint(index)
|
||||
assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
|
||||
assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
|
||||
assertInsecureIndex(index)
|
||||
|
||||
index.Name = makeURL("")
|
||||
endpoint = expandEndpoint(index)
|
||||
assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
|
||||
assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
|
||||
assertInsecureIndex(index)
|
||||
|
||||
httpURL := makeURL("")
|
||||
index.Name = strings.SplitN(httpURL, "://", 2)[1]
|
||||
endpoint = expandEndpoint(index)
|
||||
assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
|
||||
assert.Equal(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
|
||||
assertInsecureIndex(index)
|
||||
|
||||
index.Name = makeHTTPSURL("/v1/")
|
||||
endpoint = expandEndpoint(index)
|
||||
assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
|
||||
assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
|
||||
assertSecureIndex(index)
|
||||
|
||||
index.Name = makeHTTPSURL("")
|
||||
endpoint = expandEndpoint(index)
|
||||
assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
|
||||
assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
|
||||
assertSecureIndex(index)
|
||||
|
||||
httpsURL := makeHTTPSURL("")
|
||||
index.Name = strings.SplitN(httpsURL, "://", 2)[1]
|
||||
endpoint = expandEndpoint(index)
|
||||
assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
|
||||
assert.Equal(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
|
||||
assertSecureIndex(index)
|
||||
|
||||
badEndpoints := []string{
|
||||
|
@ -132,14 +133,14 @@ func TestEndpoint(t *testing.T) {
|
|||
}
|
||||
for _, address := range badEndpoints {
|
||||
index.Name = address
|
||||
_, err := NewV1Endpoint(index, "", nil)
|
||||
checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
|
||||
_, err := newV1Endpoint(index, "", nil)
|
||||
assert.Check(t, err != nil, "Expected error while expanding bad endpoint: %s", address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRepositoryInfo(t *testing.T) {
|
||||
type staticRepositoryInfo struct {
|
||||
Index *registrytypes.IndexInfo
|
||||
Index *registry.IndexInfo
|
||||
RemoteName string
|
||||
CanonicalName string
|
||||
LocalName string
|
||||
|
@ -148,7 +149,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
|
||||
expectedRepoInfos := map[string]staticRepositoryInfo{
|
||||
"fooo/bar": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -158,7 +159,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"library/ubuntu": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -168,7 +169,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: true,
|
||||
},
|
||||
"nonlibrary/ubuntu": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -178,7 +179,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"ubuntu": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -188,7 +189,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: true,
|
||||
},
|
||||
"other/library": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -198,7 +199,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"127.0.0.1:8000/private/moonbase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "127.0.0.1:8000",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -208,7 +209,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"127.0.0.1:8000/privatebase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "127.0.0.1:8000",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -218,7 +219,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"localhost:8000/private/moonbase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "localhost:8000",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -228,7 +229,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"localhost:8000/privatebase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "localhost:8000",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -238,7 +239,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"example.com/private/moonbase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "example.com",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -248,7 +249,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"example.com/privatebase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "example.com",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -258,7 +259,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"example.com:8000/private/moonbase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "example.com:8000",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -268,7 +269,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"example.com:8000/privatebase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "example.com:8000",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -278,7 +279,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"localhost/private/moonbase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "localhost",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -288,7 +289,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"localhost/privatebase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: "localhost",
|
||||
Official: false,
|
||||
},
|
||||
|
@ -298,7 +299,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
IndexName + "/public/moonbase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -308,7 +309,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"index." + IndexName + "/public/moonbase": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -318,7 +319,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: false,
|
||||
},
|
||||
"ubuntu-12.04-base": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -328,7 +329,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: true,
|
||||
},
|
||||
IndexName + "/ubuntu-12.04-base": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -338,7 +339,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
Official: true,
|
||||
},
|
||||
"index." + IndexName + "/ubuntu-12.04-base": {
|
||||
Index: ®istrytypes.IndexInfo{
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
},
|
||||
|
@ -359,34 +360,34 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName)
|
||||
checkEqual(t, reference.Path(repoInfo.Name), expectedRepoInfo.RemoteName, reposName)
|
||||
checkEqual(t, reference.FamiliarName(repoInfo.Name), expectedRepoInfo.LocalName, reposName)
|
||||
checkEqual(t, repoInfo.Name.Name(), expectedRepoInfo.CanonicalName, reposName)
|
||||
checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName)
|
||||
checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName)
|
||||
assert.Check(t, is.Equal(repoInfo.Index.Name, expectedRepoInfo.Index.Name), reposName)
|
||||
assert.Check(t, is.Equal(reference.Path(repoInfo.Name), expectedRepoInfo.RemoteName), reposName)
|
||||
assert.Check(t, is.Equal(reference.FamiliarName(repoInfo.Name), expectedRepoInfo.LocalName), reposName)
|
||||
assert.Check(t, is.Equal(repoInfo.Name.Name(), expectedRepoInfo.CanonicalName), reposName)
|
||||
assert.Check(t, is.Equal(repoInfo.Index.Official, expectedRepoInfo.Index.Official), reposName)
|
||||
assert.Check(t, is.Equal(repoInfo.Official, expectedRepoInfo.Official), reposName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIndexInfo(t *testing.T) {
|
||||
testIndexInfo := func(config *serviceConfig, expectedIndexInfos map[string]*registrytypes.IndexInfo) {
|
||||
testIndexInfo := func(config *serviceConfig, expectedIndexInfos map[string]*registry.IndexInfo) {
|
||||
for indexName, expectedIndexInfo := range expectedIndexInfos {
|
||||
index, err := newIndexInfo(config, indexName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name")
|
||||
checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official")
|
||||
checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure")
|
||||
checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors")
|
||||
assert.Check(t, is.Equal(index.Name, expectedIndexInfo.Name), indexName+" name")
|
||||
assert.Check(t, is.Equal(index.Official, expectedIndexInfo.Official), indexName+" is official")
|
||||
assert.Check(t, is.Equal(index.Secure, expectedIndexInfo.Secure), indexName+" is secure")
|
||||
assert.Check(t, is.Equal(len(index.Mirrors), len(expectedIndexInfo.Mirrors)), indexName+" mirrors")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config := emptyServiceConfig
|
||||
var noMirrors []string
|
||||
expectedIndexInfos := map[string]*registrytypes.IndexInfo{
|
||||
expectedIndexInfos := map[string]*registry.IndexInfo{
|
||||
IndexName: {
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
|
@ -421,7 +422,7 @@ func TestNewIndexInfo(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedIndexInfos = map[string]*registrytypes.IndexInfo{
|
||||
expectedIndexInfos = map[string]*registry.IndexInfo{
|
||||
IndexName: {
|
||||
Name: IndexName,
|
||||
Official: true,
|
||||
|
@ -471,7 +472,7 @@ func TestNewIndexInfo(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedIndexInfos = map[string]*registrytypes.IndexInfo{
|
||||
expectedIndexInfos = map[string]*registry.IndexInfo{
|
||||
"example.com": {
|
||||
Name: "example.com",
|
||||
Official: false,
|
||||
|
@ -520,7 +521,7 @@ func TestMirrorEndpointLookup(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := DefaultService{config: cfg}
|
||||
s := defaultService{config: cfg}
|
||||
|
||||
imageName, err := reference.WithName(IndexName + "/test/image")
|
||||
if err != nil {
|
||||
|
@ -545,16 +546,16 @@ func TestMirrorEndpointLookup(t *testing.T) {
|
|||
|
||||
func TestSearchRepositories(t *testing.T) {
|
||||
r := spawnTestRegistrySession(t)
|
||||
results, err := r.SearchRepositories("fakequery", 25)
|
||||
results, err := r.searchRepositories("fakequery", 25)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if results == nil {
|
||||
t.Fatal("Expected non-nil SearchResults object")
|
||||
}
|
||||
assertEqual(t, results.NumResults, 1, "Expected 1 search results")
|
||||
assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query")
|
||||
assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
|
||||
assert.Equal(t, results.NumResults, 1, "Expected 1 search results")
|
||||
assert.Equal(t, results.Query, "fakequery", "Expected 'fakequery' as query")
|
||||
assert.Equal(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
|
||||
}
|
||||
|
||||
func TestTrustedLocation(t *testing.T) {
|
||||
|
@ -580,7 +581,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
|
|||
reqFrom.Header.Add("Authorization", "super_secret")
|
||||
reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
|
||||
|
||||
addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
||||
_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
||||
|
||||
if len(reqTo.Header) != 1 {
|
||||
t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header))
|
||||
|
@ -604,7 +605,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
|
|||
reqFrom.Header.Add("Authorization", "super_secret")
|
||||
reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
|
||||
|
||||
addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
||||
_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
||||
|
||||
if len(reqTo.Header) != 2 {
|
||||
t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header))
|
||||
|
@ -659,7 +660,7 @@ func TestAllowNondistributableArtifacts(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if v := allowNondistributableArtifacts(config, tt.addr); v != tt.expected {
|
||||
if v := config.allowNondistributableArtifacts(tt.addr); v != tt.expected {
|
||||
t.Errorf("allowNondistributableArtifacts failed for %q %v, expected %v got %v", tt.addr, tt.registries, tt.expected, v)
|
||||
}
|
||||
}
|
||||
|
@ -702,7 +703,7 @@ func TestIsSecureIndex(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if sec := isSecureIndex(config, tt.addr); sec != tt.expected {
|
||||
if sec := config.isSecureIndex(tt.addr); sec != tt.expected {
|
||||
t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,8 @@ import (
|
|||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"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/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -28,85 +27,64 @@ type Service interface {
|
|||
LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
||||
LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err 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)
|
||||
ServiceConfig() *registrytypes.ServiceConfig
|
||||
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error)
|
||||
ServiceConfig() *registry.ServiceConfig
|
||||
TLSConfig(hostname string) (*tls.Config, error)
|
||||
LoadAllowNondistributableArtifacts([]string) error
|
||||
LoadMirrors([]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.
|
||||
type DefaultService struct {
|
||||
type defaultService struct {
|
||||
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.
|
||||
func NewService(options ServiceOptions) (*DefaultService, error) {
|
||||
func NewService(options ServiceOptions) (Service, error) {
|
||||
config, err := newServiceConfig(options)
|
||||
|
||||
return &DefaultService{config: config}, err
|
||||
return &defaultService{config: config}, err
|
||||
}
|
||||
|
||||
// ServiceConfig returns the public registry service configuration.
|
||||
func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
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
|
||||
// ServiceConfig returns a copy of the public registry service's configuration.
|
||||
func (s *defaultService) ServiceConfig() *registry.ServiceConfig {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.config.copy()
|
||||
}
|
||||
|
||||
// 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()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.config.LoadAllowNondistributableArtifacts(registries)
|
||||
return s.config.loadAllowNondistributableArtifacts(registries)
|
||||
}
|
||||
|
||||
// LoadMirrors loads registry mirrors for Service
|
||||
func (s *DefaultService) LoadMirrors(mirrors []string) error {
|
||||
func (s *defaultService) LoadMirrors(mirrors []string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.config.LoadMirrors(mirrors)
|
||||
return s.config.loadMirrors(mirrors)
|
||||
}
|
||||
|
||||
// LoadInsecureRegistries loads insecure registries for Service
|
||||
func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
|
||||
func (s *defaultService) LoadInsecureRegistries(registries []string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.config.LoadInsecureRegistries(registries)
|
||||
return s.config.loadInsecureRegistries(registries)
|
||||
}
|
||||
|
||||
// Auth contacts the public registry with the provided credentials,
|
||||
// and returns OK if authentication was successful.
|
||||
// It can be used to verify the validity of a client's credentials.
|
||||
func (s *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
|
||||
var registryHostName = IndexHostname
|
||||
|
||||
|
@ -117,7 +95,7 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
|
|||
}
|
||||
u, err := url.Parse(serverAddress)
|
||||
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
|
||||
}
|
||||
|
@ -127,7 +105,7 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
|
|||
// to a mirror.
|
||||
endpoints, err := s.LookupPushEndpoints(registryHostName)
|
||||
if err != nil {
|
||||
return "", "", errdefs.InvalidParameter(err)
|
||||
return "", "", invalidParam(err)
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
|
@ -159,25 +137,28 @@ func splitReposSearchTerm(reposName string) (string, string) {
|
|||
|
||||
// Search queries the public registry for images matching the specified
|
||||
// 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
|
||||
if err := validateNoScheme(term); err != nil {
|
||||
return nil, err
|
||||
if hasScheme(term) {
|
||||
return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
|
||||
}
|
||||
|
||||
indexName, remoteName := splitReposSearchTerm(term)
|
||||
|
||||
// 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)
|
||||
s.mu.Unlock()
|
||||
s.mu.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if index.Official {
|
||||
// If pull "library/foo", it's stored locally under "foo"
|
||||
remoteName = strings.TrimPrefix(remoteName, "library/")
|
||||
}
|
||||
|
||||
// *TODO: Search multiple indexes.
|
||||
endpoint, err := NewV1Endpoint(index, userAgent, headers)
|
||||
endpoint, err := newV1Endpoint(index, userAgent, headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -196,7 +177,7 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
|
|||
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
|
||||
if err != nil {
|
||||
if fErr, ok := err.(fallbackError); ok {
|
||||
logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
|
||||
logrus.WithError(fErr.err).Error("cannot use identity token for search, v2 auth not supported")
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -218,20 +199,14 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
|
|||
}
|
||||
}
|
||||
|
||||
r := newSession(client, authConfig, endpoint)
|
||||
|
||||
if index.Official {
|
||||
// If pull "library/foo", it's stored locally under "foo"
|
||||
remoteName = strings.TrimPrefix(remoteName, "library/")
|
||||
}
|
||||
return r.SearchRepositories(remoteName, limit)
|
||||
return newSession(client, endpoint).searchRepositories(remoteName, limit)
|
||||
}
|
||||
|
||||
// ResolveRepository splits a repository name into its components
|
||||
// and configuration of the associated registry.
|
||||
func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
func (s *defaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return newRepositoryInfo(s.config, name)
|
||||
}
|
||||
|
||||
|
@ -247,32 +222,28 @@ type APIEndpoint struct {
|
|||
}
|
||||
|
||||
// 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()
|
||||
func (s *defaultService) TLSConfig(hostname string) (*tls.Config, error) {
|
||||
s.mu.RLock()
|
||||
secure := s.config.isSecureIndex(hostname)
|
||||
s.mu.RUnlock()
|
||||
|
||||
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))
|
||||
return newTLSConfig(hostname, secure)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
return s.lookupV2Endpoints(hostname)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
allEndpoints, err := s.lookupV2Endpoints(hostname)
|
||||
if err == nil {
|
||||
|
|
|
@ -7,8 +7,7 @@ import (
|
|||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||
tlsConfig := tlsconfig.ServerDefault()
|
||||
func (s *defaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||
if hostname == DefaultNamespace || hostname == IndexHostname {
|
||||
for _, mirror := range s.config.Mirrors {
|
||||
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)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -35,19 +34,18 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
|||
Version: APIVersion2,
|
||||
Official: true,
|
||||
TrimHostname: true,
|
||||
TLSConfig: tlsConfig,
|
||||
TLSConfig: tlsconfig.ServerDefault(),
|
||||
})
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
ana := allowNondistributableArtifacts(s.config, hostname)
|
||||
|
||||
tlsConfig, err = s.tlsConfig(hostname)
|
||||
tlsConfig, err := newTLSConfig(hostname, s.config.isSecureIndex(hostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ana := s.config.allowNondistributableArtifacts(hostname)
|
||||
endpoints = []APIEndpoint{
|
||||
{
|
||||
URL: &url.URL{
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"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/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
|
@ -21,13 +21,11 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// A Session is used to communicate with a V1 registry
|
||||
type Session struct {
|
||||
indexEndpoint *V1Endpoint
|
||||
// A session is used to communicate with a V1 registry
|
||||
type session struct {
|
||||
indexEndpoint *v1Endpoint
|
||||
client *http.Client
|
||||
// TODO(tiborvass): remove authConfig
|
||||
authConfig *types.AuthConfig
|
||||
id string
|
||||
id string
|
||||
}
|
||||
|
||||
type authTransport struct {
|
||||
|
@ -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
|
||||
|
||||
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
||||
// alongside all our requests.
|
||||
if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" {
|
||||
info, err := endpoint.Ping()
|
||||
info, err := endpoint.ping()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -171,43 +169,32 @@ func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint
|
|||
|
||||
jar, err := cookiejar.New(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
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) *Session {
|
||||
return &Session{
|
||||
authConfig: authConfig,
|
||||
func newSession(client *http.Client, endpoint *v1Endpoint) *session {
|
||||
return &session{
|
||||
client: client,
|
||||
indexEndpoint: endpoint,
|
||||
id: stringid.GenerateRandomID(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSession creates a new session
|
||||
// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
|
||||
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) (*registrytypes.SearchResults, error) {
|
||||
// searchRepositories performs a search against the remote repository
|
||||
func (r *session) searchRepositories(term string, limit int) (*registry.SearchResults, error) {
|
||||
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)
|
||||
u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u, 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.
|
||||
req.Header.Set("X-Docker-Token", "true")
|
||||
|
@ -222,6 +209,6 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea
|
|||
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")
|
||||
}
|
||||
|
|
|
@ -2,39 +2,9 @@ package registry // import "github.com/docker/docker/registry"
|
|||
|
||||
import (
|
||||
"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
|
||||
// either 1 or 2)
|
||||
type APIVersion int
|
||||
|
@ -58,7 +28,7 @@ var apiVersions = map[APIVersion]string{
|
|||
type RepositoryInfo struct {
|
||||
Name reference.Named
|
||||
// Index points to registry information
|
||||
Index *registrytypes.IndexInfo
|
||||
Index *registry.IndexInfo
|
||||
// Official indicates whether the repository is considered official.
|
||||
// If the registry is official, and the normalized name does not
|
||||
// contain a '/' (e.g. "foo"), then it is considered an official repo.
|
||||
|
|
Loading…
Add table
Reference in a new issue