diff --git a/docs/containers-registries.conf.5.md b/docs/containers-registries.conf.5.md index 928387b69a..d5fcde6ce9 100644 --- a/docs/containers-registries.conf.5.md +++ b/docs/containers-registries.conf.5.md @@ -108,16 +108,25 @@ Each TOML table in the `mirror` array can contain the following fields, with the as if specified in the `[[registry]]` TOML table directly: - `location` - `insecure` +- `digest-only` `mirror-by-digest-only` : `true` or `false`. If `true`, mirrors will only be used during pulling if the image reference includes a digest. +Note that if this is `true`, images referenced by a tag will only use the primary +registry, failing if that registry is not accessible. + +`digest-only`: `true` or `false`. +: for each mirror in the `mirror` array, the mirror has `digest-only` set to `true` will only be used during pulling if the image reference includes a digest. +If `mirror-by-digest-only` for primary registry is `true`, +it will be honored and apply to all mirrors, regardless of whether the `digest-only` is set for each mirror. +Note that if all mirrors in the `mirror` array have `digest-only = true`, images referenced by a tag will only use the primary +registry, failing if that registry is not accessible. + 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. diff --git a/pkg/sysregistriesv2/system_registries_v2.go b/pkg/sysregistriesv2/system_registries_v2.go index c8a603c4ef..24b834bb53 100644 --- a/pkg/sysregistriesv2/system_registries_v2.go +++ b/pkg/sysregistriesv2/system_registries_v2.go @@ -53,6 +53,12 @@ type Endpoint struct { // If true, certs verification will be skipped and HTTP (non-TLS) // connections will be allowed. Insecure bool `toml:"insecure,omitempty"` + // If true, the mirror 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. + // This only applies to the mirror of the primary registry. + // If mirror-by-digest-only is set for the primary registry, mirror-by-digest-only is honored. + DigestOnly bool `toml:"digest-only,omitempty"` } // userRegistriesFile is the path to the per user registry configuration file. @@ -129,17 +135,31 @@ type PullSource struct { // PullSourcesFromReference returns a slice of PullSource's based on the passed // reference. func (r *Registry) PullSourcesFromReference(ref reference.Named) ([]PullSource, error) { + if r.DigestOnly { + return nil, fmt.Errorf("digest-only does not apply to primary registry") + } var endpoints []Endpoint - + _, isDigested := ref.(reference.Canonical) if r.MirrorByDigestOnly { // Only use mirrors when the reference is a digest one. - if _, isDigested := ref.(reference.Canonical); isDigested { + if isDigested { endpoints = append(r.Mirrors, r.Endpoint) } else { endpoints = []Endpoint{r.Endpoint} } } else { - endpoints = append(r.Mirrors, r.Endpoint) + // check digest-only for each mirror if non mirror-by-digest-only set, since mirror-by-digest-only is honored + for _, mirror := range r.Mirrors { + if mirror.DigestOnly { + if isDigested { + // Only use the mirror when the reference is a digest one. + endpoints = append(endpoints, mirror) + } + } else { + endpoints = append(endpoints, mirror) + } + } + endpoints = append(endpoints, r.Endpoint) } sources := []PullSource{} diff --git a/pkg/sysregistriesv2/system_registries_v2_test.go b/pkg/sysregistriesv2/system_registries_v2_test.go index 0c3f141b0e..70699b7bc7 100644 --- a/pkg/sysregistriesv2/system_registries_v2_test.go +++ b/pkg/sysregistriesv2/system_registries_v2_test.go @@ -637,9 +637,9 @@ func TestPullSourcesFromReference(t *testing.T) { } registries, err := GetRegistries(sys) assert.Nil(t, err) - assert.Equal(t, 2, len(registries)) + assert.Equal(t, 4, len(registries)) - // Registry A allowing any kind of pull from mirrors + // Registry A has mirrors allow any kind of pull registryA, err := FindRegistry(sys, "registry-a.com/foo/image:latest") assert.Nil(t, err) assert.NotNil(t, registryA) @@ -657,7 +657,7 @@ func TestPullSourcesFromReference(t *testing.T) { assert.Equal(t, 3, len(pullSources)) assert.Equal(t, "registry-a.com/bar/image:aaa", pullSources[2].Reference.String()) - // Registry B allowing digests pull only from mirrors + // Registry B has mirrors allow digests pull only registryB, err := FindRegistry(sys, "registry-b.com/foo/image:latest") assert.Nil(t, err) assert.NotNil(t, registryB) @@ -673,6 +673,39 @@ func TestPullSourcesFromReference(t *testing.T) { pullSources, err = registryB.PullSourcesFromReference(referenceBTag) assert.Nil(t, err) assert.Equal(t, 1, len(pullSources)) + + // Registry C has a mirror allows digest pull only and a mirror allows any kind of pull + registryC, err := FindRegistry(sys, "registry-c.com/foo/image:latest") + assert.Nil(t, err) + assert.NotNil(t, registryB) + // Digest + referenceCDigest := toNamedRef(t, "registry-c.com/foo/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + pullSources, err = registryC.PullSourcesFromReference(referenceCDigest) + assert.Nil(t, err) + assert.Equal(t, 3, len(pullSources)) + assert.Equal(t, "registry-c.com/bar", pullSources[2].Endpoint.Location) + assert.Equal(t, "registry-c.com/bar/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", pullSources[2].Reference.String()) + // Tag + referenceCTag := toNamedRef(t, "registry-c.com/foo/image:aaa") + pullSources, err = registryC.PullSourcesFromReference(referenceCTag) + assert.Nil(t, err) + assert.Equal(t, 2, len(pullSources)) + // Registry D set mirror-by-digest-only, honor this configuration for registry + // Regsitry D has no digest-only set for mirrors table + registryD, err := FindRegistry(sys, "registry-d.com/foo/image:latest") + assert.Nil(t, err) + assert.NotNil(t, registryA) + // Digest + referenceDDigest := toNamedRef(t, "registry-d.com/foo/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + pullSources, err = registryD.PullSourcesFromReference(referenceDDigest) + assert.Nil(t, err) + assert.Equal(t, 3, len(pullSources)) + assert.Equal(t, "mirror-1.registry-d.com/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", pullSources[0].Reference.String()) + // Tag + referenceDTag := toNamedRef(t, "registry-d.com/foo/image:aaa") + pullSources, err = registryD.PullSourcesFromReference(referenceDTag) + assert.Nil(t, err) + assert.Equal(t, 1, len(pullSources)) } func TestTryUpdatingCache(t *testing.T) { diff --git a/pkg/sysregistriesv2/testdata/pull-sources-from-reference.conf b/pkg/sysregistriesv2/testdata/pull-sources-from-reference.conf index f01176dbae..3682cb2921 100644 --- a/pkg/sysregistriesv2/testdata/pull-sources-from-reference.conf +++ b/pkg/sysregistriesv2/testdata/pull-sources-from-reference.conf @@ -12,10 +12,33 @@ insecure = true [[registry]] prefix = "registry-b.com/foo" location = "registry-b.com/bar" -mirror-by-digest-only = true [[registry.mirror]] +digest-only = true location = "mirror-1.registry-b.com" [[registry.mirror]] +digest-only = true location = "mirror-2.registry-b.com" + +[[registry]] +prefix = "registry-c.com/foo" +location = "registry-c.com/bar" + +[[registry.mirror]] +location = "mirror-1.registry-c.com" + +[[registry.mirror]] +digest-only = true +location = "mirror-2.registry-c.com" + +[[registry]] +prefix = "registry-d.com/foo" +location = "registry-d.com/bar" +mirror-by-digest-only = true + +[[registry.mirror]] +location = "mirror-1.registry-d.com" + +[[registry.mirror]] +location = "mirror-2.registry-d.com"