0
0
Fork 0
mirror of https://github.com/crazy-max/diun.git synced 2025-04-11 06:01:21 +00:00

Check digest from HEAD request ()

* Check digest from HEAD request
* Add FAQ note about Docker Hub rate limits
* Compare digest as watch setting

Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2020-11-13 00:00:01 +01:00 committed by GitHub
parent 2fa4696f3a
commit b1953afdae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 244 additions and 72 deletions

View file

@ -7,6 +7,7 @@ watch:
workers: 10
schedule: "0 */6 * * *"
firstCheckNotif: false
compareDigest: true
healthchecks:
baseURL: https://hc-ping.com/
uuid: 5bf66975-d4c7-4bf5-bcc8-b8d8a82ea278
@ -53,6 +54,21 @@ Send notification at the very first analysis of an image. (default `false`)
!!! abstract "Environment variables"
* `DIUN_WATCH_FIRSTCHECKNOTIF`
### `compareDigest`
Compare the digest of an image with the registry before downloading the image manifest. It is strongly
recommended to leave this value at `true`, especially with [Docker Hub which imposes a rate-limit](../faq.md#docker-hub-rate-limits)
on image pull. (default `true`)
!!! example "Config file"
```yaml
watch:
compareDigest: false
```
!!! abstract "Environment variables"
* `DIUN_WATCH_COMPAREDIGEST`
### `healthchecks`
Healthchecks allows to monitor Diun watcher by sending start and success notification

View file

@ -96,3 +96,18 @@ regopts:
If this is not enough, tweak the [`schedule` setting](config/watch.md#schedule) with something
like `0 */6 * * *` (every 6 hours).
## Docker Hub rate limits
Docker is now [enforcing Docker Hub pull rate limits](https://www.docker.com/increase-rate-limits). This means you can
make 100 pull image requests per six hours for anonymous usage, and 200 pull image requests per six hours
for free Docker accounts. But this rate limit is not necessarily an indicator on the number of times an image has
actually been downloaded. In fact, their _pulls_ counter/metric is actually a representation of the number of times a
manifest for a particular image has been retrieved.
As you probably know, Diun download the manifest of an image from its registry through a `GET` request to be able to
retrive its inside metadata. Fortunately Diun doesn't perform a `GET` request at each scan but only when an image
has been updated and/or added on the registry. This allows us not to exceed this rate limit in our situation, but
it also strongly depends on the number of images you scan. To increase your pull rate limits you can upgrade
your account to a [Docker Pro or Team subscription](https://www.docker.com/pricing) or you can tweak the
[`schedule` setting](config/watch.md#schedule) with something like `0 */6 * * *` (every 6 hours).

2
go.mod
View file

@ -4,7 +4,7 @@ go 1.13
require (
github.com/alecthomas/kong v0.2.11
github.com/containers/image/v5 v5.7.0
github.com/containers/image/v5 v5.7.1-0.20201112143342-1aef8ae0fec6
github.com/crazy-max/gohealthchecks v0.2.0
github.com/crazy-max/gonfig v0.4.0
github.com/docker/docker v1.4.2-0.20200204220554-5f6d6f3f2203

14
go.sum
View file

@ -34,6 +34,7 @@ github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
@ -70,19 +71,20 @@ github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on
github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA=
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/containers/image/v5 v5.7.0 h1:fiTC8/Xbr+zEP6njGTZtPW/3UD7MC93nC9DbUoWdxkA=
github.com/containers/image/v5 v5.7.0/go.mod h1:8aOy+YaItukxghRORkvhq5ibWttHErzDLy6egrKfKos=
github.com/containers/image/v5 v5.7.1-0.20201112143342-1aef8ae0fec6 h1:9kFbRTAbQetdPu0L9t101KQW1FoUw/KccEjaIDPMysg=
github.com/containers/image/v5 v5.7.1-0.20201112143342-1aef8ae0fec6/go.mod h1:Q4rhU5QLVQuKjJj6F1bF7uPXxdzpWXDI9mdIkztsvhQ=
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE=
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.0.3 h1:vYgl+RZ9Q3DPMuTfxmN+qp0X2Bj52uuY2vnt6GzVe1c=
github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g=
github.com/containers/storage v1.23.6 h1:3rcZ1KTNv8q7SkZ75gcrFGYqTeiuI04Zg7m9X1sCg/s=
github.com/containers/storage v1.23.6/go.mod h1:haFs0HRowKwyzvWEx9EgI3WsL8XCSnBDb5f8P5CAxJY=
github.com/containers/storage v1.23.7 h1:43ImvG/npvQSZXRjaudVvKISIuZSfI6qvtSNQQSGO/A=
github.com/containers/storage v1.23.7/go.mod h1:cUT2zHjtx+WlVri30obWmM2gpqpi8jfPsmIzP1TVpEI=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@ -258,7 +260,8 @@ github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDE
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
github.com/moby/sys/mountinfo v0.3.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM=
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -289,6 +292,7 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU=
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc91 h1:Tp8LWs5G8rFpzTsbRjAtQkPVexhCu0bnANE5IfIhJ6g=
github.com/opencontainers/runc v1.0.0-rc91/go.mod h1:3Sm6Dt7OT8z88EbdQqqcRN2oCT54jbi72tT/HqgflT8=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=

View file

@ -80,14 +80,15 @@ func (di *Diun) createJob(job model.Job) {
}
job.Registry, err = registry.New(registry.Options{
Username: regUser,
Password: regPassword,
Timeout: *reg.Timeout,
InsecureTLS: *reg.InsecureTLS,
UserAgent: di.meta.UserAgent,
ImageOs: job.Image.Platform.Os,
ImageArch: job.Image.Platform.Arch,
ImageVariant: job.Image.Platform.Variant,
Username: regUser,
Password: regPassword,
Timeout: *reg.Timeout,
InsecureTLS: *reg.InsecureTLS,
UserAgent: di.meta.UserAgent,
CompareDigest: *di.cfg.Watch.CompareDigest,
ImageOs: job.Image.Platform.Os,
ImageArch: job.Image.Platform.Arch,
ImageVariant: job.Image.Platform.Variant,
})
if err != nil {
sublog.Error().Err(err).Msg("Cannot create registry client")
@ -162,18 +163,18 @@ func (di *Diun) runJob(job model.Job) (entry model.NotifEntry) {
return
}
entry.Manifest, err = job.Registry.Manifest(job.RegImage)
if err != nil {
sublog.Warn().Err(err).Msg("Cannot get remote manifest")
return
}
dbManifest, err := di.db.GetManifest(job.RegImage)
if err != nil {
sublog.Error().Err(err).Msg("Cannot get manifest from db")
return
}
entry.Manifest, err = job.Registry.Manifest(job.RegImage, dbManifest)
if err != nil {
sublog.Warn().Err(err).Msg("Cannot get remote manifest")
return
}
if len(dbManifest.Name) == 0 {
entry.Status = model.ImageStatusNew
sublog.Info().Msg("New image found")

View file

@ -75,6 +75,7 @@ func TestLoadFile(t *testing.T) {
Workers: 100,
Schedule: "*/30 * * * *",
FirstCheckNotif: utl.NewTrue(),
CompareDigest: utl.NewTrue(),
Healthchecks: &model.Healthchecks{
BaseURL: "https://hc-ping.com/",
UUID: "5bf66975-d4c7-4bf5-bcc8-b8d8a82ea278",

View file

@ -5,6 +5,7 @@ watch:
workers: 100
schedule: "*/30 * * * *"
firstCheckNotif: true
compareDigest: true
healthchecks:
baseURL: https://hc-ping.com/
uuid: 5bf66975-d4c7-4bf5-bcc8-b8d8a82ea278

View file

@ -5,6 +5,7 @@ watch:
workers: 100
schedule: "*/30 * * * *"
firstCheckNotif: false
compareDigest: true
healthchecks:
baseURL: https://hc-ping.com/
uuid: 5bf66975-d4c7-4bf5-bcc8-b8d8a82ea278

View file

@ -9,6 +9,7 @@ type Watch struct {
Workers int `yaml:"workers,omitempty" json:"workers,omitempty" validate:"required,min=1"`
Schedule string `yaml:"schedule,omitempty" json:"schedule,omitempty" validate:"required"`
FirstCheckNotif *bool `yaml:"firstCheckNotif,omitempty" json:"firstCheckNotif,omitempty" validate:"required"`
CompareDigest *bool `yaml:"compareDigest,omitempty" json:"compareDigest,omitempty" validate:"required"`
Healthchecks *Healthchecks `yaml:"healthchecks,omitempty" json:"healthchecks,omitempty"`
}
@ -24,4 +25,5 @@ func (s *Watch) SetDefaults() {
s.Workers = 10
s.Schedule = "0 * * * *"
s.FirstCheckNotif = utl.NewFalse()
s.CompareDigest = utl.NewTrue()
}

View file

@ -4,7 +4,9 @@ import (
"fmt"
"time"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
@ -23,11 +25,38 @@ type Manifest struct {
}
// Manifest returns the manifest for a specific image
func (c *Client) Manifest(image Image) (Manifest, error) {
func (c *Client) Manifest(image Image, dbManifest Manifest) (Manifest, error) {
ctx, cancel := c.timeoutContext()
defer cancel()
imgCloser, err := c.newImage(ctx, image.String())
if c.sysCtx.DockerAuthConfig == nil {
c.sysCtx.DockerAuthConfig = &types.DockerAuthConfig{}
// TODO: Seek credentials
//auth, err := config.GetCredentials(c.sysCtx, reference.Domain(ref.DockerReference()))
//if err != nil {
// return nil, errors.Wrap(err, "Cannot get registry credentials")
//}
//*c.sysCtx.DockerAuthConfig = auth
}
imgRef, err := ParseReference(image.String())
if err != nil {
return Manifest{}, errors.Wrap(err, "Cannot parse reference")
}
var imgDigest digest.Digest
if c.opts.CompareDigest {
imgDigest, err = docker.GetDigest(ctx, c.sysCtx, imgRef)
if err != nil {
return Manifest{}, errors.Wrap(err, "Cannot get image digest from HEAD request")
}
if dbManifest.Digest != "" && dbManifest.Digest == imgDigest {
return dbManifest, nil
}
}
imgCloser, err := imgRef.NewImage(ctx, c.sysCtx)
if err != nil {
return Manifest{}, errors.Wrap(err, "Cannot create image closer")
}
@ -38,16 +67,18 @@ func (c *Client) Manifest(image Image) (Manifest, error) {
return Manifest{}, errors.Wrap(err, "Cannot get raw manifest")
}
if !c.opts.CompareDigest {
imgDigest, err = manifest.Digest(rawManifest)
if err != nil {
return Manifest{}, errors.Wrap(err, "Cannot get digest")
}
}
imgInspect, err := imgCloser.Inspect(ctx)
if err != nil {
return Manifest{}, errors.Wrap(err, "Cannot inspect")
}
imgDigest, err := manifest.Digest(rawManifest)
if err != nil {
return Manifest{}, errors.Wrap(err, "Cannot get digest")
}
imgTag := imgInspect.Tag
if len(imgTag) == 0 {
imgTag = image.Tag

View file

@ -7,6 +7,49 @@ import (
"github.com/stretchr/testify/assert"
)
func TestCompareDigest(t *testing.T) {
rc, err := registry.New(registry.Options{
CompareDigest: true,
})
if err != nil {
t.Error(err)
}
img, err := registry.ParseImage(registry.ParseImageOptions{
Name: "crazymax/diun:2.5.0",
})
if err != nil {
t.Error(err)
}
manifest, err := rc.Manifest(img, registry.Manifest{
Name: "docker.io/crazymax/diun",
Tag: "2.5.0",
MIMEType: "application/vnd.docker.distribution.manifest.list.v2+json",
Digest: "sha256:db618981ef3d07699ff6cd8b9d2a81f51a021747bc08c85c1b0e8d11130c2be5",
DockerVersion: "",
Labels: map[string]string{
"maintainer": "CrazyMax",
"org.label-schema.build-date": "2020-03-01T18:00:42Z",
"org.label-schema.description": "Docker image update notifier",
"org.label-schema.name": "Diun",
"org.label-schema.schema-version": "1.0",
"org.label-schema.url": "https://github.com/crazy-max/diun",
"org.label-schema.vcs-ref": "488ce441",
"org.label-schema.vcs-url": "https://github.com/crazy-max/diun",
"org.label-schema.vendor": "CrazyMax",
"org.label-schema.version": "2.5.0",
},
Platform: "linux/amd64",
})
assert.NoError(t, err)
assert.Equal(t, "docker.io/crazymax/diun", manifest.Name)
assert.Equal(t, "2.5.0", manifest.Tag)
assert.Equal(t, "application/vnd.docker.distribution.manifest.list.v2+json", manifest.MIMEType)
assert.Equal(t, "linux/amd64", manifest.Platform)
assert.Empty(t, manifest.DockerVersion)
}
func TestManifestVariant(t *testing.T) {
rc, err := registry.New(registry.Options{
ImageOs: "linux",
@ -24,7 +67,7 @@ func TestManifestVariant(t *testing.T) {
t.Error(err)
}
manifest, err := rc.Manifest(img)
manifest, err := rc.Manifest(img, registry.Manifest{})
assert.NoError(t, err)
assert.Equal(t, "docker.io/crazymax/diun", manifest.Name)
assert.Equal(t, "2.5.0", manifest.Tag)

16
pkg/registry/ref.go Normal file
View file

@ -0,0 +1,16 @@
package registry
import (
"fmt"
"strings"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/types"
)
func ParseReference(imageStr string) (types.ImageReference, error) {
if !strings.HasPrefix(imageStr, "//") {
imageStr = fmt.Sprintf("//%s", imageStr)
}
return docker.ParseReference(imageStr)
}

65
pkg/registry/ref_test.go Normal file
View file

@ -0,0 +1,65 @@
package registry_test
import (
"testing"
"github.com/crazy-max/diun/v4/pkg/registry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
sha256digestHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
sha256digest = "@sha256:" + sha256digestHex
)
func TestParseReference(t *testing.T) {
testCases := []struct {
input string
expected string
wantErr bool
}{
{
input: "busybox",
expected: "docker.io/library/busybox:latest",
},
{
input: "//busybox:notlatest",
expected: "docker.io/library/busybox:notlatest",
},
{
input: "//busybox" + sha256digest,
expected: "docker.io/library/busybox" + sha256digest,
},
{
input: "//busybox",
expected: "docker.io/library/busybox:latest",
},
{
input: "//busybox:latest" + sha256digest,
expected: "",
wantErr: true,
},
{
input: "//docker.io/library/busybox:latest",
expected: "docker.io/library/busybox:latest",
},
{
input: "//UPPERCASEISINVALID",
expected: "",
wantErr: true,
},
}
for _, tt := range testCases {
t.Run(tt.input, func(t *testing.T) {
ref, err := registry.ParseReference(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, ref.DockerReference().String(), tt.input)
})
}
}

View file

@ -2,13 +2,9 @@ package registry
import (
"context"
"fmt"
"strings"
"time"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/types"
"github.com/pkg/errors"
)
// Client represents an active docker registry object
@ -19,14 +15,15 @@ type Client struct {
// Options holds docker registry object options
type Options struct {
Username string
Password string
InsecureTLS bool
Timeout time.Duration
UserAgent string
ImageOs string
ImageArch string
ImageVariant string
Username string
Password string
InsecureTLS bool
Timeout time.Duration
UserAgent string
CompareDigest bool
ImageOs string
ImageArch string
ImageVariant string
}
// New creates new docker registry client instance
@ -52,6 +49,7 @@ func New(opts Options) (*Client, error) {
}
return &Client{
opts: opts,
sysCtx: sysCtx,
}, nil
}
@ -64,31 +62,3 @@ func (c *Client) timeoutContext() (context.Context, context.CancelFunc) {
}
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, errors.Wrap(err, "Invalid image name")
}
if c.sysCtx.DockerAuthConfig == nil {
c.sysCtx.DockerAuthConfig = &types.DockerAuthConfig{}
// TODO: Seek credentials
//auth, err := config.GetCredentials(c.sysCtx, reference.Domain(ref.DockerReference()))
//if err != nil {
// return nil, errors.Wrap(err, "Cannot get registry credentials")
//}
//*c.sysCtx.DockerAuthConfig = auth
}
img, err := ref.NewImage(ctx, c.sysCtx)
if err != nil {
return nil, err
}
return img, nil
}

View file

@ -3,6 +3,7 @@ package registry
import (
"github.com/containers/image/v5/docker"
"github.com/crazy-max/diun/v4/pkg/utl"
"github.com/pkg/errors"
)
// Tags holds information about image tags.
@ -26,13 +27,18 @@ func (c *Client) Tags(opts TagsOptions) (*Tags, error) {
ctx, cancel := c.timeoutContext()
defer cancel()
imgCls, err := c.newImage(ctx, opts.Image.String())
imgRef, err := ParseReference(opts.Image.String())
if err != nil {
return nil, err
return nil, errors.Wrap(err, "Cannot parse reference")
}
defer imgCls.Close()
tags, err := docker.GetRepositoryTags(ctx, c.sysCtx, imgCls.Reference())
imgCloser, err := imgRef.NewImage(ctx, c.sysCtx)
if err != nil {
return nil, errors.Wrap(err, "Cannot create image closer")
}
defer imgCloser.Close()
tags, err := docker.GetRepositoryTags(ctx, c.sysCtx, imgCloser.Reference())
if err != nil {
return nil, err
}