From 5b2960efff8b38af85b687a25fa93f01256016de Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Fri, 2 Dec 2022 15:29:52 +0400 Subject: [PATCH] fix: introduce 'overridePath' setting and fix Talos resolver There was inconsistency in the way `/v2` was appended to registry endpoint path between containerd (CRI) and Talos: * Talos only appended `/v2` to empty paths * containerd appended `/v2` if it's not the suffix already Fix Talos to act same as containerd, and introduce a setting `overridePath` which stops both Talos and `containerd` from appending `/v2` (should be required with e.g. Harbor registry mirror). Signed-off-by: Andrey Smirnov --- hack/release.toml | 30 ++++ .../pkg/containers/cri/containerd/hosts.go | 25 +-- .../containers/cri/containerd/hosts_test.go | 90 ++++++++-- internal/pkg/containers/image/resolver.go | 41 ++--- .../pkg/containers/image/resolver_test.go | 160 ++++++++++++++---- pkg/machinery/config/provider.go | 1 + .../types/v1alpha1/v1alpha1_provider.go | 5 + .../config/types/v1alpha1/v1alpha1_types.go | 5 + .../types/v1alpha1/v1alpha1_types_doc.go | 7 +- .../types/v1alpha1/zz_generated.deepcopy.go | 5 + .../content/v1.3/reference/configuration.md | 1 + 11 files changed, 292 insertions(+), 78 deletions(-) diff --git a/hack/release.toml b/hack/release.toml index a58966eb22..85ffe98041 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -263,6 +263,36 @@ talosctl machineconfig patch controlplane.yaml \ ``` Additionally, `talosctl machineconfig gen` subcommand is introduced as an alias to `talosctl gen config`. +""" + + [notes.registry-mirrors] + title = "Registry Mirrors" + description = """\ +Talos had an inconsistency in the way registry mirror endpoints are handled when compared with `containerd` implementation: + +```yaml +machine: + registries: + mirrors: + docker.io: + endpoints: + - "https://mirror-registry/v2/mirror.docker.io" +``` + +Talos would use endpoint `https://mirror-registry/v2/mirror.docker.io`, while `containerd` would use `https://mirror-registry/v2/mirror.docker.io/v2`. +This inconsistency is now fixed, and Talos uses same endpoint as `containerd`. + +New `overridePath` configuration is introduced to skip appending `/v2` both on Talos and containerd side: + +```yaml +machine: + registries: + mirrors: + docker.io: + endpoints: + - "https://mirror-registry/v2/mirror.docker.io" + overridePath: true +``` """ [make_deps] diff --git a/internal/pkg/containers/cri/containerd/hosts.go b/internal/pkg/containers/cri/containerd/hosts.go index 6c4529677d..7d9d697494 100644 --- a/internal/pkg/containers/cri/containerd/hosts.go +++ b/internal/pkg/containers/cri/containerd/hosts.go @@ -47,24 +47,24 @@ func GenerateHosts(cfg config.Registries, basePath string) (*HostsConfig, error) Directories: map[string]*HostsDirectory{}, } - configureTLS := func(host string, directoryName string, hostToml *HostToml, directory *HostsDirectory) { - tlsConfig, ok := cfg.Config()[host] + configureEndpoint := func(host string, directoryName string, hostToml *HostToml, directory *HostsDirectory) { + endpointConfig, ok := cfg.Config()[host] if !ok { return } - if tlsConfig.TLS() != nil { - if tlsConfig.TLS().InsecureSkipVerify() { + if endpointConfig.TLS() != nil { + if endpointConfig.TLS().InsecureSkipVerify() { hostToml.SkipVerify = true } - if tlsConfig.TLS().CA() != nil { + if endpointConfig.TLS().CA() != nil { relPath := fmt.Sprintf("%s-ca.crt", host) directory.Files = append(directory.Files, &HostsFile{ Name: relPath, - Contents: tlsConfig.TLS().CA(), + Contents: endpointConfig.TLS().CA(), Mode: 0o600, }, ) @@ -72,19 +72,19 @@ func GenerateHosts(cfg config.Registries, basePath string) (*HostsConfig, error) hostToml.CACert = filepath.Join(basePath, directoryName, relPath) } - if tlsConfig.TLS().ClientIdentity() != nil { + if endpointConfig.TLS().ClientIdentity() != nil { relPathCrt := fmt.Sprintf("%s-client.crt", host) relPathKey := fmt.Sprintf("%s-client.key", host) directory.Files = append(directory.Files, &HostsFile{ Name: relPathCrt, - Contents: tlsConfig.TLS().ClientIdentity().Crt, + Contents: endpointConfig.TLS().ClientIdentity().Crt, Mode: 0o600, }, &HostsFile{ Name: relPathKey, - Contents: tlsConfig.TLS().ClientIdentity().Key, + Contents: endpointConfig.TLS().ClientIdentity().Key, Mode: 0o600, }, ) @@ -122,9 +122,10 @@ func GenerateHosts(cfg config.Registries, basePath string) (*HostsConfig, error) hostsToml.HostConfigs[endpoint] = &HostToml{ Capabilities: []string{"pull", "resolve"}, // TODO: we should make it configurable eventually + OverridePath: endpoints.OverridePath(), } - configureTLS(u.Host, directoryName, hostsToml.HostConfigs[endpoint], directory) + configureEndpoint(u.Host, directoryName, hostsToml.HostConfigs[endpoint], directory) tomlBytes, err := toml.Marshal(hostsToml) if err != nil { @@ -192,13 +193,12 @@ func GenerateHosts(cfg config.Registries, basePath string) (*HostsConfig, error) defaultHost = "https://" + defaultHost hostsToml := HostsToml{ - Server: defaultHost, HostConfigs: map[string]*HostToml{ defaultHost: {}, }, } - configureTLS(hostname, directoryName, hostsToml.HostConfigs[defaultHost], directory) + configureEndpoint(hostname, directoryName, hostsToml.HostConfigs[defaultHost], directory) marshaled, err := toml.Marshal(hostsToml) if err != nil { @@ -238,6 +238,7 @@ type HostsToml struct { // HostToml is a single entry in `hosts.toml`. type HostToml struct { Capabilities []string `toml:"capabilities,omitempty"` + OverridePath bool `toml:"override_path,omitempty"` CACert string `toml:"ca,omitempty"` Client [][2]string `toml:"client,omitempty"` SkipVerify bool `toml:"skip_verify,omitempty"` diff --git a/internal/pkg/containers/cri/containerd/hosts_test.go b/internal/pkg/containers/cri/containerd/hosts_test.go index 21c0c27d80..f27b39cf53 100644 --- a/internal/pkg/containers/cri/containerd/hosts_test.go +++ b/internal/pkg/containers/cri/containerd/hosts_test.go @@ -17,8 +17,8 @@ import ( "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1" ) -func TestGenerateHosts(t *testing.T) { - cfgWithTLS := &mockConfig{ +func TestGenerateHostsWithTLS(t *testing.T) { + cfg := &mockConfig{ mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ "docker.io": { MirrorEndpoints: []string{"https://registry-1.docker.io", "https://registry-2.docker.io"}, @@ -49,7 +49,7 @@ func TestGenerateHosts(t *testing.T) { }, } - resultWithTLS, err := containerd.GenerateHosts(cfgWithTLS, "/etc/cri/conf.d/hosts") + result, err := containerd.GenerateHosts(cfg, "/etc/cri/conf.d/hosts") require.NoError(t, err) assert.Equal(t, &containerd.HostsConfig{ @@ -83,7 +83,7 @@ func TestGenerateHosts(t *testing.T) { { Name: "hosts.toml", Mode: 0o600, - Contents: []byte("server = \"https://some.host:123\"\n\n[host]\n\n [host.\"https://some.host:123\"]\n ca = \"/etc/cri/conf.d/hosts/some.host_123_/some.host:123-ca.crt\"\n client = [[\"/etc/cri/conf.d/hosts/some.host_123_/some.host:123-client.crt\", \"/etc/cri/conf.d/hosts/some.host_123_/some.host:123-client.key\"]]\n skip_verify = true\n"), //nolint:lll + Contents: []byte("\n[host]\n\n [host.\"https://some.host:123\"]\n ca = \"/etc/cri/conf.d/hosts/some.host_123_/some.host:123-ca.crt\"\n client = [[\"/etc/cri/conf.d/hosts/some.host_123_/some.host:123-client.crt\", \"/etc/cri/conf.d/hosts/some.host_123_/some.host:123-client.key\"]]\n skip_verify = true\n"), //nolint:lll }, }, }, @@ -92,14 +92,16 @@ func TestGenerateHosts(t *testing.T) { { Name: "hosts.toml", Mode: 0o600, - Contents: []byte("server = \"https://registry-2.docker.io\"\n\n[host]\n\n [host.\"https://registry-2.docker.io\"]\n skip_verify = true\n"), + Contents: []byte("\n[host]\n\n [host.\"https://registry-2.docker.io\"]\n skip_verify = true\n"), }, }, }, }, - }, resultWithTLS) + }, result) +} - cfgWithoutTLS := &mockConfig{ +func TestGenerateHostsWithoutTLS(t *testing.T) { + cfg := &mockConfig{ mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ "docker.io": { MirrorEndpoints: []string{"https://registry-1.docker.io", "https://registry-2.docker.io"}, @@ -117,7 +119,7 @@ func TestGenerateHosts(t *testing.T) { }, } - resultWithoutTLS, err := containerd.GenerateHosts(cfgWithoutTLS, "/etc/cri/conf.d/hosts") + result, err := containerd.GenerateHosts(cfg, "/etc/cri/conf.d/hosts") require.NoError(t, err) assert.Equal(t, &containerd.HostsConfig{ @@ -136,10 +138,78 @@ func TestGenerateHosts(t *testing.T) { { Name: "hosts.toml", Mode: 0o600, - Contents: []byte("server = \"https://some.host:123\"\n\n[host]\n\n [host.\"https://some.host:123\"]\n"), + Contents: []byte("\n[host]\n\n [host.\"https://some.host:123\"]\n"), + }, + }, + }, + }, + }, result) +} + +func TestGenerateHostsWithHarbor(t *testing.T) { + cfg := &mockConfig{ + mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ + "docker.io": { + MirrorEndpoints: []string{"https://harbor/v2/mirrors/proxy.docker.io"}, + MirrorOverridePath: pointer.To(true), + }, + "ghcr.io": { + MirrorEndpoints: []string{"https://harbor/v2/mirrors/proxy.ghcr.io"}, + MirrorOverridePath: pointer.To(true), + }, + }, + config: map[string]*v1alpha1.RegistryConfig{ + "harbor": { + RegistryAuth: &v1alpha1.RegistryAuthConfig{ + RegistryUsername: "root", + RegistryPassword: "secret", + RegistryAuth: "auth", + RegistryIdentityToken: "token", + }, + RegistryTLS: &v1alpha1.RegistryTLSConfig{ + TLSInsecureSkipVerify: pointer.To(true), + }, + }, + }, + } + + result, err := containerd.GenerateHosts(cfg, "/etc/cri/conf.d/hosts") + require.NoError(t, err) + + t.Logf( + "config %q", + string(result.Directories["harbor"].Files[0].Contents), + ) + + assert.Equal(t, &containerd.HostsConfig{ + Directories: map[string]*containerd.HostsDirectory{ + "docker.io": { + Files: []*containerd.HostsFile{ + { + Name: "hosts.toml", + Mode: 0o600, + Contents: []byte("\n[host]\n\n [host.\"https://harbor/v2/mirrors/proxy.docker.io\"]\n capabilities = [\"pull\", \"resolve\"]\n override_path = true\n skip_verify = true\n"), + }, + }, + }, + "ghcr.io": { + Files: []*containerd.HostsFile{ + { + Name: "hosts.toml", + Mode: 0o600, + Contents: []byte("\n[host]\n\n [host.\"https://harbor/v2/mirrors/proxy.ghcr.io\"]\n capabilities = [\"pull\", \"resolve\"]\n override_path = true\n skip_verify = true\n"), + }, + }, + }, + "harbor": { + Files: []*containerd.HostsFile{ + { + Name: "hosts.toml", + Mode: 0o600, + Contents: []byte("\n[host]\n\n [host.\"https://harbor\"]\n skip_verify = true\n"), }, }, }, }, - }, resultWithoutTLS) + }, result) } diff --git a/internal/pkg/containers/image/resolver.go b/internal/pkg/containers/image/resolver.go index 7e5c5e4e3f..da0a611be9 100644 --- a/internal/pkg/containers/image/resolver.go +++ b/internal/pkg/containers/image/resolver.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/url" + "path" "strings" "github.com/containerd/containerd/remotes" @@ -33,7 +34,7 @@ func RegistryHosts(reg config.Registries) docker.RegistryHosts { return func(host string) ([]docker.RegistryHost, error) { var registries []docker.RegistryHost - endpoints, err := RegistryEndpoints(reg, host) + endpoints, overridePath, err := RegistryEndpoints(reg, host) if err != nil { return nil, err } @@ -61,7 +62,15 @@ func RegistryHosts(reg config.Registries) docker.RegistryHosts { } if u.Path == "" { - u.Path = "/v2" + if !overridePath { + u.Path = "/v2" + } + } else { + u.Path = path.Clean(u.Path) + + if !strings.HasSuffix(u.Path, "/v2") && !overridePath { + u.Path += "/v2" + } } uu := u @@ -89,30 +98,24 @@ func RegistryHosts(reg config.Registries) docker.RegistryHosts { } // RegistryEndpoints returns registry endpoints per host using reg. -func RegistryEndpoints(reg config.Registries, host string) ([]string, error) { - var endpoints []string - +func RegistryEndpoints(reg config.Registries, host string) (endpoints []string, overridePath bool, err error) { + // direct hit by host if hostConfig, ok := reg.Mirrors()[host]; ok { - endpoints = hostConfig.Endpoints() + return hostConfig.Endpoints(), hostConfig.OverridePath(), nil } - if endpoints == nil { - if catchAllConfig, ok := reg.Mirrors()["*"]; ok { - endpoints = catchAllConfig.Endpoints() - } + // '*' + if catchAllConfig, ok := reg.Mirrors()["*"]; ok { + return catchAllConfig.Endpoints(), catchAllConfig.OverridePath(), nil } - if len(endpoints) == 0 { - // still no endpoints, use default - defaultHost, err := docker.DefaultHost(host) - if err != nil { - return nil, fmt.Errorf("error getting default host for %q: %w", host, err) - } - - endpoints = append(endpoints, "https://"+defaultHost) + // still no endpoints, use default + defaultHost, err := docker.DefaultHost(host) + if err != nil { + return nil, false, fmt.Errorf("error getting default host for %q: %w", host, err) } - return endpoints, nil + return []string{"https://" + defaultHost}, false, nil } // PrepareAuth returns authentication info in the format expected by containerd. diff --git a/internal/pkg/containers/image/resolver_test.go b/internal/pkg/containers/image/resolver_test.go index 7d2c374ff3..fd7c5f8792 100644 --- a/internal/pkg/containers/image/resolver_test.go +++ b/internal/pkg/containers/image/resolver_test.go @@ -10,6 +10,7 @@ import ( "net/http" "testing" + "github.com/siderolabs/go-pointer" "github.com/stretchr/testify/suite" "github.com/siderolabs/talos/internal/pkg/containers/image" @@ -51,51 +52,127 @@ type ResolverSuite struct { } func (suite *ResolverSuite) TestRegistryEndpoints() { - // defaults - endpoints, err := image.RegistryEndpoints(&mockConfig{}, "docker.io") - suite.Assert().NoError(err) - suite.Assert().Equal([]string{"https://registry-1.docker.io"}, endpoints) + type request struct { + host string - endpoints, err = image.RegistryEndpoints(&mockConfig{}, "quay.io") - suite.Assert().NoError(err) - suite.Assert().Equal([]string{"https://quay.io"}, endpoints) + expectedEndpoints []string + expectedOverridePath bool + } - // overrides without catch-all - cfg := &mockConfig{ - mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ - "docker.io": { - MirrorEndpoints: []string{"http://127.0.0.1:5000", "https://some.host"}, + for _, tt := range []struct { + name string + config *mockConfig + + requests []request + }{ + { + name: "no config", + config: &mockConfig{}, + requests: []request{ + { + host: "docker.io", + expectedEndpoints: []string{"https://registry-1.docker.io"}, + }, + { + host: "quay.io", + expectedEndpoints: []string{"https://quay.io"}, + }, }, }, - } - - endpoints, err = image.RegistryEndpoints(cfg, "docker.io") - suite.Assert().NoError(err) - suite.Assert().Equal([]string{"http://127.0.0.1:5000", "https://some.host"}, endpoints) + { + name: "config with mirror", + config: &mockConfig{ + mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ + "docker.io": { + MirrorEndpoints: []string{"http://127.0.0.1:5000", "https://some.host"}, + }, + }, + }, - endpoints, err = image.RegistryEndpoints(cfg, "quay.io") - suite.Assert().NoError(err) - suite.Assert().Equal([]string{"https://quay.io"}, endpoints) + requests: []request{ + { + host: "docker.io", + expectedEndpoints: []string{"http://127.0.0.1:5000", "https://some.host"}, + }, + { + host: "quay.io", + expectedEndpoints: []string{"https://quay.io"}, + }, + }, + }, + { + name: "config with catch-all", + config: &mockConfig{ + mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ + "docker.io": { + MirrorEndpoints: []string{"http://127.0.0.1:5000", "https://some.host"}, + }, + "*": { + MirrorEndpoints: []string{"http://127.0.0.1:5001"}, + }, + }, + }, - // overrides with catch-all - cfg = &mockConfig{ - mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ - "docker.io": { - MirrorEndpoints: []string{"http://127.0.0.1:5000", "https://some.host"}, + requests: []request{ + { + host: "docker.io", + expectedEndpoints: []string{"http://127.0.0.1:5000", "https://some.host"}, + }, + { + host: "quay.io", + expectedEndpoints: []string{"http://127.0.0.1:5001"}, + }, + }, + }, + { + name: "config with override path", + config: &mockConfig{ + mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ + "docker.io": { + MirrorEndpoints: []string{"https://harbor/v2/registry.docker.io"}, + MirrorOverridePath: pointer.To(true), + }, + "ghcr.io": { + MirrorEndpoints: []string{"https://harbor/v2/registry.ghcr.io"}, + MirrorOverridePath: pointer.To(true), + }, + }, }, - "*": { - MirrorEndpoints: []string{"http://127.0.0.1:5001"}, + + requests: []request{ + { + host: "docker.io", + expectedEndpoints: []string{"https://harbor/v2/registry.docker.io"}, + expectedOverridePath: true, + }, + { + host: "ghcr.io", + expectedEndpoints: []string{"https://harbor/v2/registry.ghcr.io"}, + expectedOverridePath: true, + }, + { + host: "quay.io", + expectedEndpoints: []string{"https://quay.io"}, + }, }, }, + } { + tt := tt + + suite.Run(tt.name, func() { + for _, req := range tt.requests { + req := req + + suite.Run(req.host, func() { + endpoints, overridePath, err := image.RegistryEndpoints(tt.config, req.host) + + suite.Assert().NoError(err) + suite.Assert().Equal(req.expectedEndpoints, endpoints) + suite.Assert().Equal(req.expectedOverridePath, overridePath) + }) + } + }) } - - endpoints, err = image.RegistryEndpoints(cfg, "docker.io") - suite.Assert().NoError(err) - suite.Assert().Equal([]string{"http://127.0.0.1:5000", "https://some.host"}, endpoints) - - endpoints, err = image.RegistryEndpoints(cfg, "quay.io") - suite.Assert().NoError(err) - suite.Assert().Equal([]string{"http://127.0.0.1:5001"}, endpoints) } func (suite *ResolverSuite) TestPrepareAuth() { @@ -152,6 +229,10 @@ func (suite *ResolverSuite) TestRegistryHosts() { "docker.io": { MirrorEndpoints: []string{"http://127.0.0.1:5000/docker.io", "https://some.host"}, }, + "ghcr.io": { + MirrorEndpoints: []string{"https://harbor/v2/registry.ghcr.io"}, + MirrorOverridePath: pointer.To(true), + }, }, } @@ -160,13 +241,20 @@ func (suite *ResolverSuite) TestRegistryHosts() { suite.Assert().Len(registryHosts, 2) suite.Assert().Equal("http", registryHosts[0].Scheme) suite.Assert().Equal("127.0.0.1:5000", registryHosts[0].Host) - suite.Assert().Equal("/docker.io", registryHosts[0].Path) + suite.Assert().Equal("/docker.io/v2", registryHosts[0].Path) suite.Assert().Nil(registryHosts[0].Client.Transport.(*http.Transport).TLSClientConfig.Certificates) suite.Assert().Equal("https", registryHosts[1].Scheme) suite.Assert().Equal("some.host", registryHosts[1].Host) suite.Assert().Equal("/v2", registryHosts[1].Path) suite.Assert().Nil(registryHosts[1].Client.Transport.(*http.Transport).TLSClientConfig.Certificates) + registryHosts, err = image.RegistryHosts(cfg)("ghcr.io") + suite.Require().NoError(err) + suite.Assert().Len(registryHosts, 1) + suite.Assert().Equal("https", registryHosts[0].Scheme) + suite.Assert().Equal("harbor", registryHosts[0].Host) + suite.Assert().Equal("/v2/registry.ghcr.io", registryHosts[0].Path) + cfg = &mockConfig{ mirrors: map[string]*v1alpha1.RegistryMirrorConfig{ "docker.io": { diff --git a/pkg/machinery/config/provider.go b/pkg/machinery/config/provider.go index 303340a15c..8d526b7106 100644 --- a/pkg/machinery/config/provider.go +++ b/pkg/machinery/config/provider.go @@ -351,6 +351,7 @@ type Registries interface { // RegistryMirrorConfig represents mirror configuration for a registry. type RegistryMirrorConfig interface { Endpoints() []string + OverridePath() bool } // RegistryConfig specifies auth & TLS config per registry. diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go index 6b366dc1e2..f253c7b321 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go @@ -1197,6 +1197,11 @@ func (r *RegistryMirrorConfig) Endpoints() []string { return r.MirrorEndpoints } +// OverridePath implements the Registries interface. +func (r *RegistryMirrorConfig) OverridePath() bool { + return pointer.SafeDeref(r.MirrorOverridePath) +} + // Content implements the config.Provider interface. func (f *MachineFile) Content() string { return f.FileContent diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go index dabc04d7b6..75b4b5dbeb 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go @@ -2348,6 +2348,11 @@ type RegistryMirrorConfig struct { // Endpoint configures HTTP/HTTPS access mode, host name, // port and path (if path is not set, it defaults to `/v2`). MirrorEndpoints []string `yaml:"endpoints"` + // description: | + // Use the exact path specified for the endpoint (don't append /v2/). + // This setting is often required for setting up multiple mirrors + // on a single instance of a registry. + MirrorOverridePath *bool `yaml:"overridePath,omitempty"` } // RegistryConfig specifies auth & TLS config per registry. diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go index c06c3ed696..7d5e1fce05 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go @@ -2219,12 +2219,17 @@ func init() { FieldName: "mirrors", }, } - RegistryMirrorConfigDoc.Fields = make([]encoder.Doc, 1) + RegistryMirrorConfigDoc.Fields = make([]encoder.Doc, 2) RegistryMirrorConfigDoc.Fields[0].Name = "endpoints" RegistryMirrorConfigDoc.Fields[0].Type = "[]string" RegistryMirrorConfigDoc.Fields[0].Note = "" RegistryMirrorConfigDoc.Fields[0].Description = "List of endpoints (URLs) for registry mirrors to use.\nEndpoint configures HTTP/HTTPS access mode, host name,\nport and path (if path is not set, it defaults to `/v2`)." RegistryMirrorConfigDoc.Fields[0].Comments[encoder.LineComment] = "List of endpoints (URLs) for registry mirrors to use." + RegistryMirrorConfigDoc.Fields[1].Name = "overridePath" + RegistryMirrorConfigDoc.Fields[1].Type = "bool" + RegistryMirrorConfigDoc.Fields[1].Note = "" + RegistryMirrorConfigDoc.Fields[1].Description = "Use the exact path specified for the endpoint (don't append /v2/).\nThis setting is often required for setting up multiple mirrors\non a single instance of a registry." + RegistryMirrorConfigDoc.Fields[1].Comments[encoder.LineComment] = "Use the exact path specified for the endpoint (don't append /v2/)." RegistryConfigDoc.Type = "RegistryConfig" RegistryConfigDoc.Comments[encoder.LineComment] = "RegistryConfig specifies auth & TLS config per registry." diff --git a/pkg/machinery/config/types/v1alpha1/zz_generated.deepcopy.go b/pkg/machinery/config/types/v1alpha1/zz_generated.deepcopy.go index a56c054ef7..6fd4733fb4 100644 --- a/pkg/machinery/config/types/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/machinery/config/types/v1alpha1/zz_generated.deepcopy.go @@ -1876,6 +1876,11 @@ func (in *RegistryMirrorConfig) DeepCopyInto(out *RegistryMirrorConfig) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.MirrorOverridePath != nil { + in, out := &in.MirrorOverridePath, &out.MirrorOverridePath + *out = new(bool) + **out = **in + } return } diff --git a/website/content/v1.3/reference/configuration.md b/website/content/v1.3/reference/configuration.md index e37604f54c..dc04876605 100644 --- a/website/content/v1.3/reference/configuration.md +++ b/website/content/v1.3/reference/configuration.md @@ -2429,6 +2429,7 @@ ghcr.io: | Field | Type | Description | Value(s) | |-------|------|-------------|----------| |`endpoints` |[]string |
List of endpoints (URLs) for registry mirrors to use.Endpoint configures HTTP/HTTPS access mode, host name,
port and path (if path is not set, it defaults to `/v2`).
| | +|`overridePath` |bool |
Use the exact path specified for the endpoint (don't append /v2/).This setting is often required for setting up multiple mirrors
on a single instance of a registry.
| |