lib/dockerregistry/paths.go (162 lines of code) (raw):

// Copyright (c) 2016-2019 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package dockerregistry import ( "fmt" "regexp" "github.com/uber/kraken/core" ) const _repositoryRoot = "/docker/registry/v2/repositories" // InvalidRegistryPathError indicates path error type InvalidRegistryPathError struct { pathType PathType path string } func (e InvalidRegistryPathError) Error() string { return fmt.Sprintf("invalid registry path: %s, type: %s", e.path, e.pathType) } // PathType describes the type of a path // i.e. _manfiests, _layers, _uploads, and blobs type PathType string func (pt PathType) String() string { return string(pt) } const ( _repositories PathType = "repositories" _blobs PathType = "blobs" _manifests PathType = "_manifests" _uploads PathType = "_uploads" _layers PathType = "_layers" _invalidPathType PathType = "invalidPathType" ) // PathSubType describes the subtype of a path // i.e. tags, revisions, data type PathSubType string const ( _revisions PathSubType = "revisions" _tags PathSubType = "tags" _data PathSubType = "data" _link PathSubType = "link" _startedat PathSubType = "startedat" _hashstates PathSubType = "hashstates" _invalidPathSubType PathSubType = "invalidPathSubType" ) // ParsePath returns PathType, PathSubtype, and error given path string func ParsePath(path string) (PathType, PathSubType, error) { if ok, subtype := matchManifestsPath(path); ok { return _manifests, subtype, nil } if ok, subtype := matchUploadsPath(path); ok { return _uploads, subtype, nil } if ok, subtype := matchLayersPath(path); ok { return _layers, subtype, nil } if ok, subtype := matchBlobsPath(path); ok { return _blobs, subtype, nil } return _invalidPathType, _invalidPathSubType, InvalidRegistryPathError{"all", path} } // GetRepo returns repo name func GetRepo(path string) (string, error) { re := regexp.MustCompile("^.+/repositories/(.+)/(?:_manifests|_layers|_uploads)") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return "", InvalidRegistryPathError{_repositories, path} } return matches[1], nil } // GetBlobDigest returns blob digest func GetBlobDigest(path string) (core.Digest, error) { re := regexp.MustCompile("^.+/blobs/sha256/[0-9a-z]{2}/([0-9a-z]+)/data$") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return core.Digest{}, InvalidRegistryPathError{_blobs, path} } d, err := core.NewSHA256DigestFromHex(matches[1]) if err != nil { return core.Digest{}, fmt.Errorf("new digest: %s", err) } return d, nil } // GetLayerDigest returns digest of the layer func GetLayerDigest(path string) (core.Digest, error) { re := regexp.MustCompile("^.+/_layers/sha256/([0-9a-z]+)/(?:link|data)$") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return core.Digest{}, InvalidRegistryPathError{_layers, path} } d, err := core.NewSHA256DigestFromHex(matches[1]) if err != nil { return core.Digest{}, fmt.Errorf("new digest: %s", err) } return d, nil } // GetManifestDigest returns manifest or tag digest func GetManifestDigest(path string) (core.Digest, error) { re := regexp.MustCompile("^.+/_manifests/(?:revisions|tags/.+/index)/sha256/([0-9a-z]+)/link$") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return core.Digest{}, InvalidRegistryPathError{_manifests, path} } d, err := core.NewSHA256DigestFromHex(matches[1]) if err != nil { return core.Digest{}, fmt.Errorf("new digest: %s", err) } return d, nil } // GetManifestTag returns tag name func GetManifestTag(path string) (string, bool, error) { re := regexp.MustCompile("^.+/_manifests/tags/([^/]+)/(current|index/sha256/[0-9a-z]+)/link$") matches := re.FindStringSubmatch(path) if len(matches) < 3 { return "", false, InvalidRegistryPathError{_manifests, path} } if matches[2] == "current" { return matches[1], true, nil } return matches[1], false, nil } // GetUploadUUID returns upload UUID func GetUploadUUID(path string) (string, error) { re := regexp.MustCompile("^.+/_uploads/([^/]+)/(?:data$|startedat$|hashstates/[a-zA-Z0-9]+(?:/[0-9]+)?$)") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return "", InvalidRegistryPathError{_uploads, path} } return matches[1], nil } // GetUploadAlgoAndOffset returns the algorithm and offset of the hashstates func GetUploadAlgoAndOffset(path string) (string, string, error) { re := regexp.MustCompile("^.+/_uploads/[^/]+/hashstates/([a-zA-Z0-9]+)/([0-9]+)$") matches := re.FindStringSubmatch(path) if len(matches) < 3 { return "", "", InvalidRegistryPathError{_uploads, path} } return matches[1], matches[2], nil } // matchManifestsPath returns true if it is a valid /_manifests path and returns the path subtype // Possible subtypes are tags and revisions func matchManifestsPath(path string) (bool, PathSubType) { re := regexp.MustCompile("^.+/_manifests/(tags|revisions)(?:/.+/link)?$") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return false, _invalidPathSubType } return true, PathSubType(matches[1]) } // matchBlobsPath returns true if it if a valid /blobs path and returns a subtype func matchBlobsPath(path string) (bool, PathSubType) { re := regexp.MustCompile("^.+/blobs/sha256/[0-9a-z]{2}/[0-9a-z]+/data$") ok := re.Match([]byte(path)) if !ok { return false, _invalidPathSubType } return true, PathSubType(_data) } // matchLayersPath returns true if it is a valid /_layers path and returns a subtype func matchLayersPath(path string) (bool, PathSubType) { re := regexp.MustCompile("^.+/_layers/sha256/[0-9a-z]+/(link|data)$") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return false, _invalidPathSubType } return true, PathSubType(matches[1]) } // matchUploadsPath returns true if it is a valid /_uploads path and returns the path subtype // Possible subtypes are data, startedat and hashstates func matchUploadsPath(path string) (bool, PathSubType) { re := regexp.MustCompile("^.+/_uploads/[^/]+/(data$|startedat$|hashstates)") matches := re.FindStringSubmatch(path) if len(matches) < 2 { return false, _invalidPathSubType } subtype := PathSubType(matches[1]) switch subtype { case _hashstates: re := regexp.MustCompile("^.+/_uploads/[^/]+/hashstates/[a-zA-Z0-9]+(?:/[0-9]+)?$") if !re.Match([]byte(path)) { return false, _invalidPathSubType } } return true, subtype }