From dff5d35640823ade3b7ecf5ac4cab94f8b838432 Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Thu, 18 Nov 2021 22:25:39 -0500 Subject: [PATCH] set digest-only for each mirror Close: https://github.com/containers/image/issues/1407 Add the `digest-only` field to each mirror table, so it will be configured for each mirror instead of for primary registry. For the same primary registry, it can support both digest only pull and tag allowed pull. The `mirror-by-digest-only` for primary is still allowed configuring, and it is honored for compatibility Signed-off-by: Qi Wang --- docs/containers-registries.conf.5.md | 12 +++- pkg/sysregistriesv2/system_registries_v2.go | 35 ++++++++-- .../system_registries_v2_test.go | 66 +++++++++++++++++++ .../pull-sources-mirror-reference.conf | 44 +++++++++++++ 4 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 pkg/sysregistriesv2/testdata/pull-sources-mirror-reference.conf diff --git a/docs/containers-registries.conf.5.md b/docs/containers-registries.conf.5.md index 928387b69a..a241095800 100644 --- a/docs/containers-registries.conf.5.md +++ b/docs/containers-registries.conf.5.md @@ -108,16 +108,24 @@ 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`. +: if `true` an individual mirror will only be used during pulling if the image reference includes a digest. +Note that digest-only will be overridden if mirror-by-digest is set for the entire registry. +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..a7f2007130 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. @@ -130,16 +136,27 @@ type PullSource struct { // reference. func (r *Registry) PullSourcesFromReference(ref reference.Named) ([]PullSource, error) { 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 { + // Only use mirrors when the reference is a digested one. + 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 digested one. + endpoints = append(endpoints, mirror) + } + } else { + endpoints = append(endpoints, mirror) + } + } + endpoints = append(endpoints, r.Endpoint) } sources := []PullSource{} @@ -919,15 +936,19 @@ func loadConfigFile(path string, forceV2 bool) (*parsedConfig, error) { res.shortNameMode = types.ShortNameModeInvalid } - // Valid wildcarded prefixes must be in the format: *.example.com - // FIXME: Move to postProcessRegistries - // https://github.com/containers/image/pull/1191#discussion_r610623829 for i := range res.partialV2.Registries { + // Valid wildcarded prefixes must be in the format: *.example.com + // FIXME: Move to postProcessRegistries + // https://github.com/containers/image/pull/1191#discussion_r610623829 prefix := res.partialV2.Registries[i].Prefix if strings.HasPrefix(prefix, "*.") && strings.ContainsAny(prefix, "/@:") { msg := fmt.Sprintf("Wildcarded prefix should be in the format: *.example.com. Current prefix %q is incorrectly formatted", prefix) return nil, &InvalidRegistries{s: msg} } + // validate the mirror usage settings does not apply to primary registry + if res.partialV2.Registries[i].DigestOnly { + return nil, fmt.Errorf("digest-only does not apply to primary registry %s", prefix) + } } // Parse and validate short-name aliases. diff --git a/pkg/sysregistriesv2/system_registries_v2_test.go b/pkg/sysregistriesv2/system_registries_v2_test.go index 0c3f141b0e..c366561a9f 100644 --- a/pkg/sysregistriesv2/system_registries_v2_test.go +++ b/pkg/sysregistriesv2/system_registries_v2_test.go @@ -675,6 +675,72 @@ func TestPullSourcesFromReference(t *testing.T) { assert.Equal(t, 1, len(pullSources)) } +func TestPullSourcesMirrorFromReference(t *testing.T) { + sys := &types.SystemContext{ + SystemRegistriesConfPath: "testdata/pull-sources-mirror-reference.conf", + SystemRegistriesConfDirPath: "testdata/this-does-not-exist", + } + registries, err := GetRegistries(sys) + assert.Nil(t, err) + assert.Equal(t, 4, len(registries)) + + for _, tc := range []struct { + registry string + digestRef string + digestSources int + tagRef string + tagSources int + }{ + // Registry A has mirrors allow any kind of pull + { + "registry-a.com/foo/image:latest", + "registry-a.com/foo/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 3, + "registry-a.com/foo/image:aaa", + 3, + }, + // Registry B has mirrors allow digests pull only + { + "registry-b.com/foo/image:latest", + "registry-b.com/foo/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 3, + "registry-b.com/foo/image:aaa", + 1, + }, + // Registry C has a mirror allows digest pull only and a mirror allows any kind of pull + { + "registry-c.com/foo/image:latest", + "registry-c.com/foo/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 3, + "registry-c.com/foo/image:aaa", + 2, + }, + // Registry D set mirror-by-digest-only, honor this configuration for registry + // Regsitry D has no digest-only set for mirrors table + { + "registry-d.com/foo/image:latest", + "registry-d.com/foo/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 3, + "registry-d.com/foo/image:aaa", + 1, + }, + } { + registry, err := FindRegistry(sys, tc.registry) + assert.Nil(t, err) + assert.NotNil(t, registry) + // Digest + referenceDigest := toNamedRef(t, tc.digestRef) + pullSource, err := registry.PullSourcesFromReference(referenceDigest) + assert.Nil(t, err) + assert.Equal(t, tc.digestSources, len(pullSource)) + // Tag + referenceTag := toNamedRef(t, tc.tagRef) + pullSource, err = registry.PullSourcesFromReference(referenceTag) + assert.Nil(t, err) + assert.Equal(t, tc.tagSources, len(pullSource)) + } +} + func TestTryUpdatingCache(t *testing.T) { ctx := &types.SystemContext{ SystemRegistriesConfPath: "testdata/try-update-cache-valid.conf", diff --git a/pkg/sysregistriesv2/testdata/pull-sources-mirror-reference.conf b/pkg/sysregistriesv2/testdata/pull-sources-mirror-reference.conf new file mode 100644 index 0000000000..3682cb2921 --- /dev/null +++ b/pkg/sysregistriesv2/testdata/pull-sources-mirror-reference.conf @@ -0,0 +1,44 @@ +[[registry]] +prefix = "registry-a.com/foo" +location = "registry-a.com/bar" + +[[registry.mirror]] +location = "mirror-1.registry-a.com" + +[[registry.mirror]] +location = "mirror-2.registry-a.com" +insecure = true + +[[registry]] +prefix = "registry-b.com/foo" +location = "registry-b.com/bar" + +[[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"