github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/casext/refname.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2017, 2018 SUSE LLC. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package casext 19 20 import ( 21 "regexp" 22 23 "github.com/apex/log" 24 ispec "github.com/opencontainers/image-spec/specs-go/v1" 25 "github.com/pkg/errors" 26 "golang.org/x/net/context" 27 ) 28 29 // isKnownMediaType returns whether a media type is known by the spec. This 30 // probably should be moved somewhere else to avoid going out of date. 31 func isKnownMediaType(mediaType string) bool { 32 return mediaType == ispec.MediaTypeDescriptor || 33 mediaType == ispec.MediaTypeImageManifest || 34 mediaType == ispec.MediaTypeImageIndex || 35 mediaType == ispec.MediaTypeImageLayer || 36 mediaType == ispec.MediaTypeImageLayerGzip || 37 mediaType == ispec.MediaTypeImageLayerNonDistributable || 38 mediaType == ispec.MediaTypeImageLayerNonDistributableGzip || 39 mediaType == ispec.MediaTypeImageConfig 40 } 41 42 // refnameRegex is a regex that only matches reference names that are valid 43 // according to the OCI specification. See IsValidReferenceName for the EBNF. 44 var refnameRegex = regexp.MustCompile(`^([A-Za-z0-9]+(([-._:@+]|--)[A-Za-z0-9]+)*)(/([A-Za-z0-9]+(([-._:@+]|--)[A-Za-z0-9]+)*))*$`) 45 46 // IsValidReferenceName returns whether the provided annotation value for 47 // "org.opencontainers.image.ref.name" is actually valid according to the 48 // OCI specification. This only matches against the MUST requirement, not the 49 // SHOULD requirement. The EBNF defined in the specification is: 50 // 51 // refname ::= component ("/" component)* 52 // component ::= alphanum (separator alphanum)* 53 // alphanum ::= [A-Za-z0-9]+ 54 // separator ::= [-._:@+] | "--" 55 func IsValidReferenceName(refname string) bool { 56 return refnameRegex.MatchString(refname) 57 } 58 59 // ResolveReference will attempt to resolve all possible descriptor paths to 60 // Manifests (or any unknown blobs) that match a particular reference name (if 61 // descriptors are stored in non-standard blobs, Resolve will be unable to find 62 // them but will return the top-most unknown descriptor). 63 // ResolveReference assumes that "reference name" refers to the value of the 64 // "org.opencontainers.image.ref.name" descriptor annotation. It is recommended 65 // that if the returned slice of descriptors is greater than zero that the user 66 // be consulted to resolve the conflict (due to ambiguity in resolution paths). 67 // 68 // TODO: How are we meant to implement other restrictions such as the 69 // architecture and feature flags? The API will need to change. 70 func (e Engine) ResolveReference(ctx context.Context, refname string) ([]DescriptorPath, error) { 71 // XXX: It should be possible to override this somehow, in case we are 72 // dealing with an image that abuses the image specification in some 73 // way. 74 if !IsValidReferenceName(refname) { 75 return nil, errors.Errorf("refusing to resolve invalid reference %q", refname) 76 } 77 78 index, err := e.GetIndex(ctx) 79 if err != nil { 80 return nil, errors.Wrap(err, "get top-level index") 81 } 82 83 // Set of root links that match the given refname. 84 var roots []ispec.Descriptor 85 86 // We only consider the case where AnnotationRefName is defined on the 87 // top-level of the index tree. While this isn't codified in the spec (at 88 // the time of writing -- 1.0.0-rc5) there are some discussions to add this 89 // restriction in 1.0.0-rc6. 90 for _, descriptor := range index.Manifests { 91 // XXX: What should we do if refname == "". 92 if descriptor.Annotations[ispec.AnnotationRefName] == refname { 93 roots = append(roots, descriptor) 94 } 95 } 96 97 // The resolved set of descriptors. 98 var resolutions []DescriptorPath 99 for _, root := range roots { 100 // Find all manifests or other blobs that are reachable from the given 101 // descriptor. 102 if err := e.Walk(ctx, root, func(descriptorPath DescriptorPath) error { 103 descriptor := descriptorPath.Descriptor() 104 105 // It is very important that we do not ignore unknown media types 106 // here. We only recurse into mediaTypes that are *known* and are 107 // also not ispec.MediaTypeImageManifest. 108 if isKnownMediaType(descriptor.MediaType) && descriptor.MediaType != ispec.MediaTypeImageManifest { 109 return nil 110 } 111 112 // Add the resolution and do not recurse any deeper. 113 resolutions = append(resolutions, descriptorPath) 114 return ErrSkipDescriptor 115 }); err != nil { 116 return nil, errors.Wrapf(err, "walk %s", root.Digest) 117 } 118 } 119 120 log.WithFields(log.Fields{ 121 "refs": resolutions, 122 }).Debugf("casext.ResolveReference(%s) got these descriptors", refname) 123 return resolutions, nil 124 } 125 126 // XXX: Should the *Reference set of interfaces support DescriptorPath? While 127 // it might seem like it doesn't make sense, a DescriptorPath entirely 128 // removes ambiguity with regards to which root needs to be operated on. 129 // If a user has that information we should provide them a way to use it. 130 131 // UpdateReference replaces an existing entry for refname with the given 132 // descriptor. If there are multiple descriptors that match the refname they 133 // are all replaced with the given descriptor. 134 func (e Engine) UpdateReference(ctx context.Context, refname string, descriptor ispec.Descriptor) error { 135 // XXX: It should be possible to override this somehow, in case we are 136 // dealing with an image that abuses the image specification in some 137 // way. 138 if !IsValidReferenceName(refname) { 139 return errors.Errorf("refusing to update invalid reference %q", refname) 140 } 141 142 // Get index to modify. 143 index, err := e.GetIndex(ctx) 144 if err != nil { 145 return errors.Wrap(err, "get top-level index") 146 } 147 148 // TODO: Handle refname = "". 149 var newIndex []ispec.Descriptor 150 for _, descriptor := range index.Manifests { 151 if descriptor.Annotations[ispec.AnnotationRefName] != refname { 152 newIndex = append(newIndex, descriptor) 153 } 154 } 155 if len(newIndex)-len(index.Manifests) > 1 { 156 // Warn users if the operation is going to remove more than one references. 157 log.Warn("multiple references match the given reference name -- all of them have been replaced due to this ambiguity") 158 } 159 160 // Append the descriptor. 161 if descriptor.Annotations == nil { 162 descriptor.Annotations = map[string]string{} 163 } 164 descriptor.Annotations[ispec.AnnotationRefName] = refname 165 newIndex = append(newIndex, descriptor) 166 167 // Commit to image. 168 index.Manifests = newIndex 169 if err := e.PutIndex(ctx, index); err != nil { 170 return errors.Wrap(err, "replace index") 171 } 172 return nil 173 } 174 175 // DeleteReference removes all entries in the index that match the given 176 // refname. 177 func (e Engine) DeleteReference(ctx context.Context, refname string) error { 178 // XXX: It should be possible to override this somehow, in case we are 179 // dealing with an image that abuses the image specification in some 180 // way. 181 if !IsValidReferenceName(refname) { 182 return errors.Errorf("refusing to delete invalid reference %q", refname) 183 } 184 185 // Get index to modify. 186 index, err := e.GetIndex(ctx) 187 if err != nil { 188 return errors.Wrap(err, "get top-level index") 189 } 190 191 // TODO: Handle refname = "". 192 var newIndex []ispec.Descriptor 193 for _, descriptor := range index.Manifests { 194 if descriptor.Annotations[ispec.AnnotationRefName] != refname { 195 newIndex = append(newIndex, descriptor) 196 } 197 } 198 if len(newIndex)-len(index.Manifests) > 1 { 199 // Warn users if the operation is going to remove more than one references. 200 log.Warn("multiple references match the given reference name -- all of them have been deleted due to this ambiguity") 201 } 202 203 // Commit to image. 204 index.Manifests = newIndex 205 if err := e.PutIndex(ctx, index); err != nil { 206 return errors.Wrap(err, "replace index") 207 } 208 return nil 209 } 210 211 // ListReferences returns all of the ref.name entries that are specified in the 212 // top-level index. Note that the list may contain duplicates, due to the 213 // nature of references in the image-spec. 214 func (e Engine) ListReferences(ctx context.Context) ([]string, error) { 215 // Get index. 216 index, err := e.GetIndex(ctx) 217 if err != nil { 218 return nil, errors.Wrap(err, "get top-level index") 219 } 220 221 var refs []string 222 for _, descriptor := range index.Manifests { 223 ref, ok := descriptor.Annotations[ispec.AnnotationRefName] 224 if ok { 225 refs = append(refs, ref) 226 } 227 } 228 return refs, nil 229 }