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  }