Merge pull request #666 from mtrmac/registries.conf-mirrors

Rebase containers/image to v2.0.0
This commit is contained in:
Miloslav Trmač 2019-06-14 01:07:43 +02:00 committed by GitHub
commit d2d1796eb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 351 additions and 208 deletions

View File

@ -2,7 +2,7 @@
github.com/urfave/cli v1.20.0
github.com/kr/pretty v0.1.0
github.com/kr/text v0.1.0
github.com/containers/image 2c0349c99af7d90694b3faa0e9bde404d407b145
github.com/containers/image v2.0.0
github.com/containers/buildah 810efa340ab43753034e2ed08ec290e4abab7e72
github.com/vbauerster/mpb v3.3.4
github.com/mattn/go-isatty v0.0.4

View File

@ -29,44 +29,16 @@ type dockerImageSource struct {
cachedManifestMIMEType string // Only valid if cachedManifest != nil
}
// newImageSource creates a new `ImageSource` for the specified image reference
// `ref`.
//
// The following steps will be done during the instance creation:
//
// - Lookup the registry within the configured location in
// `sys.SystemRegistriesConfPath`. If there is no configured registry available,
// we fallback to the provided docker reference `ref`.
//
// - References which contain a configured prefix will be automatically rewritten
// to the correct target reference. For example, if the configured
// `prefix = "example.com/foo"`, `location = "example.com"` and the image will be
// pulled from the ref `example.com/foo/image`, then the resulting pull will
// effectively point to `example.com/image`.
//
// - If the rewritten reference succeeds, it will be used as the `dockerRef`
// in the client. If the rewrite fails, the function immediately returns an error.
//
// - Each mirror will be used (in the configured order) to test the
// availability of the image manifest on the remote location. For example,
// if the manifest is not reachable due to connectivity issues, then the next
// mirror will be tested instead. If no mirror is configured or contains the
// target manifest, then the initial `ref` will be tested as fallback. The
// creation of the new `dockerImageSource` only succeeds if a remote
// location with the available manifest was found.
//
// A cleanup call to `.Close()` is needed if the caller is done using the returned
// `ImageSource`.
// newImageSource creates a new ImageSource for the specified image reference.
// The caller must call .Close() on the returned ImageSource.
func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
registry, err := sysregistriesv2.FindRegistry(sys, ref.ref.Name())
if err != nil {
return nil, errors.Wrapf(err, "error loading registries configuration")
}
if registry == nil {
// No configuration was found for the provided reference, so we create
// a fallback registry by hand to make the client creation below work
// as intended.
// No configuration was found for the provided reference, so use the
// equivalent of a default configuration.
registry = &sysregistriesv2.Registry{
Endpoint: sysregistriesv2.Endpoint{
Location: ref.ref.String(),
@ -76,18 +48,19 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef
}
primaryDomain := reference.Domain(ref.ref)
// Found the registry within the sysregistriesv2 configuration. Now we test
// all endpoints for the manifest availability. If a working image source
// was found, it will be used for all future pull actions.
// Check all endpoints for the manifest availability. If we find one that does
// contain the image, it will be used for all future pull actions. Always try the
// non-mirror original location last; this both transparently handles the case
// of no mirrors configured, and ensures we return the error encountered when
// acessing the upstream location if all endpoints fail.
manifestLoadErr := errors.New("Internal error: newImageSource returned without trying any endpoint")
for _, endpoint := range append(registry.Mirrors, registry.Endpoint) {
logrus.Debugf("Trying to pull %q from endpoint %q", ref.ref, endpoint.Location)
newRef, err := endpoint.RewriteReference(ref.ref, registry.Prefix)
if err != nil {
return nil, err
}
dockerRef, err := newReference(newRef)
pullSources, err := registry.PullSourcesFromReference(ref.ref)
if err != nil {
return nil, err
}
for _, pullSource := range pullSources {
logrus.Debugf("Trying to pull %q", pullSource.Reference)
dockerRef, err := newReference(pullSource.Reference)
if err != nil {
return nil, err
}
@ -104,7 +77,7 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef
if err != nil {
return nil, err
}
client.tlsClientConfig.InsecureSkipVerify = endpoint.Insecure
client.tlsClientConfig.InsecureSkipVerify = pullSource.Endpoint.Insecure
testImageSource := &dockerImageSource{
ref: dockerRef,

View File

@ -1,2 +1,2 @@
This is a copy of github.com/docker/distribution/reference as of commit fb0bebc4b64e3881cc52a2478d749845ed76d2a8,
This is a copy of github.com/docker/distribution/reference as of commit 3226863cbcba6dbc2f6c83a37b28126c934af3f8,
except that ParseAnyReferenceWithSet has been removed to drop the dependency on github.com/docker/distribution/digestset.

View File

@ -55,6 +55,35 @@ func ParseNormalizedNamed(s string) (Named, error) {
return named, nil
}
// ParseDockerRef normalizes the image reference following the docker convention. This is added
// mainly for backward compatibility.
// The reference returned can only be either tagged or digested. For reference contains both tag
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
func ParseDockerRef(ref string) (Named, error) {
named, err := ParseNormalizedNamed(ref)
if err != nil {
return nil, err
}
if _, ok := named.(NamedTagged); ok {
if canonical, ok := named.(Canonical); ok {
// The reference is both tagged and digested, only
// return digested.
newNamed, err := WithName(canonical.Name())
if err != nil {
return nil, err
}
newCanonical, err := WithDigest(newNamed, canonical.Digest())
if err != nil {
return nil, err
}
return newCanonical, nil
}
}
return TagNameOnly(named), nil
}
// splitDockerDomain splits a repository name to domain and remotename string.
// If no valid domain is found, the default domain is used. Repository name
// needs to be already validated before.

View File

@ -15,7 +15,7 @@
// tag := /[\w][\w.-]{0,127}/
//
// digest := digest-algorithm ":" digest-hex
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
// digest-algorithm-separator := /[+.-_]/
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
@ -205,7 +205,7 @@ func Parse(s string) (Reference, error) {
var repo repository
nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
if nameMatch != nil && len(nameMatch) == 3 {
if len(nameMatch) == 3 {
repo.domain = nameMatch[1]
repo.path = nameMatch[2]
} else {

View File

@ -20,15 +20,15 @@ var (
optional(repeated(separatorRegexp, alphaNumericRegexp)))
// domainComponentRegexp restricts the registry domain component of a
// repository name to start with a component as defined by domainRegexp
// repository name to start with a component as defined by DomainRegexp
// and followed by an optional port.
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
// domainRegexp defines the structure of potential domain components
// DomainRegexp defines the structure of potential domain components
// that may be part of image names. This is purposely a subset of what is
// allowed by DNS to ensure backwards compatibility with Docker image
// names.
domainRegexp = expression(
DomainRegexp = expression(
domainComponentRegexp,
optional(repeated(literal(`.`), domainComponentRegexp)),
optional(literal(`:`), match(`[0-9]+`)))
@ -51,14 +51,14 @@ var (
// regexp has capturing groups for the domain and name part omitting
// the separating forward slash from either.
NameRegexp = expression(
optional(domainRegexp, literal(`/`)),
optional(DomainRegexp, literal(`/`)),
nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp)))
// anchoredNameRegexp is used to parse a name value, capturing the
// domain and trailing components.
anchoredNameRegexp = anchored(
optional(capture(domainRegexp), literal(`/`)),
optional(capture(DomainRegexp), literal(`/`)),
capture(nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp))))

View File

@ -18,57 +18,127 @@ VERSION 2 is the latest format of the `registries.conf` and is currently in
beta. This means in general VERSION 1 should be used in production environments
for now.
Every registry can have its own mirrors configured. The mirrors will be tested
in order for the availability of the remote manifest. This happens currently
only during an image pull. If the manifest is not reachable due to connectivity
issues or the unavailability of the remote manifest, then the next mirror will
be tested instead. If no mirror is configured or contains the manifest to be
pulled, then the initially provided reference will be used as fallback. It is
possible to set the `insecure` option per mirror, too.
### GLOBAL SETTINGS
Furthermore it is possible to specify a `prefix` for a registry. The `prefix`
is used to find the relevant target registry from where the image has to be
pulled. During the test for the availability of the image, the prefixed
location will be rewritten to the correct remote location. This applies to
mirrors as well as the fallback `location`. If no prefix is specified, it
defaults to the specified `location`. For example, if
`prefix = "example.com/foo"`, `location = "example.com"` and the image will be
pulled from `example.com/foo/image`, then the resulting pull will be effectively
point to `example.com/image`.
`unqualified-search-registries`
: An array of _host_[`:`_port_] registries to try when pulling an unqualified image, in order.
By default container runtimes use TLS when retrieving images from a registry.
If the registry is not setup with TLS, then the container runtime will fail to
pull images from the registry. If you set `insecure = true` for a registry or a
mirror you overwrite the `insecure` flag for that specific entry. This means
that the container runtime will attempt use unencrypted HTTP to pull the image.
It also allows you to pull from a registry with self-signed certificates.
### NAMESPACED `[[registry]]` SETTINGS
If you set the `unqualified-search = true` for the registry, then it is possible
to omit the registry hostname when pulling images. This feature does not work
together with a specified `prefix`.
The bulk of the configuration is represented as an array of `[[registry]]`
TOML tables; the settings may therefore differ among different registries
as well as among different namespaces/repositories within a registry.
If `blocked = true` then it is not allowed to pull images from that registry.
#### Choosing a `[[registry]]` TOML table
Given an image name, a single `[[registry]]` TOML table is chosen based on its `prefix` field.
`prefix`
: A prefix of the user-specified image name, i.e. using one of the following formats:
- _host_[`:`_port_]
- _host_[`:`_port_]`/`_namespace_[`/`_namespace_…]
- _host_[`:`_port_]`/`_namespace_[`/`_namespace_…]`/`_repo_
- _host_[`:`_port_]`/`_namespace_[`/`_namespace_…]`/`_repo_(`:`_tag|`@`_digest_)
The user-specified image name must start with the specified `prefix` (and continue
with the appropriate separator) for a particular `[[registry]]` TOML table to be
considered; (only) the TOML table with the longest match is used.
As a special case, the `prefix` field can be missing; if so, it defaults to the value
of the `location` field (described below).
#### Per-namespace settings
`insecure`
: `true` or `false`.
By default, container runtimes require TLS when retrieving images from a registry.
If `insecure` is set to `true`, unencrypted HTTP as well as TLS connections with untrusted
certificates are allowed.
`blocked`
: `true` or `false`.
If `true`, pulling images with matching names is forbidden.
#### Remapping and mirroring registries
The user-specified image reference is, primarily, a "logical" image name, always used for naming
the image. By default, the image reference also directly specifies the registry and repository
to use, but the following options can be used to redirect the underlying accesses
to different registry servers or locations (e.g. to support configurations with no access to the
internet without having to change `Dockerfile`s, or to add redundancy).
`location`
: Accepts the same format as the `prefix` field, and specifies the physical location
of the `prefix`-rooted namespace.
By default, this equal to `prefix` (in which case `prefix` can be omitted and the
`[[registry]]` TOML table can only specify `location`).
Example: Given
```
prefix = "example.com/foo"
location = "internal-registry-for-example.net/bar"
```
requests for the image `example.com/foo/myimage:latest` will actually work with the
`internal-registry-for-example.net/bar/myimage:latest` image.
`mirror`
: An array of TOML tables specifiying (possibly-partial) mirrors for the
`prefix`-rooted namespace.
The mirrors are attempted in the specified order; the first one that can be
contacted and contains the image will be used (and if none of the mirrors contains the image,
the primary location specified by the `registry.location` field, or using the unmodified
user-specified reference, is tried last).
Each TOML table in the `mirror` array can contain the following fields, with the same semantics
as if specified in the `[[registry]]` TOML table directly:
- `location`
- `insecure`
`mirror-by-digest-only`
: `true` or `false`.
If `true`, mirrors will only be used during pulling if the image reference includes a digest.
Referencing an image by digest ensures that the same is always used
(whereas referencing an image by a tag may cause different registries to return
different images if the tag mapping is out of sync).
Note that if this is `true`, images referenced by a tag will only use the primary
registry, failing if that registry is not accessible.
*Note*: Redirection and mirrors are currently processed only when reading images, not when pushing
to a registry; that may change in the future.
### EXAMPLE
```
unqualified-search-registries = ["example.com"]
[[registry]]
location = "example.com"
insecure = false
prefix = "example.com/foo"
unqualified-search = false
insecure = false
blocked = false
mirror = [
{ location = "example-mirror-0.local", insecure = false },
{ location = "example-mirror-1.local", insecure = true }
]
location = "internal-registry-for-example.com/bar"
[[registry.mirror]]
location = "example-mirror-0.local/mirror-for-foo"
[[registry.mirror]]
location = "example-mirror-1.local/mirrors/foo"
insecure = true
```
Given the above, a pull of `example.com/foo/image:latest` will try:
1. `example-mirror-0.local/mirror-for-foo/image:latest`
2. `example-mirror-1.local/mirrors/foo/image:latest`
3. `internal-registry-for-example.net/bar/myimage:latest`
in order, and use the first one that exists.
## VERSION 1
VERSION 1 can be used as alternative to the VERSION 2, but it is not capable in
using registry mirrors or a prefix.
VERSION 1 can be used as alternative to the VERSION 2, but it does not support
using registry mirrors, longest-prefix matches, or location rewriting.
The TOML_format is used to build a simple list for registries under three
The TOML format is used to build a simple list of registries under three
categories: `registries.search`, `registries.insecure`, and `registries.block`.
You can list multiple registries using a comma separated list.
@ -76,11 +146,11 @@ Search registries are used when the caller of a container runtime does not fully
container image that they want to execute. These registries are prepended onto the front
of the specified container image until the named image is found at a registry.
Note insecure registries can be used for any registry, not just the registries listed
Note that insecure registries can be used for any registry, not just the registries listed
under search.
The fields `registries.insecure` and `registries.block` work as like as the
`insecure` and `blocked` from VERSION 2.
The `registries.insecure` and `registries.block` lists have the same meaning as the
`insecure` and `blocked` fields in VERSION 2.
### EXAMPLE
The following example configuration defines two searchable registries, one

View File

@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
@ -35,10 +36,10 @@ type Endpoint struct {
Insecure bool `toml:"insecure"`
}
// RewriteReference will substitute the provided reference `prefix` to the
// rewriteReference will substitute the provided reference `prefix` to the
// endpoints `location` from the `ref` and creates a new named reference from it.
// The function errors if the newly created reference is not parsable.
func (e *Endpoint) RewriteReference(ref reference.Named, prefix string) (reference.Named, error) {
func (e *Endpoint) rewriteReference(ref reference.Named, prefix string) (reference.Named, error) {
refString := ref.String()
if !refMatchesPrefix(refString, prefix) {
return nil, fmt.Errorf("invalid prefix '%v' for reference '%v'", prefix, refString)
@ -61,8 +62,10 @@ type Registry struct {
Mirrors []Endpoint `toml:"mirror"`
// If true, pulling from the registry will be blocked.
Blocked bool `toml:"blocked"`
// If true, the registry can be used when pulling an unqualified image.
Search bool `toml:"unqualified-search"`
// If true, mirrors will only be used for digest pulls. Pulling images by
// tag can potentially yield different images, depending on which endpoint
// we pull from. Forcing digest-pulls for mirrors avoids that issue.
MirrorByDigestOnly bool `toml:"mirror-by-digest-only"`
// Prefix is used for matching images, and to translate one namespace to
// another. If `Prefix="example.com/bar"`, `location="example.com/foo/bar"`
// and we pull from "example.com/bar/myimage:latest", the image will
@ -71,6 +74,41 @@ type Registry struct {
Prefix string `toml:"prefix"`
}
// PullSource consists of an Endpoint and a Reference. Note that the reference is
// rewritten according to the registries prefix and the Endpoint's location.
type PullSource struct {
Endpoint Endpoint
Reference reference.Named
}
// PullSourcesFromReference returns a slice of PullSource's based on the passed
// reference.
func (r *Registry) PullSourcesFromReference(ref reference.Named) ([]PullSource, error) {
var endpoints []Endpoint
if r.MirrorByDigestOnly {
// Only use mirrors when the reference is a digest one.
if _, isDigested := ref.(reference.Canonical); isDigested {
endpoints = append(r.Mirrors, r.Endpoint)
} else {
endpoints = []Endpoint{r.Endpoint}
}
} else {
endpoints = append(r.Mirrors, r.Endpoint)
}
sources := []PullSource{}
for _, ep := range endpoints {
rewritten, err := ep.rewriteReference(ref, r.Prefix)
if err != nil {
return nil, err
}
sources = append(sources, PullSource{Endpoint: ep, Reference: rewritten})
}
return sources, nil
}
// V1TOMLregistries is for backwards compatibility to sysregistries v1
type V1TOMLregistries struct {
Registries []string `toml:"registries"`
@ -83,11 +121,35 @@ type V1TOMLConfig struct {
Block V1TOMLregistries `toml:"block"`
}
// V1RegistriesConf is the sysregistries v1 configuration format.
type V1RegistriesConf struct {
V1TOMLConfig `toml:"registries"`
}
// Nonempty returns true if config contains at least one configuration entry.
func (config *V1RegistriesConf) Nonempty() bool {
return (len(config.V1TOMLConfig.Search.Registries) != 0 ||
len(config.V1TOMLConfig.Insecure.Registries) != 0 ||
len(config.V1TOMLConfig.Block.Registries) != 0)
}
// V2RegistriesConf is the sysregistries v2 configuration format.
type V2RegistriesConf struct {
Registries []Registry `toml:"registry"`
// An array of host[:port] (not prefix!) entries to use for resolving unqualified image references
UnqualifiedSearchRegistries []string `toml:"unqualified-search-registries"`
}
// Nonempty returns true if config contains at least one configuration entry.
func (config *V2RegistriesConf) Nonempty() bool {
return (len(config.Registries) != 0 ||
len(config.UnqualifiedSearchRegistries) != 0)
}
// tomlConfig is the data type used to unmarshal the toml config.
type tomlConfig struct {
Registries []Registry `toml:"registry"`
// backwards compatability to sysregistries v1
V1TOMLConfig `toml:"registries"`
V2RegistriesConf
V1RegistriesConf // for backwards compatibility with sysregistries v1
}
// InvalidRegistries represents an invalid registry configurations. An example
@ -120,12 +182,10 @@ func parseLocation(input string) (string, error) {
return trimmed, nil
}
// getV1Registries transforms v1 registries in the config into an array of v2
// registries of type Registry.
func getV1Registries(config *tomlConfig) ([]Registry, error) {
// ConvertToV2 returns a v2 config corresponding to a v1 one.
func (config *V1RegistriesConf) ConvertToV2() (*V2RegistriesConf, error) {
regMap := make(map[string]*Registry)
// We must preserve the order of config.V1Registries.Search.Registries at least. The order of the
// other registries is not really important, but make it deterministic (the same for the same config file)
// The order of the registries is not really important, but make it deterministic (the same for the same config file)
// to minimize behavior inconsistency and not contribute to difficult-to-reproduce situations.
registryOrder := []string{}
@ -148,15 +208,6 @@ func getV1Registries(config *tomlConfig) ([]Registry, error) {
return reg, nil
}
// Note: config.V1Registries.Search needs to be processed first to ensure registryOrder is populated in the right order
// if one of the search registries is also in one of the other lists.
for _, search := range config.V1TOMLConfig.Search.Registries {
reg, err := getRegistry(search)
if err != nil {
return nil, err
}
reg.Search = true
}
for _, blocked := range config.V1TOMLConfig.Block.Registries {
reg, err := getRegistry(blocked)
if err != nil {
@ -172,28 +223,31 @@ func getV1Registries(config *tomlConfig) ([]Registry, error) {
reg.Insecure = true
}
registries := []Registry{}
res := &V2RegistriesConf{
UnqualifiedSearchRegistries: config.V1TOMLConfig.Search.Registries,
}
for _, location := range registryOrder {
reg := regMap[location]
registries = append(registries, *reg)
res.Registries = append(res.Registries, *reg)
}
return registries, nil
return res, nil
}
// postProcessRegistries checks the consistency of all registries (e.g., set
// the Prefix to Location if not set) and applies conflict checks. It returns an
// array of cleaned registries and error in case of conflicts.
func postProcessRegistries(regs []Registry) ([]Registry, error) {
var registries []Registry
regMap := make(map[string][]Registry)
// anchoredDomainRegexp is an internal implementation detail of postProcess, defining the valid values of elements of UnqualifiedSearchRegistries.
var anchoredDomainRegexp = regexp.MustCompile("^" + reference.DomainRegexp.String() + "$")
for _, reg := range regs {
var err error
// postProcess checks the consistency of all the configuration, looks for conflicts,
// and normalizes the configuration (e.g., sets the Prefix to Location if not set).
func (config *V2RegistriesConf) postProcess() error {
regMap := make(map[string][]*Registry)
for i := range config.Registries {
reg := &config.Registries[i]
// make sure Location and Prefix are valid
var err error
reg.Location, err = parseLocation(reg.Location)
if err != nil {
return nil, err
return err
}
if reg.Prefix == "" {
@ -201,7 +255,7 @@ func postProcessRegistries(regs []Registry) ([]Registry, error) {
} else {
reg.Prefix, err = parseLocation(reg.Prefix)
if err != nil {
return nil, err
return err
}
}
@ -209,10 +263,9 @@ func postProcessRegistries(regs []Registry) ([]Registry, error) {
for _, mir := range reg.Mirrors {
mir.Location, err = parseLocation(mir.Location)
if err != nil {
return nil, err
return err
}
}
registries = append(registries, reg)
regMap[reg.Location] = append(regMap[reg.Location], reg)
}
@ -222,22 +275,32 @@ func postProcessRegistries(regs []Registry) ([]Registry, error) {
//
// Note: we need to iterate over the registries array to ensure a
// deterministic behavior which is not guaranteed by maps.
for _, reg := range registries {
for _, reg := range config.Registries {
others, _ := regMap[reg.Location]
for _, other := range others {
if reg.Insecure != other.Insecure {
msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'insecure' setting", reg.Location)
return nil, &InvalidRegistries{s: msg}
return &InvalidRegistries{s: msg}
}
if reg.Blocked != other.Blocked {
msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'blocked' setting", reg.Location)
return nil, &InvalidRegistries{s: msg}
return &InvalidRegistries{s: msg}
}
}
}
return registries, nil
for i := range config.UnqualifiedSearchRegistries {
registry, err := parseLocation(config.UnqualifiedSearchRegistries[i])
if err != nil {
return err
}
if !anchoredDomainRegexp.MatchString(registry) {
return &InvalidRegistries{fmt.Sprintf("Invalid unqualified-search-registries entry %#v", registry)}
}
config.UnqualifiedSearchRegistries[i] = registry
}
return nil
}
// getConfigPath returns the system-registries config path if specified.
@ -260,7 +323,7 @@ var configMutex = sync.Mutex{}
// configCache caches already loaded configs with config paths as keys and is
// used to avoid redudantly parsing configs. Concurrent accesses to the cache
// are synchronized via configMutex.
var configCache = make(map[string][]Registry)
var configCache = make(map[string]*V2RegistriesConf)
// InvalidateCache invalidates the registry cache. This function is meant to be
// used for long-running processes that need to reload potential changes made to
@ -268,20 +331,18 @@ var configCache = make(map[string][]Registry)
func InvalidateCache() {
configMutex.Lock()
defer configMutex.Unlock()
configCache = make(map[string][]Registry)
configCache = make(map[string]*V2RegistriesConf)
}
// GetRegistries loads and returns the registries specified in the config.
// Note the parsed content of registry config files is cached. For reloading,
// use `InvalidateCache` and re-call `GetRegistries`.
func GetRegistries(ctx *types.SystemContext) ([]Registry, error) {
// getConfig returns the config object corresponding to ctx, loading it if it is not yet cached.
func getConfig(ctx *types.SystemContext) (*V2RegistriesConf, error) {
configPath := getConfigPath(ctx)
configMutex.Lock()
defer configMutex.Unlock()
// if the config has already been loaded, return the cached registries
if registries, inCache := configCache[configPath]; inCache {
return registries, nil
if config, inCache := configCache[configPath]; inCache {
return config, nil
}
// load the config
@ -292,51 +353,53 @@ func GetRegistries(ctx *types.SystemContext) ([]Registry, error) {
// isn't set. Note: if ctx.SystemRegistriesConfPath points to
// the default config, we will still return an error.
if os.IsNotExist(err) && (ctx == nil || ctx.SystemRegistriesConfPath == "") {
return []Registry{}, nil
return &V2RegistriesConf{Registries: []Registry{}}, nil
}
return nil, err
}
registries := config.Registries
v2Config := &config.V2RegistriesConf
// backwards compatibility for v1 configs
v1Registries, err := getV1Registries(config)
if err != nil {
return nil, err
}
if len(v1Registries) > 0 {
if len(registries) > 0 {
if config.V1RegistriesConf.Nonempty() {
if config.V2RegistriesConf.Nonempty() {
return nil, &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"}
}
registries = v1Registries
v2, err := config.V1RegistriesConf.ConvertToV2()
if err != nil {
return nil, err
}
v2Config = v2
}
registries, err = postProcessRegistries(registries)
if err != nil {
if err := v2Config.postProcess(); err != nil {
return nil, err
}
// populate the cache
configCache[configPath] = registries
return registries, err
configCache[configPath] = v2Config
return v2Config, nil
}
// FindUnqualifiedSearchRegistries returns all registries that are configured
// for unqualified image search (i.e., with Registry.Search == true).
func FindUnqualifiedSearchRegistries(ctx *types.SystemContext) ([]Registry, error) {
registries, err := GetRegistries(ctx)
// GetRegistries loads and returns the registries specified in the config.
// Note the parsed content of registry config files is cached. For reloading,
// use `InvalidateCache` and re-call `GetRegistries`.
func GetRegistries(ctx *types.SystemContext) ([]Registry, error) {
config, err := getConfig(ctx)
if err != nil {
return nil, err
}
return config.Registries, nil
}
unqualified := []Registry{}
for _, reg := range registries {
if reg.Search {
unqualified = append(unqualified, reg)
}
// UnqualifiedSearchRegistries returns a list of host[:port] entries to try
// for unqualified image search, in the returned order)
func UnqualifiedSearchRegistries(ctx *types.SystemContext) ([]string, error) {
config, err := getConfig(ctx)
if err != nil {
return nil, err
}
return unqualified, nil
return config.UnqualifiedSearchRegistries, nil
}
// refMatchesPrefix returns true iff ref,
@ -371,14 +434,14 @@ func refMatchesPrefix(ref, prefix string) bool {
// — note that this requires the name to start with an explicit hostname!).
// If no Registry prefixes the image, nil is returned.
func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) {
registries, err := GetRegistries(ctx)
config, err := getConfig(ctx)
if err != nil {
return nil, err
}
reg := Registry{}
prefixLen := 0
for _, r := range registries {
for _, r := range config.Registries {
if refMatchesPrefix(ref, r.Prefix) {
length := len(r.Prefix)
if length > prefixLen {
@ -393,21 +456,12 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) {
return nil, nil
}
// Reads the global registry file from the filesystem. Returns a byte array.
func readRegistryConf(configPath string) ([]byte, error) {
configBytes, err := ioutil.ReadFile(configPath)
return configBytes, err
}
// Used in unittests to parse custom configs without a types.SystemContext.
var readConf = readRegistryConf
// Loads the registry configuration file from the filesystem and then unmarshals
// it. Returns the unmarshalled object.
func loadRegistryConf(configPath string) (*tomlConfig, error) {
config := &tomlConfig{}
configBytes, err := readConf(configPath)
configBytes, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, err
}

View File

@ -29,37 +29,54 @@ registries = []
# The second version of the configuration format allows to specify registry
# mirrors:
#
# # An array of host[:port] registries to try when pulling an unqualified image, in order.
# unqualified-search-registries = ["example.com"]
#
# [[registry]]
# # The main location of the registry
# location = "example.com"
#
# # If true, certs verification will be skipped and HTTP (non-TLS) connections
# # will be allowed.
# insecure = false
#
# # Prefix is used for matching images, and to translate one namespace to
# # another. If `prefix = "example.com/foo"`, `location = "example.com"` and
# # we pull from "example.com/foo/myimage:latest", the image will effectively be
# # pulled from "example.com/myimage:latest". If no Prefix is specified,
# # it defaults to the specified `location`. When a prefix is used, then a pull
# # without specifying the prefix is not possible any more.
# # The "prefix" field is used to choose the relevant [[registry]] TOML table;
# # (only) the TOML table with the longest match for the input image name
# # (taking into account namespace/repo/tag/digest separators) is used.
# #
# # If the prefix field is missing, it defaults to be the same as the "location" field.
# prefix = "example.com/foo"
#
# # If true, the registry can be used when pulling an unqualified image. If a
# # prefix is specified, unqualified pull is not possible any more.
# unqualified-search = false
# # If true, unencrypted HTTP as well as TLS connections with untrusted
# # certificates are allowed.
# insecure = false
#
# # If true, pulling from the registry will be blocked.
# # If true, pulling images with matching names is forbidden.
# blocked = false
#
# # All available mirrors of the registry. The mirrors will be evaluated in
# # order during an image pull. Furthermore it is possible to specify the
# # `insecure` flag per registry mirror, too.
# mirror = [
# { location = "example-mirror-0.local", insecure = false },
# { location = "example-mirror-1.local", insecure = true },
# # It is also possible to specify an additional path within the `location`.
# # A pull to `example.com/foo/image:latest` will then result in
# # `example-mirror-2.local/path/image:latest`.
# { location = "example-mirror-2.local/path" },
# ]
# # The physical location of the "prefix"-rooted namespace.
# #
# # By default, this equal to "prefix" (in which case "prefix" can be omitted
# # and the [[registry]] TOML table can only specify "location").
# #
# # Example: Given
# # prefix = "example.com/foo"
# # location = "internal-registry-for-example.net/bar"
# # requests for the image example.com/foo/myimage:latest will actually work with the
# # internal-registry-for-example.net/bar/myimage:latest image.
# location = internal-registry-for-example.com/bar"
#
# # (Possibly-partial) mirrors for the "prefix"-rooted namespace.
# #
# # The mirrors are attempted in the specified order; the first one that can be
# # contacted and contains the image will be used (and if none of the mirrors contains the image,
# # the primary location specified by the "registry.location" field, or using the unmodified
# # user-specified reference, is tried last).
# #
# # Each TOML table in the "mirror" array can contain the following fields, with the same semantics
# # as if specified in the [[registry]] TOML table directly:
# # - location
# # - insecure
# [[registry.mirror]]
# location = "example-mirror-0.local/mirror-for-foo"
# [[registry.mirror]]
# location = "example-mirror-1.local/mirrors/foo"
# insecure = true
# # Given the above, a pull of example.com/foo/image:latest will try:
# # 1. example-mirror-0.local/mirror-for-foo/image:latest
# # 2. example-mirror-1.local/mirrors/foo/image:latest
# # 3. internal-registry-for-example.net/bar/myimage:latest
# # in order, and use the first one that exists.

View File

@ -4,14 +4,14 @@ import "fmt"
const (
// VersionMajor is for an API incompatible changes
VersionMajor = 1
VersionMajor = 2
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 7
VersionMinor = 0
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = "-dev"
VersionDev = ""
)
// Version is the specification version that the package types support.