regclient / regclient

Docker and OCI Registry Client in Go and tooling using those libraries.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Feature] Reduce the amount of auth calls needed.

mgazza opened this issue · comments

Current Behavior

Not all registries are created equal. I have encountered a situation where-by a registry was created as a dumping ground of a lot of images from other registries. When I copy image from all of these repos to a path on the uber registry reg client re-auths for each sub path. This is because reg client treats each of the sub paths as separate repo's and adds and rebuilds the scope which invalidates the current token.

Expected Behavior

Reg client would identify that these are all part of the same registry and that its current auth token is valid.

Example Solution

Someway to pre-warm the bearer handler with a registry so that AddScope performs a NoOp when a sub registry is added

Version

devel 0.5.7

Environment

Please include:

  • How you are running the tool: ( via an import )

  • Your platform: Linux, or Mac, x86 or ARM.

  • Your registry: all of them, target right now is gitlab which is GCP

  • Running as binary or container: Both

  • Host platform: Linux, or Mac, x86 or ARM.

  • Registry description: ALL - Docker Hub, ECR, GCR, ACR, Harbor, registry:2.

Unfortunately I don't believe there's a way around this. Some registries like GCR will fail on a request if a token for a different repository is used. And not fail with a request to reauth with a new scope, but fail with a forbidden response even through the user would otherwise have access. To avoid those behaviors, regclient does the best it can to mimic the behavior of other registry clients, which is to seed the scope with each repo it attempts to access.

I've tried to add a work around in this particular case where-by when parsing the ref I would move a portion of the registry to the path. however it doesn't look like the path field is used

func (c *Client) MapRef(image models.DockerImage) (ref.Ref, error) {
	r, err := ref.New(image.String())
	if err != nil {
		return ref.Ref{}, fmt.Errorf("error parsing ref: %w", err)
	}

	for _, s := range c.ParentRegistries {
		p := path.Join(r.Registry, r.Repository)
		i := strings.Index(p, s)
		if i > -1 {
			r.Path = p[len(s):]
			r.Repository = strings.TrimRight(s[len(r.Registry)+1:], "/")
		}
	}

	return r, nil
}
func TestClient_MapRef(t *testing.T) {
	type fields struct {
		RegClient        *regclient.RegClient
		ParentRegistries []string
	}
	type args struct {
		image models.DockerImage
	}
	tests := map[string]struct {
		fields  fields
		args    args
		want    ref.Ref
		wantErr assert.ErrorAssertionFunc
	}{
		"when using sub registries": {
			fields: fields{
				RegClient:        nil,
				ParentRegistries: []string{"registry.example.com/myrepo/"},
			},
			args: args{
				image: util.ParseDockerImage("registry.example.com/myrepo/somepath/myimage:latest"),
			},
			want: ref.Ref{
				Scheme:     "reg",
				Reference:  "registry.example.com/myrepo/somepath/myimage:latest",
				Registry:   "registry.example.com",
				Repository: "myrepo",
				Tag:        "latest",
				Digest:     "",
				Path:       "somepath/myimage",
			},
			wantErr: assert.NoError,
		},
	}
	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			c := &docker.Client{
				RegClient:        tt.fields.RegClient,
				ParentRegistries: tt.fields.ParentRegistries,
			}
			got, err := c.MapRef(tt.args.image)
			if !tt.wantErr(t, err, fmt.Sprintf("MapRef(%v)", tt.args.image)) {
				return
			}
			assert.Equalf(t, tt.want, got, "MapRef(%v)", tt.args.image)
		})
	}
}

I've fat thumbed a PR to fix this I think, I need to add some tests and other stuff but to illustrate the point right now its ok.

Ah I just tried the work around an the registry did indeed give me an error -> insufficient_scope.

Feel free the close the PR and this Issue :D