mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-24 06:28:13 +01:00
Add link to respective hub (#40)
This commit is contained in:
@@ -144,7 +144,9 @@ func (di *Diun) Close() {
|
|||||||
// TestNotif test the notification settings
|
// TestNotif test the notification settings
|
||||||
func (di *Diun) TestNotif() {
|
func (di *Diun) TestNotif() {
|
||||||
createdAt, _ := time.Parse("2006-01-02T15:04:05Z", "2020-03-26T12:23:56Z")
|
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...")
|
log.Info().Msg("Testing notification settings...")
|
||||||
di.notif.Send(model.NotifEntry{
|
di.notif.Send(model.NotifEntry{
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ func (di *Diun) createJob(job model.Job) {
|
|||||||
Logger()
|
Logger()
|
||||||
|
|
||||||
// Validate image
|
// 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 {
|
if err != nil {
|
||||||
sublog.Error().Err(err).Msg("Cannot parse image")
|
sublog.Error().Err(err).Msg("Cannot parse image")
|
||||||
return
|
return
|
||||||
@@ -118,7 +121,10 @@ func (di *Diun) createJob(job model.Job) {
|
|||||||
|
|
||||||
for _, tag := range tags.List {
|
for _, tag := range tags.List {
|
||||||
job.Image.Name = fmt.Sprintf("%s/%s:%s", job.RegImage.Domain, job.RegImage.Path, tag)
|
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 {
|
if err != nil {
|
||||||
sublog.Error().Err(err).Msg("Cannot parse image (tag)")
|
sublog.Error().Err(err).Msg("Cannot parse image (tag)")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type Image struct {
|
|||||||
MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"`
|
MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"`
|
||||||
IncludeTags []string `yaml:"include_tags,omitempty" json:",omitempty"`
|
IncludeTags []string `yaml:"include_tags,omitempty" json:",omitempty"`
|
||||||
ExcludeTags []string `yaml:"exclude_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
|
// ImagePlatform holds image platform configuration
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ func ValidateContainerImage(image string, labels map[string]string, watchByDef b
|
|||||||
img.IncludeTags = strings.Split(value, ";")
|
img.IncludeTags = strings.Split(value, ";")
|
||||||
case "diun.exclude_tags":
|
case "diun.exclude_tags":
|
||||||
img.ExcludeTags = strings.Split(value, ";")
|
img.ExcludeTags = strings.Split(value, ";")
|
||||||
|
case "diun.hub_tpl":
|
||||||
|
img.HubTpl = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/containers/image/v5/docker/reference"
|
"github.com/containers/image/v5/docker/reference"
|
||||||
digest "github.com/opencontainers/go-digest"
|
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.
|
// Image holds information about an image.
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Domain string
|
Domain string
|
||||||
Path string
|
Path string
|
||||||
Tag string
|
Tag string
|
||||||
Digest digest.Digest
|
Digest digest.Digest
|
||||||
named reference.Named
|
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.
|
// Name returns the full name representation of an image.
|
||||||
@@ -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.
|
// 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.
|
// Parse the image name and tag.
|
||||||
named, err := reference.ParseNormalizedNamed(image)
|
named, err := reference.ParseNormalizedNamed(parseOpts.Name)
|
||||||
if err != nil {
|
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.
|
// Add the latest lag if they did not provide one.
|
||||||
named = reference.TagNameOnly(named)
|
named = reference.TagNameOnly(named)
|
||||||
|
|
||||||
i := Image{
|
i := Image{
|
||||||
|
opts: parseOpts,
|
||||||
named: named,
|
named: named,
|
||||||
Domain: reference.Domain(named),
|
Domain: reference.Domain(named),
|
||||||
Path: reference.Path(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.
|
// Add the tag if there was one.
|
||||||
if tagged, ok := named.(reference.Tagged); ok {
|
if tagged, ok := named.(reference.Tagged); ok {
|
||||||
i.Tag = tagged.Tag()
|
i.Tag = tagged.Tag()
|
||||||
@@ -72,3 +90,42 @@ func ParseImage(image string) (Image, error) {
|
|||||||
|
|
||||||
return i, nil
|
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
pkg/registry/image_test.go
Normal file
214
pkg/registry/image_test.go
Normal file
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package registry_test
|
package registry_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/crazy-max/diun/v4/pkg/registry"
|
"github.com/crazy-max/diun/v4/pkg/registry"
|
||||||
@@ -19,15 +17,14 @@ func TestManifestVariant(t *testing.T) {
|
|||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := registry.ParseImage("crazymax/diun:2.5.0")
|
img, err := registry.ParseImage(registry.ParseImageOptions{
|
||||||
|
Name: "crazymax/diun:2.5.0",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest, err := rc.Manifest(img)
|
manifest, err := rc.Manifest(img)
|
||||||
b, _ := json.MarshalIndent(manifest, "", " ")
|
|
||||||
fmt.Println(string(b))
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "docker.io/crazymax/diun", manifest.Name)
|
assert.Equal(t, "docker.io/crazymax/diun", manifest.Name)
|
||||||
assert.Equal(t, "2.5.0", manifest.Tag)
|
assert.Equal(t, "2.5.0", manifest.Tag)
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ var (
|
|||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
rc, err = registry.New(registry.Options{})
|
rc, err = registry.New(registry.Options{
|
||||||
|
ImageOs: "linux",
|
||||||
|
ImageArch: "amd64",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
@@ -26,3 +29,24 @@ func TestMain(m *testing.M) {
|
|||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
assert.NotNil(t, rc)
|
assert.NotNil(t, rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTags(t *testing.T) {
|
||||||
|
assert.NotNil(t, rc)
|
||||||
|
|
||||||
|
image, err := registry.ParseImage(registry.ParseImageOptions{
|
||||||
|
Name: "crazymax/diun:3.0.0",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := rc.Tags(registry.TagsOptions{
|
||||||
|
Image: image,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, tags.Total > 0)
|
||||||
|
assert.True(t, len(tags.List) > 0)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user