Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions api/types/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,209 @@ package registry // import "github.com/docker/docker/api/types/registry"

import (
"encoding/json"
"fmt"
"net"
"net/url"
"strings"

"github.com/docker/distribution/reference"
"github.com/opencontainers/image-spec/specs-go/v1"
)

var (
// DefaultEndpoint for docker.io
DefaultEndpoint = Endpoint{
Address: "https://registry-1.docker.io",
url: url.URL{
Scheme: "https",
Host: "registry-1.docker.io",
},
}
)

// ServiceConfig stores daemon registry services configuration.
type ServiceConfig struct {
AllowNondistributableArtifactsCIDRs []*NetIPNet
AllowNondistributableArtifactsHostnames []string
InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
Mirrors []string
Registries Registries
}

// Registries is a slice of type Registry.
type Registries []Registry

// Registry includes all data relevant for the lookup of push and pull
// endpoints.
type Registry struct {
// Prefix will be used for the lookup of push and pull endpoints.
Prefix string `json:"prefix"`
// Pull is a slice of registries serving as pull endpoints.
Pull []Endpoint `json:"pull,omitempty"`
// Push is a slice of registries serving as push endpoints.
Push []Endpoint `json:"push,omitempty"`
// prefixLength is the length of prefix and avoids redundant length
// calculations.
prefixLength int
}

// Endpoint includes all data associated with a given registry endpoint.
type Endpoint struct {
// Address is the endpoints base URL when assembling a repository in a
// registry (e.g., "registry.com:5000/v2").
Address string `json:"address"`
// url is used during endpoint lookup and avoids to redundantly parse
// Address when the Endpoint is used.
url url.URL
// InsecureSkipVerify: if true, TLS accepts any certificate presented
// by the server and any host name in that certificate. In this mode,
// TLS is susceptible to man-in-the-middle attacks. This should be used
// only for testing
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
}

// RewriteReference strips the prefix from ref and appends it to registry.
// If the prefix is empty, ref remains unchanged. An error is returned if
// prefix doesn't prefix ref.
func RewriteReference(ref reference.Named, prefix string, registry *url.URL) (reference.Named, error) {
// Sanity check the provided arguments
if ref == nil {
return nil, fmt.Errorf("provided reference is nil")
}
if registry == nil {
return nil, fmt.Errorf("provided registry is nil")
}

// don't rewrite the default endpoints
if *registry == DefaultEndpoint.url {
return ref, nil
}

if prefix == "" {
return ref, nil
}

baseAddress := strings.TrimPrefix(registry.String(), registry.Scheme+"://")

refStr := ref.String()
if !strings.HasPrefix(refStr, prefix) {
return nil, fmt.Errorf("unable to rewrite reference %q with prefix %q", refStr, prefix)
}
remainder := strings.TrimPrefix(refStr, prefix)
remainder = strings.TrimPrefix(remainder, "/")
baseAddress = strings.TrimSuffix(baseAddress, "/")

newRefStr := baseAddress + "/" + remainder
newRef, err := reference.ParseNamed(newRefStr)
if err != nil {
return nil, fmt.Errorf("unable to rewrite reference %q with prefix %q to %q: %v", refStr, prefix, newRefStr, err)
}
return newRef, nil
}

// GetURL returns the Endpoint's URL.
func (r *Endpoint) GetURL() *url.URL {
// return the pointer of a copy
url := r.url
return &url
}

// CalcPrefix checks if the registry prefixes reference and returns the length
// of the prefix. It returns 0 if the registry does not prefix reference.
func (r *Registry) CalcPrefix(reference string) int {
if strings.HasPrefix(reference, r.Prefix) {
return r.prefixLength
}
return 0
}

// FindRegistry returns the Registry with the longest Prefix for reference or
// nil if no Registry prefixes reference.
func (r Registries) FindRegistry(reference string) *Registry {
var max int
var reg *Registry

max = 0
reg = nil
for i := range r {
lenPref := r[i].CalcPrefix(reference)
if lenPref > max {
max = lenPref
reg = &r[i]
}
}

return reg
}

