github.com/uber/kraken@v0.1.4/lib/dockerregistry/paths.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 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 package dockerregistry 15 16 import ( 17 "fmt" 18 "regexp" 19 20 "github.com/uber/kraken/core" 21 ) 22 23 const _repositoryRoot = "/docker/registry/v2/repositories" 24 25 // InvalidRegistryPathError indicates path error 26 type InvalidRegistryPathError struct { 27 pathType PathType 28 path string 29 } 30 31 func (e InvalidRegistryPathError) Error() string { 32 return fmt.Sprintf("invalid registry path: %s, type: %s", e.path, e.pathType) 33 } 34 35 // PathType describes the type of a path 36 // i.e. _manfiests, _layers, _uploads, and blobs 37 type PathType string 38 39 func (pt PathType) String() string { 40 return string(pt) 41 } 42 43 const ( 44 _repositories PathType = "repositories" 45 _blobs PathType = "blobs" 46 _manifests PathType = "_manifests" 47 _uploads PathType = "_uploads" 48 _layers PathType = "_layers" 49 _invalidPathType PathType = "invalidPathType" 50 ) 51 52 // PathSubType describes the subtype of a path 53 // i.e. tags, revisions, data 54 type PathSubType string 55 56 const ( 57 _revisions PathSubType = "revisions" 58 _tags PathSubType = "tags" 59 _data PathSubType = "data" 60 _link PathSubType = "link" 61 _startedat PathSubType = "startedat" 62 _hashstates PathSubType = "hashstates" 63 _invalidPathSubType PathSubType = "invalidPathSubType" 64 ) 65 66 // ParsePath returns PathType, PathSubtype, and error given path string 67 func ParsePath(path string) (PathType, PathSubType, error) { 68 if ok, subtype := matchManifestsPath(path); ok { 69 return _manifests, subtype, nil 70 } 71 if ok, subtype := matchUploadsPath(path); ok { 72 return _uploads, subtype, nil 73 } 74 if ok, subtype := matchLayersPath(path); ok { 75 return _layers, subtype, nil 76 } 77 if ok, subtype := matchBlobsPath(path); ok { 78 return _blobs, subtype, nil 79 } 80 return _invalidPathType, _invalidPathSubType, InvalidRegistryPathError{"all", path} 81 } 82 83 // GetRepo returns repo name 84 func GetRepo(path string) (string, error) { 85 re := regexp.MustCompile("^.+/repositories/(.+)/(?:_manifests|_layers|_uploads)") 86 matches := re.FindStringSubmatch(path) 87 if len(matches) < 2 { 88 return "", InvalidRegistryPathError{_repositories, path} 89 } 90 return matches[1], nil 91 } 92 93 // GetBlobDigest returns blob digest 94 func GetBlobDigest(path string) (core.Digest, error) { 95 re := regexp.MustCompile("^.+/blobs/sha256/[0-9a-z]{2}/([0-9a-z]+)/data$") 96 matches := re.FindStringSubmatch(path) 97 if len(matches) < 2 { 98 return core.Digest{}, InvalidRegistryPathError{_blobs, path} 99 } 100 d, err := core.NewSHA256DigestFromHex(matches[1]) 101 if err != nil { 102 return core.Digest{}, fmt.Errorf("new digest: %s", err) 103 } 104 return d, nil 105 } 106 107 // GetLayerDigest returns digest of the layer 108 func GetLayerDigest(path string) (core.Digest, error) { 109 re := regexp.MustCompile("^.+/_layers/sha256/([0-9a-z]+)/(?:link|data)$") 110 matches := re.FindStringSubmatch(path) 111 if len(matches) < 2 { 112 return core.Digest{}, InvalidRegistryPathError{_layers, path} 113 } 114 d, err := core.NewSHA256DigestFromHex(matches[1]) 115 if err != nil { 116 return core.Digest{}, fmt.Errorf("new digest: %s", err) 117 } 118 return d, nil 119 } 120 121 // GetManifestDigest returns manifest or tag digest 122 func GetManifestDigest(path string) (core.Digest, error) { 123 re := regexp.MustCompile("^.+/_manifests/(?:revisions|tags/.+/index)/sha256/([0-9a-z]+)/link$") 124 matches := re.FindStringSubmatch(path) 125 if len(matches) < 2 { 126 return core.Digest{}, InvalidRegistryPathError{_manifests, path} 127 } 128 d, err := core.NewSHA256DigestFromHex(matches[1]) 129 if err != nil { 130 return core.Digest{}, fmt.Errorf("new digest: %s", err) 131 } 132 return d, nil 133 } 134 135 // GetManifestTag returns tag name 136 func GetManifestTag(path string) (string, bool, error) { 137 re := regexp.MustCompile("^.+/_manifests/tags/([^/]+)/(current|index/sha256/[0-9a-z]+)/link$") 138 matches := re.FindStringSubmatch(path) 139 if len(matches) < 3 { 140 return "", false, InvalidRegistryPathError{_manifests, path} 141 } 142 if matches[2] == "current" { 143 return matches[1], true, nil 144 } 145 return matches[1], false, nil 146 } 147 148 // GetUploadUUID returns upload UUID 149 func GetUploadUUID(path string) (string, error) { 150 re := regexp.MustCompile("^.+/_uploads/([^/]+)/(?:data$|startedat$|hashstates/[a-zA-Z0-9]+(?:/[0-9]+)?$)") 151 matches := re.FindStringSubmatch(path) 152 if len(matches) < 2 { 153 return "", InvalidRegistryPathError{_uploads, path} 154 } 155 return matches[1], nil 156 } 157 158 // GetUploadAlgoAndOffset returns the algorithm and offset of the hashstates 159 func GetUploadAlgoAndOffset(path string) (string, string, error) { 160 re := regexp.MustCompile("^.+/_uploads/[^/]+/hashstates/([a-zA-Z0-9]+)/([0-9]+)$") 161 matches := re.FindStringSubmatch(path) 162 if len(matches) < 3 { 163 return "", "", InvalidRegistryPathError{_uploads, path} 164 } 165 return matches[1], matches[2], nil 166 } 167 168 // matchManifestsPath returns true if it is a valid /_manifests path and returns the path subtype 169 // Possible subtypes are tags and revisions 170 func matchManifestsPath(path string) (bool, PathSubType) { 171 re := regexp.MustCompile("^.+/_manifests/(tags|revisions)(?:/.+/link)?$") 172 matches := re.FindStringSubmatch(path) 173 if len(matches) < 2 { 174 return false, _invalidPathSubType 175 } 176 return true, PathSubType(matches[1]) 177 } 178 179 // matchBlobsPath returns true if it if a valid /blobs path and returns a subtype 180 func matchBlobsPath(path string) (bool, PathSubType) { 181 re := regexp.MustCompile("^.+/blobs/sha256/[0-9a-z]{2}/[0-9a-z]+/data$") 182 ok := re.Match([]byte(path)) 183 if !ok { 184 return false, _invalidPathSubType 185 } 186 return true, PathSubType(_data) 187 } 188 189 // matchLayersPath returns true if it is a valid /_layers path and returns a subtype 190 func matchLayersPath(path string) (bool, PathSubType) { 191 re := regexp.MustCompile("^.+/_layers/sha256/[0-9a-z]+/(link|data)$") 192 matches := re.FindStringSubmatch(path) 193 if len(matches) < 2 { 194 return false, _invalidPathSubType 195 } 196 return true, PathSubType(matches[1]) 197 } 198 199 // matchUploadsPath returns true if it is a valid /_uploads path and returns the path subtype 200 // Possible subtypes are data, startedat and hashstates 201 func matchUploadsPath(path string) (bool, PathSubType) { 202 re := regexp.MustCompile("^.+/_uploads/[^/]+/(data$|startedat$|hashstates)") 203 matches := re.FindStringSubmatch(path) 204 if len(matches) < 2 { 205 return false, _invalidPathSubType 206 } 207 208 subtype := PathSubType(matches[1]) 209 switch subtype { 210 case _hashstates: 211 re := regexp.MustCompile("^.+/_uploads/[^/]+/hashstates/[a-zA-Z0-9]+(?:/[0-9]+)?$") 212 if !re.Match([]byte(path)) { 213 return false, _invalidPathSubType 214 } 215 } 216 return true, subtype 217 }