github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/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 "github.com/opcr-io/oras-go/v2/content" 23 "github.com/opcr-io/oras-go/v2/internal/descriptor" 24 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 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 // buildReferrersTag builds the referrers tag for the given manifest descriptor. 72 // Format: <algorithm>-<digest> 73 // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#unavailable-referrers-api 74 func buildReferrersTag(desc ocispec.Descriptor) string { 75 alg := desc.Digest.Algorithm().String() 76 encoded := desc.Digest.Encoded() 77 return alg + "-" + encoded 78 } 79 80 // isReferrersFilterApplied checks annotations to see if requested is in the 81 // applied filter list. 82 func isReferrersFilterApplied(annotations map[string]string, requested string) bool { 83 applied := annotations[ocispec.AnnotationReferrersFiltersApplied] 84 if applied == "" || requested == "" { 85 return false 86 } 87 filters := strings.Split(applied, ",") 88 for _, f := range filters { 89 if f == requested { 90 return true 91 } 92 } 93 return false 94 } 95 96 // filterReferrers filters a slice of referrers by artifactType in place. 97 // The returned slice contains matching referrers. 98 func filterReferrers(refs []ocispec.Descriptor, artifactType string) []ocispec.Descriptor { 99 if artifactType == "" { 100 return refs 101 } 102 var j int 103 for i, ref := range refs { 104 if ref.ArtifactType == artifactType { 105 if i != j { 106 refs[j] = ref 107 } 108 j++ 109 } 110 } 111 return refs[:j] 112 } 113 114 // applyReferrerChanges applies referrerChanges on referrers and returns the 115 // updated referrers. 116 // Returns errNoReferrerUpdate if there is no any referrers updates. 117 func applyReferrerChanges(referrers []ocispec.Descriptor, referrerChanges []referrerChange) ([]ocispec.Descriptor, error) { 118 referrersMap := make(map[descriptor.Descriptor]int, len(referrers)+len(referrerChanges)) 119 updatedReferrers := make([]ocispec.Descriptor, 0, len(referrers)+len(referrerChanges)) 120 var updateRequired bool 121 for _, r := range referrers { 122 if content.Equal(r, ocispec.Descriptor{}) { 123 // skip bad entry 124 updateRequired = true 125 continue 126 } 127 key := descriptor.FromOCI(r) 128 if _, ok := referrersMap[key]; ok { 129 // skip duplicates 130 updateRequired = true 131 continue 132 } 133 updatedReferrers = append(updatedReferrers, r) 134 referrersMap[key] = len(updatedReferrers) - 1 135 } 136 137 // apply changes 138 for _, change := range referrerChanges { 139 key := descriptor.FromOCI(change.referrer) 140 switch change.operation { 141 case referrerOperationAdd: 142 if _, ok := referrersMap[key]; !ok { 143 // add distinct referrers 144 updatedReferrers = append(updatedReferrers, change.referrer) 145 referrersMap[key] = len(updatedReferrers) - 1 146 } 147 case referrerOperationRemove: 148 if pos, ok := referrersMap[key]; ok { 149 // remove referrers that are already in the map 150 updatedReferrers[pos] = ocispec.Descriptor{} 151 delete(referrersMap, key) 152 } 153 } 154 } 155 156 // skip unnecessary update 157 if !updateRequired && len(referrersMap) == len(referrers) { 158 // if the result referrer map contains the same content as the 159 // original referrers, consider that there is no update on the 160 // referrers. 161 for _, r := range referrers { 162 key := descriptor.FromOCI(r) 163 if _, ok := referrersMap[key]; !ok { 164 updateRequired = true 165 } 166 } 167 if !updateRequired { 168 return nil, errNoReferrerUpdate 169 } 170 } 171 172 return removeEmptyDescriptors(updatedReferrers, len(referrersMap)), nil 173 } 174 175 // removeEmptyDescriptors in-place removes empty items from descs, given a hint 176 // of the number of non-empty descriptors. 177 func removeEmptyDescriptors(descs []ocispec.Descriptor, hint int) []ocispec.Descriptor { 178 j := 0 179 for i, r := range descs { 180 if !content.Equal(r, ocispec.Descriptor{}) { 181 if i > j { 182 descs[j] = r 183 } 184 j++ 185 } 186 if j == hint { 187 break 188 } 189 } 190 return descs[:j] 191 }