diff --git a/pkg/cri/config/config.go b/pkg/cri/config/config.go index 2200e2778fae0..48430cfa3e76a 100644 --- a/pkg/cri/config/config.go +++ b/pkg/cri/config/config.go @@ -199,6 +199,23 @@ type Mirror struct { // with host specified. // The scheme, host and path from the endpoint URL will be used. Endpoints []string `toml:"endpoint" json:"endpoint"` + + // Rewrites are repository rewrite rules for a namespace. When fetching image resources + // from an endpoint and a key matches the repository via regular expression matching + // it will be replaced with the corresponding value from the map in the resource request. + // + // This example configures CRI to pull docker.io/library/* images from docker.io/my-org/*: + // + // [plugins] + // [plugins."io.containerd.grpc.v1.cri"] + // [plugins."io.containerd.grpc.v1.cri".registry] + // [plugins."io.containerd.grpc.v1.cri".registry.mirrors] + // [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] + // endpoint = ["https://registry-1.docker.io/v2"] + // [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io".rewrite] + // "^library/(.*)" = "my-org/$1" + // + Rewrites map[string]string `toml:"rewrite" json:"rewrite"` } // AuthConfig contains the config related to authentication to a specific registry diff --git a/pkg/cri/server/image_pull.go b/pkg/cri/server/image_pull.go index 6b321515bd70c..1b5457074d3ff 100644 --- a/pkg/cri/server/image_pull.go +++ b/pkg/cri/server/image_pull.go @@ -449,6 +449,10 @@ func (c *criService) registryHosts(ctx context.Context, auth *runtime.AuthConfig if err != nil { return nil, fmt.Errorf("get registry endpoints: %w", err) } + rewrites, err := c.registryRewrites(host) + if err != nil { + return nil, fmt.Errorf("get registry rewrites: %w", err) + } for _, e := range endpoints { u, err := url.Parse(e) if err != nil { @@ -503,6 +507,7 @@ func (c *criService) registryHosts(ctx context.Context, auth *runtime.AuthConfig Scheme: u.Scheme, Path: u.Path, Capabilities: docker.HostCapabilityResolve | docker.HostCapabilityPull, + Rewrites: rewrites, }) } return registries, nil @@ -565,6 +570,20 @@ func (c *criService) registryEndpoints(host string) ([]string, error) { return append(endpoints, defaultScheme(defaultHost)+"://"+defaultHost), nil } +func (c *criService) registryRewrites(host string) (map[string]string, error) { + var rewrites map[string]string + _, ok := c.config.Registry.Mirrors[host] + if ok { + rewrites = c.config.Registry.Mirrors[host].Rewrites + } else { + rewrites = c.config.Registry.Mirrors["*"].Rewrites + } + if rewrites == nil { + rewrites = map[string]string{} + } + return rewrites, nil +} + // newTransport returns a new HTTP transport used to pull image. // TODO(random-liu): Create a library and share this code with `ctr`. func newTransport() *http.Transport { diff --git a/remotes/docker/registry.go b/remotes/docker/registry.go index 98cafcd069e6f..5c01a4b279740 100644 --- a/remotes/docker/registry.go +++ b/remotes/docker/registry.go @@ -74,6 +74,7 @@ type RegistryHost struct { Path string Capabilities HostCapabilities Header http.Header + Rewrites map[string]string } func (h RegistryHost) isProxy(refhost string) bool { diff --git a/remotes/docker/resolver.go b/remotes/docker/resolver.go index fe8a4399b102b..3397381ca5eb3 100644 --- a/remotes/docker/resolver.go +++ b/remotes/docker/resolver.go @@ -26,6 +26,7 @@ import ( "net/http" "net/url" "path" + "regexp" "strings" "github.com/containerd/log" @@ -237,15 +238,14 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp if err != nil { return "", ocispec.Descriptor{}, err } - refspec := base.refspec - if refspec.Object == "" { + if base.refspec.Object == "" { return "", ocispec.Descriptor{}, reference.ErrObjectRequired } var ( firstErr error paths [][]string - dgst = refspec.Digest() + dgst = base.refspec.Digest() caps = HostCapabilityPull ) @@ -263,7 +263,7 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp paths = append(paths, []string{"blobs", dgst.String()}) } else { // Add - paths = append(paths, []string{"manifests", refspec.Object}) + paths = append(paths, []string{"manifests", base.refspec.Object}) caps |= HostCapabilityResolve } @@ -272,15 +272,14 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp return "", ocispec.Descriptor{}, fmt.Errorf("no resolve hosts: %w", errdefs.ErrNotFound) } - ctx, err = ContextWithRepositoryScope(ctx, refspec, false) - if err != nil { - return "", ocispec.Descriptor{}, err - } - for _, u := range paths { for _, host := range hosts { ctx := log.WithLogger(ctx, log.G(ctx).WithField("host", host.Host)) - + base := base.withRewritesFromHost(host) + ctx, err = ContextWithRepositoryScope(ctx, base.refspec, false) + if err != nil { + return "", ocispec.Descriptor{}, err + } req := base.request(host, http.MethodHead, u...) if err := req.addNamespace(base.refspec.Hostname()); err != nil { return "", ocispec.Descriptor{}, err @@ -481,7 +480,6 @@ func (r *dockerBase) request(host RegistryHost, method string, ps ...string) *re if header == nil { header = http.Header{} } - for key, value := range host.Header { header[key] = append(header[key], value...) } @@ -499,6 +497,27 @@ func (r *dockerBase) request(host RegistryHost, method string, ps ...string) *re } } +func (r *dockerBase) withRewritesFromHost(host RegistryHost) *dockerBase { + for pattern, replace := range host.Rewrites { + exp, err := regexp.Compile(pattern) + if err != nil { + log.L.WithError(err).Warnf("Failed to compile rewrite, `%s`, for %s", pattern, host.Host) + continue + } + if rr := exp.ReplaceAllString(r.repository, replace); rr != r.repository { + return &dockerBase{ + refspec: reference.Spec{ + Locator: r.refspec.Hostname() + "/" + rr, + Object: r.refspec.Object, + }, + repository: rr, + header: r.header, + } + } + } + return r +} + func (r *request) authorize(ctx context.Context, req *http.Request) error { // Check if has header for host if r.host.Authorizer != nil {