package oci
import (
"errors"
"fmt"
"net/url"
"regexp"
"github.com/opencontainers/go-digest"
)
var (
nameRegex = regexp.MustCompile(`([a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*)`)
tagRegex = regexp.MustCompile(`([a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})`)
manifestRegexTag = regexp.MustCompile(`/v2/` + nameRegex.String() + `/manifests/` + tagRegex.String() + `$`)
manifestRegexDigest = regexp.MustCompile(`/v2/` + nameRegex.String() + `/manifests/(.*)`)
blobsRegexDigest = regexp.MustCompile(`/v2/` + nameRegex.String() + `/blobs/(.*)`)
)
type DistributionKind string
const (
DistributionKindManifest = "manifests"
DistributionKindBlob = "blobs"
)
type DistributionPath struct {
Kind DistributionKind
Name string
Digest digest.Digest
Tag string
Registry string
}
func (d DistributionPath) Reference() string {
if d.Digest != "" {
return d.Digest.String()
}
return fmt.Sprintf("%s/%s:%s", d.Registry, d.Name, d.Tag)
}
func (d DistributionPath) IsLatestTag() bool {
return d.Tag == "latest"
}
func (d DistributionPath) URL() *url.URL {
ref := d.Digest.String()
if ref == "" {
ref = d.Tag
}
return &url.URL{
Path: fmt.Sprintf("/v2/%s/%s/%s", d.Name, d.Kind, ref),
RawQuery: fmt.Sprintf("ns=%s", d.Registry),
}
}
func ParseDistributionPath(u *url.URL) (DistributionPath, error) {
registry := u.Query().Get("ns")
comps := manifestRegexTag.FindStringSubmatch(u.Path)
if len(comps) == 6 {
if registry == "" {
return DistributionPath{}, errors.New("registry parameter needs to be set for tag references")
}
dist := DistributionPath{
Kind: DistributionKindManifest,
Name: comps[1],
Tag: comps[5],
Registry: registry,
}
return dist, nil
}
comps = manifestRegexDigest.FindStringSubmatch(u.Path)
if len(comps) == 6 {
dgst, err := digest.Parse(comps[5])
if err != nil {
return DistributionPath{}, err
}
dist := DistributionPath{
Kind: DistributionKindManifest,
Name: comps[1],
Digest: dgst,
Registry: registry,
}
return dist, nil
}
comps = blobsRegexDigest.FindStringSubmatch(u.Path)
if len(comps) == 6 {
dgst, err := digest.Parse(comps[5])
if err != nil {
return DistributionPath{}, err
}
dist := DistributionPath{
Kind: DistributionKindBlob,
Name: comps[1],
Digest: dgst,
Registry: registry,
}
return dist, nil
}
return DistributionPath{}, errors.New("distribution path could not be parsed")
}