// Prepare sets up the Endpoint.
func (r *Endpoint) Prepare() error {
if !strings.HasPrefix(r.Address, "http://") && !strings.HasPrefix(r.Address, "https://") {
return fmt.Errorf("%s: address must start with %q or %q", r.Address, "http://", "https://")
}

u, err := url.Parse(r.Address)
if err != nil {
return err
}
r.url = *u
return nil
}

// Prepare must be called on each new Registry. It sets up all specified push
// and pull endpoints, and fills in defaults for the official "docker.io"
// registry.
func (r *Registry) Prepare() error {
if len(r.Prefix) == 0 {
return fmt.Errorf("Registry requires a prefix")
}

// return an error with the preifx doesn't end with a '/'
if !strings.HasSuffix(r.Prefix, "/") {
return fmt.Errorf("Prefix must end with a '/': prefixes match only at path boundaries")
}
r.prefixLength = len(r.Prefix)

official := false
// any prefix pointing to "docker.io/" is considered official
if strings.HasPrefix(r.Prefix, "docker.io/") {
official = true
}

prepareEndpoints := func(endpoints []Endpoint) ([]Endpoint, error) {
addDefaultEndpoint := official
for i := range endpoints {
if err := endpoints[i].Prepare(); err != nil {
return nil, err
}
if official && addDefaultEndpoint {
if endpoints[i].Address == DefaultEndpoint.Address {
addDefaultEndpoint = false
}
}
}
// if the default endpoint isn't specified, add it
if addDefaultEndpoint {
r.Pull = append(endpoints, DefaultEndpoint)
}
return endpoints, nil
}

var err error
if r.Pull, err = prepareEndpoints(r.Pull); err != nil {
return err
}

if r.Push, err = prepareEndpoints(r.Push); err != nil {
return err
}

if len(r.Pull) == 0 && len(r.Push) == 0 {
return fmt.Errorf("Registry with prefix %q without push or pull endpoints", r.Prefix)
}

return nil
}

// NetIPNet is the net.IPNet type, which can be marshalled and
Expand Down
4 changes: 4 additions & 0 deletions daemon/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ func findConfigurationConflicts(config map[string]interface{}, flags *pflag.Flag
unknownKeys := make(map[string]interface{})
for key, value := range config {
if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] {
// skip config-only flags
if key == "registries" {
continue
}
unknownKeys[key] = value
}
}
Expand Down
3 changes: 1 addition & 2 deletions daemon/images/image_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (i *ImageService) GetRepository(ctx context.Context, ref reference.Named, a
}

