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  }