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