// get endpoints
endpoints, err := i.registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
endpoints, err := i.registryService.LookupPullEndpoints(ref)
if err != nil {
return nil, false, err
}
Expand All @@ -116,7 +116,6 @@ func (i *ImageService) GetRepository(ctx context.Context, ref reference.Named, a
if endpoint.Version == registry.APIVersion1 {
continue
}

repository, confirmedV2, lastError = distribution.NewV2Repository(ctx, repoInfo, endpoint, nil, authConfig, "pull")
if lastError == nil && confirmedV2 {
break
Expand Down
34 changes: 34 additions & 0 deletions daemon/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ import (
// - Registry mirrors
// - Daemon live restore
func (daemon *Daemon) Reload(conf *config.Config) (err error) {
if conf == nil {
return fmt.Errorf("prodived config is nil")
}
// check for incompatible options
if err := conf.ServiceOptions.CompatCheck(); err != nil {
return nil
}

daemon.configStore.Lock()
attributes := map[string]string{}

Expand Down Expand Up @@ -62,6 +70,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
if err := daemon.reloadRegistryMirrors(conf, attributes); err != nil {
return err
}
if err := daemon.reloadRegistries(conf, attributes); err != nil {
return err
}
if err := daemon.reloadLiveRestore(conf, attributes); err != nil {
return err
}
Expand Down Expand Up @@ -295,6 +306,29 @@ func (daemon *Daemon) reloadRegistryMirrors(conf *config.Config, attributes map[
return nil
}

// reloadRegistries updates the registries configuration and the passed attributes
func (daemon *Daemon) reloadRegistries(conf *config.Config, attributes map[string]string) error {
// update corresponding configuration
if conf.IsValueSet("registries") {
daemon.configStore.Registries = conf.Registries
if err := daemon.RegistryService.LoadRegistries(conf.Registries); err != nil {
return err
}
}

// prepare reload event attributes with updatable configurations
if daemon.configStore.Registries != nil {
registries, err := json.Marshal(daemon.configStore.Registries)
if err != nil {
return err
}
attributes["registries"] = string(registries)
} else {
attributes["registries"] = "[]"
}
return nil
}

// reloadLiveRestore updates configuration with live restore option
// and updates the passed attributes
func (daemon *Daemon) reloadLiveRestore(conf *config.Config, attributes map[string]string) error {
Expand Down
2 changes: 1 addition & 1 deletion distribution/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
return err
}

endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(ref)
if err != nil {
return err
}
Expand Down
17 changes: 13 additions & 4 deletions distribution/pull_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/distribution/metadata"
"github.com/docker/docker/distribution/xfer"
"github.com/docker/docker/image"
Expand Down Expand Up @@ -334,6 +335,14 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
return false, err
}

// Note that pullRef is only used for pulling while ref is used as
// the reference for storing the image
pullRef, err := registrytypes.RewriteReference(ref, p.endpoint.Prefix, p.endpoint.URL)
if err != nil {
return false, err
}
logrus.Infof("rewriting %q to %q", ref.String(), pullRef.String())

var (
manifest distribution.Manifest
tagOrDigest string // Used for logging/progress only
Expand Down Expand Up @@ -379,7 +388,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
// the other side speaks the v2 protocol.
p.confirmedV2 = true

logrus.Debugf("Pulling ref from V2 registry: %s", reference.FamiliarString(ref))
logrus.Infof("Pulling ref %q from V2 registry: %s", ref, p.endpoint.URL)
progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+reference.FamiliarName(p.repo.Named()))

var (
Expand All @@ -392,17 +401,17 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
if p.config.RequireSchema2 {
return false, fmt.Errorf("invalid manifest: not schema2")
}
id, manifestDigest, err = p.pullSchema1(ctx, ref, v, platform)
id, manifestDigest, err = p.pullSchema1(ctx, pullRef, v, platform)
if err != nil {
return false, err
}
case *schema2.DeserializedManifest:
id, manifestDigest, err = p.pullSchema2(ctx, ref, v, platform)
id, manifestDigest, err = p.pullSchema2(ctx, pullRef, v, platform)
if err != nil {
return false, err
}
case *manifestlist.DeserializedManifestList:
id, manifestDigest, err = p.pullManifestList(ctx, ref, v, platform)
id, manifestDigest, err = p.pullManifestList(ctx, pullRef, v, platform)
if err != nil {
return false, err
}
Expand Down
2 changes: 1 addition & 1 deletion distribution/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
return err
}

endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(reference.Domain(repoInfo.Name))
endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(ref)
if err != nil {
return err
}
Expand Down
11 changes: 11 additions & 0 deletions distribution/push_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/client"
apitypes "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/distribution/metadata"
"github.com/docker/docker/distribution/xfer"
"github.com/docker/docker/layer"
Expand Down Expand Up @@ -113,6 +114,16 @@ func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) {
}

func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id digest.Digest) error {
newRef, err := registrytypes.RewriteReference(ref, p.endpoint.Prefix, p.endpoint.URL)
if err != nil {
return err
}
logrus.Infof("rewriting %q to %q", ref.String(), newRef.String())
ref, err = reference.WithTag(newRef, ref.Tag())
if err != nil {
return err
}

logrus.Debugf("Pushing repository: %s", reference.FamiliarString(ref))

imgConfig, err := p.config.ImageStore.Get(id)
Expand Down
Loading