mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-24 06:28:13 +01:00
Tags sorting support (#645)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
93
docs/faq.md
93
docs/faq.md
@@ -55,7 +55,7 @@ The title and body of a notification message can be customized for each notifier
|
||||
Templating is supported with the following fields:
|
||||
|
||||
| Key | Description |
|
||||
|----------------------------------|-------------|
|
||||
|---------------------------------|---------------------------------------------------------------------------------------|
|
||||
| `.Meta.ID` | App ID: `diun` |
|
||||
| `.Meta.Name` | App Name: `Diun` |
|
||||
| `.Meta.Desc` | App description: `Docker image update notifier` |
|
||||
@@ -205,6 +205,97 @@ Or you can tweak the [`schedule` setting](config/watch.md#schedule) with somethi
|
||||
!!! warning
|
||||
Also be careful with the `watch_repo` setting as it will fetch manifest for **ALL** tags available for the image.
|
||||
|
||||
## Tags sorting when using `watch_repo`
|
||||
|
||||
When you use the `watch_repo` setting, Diun will fetch all tags available for
|
||||
the image. Depending on the registry, order of the tags list can change.
|
||||
|
||||
You can use the `sort_tags` setting available for each provider to use a
|
||||
specific sorting method for the tags list.
|
||||
|
||||
* `default`: do not sort and use the expected tags list from the registry
|
||||
* `reverse`: reverse order for the tags list from the registry
|
||||
* `lexicographical`: sort the tags list lexicographically
|
||||
* `semver`: sort the tags list using semantic versioning
|
||||
|
||||
Given the following list of tags received from the registry:
|
||||
|
||||
```json
|
||||
[
|
||||
"0.1.0",
|
||||
"0.4.0",
|
||||
"3.0.0-beta.1",
|
||||
"3.0.0-beta.4",
|
||||
"4",
|
||||
"4.0.0",
|
||||
"4.0.0-beta.1",
|
||||
"4.1.0",
|
||||
"4.1.1",
|
||||
"4.10.0",
|
||||
"4.11.0",
|
||||
"4.20",
|
||||
"4.20.0",
|
||||
"4.20.1",
|
||||
"4.3.0",
|
||||
"4.3.1",
|
||||
"4.9.0",
|
||||
"edge",
|
||||
"latest"
|
||||
]
|
||||
```
|
||||
|
||||
Here is the result for `reverse`:
|
||||
|
||||
```json
|
||||
[
|
||||
"latest",
|
||||
"edge",
|
||||
"4.9.0",
|
||||
"4.3.1",
|
||||
"4.3.0",
|
||||
"4.20.1",
|
||||
"4.20.0",
|
||||
"4.20",
|
||||
"4.11.0",
|
||||
"4.10.0",
|
||||
"4.1.1",
|
||||
"4.1.0",
|
||||
"4.0.0-beta.1",
|
||||
"4.0.0",
|
||||
"4",
|
||||
"3.0.0-beta.4",
|
||||
"3.0.0-beta.1",
|
||||
"0.4.0",
|
||||
"0.1.0"
|
||||
]
|
||||
```
|
||||
|
||||
And for `semver`:
|
||||
|
||||
```json
|
||||
[
|
||||
"4.20.1",
|
||||
"4.20.0",
|
||||
"4.20",
|
||||
"4.11.0",
|
||||
"4.10.0",
|
||||
"4.9.0",
|
||||
"4.3.1",
|
||||
"4.3.0",
|
||||
"4.1.1",
|
||||
"4.1.0",
|
||||
"4.0.0",
|
||||
"4",
|
||||
"4.0.0-beta.1",
|
||||
"3.0.0-beta.4",
|
||||
"3.0.0-beta.1",
|
||||
"0.4.0",
|
||||
"0.1.0",
|
||||
"edge",
|
||||
"latest"
|
||||
]
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
Diun provides a simple way to manage runtime/pprof profiling through the
|
||||
|
||||
@@ -173,11 +173,12 @@ Include created and exited containers too (default `false`).
|
||||
You can configure more finely the way to analyze the image of your container through Docker labels:
|
||||
|
||||
| Name | Default | Description |
|
||||
|-------------------------------|---------------|---------------|
|
||||
|---------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `diun.enable` | | Set to true to enable image analysis of this container |
|
||||
| `diun.regopt` | | [Registry options](../config/regopts.md) name to use |
|
||||
| `diun.watch_repo` | `false` | Watch all tags of this container image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) |
|
||||
| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. |
|
||||
| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update` |
|
||||
| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `numerical`, `lexicographical` |
|
||||
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
||||
| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||
| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||
|
||||
@@ -107,10 +107,11 @@ List of path patterns with [matching and globbing supporting patterns](https://g
|
||||
The following annotations can be added as comments before the target instruction to customize the image analysis:
|
||||
|
||||
| Name | Default | Description |
|
||||
|-------------------------------|---------------|---------------|
|
||||
|---------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `diun.regopt` | | [Registry options](../config/regopts.md) name to use |
|
||||
| `diun.watch_repo` | `false` | Watch all tags of this image |
|
||||
| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. |
|
||||
| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update` |
|
||||
| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `numerical`, `lexicographical` |
|
||||
| `diun.max_tags` | `0` | Maximum number of tags to watch if `watch_repo` enabled. `0` means all of them |
|
||||
| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||
| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||
|
||||
@@ -173,11 +173,12 @@ Defines the path to the directory that contains the [configuration files](#yaml-
|
||||
The configuration file(s) defines a slice of images to analyze with the following fields:
|
||||
|
||||
| Name | Default | Description |
|
||||
|-------------------------------|----------------------------------|---------------|
|
||||
|--------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `name` | `latest` | Docker image name to watch using `registry/path:tag` format. If registry omitted, `docker.io` will be used and if tag omitted, `latest` will be used |
|
||||
| `regopt` | | [Registry options](../config/regopts.md) name to use |
|
||||
| `watch_repo` | `false` | Watch all tags of this image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) |
|
||||
| `notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. |
|
||||
| `notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update` |
|
||||
| `sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `numerical`, `lexicographical` |
|
||||
| `max_tags` | `0` | Maximum number of tags to watch if `watch_repo` enabled. `0` means all of them |
|
||||
| `include_tags` | | List of regular expressions to include tags. Can be useful if you enable `watch_repo` |
|
||||
| `exclude_tags` | | List of regular expressions to exclude tags. Can be useful if you enable `watch_repo` |
|
||||
|
||||
@@ -281,11 +281,12 @@ Enable watch by default. If false, pods that don't have `diun.enable: "true"` an
|
||||
You can configure more finely the way to analyze the image of your pods through Kubernetes annotations:
|
||||
|
||||
| Name | Default | Description |
|
||||
|-------------------------------|---------------|---------------|
|
||||
|---------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `diun.enable` | | Set to true to enable image analysis of this pod |
|
||||
| `diun.regopt` | | [Registry options](../config/regopts.md) name to use |
|
||||
| `diun.watch_repo` | `false` | Watch all tags of this pod image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) |
|
||||
| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. |
|
||||
| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `numerical`, `lexicographical` |
|
||||
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
||||
| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||
| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||
|
||||
@@ -175,11 +175,12 @@ Enable watch by default. If false, services that don't have `diun.enable=true` l
|
||||
You can configure more finely the way to analyze the image of your service through Docker labels:
|
||||
|
||||
| Name | Default | Description |
|
||||
|-------------------------------|---------------|---------------|
|
||||
|---------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `diun.enable` | | Set to true to enable image analysis of this service |
|
||||
| `diun.regopt` | | [Registry options](../config/regopts.md) name to use |
|
||||
| `diun.watch_repo` | `false` | Watch all tags of this service image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) |
|
||||
| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. |
|
||||
| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `numerical`, `lexicographical` |
|
||||
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
||||
| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||
| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||
|
||||
1
go.mod
1
go.mod
@@ -39,6 +39,7 @@ require (
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tidwall/pretty v1.2.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/mod v0.5.1
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
|
||||
google.golang.org/grpc v1.48.0
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1144,6 +1144,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
||||
@@ -124,6 +124,7 @@ func (di *Diun) createJob(job model.Job) {
|
||||
tags, err := job.Registry.Tags(registry.TagsOptions{
|
||||
Image: job.RegImage,
|
||||
Max: job.Image.MaxTags,
|
||||
Sort: job.Image.SortTags,
|
||||
Include: job.Image.IncludeTags,
|
||||
Exclude: job.Image.ExcludeTags,
|
||||
})
|
||||
|
||||
@@ -360,6 +360,7 @@ func TestLoadEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
UnsetEnv("DIUN_")
|
||||
|
||||
@@ -484,6 +485,7 @@ for <code>{{ .Entry.Manifest.Platform }}</code> platform.
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
UnsetEnv("DIUN_")
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package model
|
||||
|
||||
import "github.com/crazy-max/diun/v4/pkg/registry"
|
||||
|
||||
// Image holds image configuration
|
||||
type Image struct {
|
||||
Name string `yaml:"name,omitempty" json:",omitempty"`
|
||||
@@ -8,6 +10,7 @@ type Image struct {
|
||||
WatchRepo bool `yaml:"watch_repo,omitempty" json:",omitempty"`
|
||||
NotifyOn []NotifyOn `yaml:"notify_on,omitempty" json:",omitempty"`
|
||||
MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"`
|
||||
SortTags registry.SortTag `yaml:"sort_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"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/crazy-max/diun/v4/internal/model"
|
||||
"github.com/crazy-max/diun/v4/pkg/registry"
|
||||
)
|
||||
|
||||
// ValidateImage returns a standard image through Docker labels
|
||||
@@ -17,6 +18,7 @@ func ValidateImage(image string, labels map[string]string, watchByDef bool) (img
|
||||
img = model.Image{
|
||||
Name: image,
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
}
|
||||
|
||||
if enableStr, ok := labels["diun.enable"]; ok {
|
||||
@@ -51,6 +53,15 @@ func ValidateImage(image string, labels map[string]string, watchByDef bool) (img
|
||||
}
|
||||
img.NotifyOn = append(img.NotifyOn, notifyOn)
|
||||
}
|
||||
case "diun.sort_tags":
|
||||
if value == "" {
|
||||
break
|
||||
}
|
||||
sortTags := registry.SortTag(value)
|
||||
if !sortTags.Valid() {
|
||||
return img, fmt.Errorf("unknown sort tags type %q", value)
|
||||
}
|
||||
img.SortTags = sortTags
|
||||
case "diun.max_tags":
|
||||
if img.MaxTags, err = strconv.Atoi(value); err != nil {
|
||||
return img, fmt.Errorf("cannot parse %s value of label %s", value, key)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/crazy-max/diun/v4/internal/model"
|
||||
"github.com/crazy-max/diun/v4/internal/provider/file"
|
||||
"github.com/crazy-max/diun/v4/pkg/registry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -16,6 +17,7 @@ var (
|
||||
Name: "jfrog-docker-reg2.bintray.io/jfrog/artifactory-oss:4.0.0",
|
||||
RegOpt: "bintrayoptions",
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -26,6 +28,7 @@ var (
|
||||
NotifyOn: []model.NotifyOn{
|
||||
model.NotifyOnNew,
|
||||
},
|
||||
SortTags: registry.SortTagLexicographical,
|
||||
MaxTags: 50,
|
||||
},
|
||||
},
|
||||
@@ -37,6 +40,7 @@ var (
|
||||
Name: "docker.io/crazymax/nextcloud:latest",
|
||||
RegOpt: "myregistry",
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -45,6 +49,7 @@ var (
|
||||
Name: "crazymax/swarm-cronjob",
|
||||
WatchRepo: true,
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagSemver,
|
||||
IncludeTags: []string{
|
||||
`^1\.2\..*`,
|
||||
},
|
||||
@@ -57,6 +62,7 @@ var (
|
||||
WatchRepo: true,
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
MaxTags: 10,
|
||||
SortTags: registry.SortTagReverse,
|
||||
IncludeTags: []string{
|
||||
`^(0|[1-9]\d*)\..*`,
|
||||
},
|
||||
@@ -68,6 +74,7 @@ var (
|
||||
Name: "traefik",
|
||||
WatchRepo: true,
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -75,6 +82,7 @@ var (
|
||||
Image: model.Image{
|
||||
Name: "alpine",
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
Platform: model.ImagePlatform{
|
||||
OS: "linux",
|
||||
Arch: "arm64",
|
||||
@@ -87,6 +95,7 @@ var (
|
||||
Image: model.Image{
|
||||
Name: "docker.io/graylog/graylog:3.2.0",
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -94,6 +103,7 @@ var (
|
||||
Image: model.Image{
|
||||
Name: "jacobalberty/unifi:5.9",
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -102,6 +112,7 @@ var (
|
||||
Name: "crazymax/ddns-route53",
|
||||
WatchRepo: true,
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
IncludeTags: []string{
|
||||
`^1\..*`,
|
||||
},
|
||||
@@ -114,6 +125,7 @@ var (
|
||||
Image: model.Image{
|
||||
Name: "quay.io/coreos/hyperkube",
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -121,6 +133,7 @@ var (
|
||||
Image: model.Image{
|
||||
Name: "quay.io/coreos/hyperkube:v1.1.7-coreos.1",
|
||||
NotifyOn: model.NotifyOnDefaults,
|
||||
SortTags: registry.SortTagReverse,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
watch_repo: true
|
||||
notify_on:
|
||||
- new
|
||||
sort_tags: lexicographical
|
||||
max_tags: 50
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
regopt: myregistry
|
||||
- name: crazymax/swarm-cronjob
|
||||
watch_repo: true
|
||||
sort_tags: semver
|
||||
include_tags:
|
||||
- ^1\.2\..*
|
||||
- name: docker.io/portainer/portainer
|
||||
watch_repo: true
|
||||
max_tags: 10
|
||||
sort_tags: reverse
|
||||
include_tags:
|
||||
- ^(0|[1-9]\d*)\..*
|
||||
- name: traefik
|
||||
watch_repo: true
|
||||
sort_tags: default
|
||||
- name: alpine
|
||||
platform:
|
||||
os: linux
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/crazy-max/diun/v4/internal/model"
|
||||
"github.com/crazy-max/diun/v4/pkg/registry"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
@@ -45,6 +46,17 @@ func (c *Client) listFileImage() []model.Image {
|
||||
}
|
||||
}
|
||||
|
||||
// Check SortType
|
||||
if item.SortTags == "" {
|
||||
item.SortTags = registry.SortTagReverse
|
||||
}
|
||||
if !item.SortTags.Valid() {
|
||||
c.logger.Error().
|
||||
Str("file", file).
|
||||
Str("img_name", item.Name).
|
||||
Msgf("unknown sort tags type %q", item.SortTags)
|
||||
}
|
||||
|
||||
// Check Platform
|
||||
if item.Platform != (model.ImagePlatform{}) {
|
||||
_, err = platforms.Parse(platforms.Format(ocispecs.Platform{
|
||||
|
||||
@@ -126,6 +126,7 @@ func TestParseImage(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
img, err := registry.ParseImage(tt.parseOpts)
|
||||
if err != nil {
|
||||
@@ -239,6 +240,7 @@ func TestHubLink(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
img, err := registry.ParseImage(tt.parseOpts)
|
||||
if err != nil {
|
||||
|
||||
@@ -52,6 +52,7 @@ func TestParseReference(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
ref, err := registry.ParseReference(tt.input)
|
||||
if tt.wantErr {
|
||||
|
||||
@@ -18,6 +18,7 @@ type Tags struct {
|
||||
type TagsOptions struct {
|
||||
Image Image
|
||||
Max int
|
||||
Sort SortTag
|
||||
Include []string
|
||||
Exclude []string
|
||||
}
|
||||
@@ -43,6 +44,9 @@ func (c *Client) Tags(opts TagsOptions) (*Tags, error) {
|
||||
Total: len(tags),
|
||||
}
|
||||
|
||||
// Sort tags
|
||||
tags = SortTags(tags, opts.Sort)
|
||||
|
||||
// Filter
|
||||
for _, tag := range tags {
|
||||
if !utl.IsIncluded(tag, opts.Include) {
|
||||
@@ -55,12 +59,6 @@ func (c *Client) Tags(opts TagsOptions) (*Tags, error) {
|
||||
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]
|
||||
}
|
||||
|
||||
79
pkg/registry/tags_sort.go
Normal file
79
pkg/registry/tags_sort.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// SortTags sorts tags list
|
||||
func SortTags(tags []string, sortTag SortTag) []string {
|
||||
switch sortTag {
|
||||
case SortTagReverse:
|
||||
for i := len(tags)/2 - 1; i >= 0; i-- {
|
||||
opp := len(tags) - 1 - i
|
||||
tags[i], tags[opp] = tags[opp], tags[i]
|
||||
}
|
||||
return tags
|
||||
case SortTagLexicographical:
|
||||
sort.Strings(tags)
|
||||
return tags
|
||||
case SortTagSemver:
|
||||
semverIsh := func(s string) string {
|
||||
if semver.IsValid(s) {
|
||||
return s
|
||||
}
|
||||
if vt := fmt.Sprintf("v%s", s); semver.IsValid(vt) {
|
||||
return vt
|
||||
}
|
||||
return ""
|
||||
}
|
||||
sort.Slice(tags, func(i, j int) bool {
|
||||
if c := semver.Compare(semverIsh(tags[i]), semverIsh(tags[j])); c > 0 {
|
||||
return true
|
||||
} else if c < 0 {
|
||||
return false
|
||||
}
|
||||
return strings.Count(tags[i], ".") > strings.Count(tags[j], ".")
|
||||
})
|
||||
return tags
|
||||
default:
|
||||
return tags
|
||||
}
|
||||
}
|
||||
|
||||
// SortTag holds sort tag type
|
||||
type SortTag string
|
||||
|
||||
// SortTag constants
|
||||
const (
|
||||
SortTagDefault = SortTag("default")
|
||||
SortTagReverse = SortTag("reverse")
|
||||
SortTagLexicographical = SortTag("lexicographical")
|
||||
SortTagSemver = SortTag("semver")
|
||||
)
|
||||
|
||||
// SortTagTypes is the list of available sort tag types
|
||||
var SortTagTypes = []SortTag{
|
||||
SortTagDefault,
|
||||
SortTagReverse,
|
||||
SortTagLexicographical,
|
||||
SortTagSemver,
|
||||
}
|
||||
|
||||
// Valid checks sort tag type is valid
|
||||
func (st *SortTag) Valid() bool {
|
||||
return st.OneOf(SortTagTypes)
|
||||
}
|
||||
|
||||
// OneOf checks if sort type is one of the values in the list
|
||||
func (st *SortTag) OneOf(stl []SortTag) bool {
|
||||
for _, n := range stl {
|
||||
if n == *st {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -27,3 +27,207 @@ func TestTags(t *testing.T) {
|
||||
assert.True(t, tags.Total > 0)
|
||||
assert.True(t, len(tags.List) > 0)
|
||||
}
|
||||
|
||||
func TestTagsSort(t *testing.T) {
|
||||
repotags := []string{
|
||||
"0.1.0",
|
||||
"0.4.0",
|
||||
"3.0.0-beta.1",
|
||||
"3.0.0-beta.3",
|
||||
"3.0.0-beta.4",
|
||||
"4",
|
||||
"4.0.0",
|
||||
"4.0.0-beta.1",
|
||||
"4.1.0",
|
||||
"4.1.1",
|
||||
"4.10.0",
|
||||
"4.11.0",
|
||||
"4.12.0",
|
||||
"4.13.0",
|
||||
"4.14.0",
|
||||
"4.19.0",
|
||||
"4.2.0",
|
||||
"4.20",
|
||||
"4.20.0",
|
||||
"4.20.1",
|
||||
"4.21",
|
||||
"4.21.0",
|
||||
"4.3.0",
|
||||
"4.3.1",
|
||||
"4.4.0",
|
||||
"4.6.1",
|
||||
"4.7.0",
|
||||
"4.8.0",
|
||||
"4.8.1",
|
||||
"4.9.0",
|
||||
"edge",
|
||||
"latest",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
sortTag registry.SortTag
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "sort default",
|
||||
sortTag: registry.SortTagDefault,
|
||||
expected: []string{
|
||||
"0.1.0",
|
||||
"0.4.0",
|
||||
"3.0.0-beta.1",
|
||||
"3.0.0-beta.3",
|
||||
"3.0.0-beta.4",
|
||||
"4",
|
||||
"4.0.0",
|
||||
"4.0.0-beta.1",
|
||||
"4.1.0",
|
||||
"4.1.1",
|
||||
"4.10.0",
|
||||
"4.11.0",
|
||||
"4.12.0",
|
||||
"4.13.0",
|
||||
"4.14.0",
|
||||
"4.19.0",
|
||||
"4.2.0",
|
||||
"4.20",
|
||||
"4.20.0",
|
||||
"4.20.1",
|
||||
"4.21",
|
||||
"4.21.0",
|
||||
"4.3.0",
|
||||
"4.3.1",
|
||||
"4.4.0",
|
||||
"4.6.1",
|
||||
"4.7.0",
|
||||
"4.8.0",
|
||||
"4.8.1",
|
||||
"4.9.0",
|
||||
"edge",
|
||||
"latest",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort lexicographical",
|
||||
sortTag: registry.SortTagLexicographical,
|
||||
expected: []string{
|
||||
"0.1.0",
|
||||
"0.4.0",
|
||||
"3.0.0-beta.1",
|
||||
"3.0.0-beta.3",
|
||||
"3.0.0-beta.4",
|
||||
"4",
|
||||
"4.0.0",
|
||||
"4.0.0-beta.1",
|
||||
"4.1.0",
|
||||
"4.1.1",
|
||||
"4.10.0",
|
||||
"4.11.0",
|
||||
"4.12.0",
|
||||
"4.13.0",
|
||||
"4.14.0",
|
||||
"4.19.0",
|
||||
"4.2.0",
|
||||
"4.20",
|
||||
"4.20.0",
|
||||
"4.20.1",
|
||||
"4.21",
|
||||
"4.21.0",
|
||||
"4.3.0",
|
||||
"4.3.1",
|
||||
"4.4.0",
|
||||
"4.6.1",
|
||||
"4.7.0",
|
||||
"4.8.0",
|
||||
"4.8.1",
|
||||
"4.9.0",
|
||||
"edge",
|
||||
"latest",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort reverse",
|
||||
sortTag: registry.SortTagReverse,
|
||||
expected: []string{
|
||||
"latest",
|
||||
"edge",
|
||||
"4.9.0",
|
||||
"4.8.1",
|
||||
"4.8.0",
|
||||
"4.7.0",
|
||||
"4.6.1",
|
||||
"4.4.0",
|
||||
"4.3.1",
|
||||
"4.3.0",
|
||||
"4.21.0",
|
||||
"4.21",
|
||||
"4.20.1",
|
||||
"4.20.0",
|
||||
"4.20",
|
||||
"4.2.0",
|
||||
"4.19.0",
|
||||
"4.14.0",
|
||||
"4.13.0",
|
||||
"4.12.0",
|
||||
"4.11.0",
|
||||
"4.10.0",
|
||||
"4.1.1",
|
||||
"4.1.0",
|
||||
"4.0.0-beta.1",
|
||||
"4.0.0",
|
||||
"4",
|
||||
"3.0.0-beta.4",
|
||||
"3.0.0-beta.3",
|
||||
"3.0.0-beta.1",
|
||||
"0.4.0",
|
||||
"0.1.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort semver",
|
||||
sortTag: registry.SortTagSemver,
|
||||
expected: []string{
|
||||
"4.21.0",
|
||||
"4.21",
|
||||
"4.20.1",
|
||||
"4.20.0",
|
||||
"4.20",
|
||||
"4.19.0",
|
||||
"4.14.0",
|
||||
"4.13.0",
|
||||
"4.12.0",
|
||||
"4.11.0",
|
||||
"4.10.0",
|
||||
"4.9.0",
|
||||
"4.8.1",
|
||||
"4.8.0",
|
||||
"4.7.0",
|
||||
"4.6.1",
|
||||
"4.4.0",
|
||||
"4.3.1",
|
||||
"4.3.0",
|
||||
"4.2.0",
|
||||
"4.1.1",
|
||||
"4.1.0",
|
||||
"4.0.0",
|
||||
"4",
|
||||
"4.0.0-beta.1",
|
||||
"3.0.0-beta.4",
|
||||
"3.0.0-beta.3",
|
||||
"3.0.0-beta.1",
|
||||
"0.4.0",
|
||||
"0.1.0",
|
||||
"edge",
|
||||
"latest",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tags := registry.SortTags(repotags, tt.sortTag)
|
||||
assert.Equal(t, tt.expected, tags)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user