mirror of
https://github.com/crazy-max/diun.git
synced 2025-04-11 06:01:21 +00:00
Check digest from HEAD request (#217)
* 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:
parent
2fa4696f3a
commit
b1953afdae
15 changed files with 244 additions and 72 deletions
|
@ -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
|
||||
|
|
15
docs/faq.md
15
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).
|
||||
|
|
2
go.mod
2
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
|
||||
|
|
14
go.sum
14
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=
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
16
pkg/registry/ref.go
Normal 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
65
pkg/registry/ref_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue