diff --git a/docs/config/watch.md b/docs/config/watch.md index 87ee42f2..2175b5a7 100644 --- a/docs/config/watch.md +++ b/docs/config/watch.md @@ -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 diff --git a/docs/faq.md b/docs/faq.md index 5c97cd6d..eb236ae7 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -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). diff --git a/go.mod b/go.mod index b004544b..87c78974 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 60f309cd..5ba0db34 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/app/job.go b/internal/app/job.go index eb26d48d..9f03a9b1 100644 --- a/internal/app/job.go +++ b/internal/app/job.go @@ -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") diff --git a/internal/config/config_test.go b/internal/config/config_test.go index cc04acb2..ea064a09 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -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", diff --git a/internal/config/fixtures/config.test.yml b/internal/config/fixtures/config.test.yml index b9709387..f2573899 100644 --- a/internal/config/fixtures/config.test.yml +++ b/internal/config/fixtures/config.test.yml @@ -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 diff --git a/internal/config/fixtures/config.validate.yml b/internal/config/fixtures/config.validate.yml index 43a7a5d3..35e622d6 100644 --- a/internal/config/fixtures/config.validate.yml +++ b/internal/config/fixtures/config.validate.yml @@ -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 diff --git a/internal/model/watch.go b/internal/model/watch.go index 072606c9..5269ee7c 100644 --- a/internal/model/watch.go +++ b/internal/model/watch.go @@ -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() } diff --git a/pkg/registry/manifest.go b/pkg/registry/manifest.go index 408fe60f..d0c774ff 100644 --- a/pkg/registry/manifest.go +++ b/pkg/registry/manifest.go @@ -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 diff --git a/pkg/registry/manifest_test.go b/pkg/registry/manifest_test.go index 0252ac1b..89523b48 100644 --- a/pkg/registry/manifest_test.go +++ b/pkg/registry/manifest_test.go @@ -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) diff --git a/pkg/registry/ref.go b/pkg/registry/ref.go new file mode 100644 index 00000000..d7ff9b33 --- /dev/null +++ b/pkg/registry/ref.go @@ -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) +} diff --git a/pkg/registry/ref_test.go b/pkg/registry/ref_test.go new file mode 100644 index 00000000..7d947efd --- /dev/null +++ b/pkg/registry/ref_test.go @@ -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) + }) + } +} diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 6b658406..98543d61 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -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 -} diff --git a/pkg/registry/tags.go b/pkg/registry/tags.go index a6eaef54..418c2f52 100644 --- a/pkg/registry/tags.go +++ b/pkg/registry/tags.go @@ -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 }