lib/backend/namepath/pather.go (98 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 namepath
import (
"errors"
"fmt"
"path"
"regexp"
"strings"
)
// Pather id strings.
const (
DockerTag = "docker_tag"
ShardedDockerBlob = "sharded_docker_blob"
Identity = "identity"
)
// New creates a Pather scoped to root.
func New(root, id string) (Pather, error) {
switch id {
case DockerTag:
return DockerTagPather{root}, nil
case ShardedDockerBlob:
return ShardedDockerBlobPather{root}, nil
case Identity:
return IdentityPather{root}, nil
case "":
return nil, fmt.Errorf("invalid pather identifier: empty")
default:
return nil, fmt.Errorf("unknown pather identifier: %s", id)
}
}
// Pather defines an interface for converting names into paths.
type Pather interface {
// BasePath returns the base path of where blobs are stored.
BasePath() string
// BlobPath converts name into a blob path.
BlobPath(name string) (string, error)
// NameFromBlobPath converts blob path bp back into the original blob name.
NameFromBlobPath(bp string) (string, error)
}
// DockerTagPather generates paths for Docker tags.
type DockerTagPather struct {
root string
}
// BasePath returns the docker registry repositories prefix.
func (p DockerTagPather) BasePath() string {
return path.Join(p.root, "docker/registry/v2/repositories")
}
// BlobPath interprets name as a "repo:tag" and generates a registry path for it.
func (p DockerTagPather) BlobPath(name string) (string, error) {
tokens := strings.Split(name, ":")
if len(tokens) != 2 {
return "", errors.New("name must be in format 'repo:tag'")
}
repo := tokens[0]
if len(repo) == 0 {
return "", errors.New("repo must be non-empty")
}
tag := tokens[1]
if len(tag) == 0 {
return "", errors.New("tag must be non-empty")
}
return path.Join(p.BasePath(), repo, "_manifests/tags", tag, "current/link"), nil
}
// NameFromBlobPath converts a tag path back into repo:tag format.
func (p DockerTagPather) NameFromBlobPath(bp string) (string, error) {
re := regexp.MustCompile(p.BasePath() + "/(.+)/_manifests/tags/(.+)/current/link")
matches := re.FindStringSubmatch(bp)
if len(matches) != 3 {
return "", errors.New("invalid docker tag path format")
}
repo := matches[1]
tag := matches[2]
return fmt.Sprintf("%s:%s", repo, tag), nil
}
// ShardedDockerBlobPather generates sharded paths for Docker blobs.
type ShardedDockerBlobPather struct {
root string
}
// BasePath returns the docker registry blobs prefix.
func (p ShardedDockerBlobPather) BasePath() string {
return path.Join(p.root, "docker/registry/v2/blobs")
}
// BlobPath interprets name as a SHA256 digest and returns a registry path
// which is sharded by the first two bytes.
func (p ShardedDockerBlobPather) BlobPath(name string) (string, error) {
if len(name) <= 2 {
return "", errors.New("name is too short, must be > 2 characters")
}
return path.Join(p.BasePath(), "sha256", name[:2], name, "data"), nil
}
// NameFromBlobPath converts a sharded blob path back into raw hex format.
func (p ShardedDockerBlobPather) NameFromBlobPath(bp string) (string, error) {
re := regexp.MustCompile(p.BasePath() + "/sha256/../(.+)/data")
matches := re.FindStringSubmatch(bp)
if len(matches) != 2 {
return "", errors.New("invalid sharded docker blob path format")
}
return matches[1], nil
}
// IdentityPather is the identity Pather.
type IdentityPather struct {
root string
}
// BasePath returns the root.
func (p IdentityPather) BasePath() string {
return p.root
}
// BlobPath always returns root/name.
func (p IdentityPather) BlobPath(name string) (string, error) {
return path.Join(p.root, name), nil
}
// NameFromBlobPath strips the root from bp.
func (p IdentityPather) NameFromBlobPath(bp string) (string, error) {
if !strings.HasPrefix(bp, p.root) {
return "", errors.New("invalid identity path format")
}
return bp[len(p.root)+1:], nil
}