Move registry client to a dedicated package

This commit is contained in:
CrazyMax
2020-01-08 14:20:25 +01:00
parent 883977b5d9
commit fbfc7a2690
13 changed files with 130 additions and 58 deletions

73
pkg/registry/image.go Normal file
View File

@@ -0,0 +1,73 @@
// Source: https://github.com/genuinetools/reg/blob/f3a9b00ec86f334702381edf842f03b3a9243a0a/registry/image.go
package registry
import (
"fmt"
"github.com/containers/image/docker/reference"
digest "github.com/opencontainers/go-digest"
)
// Image holds information about an image.
type Image struct {
Domain string
Path string
Tag string
Digest digest.Digest
named reference.Named
}
// Name returns the full name representation of an image.
func (i Image) Name() string {
return i.named.Name()
}
// String returns the string representation of an image.
func (i Image) String() string {
return i.named.String()
}
// Reference returns either the digest if it is non-empty or the tag for the image.
func (i Image) Reference() string {
if len(i.Digest.String()) > 1 {
return i.Digest.String()
}
return i.Tag
}
// WithDigest sets the digest for an image.
func (i *Image) WithDigest(digest digest.Digest) (err error) {
i.Digest = digest
i.named, err = reference.WithDigest(i.named, digest)
return err
}
// ParseImage returns an Image struct with all the values filled in for a given image.
func ParseImage(image string) (Image, error) {
// Parse the image name and tag.
named, err := reference.ParseNormalizedNamed(image)
if err != nil {
return Image{}, fmt.Errorf("parsing image %q failed: %v", image, err)
}
// Add the latest lag if they did not provide one.
named = reference.TagNameOnly(named)
i := Image{
named: named,
Domain: reference.Domain(named),
Path: reference.Path(named),
}
// Add the tag if there was one.
if tagged, ok := named.(reference.Tagged); ok {
i.Tag = tagged.Tag()
}
// Add the digest if there was one.
if canonical, ok := named.(reference.Canonical); ok {
i.Digest = canonical.Digest()
}
return i, nil
}

66
pkg/registry/manifest.go Normal file
View File

@@ -0,0 +1,66 @@
package registry
import (
"time"
"github.com/containers/image/manifest"
"github.com/opencontainers/go-digest"
)
type Manifest struct {
Name string
Tag string
MIMEType string
Digest digest.Digest
Created *time.Time
DockerVersion string
Labels map[string]string
Architecture string
Os string
Layers []string
}
// Manifest returns the manifest for a specific image
func (c *Client) Manifest(image Image) (Manifest, error) {
ctx, cancel := c.timeoutContext()
defer cancel()
imgCls, err := c.newImage(ctx, image.String())
if err != nil {
return Manifest{}, err
}
defer imgCls.Close()
rawManifest, _, err := imgCls.Manifest(ctx)
if err != nil {
return Manifest{}, err
}
imgInspect, err := imgCls.Inspect(ctx)
if err != nil {
return Manifest{}, err
}
imgDigest, err := manifest.Digest(rawManifest)
if err != nil {
return Manifest{}, err
}
imgTag := imgInspect.Tag
if imgTag == "" {
imgTag = image.Tag
}
return Manifest{
Name: imgCls.Reference().DockerReference().Name(),
Tag: imgTag,
MIMEType: manifest.GuessMIMEType(rawManifest),
Digest: imgDigest,
Created: imgInspect.Created,
DockerVersion: imgInspect.DockerVersion,
Labels: imgInspect.Labels,
Architecture: imgInspect.Architecture,
Os: imgInspect.Os,
Layers: imgInspect.Layers,
}, nil
}

81
pkg/registry/registry.go Normal file
View File

@@ -0,0 +1,81 @@
package registry
import (
"context"
"fmt"
"strings"
"time"
"github.com/containers/image/docker"
"github.com/containers/image/types"
)
// Client represents an active docker registry object
type Client struct {
opts Options
sysCtx *types.SystemContext
}
// Options holds docker registry object options
type Options struct {
Os string
Arch string
Username string
Password string
InsecureTLS bool
Timeout time.Duration
UserAgent string
}
// New creates new docker registry client instance
func New(opts Options) (*Client, error) {
// Auth
auth := &types.DockerAuthConfig{}
if opts.Username != "" {
auth = &types.DockerAuthConfig{
Username: opts.Username,
Password: opts.Password,
}
}
// Sys context
sysCtx := &types.SystemContext{
OSChoice: opts.Os,
ArchitectureChoice: opts.Arch,
DockerAuthConfig: auth,
DockerDaemonInsecureSkipTLSVerify: opts.InsecureTLS,
DockerInsecureSkipTLSVerify: types.NewOptionalBool(opts.InsecureTLS),
DockerRegistryUserAgent: opts.UserAgent,
}
return &Client{
sysCtx: sysCtx,
}, nil
}
func (c *Client) timeoutContext() (context.Context, context.CancelFunc) {
ctx := context.Background()
var cancel context.CancelFunc = func() {}
if c.opts.Timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, c.opts.Timeout)
}
return ctx, cancel
}
func (c *Client) newImage(ctx context.Context, imageStr string) (types.ImageCloser, error) {
if !strings.HasPrefix(imageStr, "//") {
imageStr = fmt.Sprintf("//%s", imageStr)
}
ref, err := docker.ParseReference(imageStr)
if err != nil {
return nil, fmt.Errorf("invalid image name %s: %v", imageStr, err)
}
img, err := ref.NewImage(ctx, c.sysCtx)
if err != nil {
return nil, err
}
return img, nil
}

67
pkg/registry/tags.go Normal file
View File

@@ -0,0 +1,67 @@
package registry
import (
"github.com/containers/image/docker"
"github.com/crazy-max/diun/pkg/utl"
)
type Tags struct {
List []string
NotIncluded int
Excluded int
Total int
}
type TagsOptions struct {
Image Image
Max int
Include []string
Exclude []string
}
// Tags returns tags of a Docker repository
func (c *Client) Tags(opts TagsOptions) (*Tags, error) {
ctx, cancel := c.timeoutContext()
defer cancel()
imgCls, err := c.newImage(ctx, opts.Image.String())
if err != nil {
return nil, err
}
defer imgCls.Close()
tags, err := docker.GetRepositoryTags(ctx, c.sysCtx, imgCls.Reference())
if err != nil {
return nil, err
}
res := &Tags{
NotIncluded: 0,
Excluded: 0,
Total: len(tags),
}
// Filter
for _, tag := range tags {
if !utl.IsIncluded(tag, opts.Include) {
res.NotIncluded++
continue
} else if utl.IsExcluded(tag, opts.Exclude) {
res.Excluded++
continue
}
res.List = append(res.List, tag)
}
// Reverse order (latest tags first)
for i := len(res.List)/2 - 1; i >= 0; i-- {
opp := len(res.List) - 1 - i
res.List[i], res.List[opp] = res.List[opp], res.List[i]
}
if opts.Max > 0 && len(res.List) >= opts.Max {
res.List = res.List[:opts.Max]
}
return res, nil
}