github.com/lusis/distribution@v2.0.1+incompatible/registry/storage/paths.go (about) 1 package storage 2 3 import ( 4 "fmt" 5 "path" 6 "strings" 7 8 "github.com/docker/distribution/digest" 9 ) 10 11 const storagePathVersion = "v2" 12 13 // pathMapper maps paths based on "object names" and their ids. The "object 14 // names" mapped by pathMapper are internal to the storage system. 15 // 16 // The path layout in the storage backend is roughly as follows: 17 // 18 // <root>/v2 19 // -> repositories/ 20 // -><name>/ 21 // -> _manifests/ 22 // revisions 23 // -> <manifest digest path> 24 // -> link 25 // -> signatures 26 // <algorithm>/<digest>/link 27 // tags/<tag> 28 // -> current/link 29 // -> index 30 // -> <algorithm>/<hex digest>/link 31 // -> _layers/ 32 // <layer links to blob store> 33 // -> _uploads/<uuid> 34 // data 35 // startedat 36 // hashstates/<algorithm>/<offset> 37 // -> blob/<algorithm> 38 // <split directory content addressable storage> 39 // 40 // The storage backend layout is broken up into a content- addressable blob 41 // store and repositories. The content-addressable blob store holds most data 42 // throughout the backend, keyed by algorithm and digests of the underlying 43 // content. Access to the blob store is controled through links from the 44 // repository to blobstore. 45 // 46 // A repository is made up of layers, manifests and tags. The layers component 47 // is just a directory of layers which are "linked" into a repository. A layer 48 // can only be accessed through a qualified repository name if it is linked in 49 // the repository. Uploads of layers are managed in the uploads directory, 50 // which is key by upload uuid. When all data for an upload is received, the 51 // data is moved into the blob store and the upload directory is deleted. 52 // Abandoned uploads can be garbage collected by reading the startedat file 53 // and removing uploads that have been active for longer than a certain time. 54 // 55 // The third component of the repository directory is the manifests store, 56 // which is made up of a revision store and tag store. Manifests are stored in 57 // the blob store and linked into the revision store. Signatures are separated 58 // from the manifest payload data and linked into the blob store, as well. 59 // While the registry can save all revisions of a manifest, no relationship is 60 // implied as to the ordering of changes to a manifest. The tag store provides 61 // support for name, tag lookups of manifests, using "current/link" under a 62 // named tag directory. An index is maintained to support deletions of all 63 // revisions of a given manifest tag. 64 // 65 // We cover the path formats implemented by this path mapper below. 66 // 67 // Manifests: 68 // 69 // manifestRevisionPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/ 70 // manifestRevisionLinkPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/link 71 // manifestSignaturesPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/signatures/ 72 // manifestSignatureLinkPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/signatures/<algorithm>/<hex digest>/link 73 // 74 // Tags: 75 // 76 // manifestTagsPathSpec: <root>/v2/repositories/<name>/_manifests/tags/ 77 // manifestTagPathSpec: <root>/v2/repositories/<name>/_manifests/tags/<tag>/ 78 // manifestTagCurrentPathSpec: <root>/v2/repositories/<name>/_manifests/tags/<tag>/current/link 79 // manifestTagIndexPathSpec: <root>/v2/repositories/<name>/_manifests/tags/<tag>/index/ 80 // manifestTagIndexEntryPathSpec: <root>/v2/repositories/<name>/_manifests/tags/<tag>/index/<algorithm>/<hex digest>/ 81 // manifestTagIndexEntryLinkPathSpec: <root>/v2/repositories/<name>/_manifests/tags/<tag>/index/<algorithm>/<hex digest>/link 82 // 83 // Layers: 84 // 85 // layerLinkPathSpec: <root>/v2/repositories/<name>/_layers/tarsum/<tarsum version>/<tarsum hash alg>/<tarsum hash>/link 86 // 87 // Uploads: 88 // 89 // uploadDataPathSpec: <root>/v2/repositories/<name>/_uploads/<uuid>/data 90 // uploadStartedAtPathSpec: <root>/v2/repositories/<name>/_uploads/<uuid>/startedat 91 // uploadHashStatePathSpec: <root>/v2/repositories/<name>/_uploads/<uuid>/hashstates/<algorithm>/<offset> 92 // 93 // Blob Store: 94 // 95 // blobPathSpec: <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest> 96 // blobDataPathSpec: <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>/data 97 // 98 // For more information on the semantic meaning of each path and their 99 // contents, please see the path spec documentation. 100 type pathMapper struct { 101 root string 102 version string // should be a constant? 103 } 104 105 var defaultPathMapper = &pathMapper{ 106 root: "/docker/registry/", 107 version: storagePathVersion, 108 } 109 110 // path returns the path identified by spec. 111 func (pm *pathMapper) path(spec pathSpec) (string, error) { 112 113 // Switch on the path object type and return the appropriate path. At 114 // first glance, one may wonder why we don't use an interface to 115 // accomplish this. By keep the formatting separate from the pathSpec, we 116 // keep separate the path generation componentized. These specs could be 117 // passed to a completely different mapper implementation and generate a 118 // different set of paths. 119 // 120 // For example, imagine migrating from one backend to the other: one could 121 // build a filesystem walker that converts a string path in one version, 122 // to an intermediate path object, than can be consumed and mapped by the 123 // other version. 124 125 rootPrefix := []string{pm.root, pm.version} 126 repoPrefix := append(rootPrefix, "repositories") 127 128 switch v := spec.(type) { 129 130 case manifestRevisionPathSpec: 131 components, err := digestPathComponents(v.revision, false) 132 if err != nil { 133 return "", err 134 } 135 136 return path.Join(append(append(repoPrefix, v.name, "_manifests", "revisions"), components...)...), nil 137 case manifestRevisionLinkPathSpec: 138 root, err := pm.path(manifestRevisionPathSpec{ 139 name: v.name, 140 revision: v.revision, 141 }) 142 143 if err != nil { 144 return "", err 145 } 146 147 return path.Join(root, "link"), nil 148 case manifestSignaturesPathSpec: 149 root, err := pm.path(manifestRevisionPathSpec{ 150 name: v.name, 151 revision: v.revision, 152 }) 153 154 if err != nil { 155 return "", err 156 } 157 158 return path.Join(root, "signatures"), nil 159 case manifestSignatureLinkPathSpec: 160 root, err := pm.path(manifestSignaturesPathSpec{ 161 name: v.name, 162 revision: v.revision, 163 }) 164 if err != nil { 165 return "", err 166 } 167 168 signatureComponents, err := digestPathComponents(v.signature, false) 169 if err != nil { 170 return "", err 171 } 172 173 return path.Join(root, path.Join(append(signatureComponents, "link")...)), nil 174 case manifestTagsPathSpec: 175 return path.Join(append(repoPrefix, v.name, "_manifests", "tags")...), nil 176 case manifestTagPathSpec: 177 root, err := pm.path(manifestTagsPathSpec{ 178 name: v.name, 179 }) 180 if err != nil { 181 return "", err 182 } 183 184 return path.Join(root, v.tag), nil 185 case manifestTagCurrentPathSpec: 186 root, err := pm.path(manifestTagPathSpec{ 187 name: v.name, 188 tag: v.tag, 189 }) 190 if err != nil { 191 return "", err 192 } 193 194 return path.Join(root, "current", "link"), nil 195 case manifestTagIndexPathSpec: 196 root, err := pm.path(manifestTagPathSpec{ 197 name: v.name, 198 tag: v.tag, 199 }) 200 if err != nil { 201 return "", err 202 } 203 204 return path.Join(root, "index"), nil 205 case manifestTagIndexEntryLinkPathSpec: 206 root, err := pm.path(manifestTagIndexEntryPathSpec{ 207 name: v.name, 208 tag: v.tag, 209 revision: v.revision, 210 }) 211 if err != nil { 212 return "", err 213 } 214 215 return path.Join(root, "link"), nil 216 case manifestTagIndexEntryPathSpec: 217 root, err := pm.path(manifestTagIndexPathSpec{ 218 name: v.name, 219 tag: v.tag, 220 }) 221 if err != nil { 222 return "", err 223 } 224 225 components, err := digestPathComponents(v.revision, false) 226 if err != nil { 227 return "", err 228 } 229 230 return path.Join(root, path.Join(components...)), nil 231 case layerLinkPathSpec: 232 components, err := digestPathComponents(v.digest, false) 233 if err != nil { 234 return "", err 235 } 236 237 layerLinkPathComponents := append(repoPrefix, v.name, "_layers") 238 239 return path.Join(path.Join(append(layerLinkPathComponents, components...)...), "link"), nil 240 case blobDataPathSpec: 241 components, err := digestPathComponents(v.digest, true) 242 if err != nil { 243 return "", err 244 } 245 246 components = append(components, "data") 247 blobPathPrefix := append(rootPrefix, "blobs") 248 return path.Join(append(blobPathPrefix, components...)...), nil 249 250 case uploadDataPathSpec: 251 return path.Join(append(repoPrefix, v.name, "_uploads", v.uuid, "data")...), nil 252 case uploadStartedAtPathSpec: 253 return path.Join(append(repoPrefix, v.name, "_uploads", v.uuid, "startedat")...), nil 254 case uploadHashStatePathSpec: 255 offset := fmt.Sprintf("%d", v.offset) 256 if v.list { 257 offset = "" // Limit to the prefix for listing offsets. 258 } 259 return path.Join(append(repoPrefix, v.name, "_uploads", v.uuid, "hashstates", v.alg, offset)...), nil 260 case repositoriesRootPathSpec: 261 return path.Join(repoPrefix...), nil 262 default: 263 // TODO(sday): This is an internal error. Ensure it doesn't escape (panic?). 264 return "", fmt.Errorf("unknown path spec: %#v", v) 265 } 266 } 267 268 // pathSpec is a type to mark structs as path specs. There is no 269 // implementation because we'd like to keep the specs and the mappers 270 // decoupled. 271 type pathSpec interface { 272 pathSpec() 273 } 274 275 // manifestRevisionPathSpec describes the components of the directory path for 276 // a manifest revision. 277 type manifestRevisionPathSpec struct { 278 name string 279 revision digest.Digest 280 } 281 282 func (manifestRevisionPathSpec) pathSpec() {} 283 284 // manifestRevisionLinkPathSpec describes the path components required to look 285 // up the data link for a revision of a manifest. If this file is not present, 286 // the manifest blob is not available in the given repo. The contents of this 287 // file should just be the digest. 288 type manifestRevisionLinkPathSpec struct { 289 name string 290 revision digest.Digest 291 } 292 293 func (manifestRevisionLinkPathSpec) pathSpec() {} 294 295 // manifestSignaturesPathSpec decribes the path components for the directory 296 // containing all the signatures for the target blob. Entries are named with 297 // the underlying key id. 298 type manifestSignaturesPathSpec struct { 299 name string 300 revision digest.Digest 301 } 302 303 func (manifestSignaturesPathSpec) pathSpec() {} 304 305 // manifestSignatureLinkPathSpec decribes the path components used to look up 306 // a signature file by the hash of its blob. 307 type manifestSignatureLinkPathSpec struct { 308 name string 309 revision digest.Digest 310 signature digest.Digest 311 } 312 313 func (manifestSignatureLinkPathSpec) pathSpec() {} 314 315 // manifestTagsPathSpec describes the path elements required to point to the 316 // manifest tags directory. 317 type manifestTagsPathSpec struct { 318 name string 319 } 320 321 func (manifestTagsPathSpec) pathSpec() {} 322 323 // manifestTagPathSpec describes the path elements required to point to the 324 // manifest tag links files under a repository. These contain a blob id that 325 // can be used to look up the data and signatures. 326 type manifestTagPathSpec struct { 327 name string 328 tag string 329 } 330 331 func (manifestTagPathSpec) pathSpec() {} 332 333 // manifestTagCurrentPathSpec describes the link to the current revision for a 334 // given tag. 335 type manifestTagCurrentPathSpec struct { 336 name string 337 tag string 338 } 339 340 func (manifestTagCurrentPathSpec) pathSpec() {} 341 342 // manifestTagCurrentPathSpec describes the link to the index of revisions 343 // with the given tag. 344 type manifestTagIndexPathSpec struct { 345 name string 346 tag string 347 } 348 349 func (manifestTagIndexPathSpec) pathSpec() {} 350 351 // manifestTagIndexEntryPathSpec contains the entries of the index by revision. 352 type manifestTagIndexEntryPathSpec struct { 353 name string 354 tag string 355 revision digest.Digest 356 } 357 358 func (manifestTagIndexEntryPathSpec) pathSpec() {} 359 360 // manifestTagIndexEntryLinkPathSpec describes the link to a revisions of a 361 // manifest with given tag within the index. 362 type manifestTagIndexEntryLinkPathSpec struct { 363 name string 364 tag string 365 revision digest.Digest 366 } 367 368 func (manifestTagIndexEntryLinkPathSpec) pathSpec() {} 369 370 // layerLink specifies a path for a layer link, which is a file with a blob 371 // id. The layer link will contain a content addressable blob id reference 372 // into the blob store. The format of the contents is as follows: 373 // 374 // <algorithm>:<hex digest of layer data> 375 // 376 // The following example of the file contents is more illustrative: 377 // 378 // sha256:96443a84ce518ac22acb2e985eda402b58ac19ce6f91980bde63726a79d80b36 379 // 380 // This says indicates that there is a blob with the id/digest, calculated via 381 // sha256 that can be fetched from the blob store. 382 type layerLinkPathSpec struct { 383 name string 384 digest digest.Digest 385 } 386 387 func (layerLinkPathSpec) pathSpec() {} 388 389 // blobAlgorithmReplacer does some very simple path sanitization for user 390 // input. Mostly, this is to provide some hierarchy for tarsum digests. Paths 391 // should be "safe" before getting this far due to strict digest requirements 392 // but we can add further path conversion here, if needed. 393 var blobAlgorithmReplacer = strings.NewReplacer( 394 "+", "/", 395 ".", "/", 396 ";", "/", 397 ) 398 399 // // blobPathSpec contains the path for the registry global blob store. 400 // type blobPathSpec struct { 401 // digest digest.Digest 402 // } 403 404 // func (blobPathSpec) pathSpec() {} 405 406 // blobDataPathSpec contains the path for the registry global blob store. For 407 // now, this contains layer data, exclusively. 408 type blobDataPathSpec struct { 409 digest digest.Digest 410 } 411 412 func (blobDataPathSpec) pathSpec() {} 413 414 // uploadDataPathSpec defines the path parameters of the data file for 415 // uploads. 416 type uploadDataPathSpec struct { 417 name string 418 uuid string 419 } 420 421 func (uploadDataPathSpec) pathSpec() {} 422 423 // uploadDataPathSpec defines the path parameters for the file that stores the 424 // start time of an uploads. If it is missing, the upload is considered 425 // unknown. Admittedly, the presence of this file is an ugly hack to make sure 426 // we have a way to cleanup old or stalled uploads that doesn't rely on driver 427 // FileInfo behavior. If we come up with a more clever way to do this, we 428 // should remove this file immediately and rely on the startetAt field from 429 // the client to enforce time out policies. 430 type uploadStartedAtPathSpec struct { 431 name string 432 uuid string 433 } 434 435 func (uploadStartedAtPathSpec) pathSpec() {} 436 437 // uploadHashStatePathSpec defines the path parameters for the file that stores 438 // the hash function state of an upload at a specific byte offset. If `list` is 439 // set, then the path mapper will generate a list prefix for all hash state 440 // offsets for the upload identified by the name, uuid, and alg. 441 type uploadHashStatePathSpec struct { 442 name string 443 uuid string 444 alg string 445 offset int64 446 list bool 447 } 448 449 func (uploadHashStatePathSpec) pathSpec() {} 450 451 // repositoriesRootPathSpec returns the root of repositories 452 type repositoriesRootPathSpec struct { 453 } 454 455 func (repositoriesRootPathSpec) pathSpec() {} 456 457 // digestPathComponents provides a consistent path breakdown for a given 458 // digest. For a generic digest, it will be as follows: 459 // 460 // <algorithm>/<hex digest> 461 // 462 // Most importantly, for tarsum, the layout looks like this: 463 // 464 // tarsum/<version>/<digest algorithm>/<full digest> 465 // 466 // If multilevel is true, the first two bytes of the digest will separate 467 // groups of digest folder. It will be as follows: 468 // 469 // <algorithm>/<first two bytes of digest>/<full digest> 470 // 471 func digestPathComponents(dgst digest.Digest, multilevel bool) ([]string, error) { 472 if err := dgst.Validate(); err != nil { 473 return nil, err 474 } 475 476 algorithm := blobAlgorithmReplacer.Replace(dgst.Algorithm()) 477 hex := dgst.Hex() 478 prefix := []string{algorithm} 479 480 var suffix []string 481 482 if multilevel { 483 suffix = append(suffix, hex[:2]) 484 } 485 486 suffix = append(suffix, hex) 487 488 if tsi, err := digest.ParseTarSum(dgst.String()); err == nil { 489 // We have a tarsum! 490 version := tsi.Version 491 if version == "" { 492 version = "v0" 493 } 494 495 prefix = []string{ 496 "tarsum", 497 version, 498 tsi.Algorithm, 499 } 500 } 501 502 return append(prefix, suffix...), nil 503 }