oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/referrers.go (about)

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package remote
    17  
    18  import (
    19  	"errors"
    20  	"strings"
    21  
    22  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    23  	"oras.land/oras-go/v2/content"
    24  	"oras.land/oras-go/v2/internal/descriptor"
    25  )
    26  
    27  // zeroDigest represents a digest that consists of zeros. zeroDigest is used
    28  // for pinging Referrers API.
    29  const zeroDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
    30  
    31  // referrersState represents the state of Referrers API.
    32  type referrersState = int32
    33  
    34  const (
    35  	// referrersStateUnknown represents an unknown state of Referrers API.
    36  	referrersStateUnknown referrersState = iota
    37  	// referrersStateSupported represents that the repository is known to
    38  	// support Referrers API.
    39  	referrersStateSupported
    40  	// referrersStateUnsupported represents that the repository is known to
    41  	// not support Referrers API.
    42  	referrersStateUnsupported
    43  )
    44  
    45  // referrerOperation represents an operation on a referrer.
    46  type referrerOperation = int32
    47  
    48  const (
    49  	// referrerOperationAdd represents an addition operation on a referrer.
    50  	referrerOperationAdd referrerOperation = iota
    51  	// referrerOperationRemove represents a removal operation on a referrer.
    52  	referrerOperationRemove
    53  )
    54  
    55  // referrerChange represents a change on a referrer.
    56  type referrerChange struct {
    57  	referrer  ocispec.Descriptor
    58  	operation referrerOperation
    59  }
    60  
    61  var (
    62  	// ErrReferrersCapabilityAlreadySet is returned by SetReferrersCapability()
    63  	// when the Referrers API capability has been already set.
    64  	ErrReferrersCapabilityAlreadySet = errors.New("referrers capability cannot be changed once set")
    65  
    66  	// errNoReferrerUpdate is returned by applyReferrerChanges() when there
    67  	// is no any referrer update.
    68  	errNoReferrerUpdate = errors.New("no referrer update")
    69  )
    70  
    71  const (
    72  	// opDeleteReferrersIndex represents the operation for deleting a
    73  	// referrers index.
    74  	opDeleteReferrersIndex = "DeleteReferrersIndex"
    75  )
    76  
    77  // ReferrersError records an error and the operation and the subject descriptor.
    78  type ReferrersError struct {
    79  	// Op represents the failing operation.
    80  	Op string
    81  	// Subject is the descriptor of referenced artifact.
    82  	Subject ocispec.Descriptor
    83  	// Err is the entity of referrers error.
    84  	Err error
    85  }
    86  
    87  // Error returns error msg of IgnorableError.
    88  func (e *ReferrersError) Error() string {
    89  	return e.Err.Error()
    90  }
    91  
    92  // Unwrap returns the inner error of IgnorableError.
    93  func (e *ReferrersError) Unwrap() error {
    94  	return errors.Unwrap(e.Err)
    95  }
    96  
    97  // IsIndexDelete tells if e is kind of error related to referrers
    98  // index deletion.
    99  func (e *ReferrersError) IsReferrersIndexDelete() bool {
   100  	return e.Op == opDeleteReferrersIndex
   101  }
   102  
   103  // buildReferrersTag builds the referrers tag for the given manifest descriptor.
   104  // Format: <algorithm>-<digest>
   105  // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#unavailable-referrers-api
   106  func buildReferrersTag(desc ocispec.Descriptor) string {
   107  	alg := desc.Digest.Algorithm().String()
   108  	encoded := desc.Digest.Encoded()
   109  	return alg + "-" + encoded
   110  }
   111  
   112  // isReferrersFilterApplied checks if requsted is in the applied filter list.
   113  func isReferrersFilterApplied(applied, requested string) bool {
   114  	if applied == "" || requested == "" {
   115  		return false
   116  	}
   117  	filters := strings.Split(applied, ",")
   118  	for _, f := range filters {
   119  		if f == requested {
   120  			return true
   121  		}
   122  	}
   123  	return false
   124  }
   125  
   126  // filterReferrers filters a slice of referrers by artifactType in place.
   127  // The returned slice contains matching referrers.
   128  func filterReferrers(refs []ocispec.Descriptor, artifactType string) []ocispec.Descriptor {
   129  	if artifactType == "" {
   130  		return refs
   131  	}
   132  	var j int
   133  	for i, ref := range refs {
   134  		if ref.ArtifactType == artifactType {
   135  			if i != j {
   136  				refs[j] = ref
   137  			}
   138  			j++
   139  		}
   140  	}
   141  	return refs[:j]
   142  }
   143  
   144  // applyReferrerChanges applies referrerChanges on referrers and returns the
   145  // updated referrers.
   146  // Returns errNoReferrerUpdate if there is no any referrers updates.
   147  func applyReferrerChanges(referrers []ocispec.Descriptor, referrerChanges []referrerChange) ([]ocispec.Descriptor, error) {
   148  	referrersMap := make(map[descriptor.Descriptor]int, len(referrers)+len(referrerChanges))
   149  	updatedReferrers := make([]ocispec.Descriptor, 0, len(referrers)+len(referrerChanges))
   150  	var updateRequired bool
   151  	for _, r := range referrers {
   152  		if content.Equal(r, ocispec.Descriptor{}) {
   153  			// skip bad entry
   154  			updateRequired = true
   155  			continue
   156  		}
   157  		key := descriptor.FromOCI(r)
   158  		if _, ok := referrersMap[key]; ok {
   159  			// skip duplicates
   160  			updateRequired = true
   161  			continue
   162  		}
   163  		updatedReferrers = append(updatedReferrers, r)
   164  		referrersMap[key] = len(updatedReferrers) - 1
   165  	}
   166  
   167  	// apply changes
   168  	for _, change := range referrerChanges {
   169  		key := descriptor.FromOCI(change.referrer)
   170  		switch change.operation {
   171  		case referrerOperationAdd:
   172  			if _, ok := referrersMap[key]; !ok {
   173  				// add distinct referrers
   174  				updatedReferrers = append(updatedReferrers, change.referrer)
   175  				referrersMap[key] = len(updatedReferrers) - 1
   176  			}
   177  		case referrerOperationRemove:
   178  			if pos, ok := referrersMap[key]; ok {
   179  				// remove referrers that are already in the map
   180  				updatedReferrers[pos] = ocispec.Descriptor{}
   181  				delete(referrersMap, key)
   182  			}
   183  		}
   184  	}
   185  
   186  	// skip unnecessary update
   187  	if !updateRequired && len(referrersMap) == len(referrers) {
   188  		// if the result referrer map contains the same content as the
   189  		// original referrers, consider that there is no update on the
   190  		// referrers.
   191  		for _, r := range referrers {
   192  			key := descriptor.FromOCI(r)
   193  			if _, ok := referrersMap[key]; !ok {
   194  				updateRequired = true
   195  			}
   196  		}
   197  		if !updateRequired {
   198  			return nil, errNoReferrerUpdate
   199  		}
   200  	}
   201  
   202  	return removeEmptyDescriptors(updatedReferrers, len(referrersMap)), nil
   203  }
   204  
   205  // removeEmptyDescriptors in-place removes empty items from descs, given a hint
   206  // of the number of non-empty descriptors.
   207  func removeEmptyDescriptors(descs []ocispec.Descriptor, hint int) []ocispec.Descriptor {
   208  	j := 0
   209  	for i, r := range descs {
   210  		if !content.Equal(r, ocispec.Descriptor{}) {
   211  			if i > j {
   212  				descs[j] = r
   213  			}
   214  			j++
   215  		}
   216  		if j == hint {
   217  			break
   218  		}
   219  	}
   220  	return descs[:j]
   221  }