From 781ca4b1928957a3d3f0f016e61330859cc4bf07 Mon Sep 17 00:00:00 2001
From: kegato <37505324+kegato@users.noreply.github.com>
Date: Mon, 27 Mar 2023 19:07:49 +0200
Subject: [PATCH] feat(datasource/docker): support registry proxy for digest
 updates (#20777)

Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
---
 lib/modules/datasource/docker/index.spec.ts | 73 +++++++++++++++++++++
 lib/modules/datasource/docker/index.ts      | 48 ++++++++++++--
 2 files changed, 115 insertions(+), 6 deletions(-)

diff --git a/lib/modules/datasource/docker/index.spec.ts b/lib/modules/datasource/docker/index.spec.ts
index 87f1e53a4d..d3317515d0 100644
--- a/lib/modules/datasource/docker/index.spec.ts
+++ b/lib/modules/datasource/docker/index.spec.ts
@@ -1086,6 +1086,79 @@ describe('modules/datasource/docker/index', () => {
       );
       expect(res).toBeNull();
     });
+
+    it('falls back to library/ prefix on non-namespaced images with existing digest', async () => {
+      const currentDigest =
+          'sha256:0000000000000000000000000000000000000000000000000000000000000000',
+        newDigest =
+          'sha256:1111111111111111111111111111111111111111111111111111111111111111';
+
+      httpMock
+        .scope('https://registry.company.com/v2')
+        .get('/')
+        .times(4)
+        .reply(200)
+        .head(`/some-dep/manifests/${currentDigest}`)
+        .reply(500)
+        .head(`/some-dep/manifests/3.17`)
+        .reply(404)
+        .head(`/library/some-dep/manifests/${currentDigest}`)
+        .reply(200, '', {
+          'content-type':
+            'application/vnd.docker.distribution.manifest.list.v2+json',
+          'docker-content-digest': currentDigest,
+        })
+        .head('/library/some-dep/manifests/3.17')
+        .reply(200, '', {
+          'content-type':
+            'application/vnd.docker.distribution.manifest.list.v2+json',
+          'docker-content-digest': newDigest,
+        });
+
+      hostRules.find.mockReturnValue({});
+      const res = await getDigest(
+        {
+          datasource: 'docker',
+          packageName: 'some-dep',
+          currentDigest,
+          registryUrls: ['https://registry.company.com'],
+        },
+        '3.17'
+      );
+
+      expect(res).toBe(newDigest);
+    });
+
+    it('falls back to library/ prefix on non-namespaced images without existing digest', async () => {
+      const newDigest =
+        'sha256:1111111111111111111111111111111111111111111111111111111111111111';
+
+      httpMock
+        .scope('https://registry.company.com/v2')
+        .get('/')
+        .times(2)
+        .reply(200)
+        .head(`/some-dep/manifests/3.17`)
+        .reply(404)
+        .head('/library/some-dep/manifests/3.17')
+        .reply(200, '', {
+          'content-type':
+            'application/vnd.docker.distribution.manifest.list.v2+json',
+          'docker-content-digest': newDigest,
+        });
+
+      hostRules.find.mockReturnValue({});
+      const res = await getDigest(
+        {
+          datasource: 'docker',
+          packageName: 'some-dep',
+          registryUrls: ['https://registry.company.com'],
+        },
+        '3.17'
+      );
+
+      expect(res).toBe(newDigest);
+    });
   });
 
   describe('getReleases', () => {
diff --git a/lib/modules/datasource/docker/index.ts b/lib/modules/datasource/docker/index.ts
index 43e66d2321..63cd8faf19 100644
--- a/lib/modules/datasource/docker/index.ts
+++ b/lib/modules/datasource/docker/index.ts
@@ -628,12 +628,30 @@ export class DockerDatasource extends Datasource {
     currentDigest: string
   ): Promise<string | null | undefined> {
     try {
-      const manifestResponse = await this.getManifestResponse(
-        registryHost,
-        dockerRepository,
-        currentDigest,
-        'head'
-      );
+      let manifestResponse: HttpResponse<string> | null;
+
+      try {
+        manifestResponse = await this.getManifestResponse(
+          registryHost,
+          dockerRepository,
+          currentDigest,
+          'head'
+        );
+      } catch (_err) {
+        const err = _err instanceof ExternalHostError ? _err.err : _err;
+
+        if (
+          typeof err.statusCode === 'number' &&
+          err.statusCode >= 500 &&
+          err.statusCode < 600
+        ) {
+          // querying the digest manifest for a non existent image leads to a 500 statusCode
+          return null;
+        }
+
+        /* istanbul ignore next */
+        throw _err;
+      }
 
       if (
         manifestResponse?.headers['content-type'] !==
@@ -1096,6 +1114,24 @@ export class DockerDatasource extends Datasource {
         }
       }
 
+      if (
+        !manifestResponse &&
+        !dockerRepository.includes('/') &&
+        !packageName.includes('/')
+      ) {
+        logger.debug(
+          `Retrying Digest for ${registryHost}/${dockerRepository} using library/ prefix`
+        );
+        return this.getDigest(
+          {
+            registryUrl,
+            packageName: 'library/' + packageName,
+            currentDigest,
+          },
+          newValue
+        );
+      }
+
       if (manifestResponse) {
         // TODO: fix types (#7154)
         logger.debug(`Got docker digest ${digest!}`);
-- 
GitLab