Repository: containerd/containerd
File: core/remotes/docker/referrers.go (Go)

Code before:

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package docker

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/containerd/errdefs"
	"github.com/containerd/log"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

func (r dockerFetcher) FetchReferrers(ctx context.Context, dgst digest.Digest, artifactTypes ...string) ([]ocispec.Descriptor, error) {
	rc, size, err := r.openReferrers(ctx, dgst, artifactTypes...)
	if err != nil {
		return nil, err
	}
	defer rc.Close()

	var index ocispec.Index
	if err := json.NewDecoder(io.LimitReader(rc, size)).Decode(&index); err != nil {
		return nil, fmt.Errorf("failed to decode referrers index: %w", err)
	}
	return index.Manifests, nil
}

func (r dockerFetcher) openReferrers(ctx context.Context, dgst digest.Digest, artifactTypes ...string) (io.ReadCloser, int64, error) {
	mediaType := ocispec.MediaTypeImageIndex
	ctx = log.WithLogger(ctx, log.G(ctx).WithField("digest", dgst))

	hosts := r.filterHosts(HostCapabilityReferrers)
	fallbackHosts := r.filterHosts(HostCapabilityResolve)
	if len(hosts) == 0 && len(fallbackHosts) == 0 {
		return nil, 0, fmt.Errorf("no pull hosts: %w", errdefs.ErrNotFound)
	}

	ctx, err := ContextWithRepositoryScope(ctx, r.refspec, false)
	if err != nil {
		return nil, 0, err

Review comment (suggestion):
If you change this to take descriptor instead of digest I could avoid defining https://github.com/containerd/containerd/compare/main...tonistiigi:containerd:referrers-wip?expand=1#diff-85dea1ce2bec55085339d4ec90127de60f896bae284b627e7c03c1ff7bcb6533R214 and just use `remotes. ReferrersFetcher` as input to puller/exporter etc. Ok if you wish to keep them separate.

Code after:

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package docker

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/containerd/errdefs"
	"github.com/containerd/log"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

func (r dockerFetcher) FetchReferrers(ctx context.Context, dgst digest.Digest, artifactTypes ...string) ([]ocispec.Descriptor, error) {
	rc, size, err := r.openReferrers(ctx, dgst, artifactTypes...)
	if err != nil {
		return nil, err
	}
	defer rc.Close()

	var index ocispec.Index
	dec := json.NewDecoder(io.LimitReader(rc, size))
	if err := dec.Decode(&index); err != nil {
		return nil, fmt.Errorf("failed to decode referrers index: %w", err)
	}
	if _, err := dec.Token(); !errors.Is(err, io.EOF) {
		return nil, fmt.Errorf("unexpected data after JSON object")
	}

	if len(artifactTypes) == 0 {
		return index.Manifests, nil
	}

	var referrers []ocispec.Descriptor
	tFilter := map[string]struct{}{}
	for _, t := range artifactTypes {
		tFilter[t] = struct{}{}
	}
	for _, desc := range index.Manifests {