Skip to content

Commit

Permalink
Add link to respective hub (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
crazy-max committed Jun 8, 2020
1 parent 2038336 commit 75652bb
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 20 deletions.
4 changes: 3 additions & 1 deletion internal/app/diun.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ func (di *Diun) Close() {
// TestNotif test the notification settings
func (di *Diun) TestNotif() {
createdAt, _ := time.Parse("2006-01-02T15:04:05Z", "2020-03-26T12:23:56Z")
image, _ := registry.ParseImage("crazymax/diun:latest")
image, _ := registry.ParseImage(registry.ParseImageOptions{
Name: "crazymax/diun:latest",
})

log.Info().Msg("Testing notification settings...")
di.notif.Send(model.NotifEntry{
Expand Down
10 changes: 8 additions & 2 deletions internal/app/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ func (di *Diun) createJob(job model.Job) {
Logger()

// Validate image
job.RegImage, err = registry.ParseImage(job.Image.Name)
job.RegImage, err = registry.ParseImage(registry.ParseImageOptions{
Name: job.Image.Name,
HubTpl: job.Image.HubTpl,
})
if err != nil {
sublog.Error().Err(err).Msg("Cannot parse image")
return
Expand Down Expand Up @@ -118,7 +121,10 @@ func (di *Diun) createJob(job model.Job) {

for _, tag := range tags.List {
job.Image.Name = fmt.Sprintf("%s/%s:%s", job.RegImage.Domain, job.RegImage.Path, tag)
job.RegImage, err = registry.ParseImage(job.Image.Name)
job.RegImage, err = registry.ParseImage(registry.ParseImageOptions{
Name: job.Image.Name,
HubTpl: job.Image.HubTpl,
})
if err != nil {
sublog.Error().Err(err).Msg("Cannot parse image (tag)")
continue
Expand Down
1 change: 1 addition & 0 deletions internal/model/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Image struct {
MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"`
IncludeTags []string `yaml:"include_tags,omitempty" json:",omitempty"`
ExcludeTags []string `yaml:"exclude_tags,omitempty" json:",omitempty"`
HubTpl string `yaml:"hub_tpl,omitempty" json:",omitempty"`
}

// ImagePlatform holds image platform configuration
Expand Down
2 changes: 2 additions & 0 deletions internal/provider/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ func ValidateContainerImage(image string, labels map[string]string, watchByDef b
img.IncludeTags = strings.Split(value, ";")
case "diun.exclude_tags":
img.ExcludeTags = strings.Split(value, ";")
case "diun.hub_tpl":
img.HubTpl = value
}
}

Expand Down
77 changes: 67 additions & 10 deletions pkg/registry/image.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
package registry

import (
"bytes"
"fmt"
"path/filepath"
"strings"
"text/template"

"github.com/containers/image/v5/docker/reference"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)

// Source: https://github.com/genuinetools/reg/blob/f3a9b00ec86f334702381edf842f03b3a9243a0a/registry/image.go

// Image holds information about an image.
type Image struct {
Domain string
Path string
Tag string
Digest digest.Digest
named reference.Named
Domain string
Path string
Tag string
Digest digest.Digest
HubLink string
named reference.Named
opts ParseImageOptions
}

// ParseImageOptions holds image options for parsing.
type ParseImageOptions struct {
Name string
HubTpl string
}

// Name returns the full name representation of an image.
Expand Down Expand Up @@ -45,21 +56,28 @@ func (i *Image) WithDigest(digest digest.Digest) (err error) {
}

// ParseImage returns an Image struct with all the values filled in for a given image.
func ParseImage(image string) (Image, error) {
func ParseImage(parseOpts ParseImageOptions) (Image, error) {
// Parse the image name and tag.
named, err := reference.ParseNormalizedNamed(image)
named, err := reference.ParseNormalizedNamed(parseOpts.Name)
if err != nil {
return Image{}, fmt.Errorf("parsing image %q failed: %v", image, err)
return Image{}, errors.Wrap(err, fmt.Sprintf("parsing image %s failed", parseOpts.Name))
}
// Add the latest lag if they did not provide one.
named = reference.TagNameOnly(named)

i := Image{
opts: parseOpts,
named: named,
Domain: reference.Domain(named),
Path: reference.Path(named),
}

// Hub link
i.HubLink, err = i.hubLink()
if err != nil {
return Image{}, errors.Wrap(err, fmt.Sprintf("resolving hub link for image %s failed", parseOpts.Name))
}

// Add the tag if there was one.
if tagged, ok := named.(reference.Tagged); ok {
i.Tag = tagged.Tag()
Expand All @@ -72,3 +90,42 @@ func ParseImage(image string) (Image, error) {

return i, nil
}

func (i Image) hubLink() (string, error) {
if i.opts.HubTpl != "" {
var out bytes.Buffer
tmpl, err := template.New("tmpl").
Option("missingkey=error").
Parse(i.opts.HubTpl)
if err != nil {
return "", err
}
err = tmpl.Execute(&out, i)
return out.String(), err
}

switch i.Domain {
case "docker.io":
prefix := "r"
path := i.Path
if strings.HasPrefix(i.Path, "library/") {
prefix = "_"
path = strings.Replace(i.Path, "library/", "", 1)
}
return fmt.Sprintf("https://hub.docker.com/%s/%s", prefix, path), nil
case "docker.bintray.io", "jfrog-docker-reg2.bintray.io":
return fmt.Sprintf("https://bintray.com/jfrog/reg2/%s", strings.ReplaceAll(i.Path, "/", "%3A")), nil
case "docker.pkg.github.com":
return fmt.Sprintf("https://github.com/%s/packages", filepath.ToSlash(filepath.Dir(i.Path))), nil
case "gcr.io":
return fmt.Sprintf("https://%s/%s", i.Domain, i.Path), nil
case "quay.io":
return fmt.Sprintf("https://quay.io/repository/%s", i.Path), nil
case "registry.access.redhat.com":
return fmt.Sprintf("https://access.redhat.com/containers/#/registry.access.redhat.com/%s", i.Path), nil
case "registry.gitlab.com":
return fmt.Sprintf("https://gitlab.com/%s/container_registry", i.Path), nil
default:
return "", nil
}
}
214 changes: 214 additions & 0 deletions pkg/registry/image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package registry_test

import (
"testing"

"github.com/crazy-max/diun/v4/pkg/registry"
"github.com/stretchr/testify/assert"
)

func TestParseImage(t *testing.T) {
testCases := []struct {
desc string
parseOpts registry.ParseImageOptions
expected registry.Image
}{
{
desc: "bintray artifactory-oss",
parseOpts: registry.ParseImageOptions{
Name: "jfrog-docker-reg2.bintray.io/jfrog/artifactory-oss:4.0.0",
},
expected: registry.Image{
Domain: "jfrog-docker-reg2.bintray.io",
Path: "jfrog/artifactory-oss",
Tag: "4.0.0",
},
},
{
desc: "bintray xray-server",
parseOpts: registry.ParseImageOptions{
Name: "docker.bintray.io/jfrog/xray-server:2.8.6",
},
expected: registry.Image{
Domain: "docker.bintray.io",
Path: "jfrog/xray-server",
Tag: "2.8.6",
},
},
{
desc: "dockerhub alpine",
parseOpts: registry.ParseImageOptions{
Name: "alpine",
},
expected: registry.Image{
Domain: "docker.io",
Path: "library/alpine",
Tag: "latest",
},
},
{
desc: "dockerhub crazymax/nextcloud",
parseOpts: registry.ParseImageOptions{
Name: "docker.io/crazymax/nextcloud:latest",
},
expected: registry.Image{
Domain: "docker.io",
Path: "crazymax/nextcloud",
Tag: "latest",
},
},
{
desc: "gcr busybox",
parseOpts: registry.ParseImageOptions{
Name: "gcr.io/google-containers/busybox:latest",
},
expected: registry.Image{
Domain: "gcr.io",
Path: "google-containers/busybox",
Tag: "latest",
},
},
{
desc: "github ddns-route53",
parseOpts: registry.ParseImageOptions{
Name: "docker.pkg.github.com/crazy-max/ddns-route53/ddns-route53:latest",
},
expected: registry.Image{
Domain: "docker.pkg.github.com",
Path: "crazy-max/ddns-route53/ddns-route53",
Tag: "latest",
},
},
{
desc: "gitlab meltano",
parseOpts: registry.ParseImageOptions{
Name: "registry.gitlab.com/meltano/meltano",
},
expected: registry.Image{
Domain: "registry.gitlab.com",
Path: "meltano/meltano",
Tag: "latest",
},
},
{
desc: "quay hypercube",
parseOpts: registry.ParseImageOptions{
Name: "quay.io/coreos/hyperkube",
},
expected: registry.Image{
Domain: "quay.io",
Path: "coreos/hyperkube",
Tag: "latest",
},
},
}

for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
img, err := registry.ParseImage(tt.parseOpts)
if err != nil {
t.Error(err)
}
assert.Equal(t, tt.expected.Domain, img.Domain)
assert.Equal(t, tt.expected.Path, img.Path)
assert.Equal(t, tt.expected.Tag, img.Tag)
})
}
}

func TestHubLink(t *testing.T) {
testCases := []struct {
desc string
parseOpts registry.ParseImageOptions
expected string
}{
{
desc: "bintray artifactory-oss",
parseOpts: registry.ParseImageOptions{
Name: "jfrog-docker-reg2.bintray.io/jfrog/artifactory-oss:4.0.0",
},
expected: "https://bintray.com/jfrog/reg2/jfrog%3Aartifactory-oss",
},
{
desc: "bintray kubexray",
parseOpts: registry.ParseImageOptions{
Name: "jfrog-docker-reg2.bintray.io/kubexray:latest",
},
expected: "https://bintray.com/jfrog/reg2/kubexray",
},
{
desc: "bintray xray-server",
parseOpts: registry.ParseImageOptions{
Name: "docker.bintray.io/jfrog/xray-server:2.8.6",
},
expected: "https://bintray.com/jfrog/reg2/jfrog%3Axray-server",
},
{
desc: "dockerhub alpine",
parseOpts: registry.ParseImageOptions{
Name: "alpine",
},
expected: "https://hub.docker.com/_/alpine",
},
{
desc: "dockerhub crazymax/nextcloud",
parseOpts: registry.ParseImageOptions{
Name: "docker.io/crazymax/nextcloud:latest",
},
expected: "https://hub.docker.com/r/crazymax/nextcloud",
},
{
desc: "gcr busybox",
parseOpts: registry.ParseImageOptions{
Name: "gcr.io/google-containers/busybox:latest",
},
expected: "https://gcr.io/google-containers/busybox",
},
{
desc: "github ddns-route53",
parseOpts: registry.ParseImageOptions{
Name: "docker.pkg.github.com/crazy-max/ddns-route53/ddns-route53:latest",
},
expected: "https://github.com/crazy-max/ddns-route53/packages",
},
{
desc: "gitlab meltano",
parseOpts: registry.ParseImageOptions{
Name: "registry.gitlab.com/meltano/meltano",
},
expected: "https://gitlab.com/meltano/meltano/container_registry",
},
{
desc: "quay hypercube",
parseOpts: registry.ParseImageOptions{
Name: "quay.io/coreos/hyperkube",
},
expected: "https://quay.io/repository/coreos/hyperkube",
},
{
desc: "redhat etcd",
parseOpts: registry.ParseImageOptions{
Name: "registry.access.redhat.com/rhel7/etcd",
},
expected: "https://access.redhat.com/containers/#/registry.access.redhat.com/rhel7/etcd",
},
{
desc: "private",
parseOpts: registry.ParseImageOptions{
Name: "myregistry.example.com/an/image:latest",
HubTpl: "https://{{ .Domain }}/ui/repos/{{ .Path }}",
},
expected: "https://myregistry.example.com/ui/repos/an/image",
},
}

for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
img, err := registry.ParseImage(tt.parseOpts)
if err != nil {
t.Error(err)
}
assert.Equal(t, tt.expected, img.HubLink)
})
}
}

0 comments on commit 75652bb

Please sign in to comment.