From 1295698744d45a3f3fc625308e22fee86041b04f Mon Sep 17 00:00:00 2001
From: Michael Kriese <michael.kriese@visualon.de>
Date: Mon, 4 Sep 2023 17:47:38 +0200
Subject: [PATCH] fix(datasource/docker): correctly split registry and
 repository (#24186)

---
 lib/modules/datasource/docker/common.spec.ts | 57 +++++++++++++++++---
 lib/modules/datasource/docker/common.ts      | 28 ++++++----
 lib/modules/datasource/docker/index.spec.ts  | 25 +++++++++
 3 files changed, 91 insertions(+), 19 deletions(-)

diff --git a/lib/modules/datasource/docker/common.spec.ts b/lib/modules/datasource/docker/common.spec.ts
index 6880e87cd3..8414323f55 100644
--- a/lib/modules/datasource/docker/common.spec.ts
+++ b/lib/modules/datasource/docker/common.spec.ts
@@ -19,14 +19,6 @@ const http = new Http(dockerDatasourceId);
 jest.mock('../../../util/host-rules');
 
 describe('modules/datasource/docker/common', () => {
-  beforeEach(() => {
-    hostRules.find.mockReturnValue({
-      username: 'some-username',
-      password: 'some-password',
-    });
-    hostRules.hosts.mockReturnValue([]);
-  });
-
   describe('getRegistryRepository', () => {
     it('handles local registries', () => {
       const res = getRegistryRepository(
@@ -71,9 +63,58 @@ describe('modules/datasource/docker/common', () => {
         registryHost: 'https://my.local.registry',
       });
     });
+
+    it('supports insecure registryUrls', () => {
+      hostRules.find.mockReturnValueOnce({ insecureRegistry: true });
+      const res = getRegistryRepository(
+        'prefix/image',
+        'my.local.registry/prefix'
+      );
+      expect(res).toStrictEqual({
+        dockerRepository: 'prefix/prefix/image',
+        registryHost: 'http://my.local.registry',
+      });
+    });
+
+    it.each([
+      {
+        name: 'strimzi-kafka-operator',
+        url: 'https://quay.io/strimzi-helm/',
+        res: {
+          dockerRepository: 'strimzi-helm/strimzi-kafka-operator',
+          registryHost: 'https://quay.io',
+        },
+      },
+      {
+        name: 'strimzi-kafka-operator',
+        url: 'https://docker.io/strimzi-helm/',
+        res: {
+          dockerRepository: 'strimzi-helm/strimzi-kafka-operator',
+          registryHost: 'https://index.docker.io',
+        },
+      },
+      {
+        name: 'nginx',
+        url: 'https://docker.io',
+        res: {
+          dockerRepository: 'library/nginx',
+          registryHost: 'https://index.docker.io',
+        },
+      },
+    ])('($name, $url)', ({ name, url, res }) => {
+      expect(getRegistryRepository(name, url)).toStrictEqual(res);
+    });
   });
 
   describe('getAuthHeaders', () => {
+    beforeEach(() => {
+      hostRules.find.mockReturnValue({
+        username: 'some-username',
+        password: 'some-password',
+      });
+      hostRules.hosts.mockReturnValue([]);
+    });
+
     it('throw page not found exception', async () => {
       httpMock
         .scope('https://my.local.registry')
diff --git a/lib/modules/datasource/docker/common.ts b/lib/modules/datasource/docker/common.ts
index 202680e296..a75788cdca 100644
--- a/lib/modules/datasource/docker/common.ts
+++ b/lib/modules/datasource/docker/common.ts
@@ -249,25 +249,31 @@ export function getRegistryRepository(
       };
     }
   }
-  let registryHost: string | undefined;
+  let registryHost = registryUrl;
   const split = packageName.split('/');
   if (split.length > 1 && (split[0].includes('.') || split[0].includes(':'))) {
     [registryHost] = split;
     split.shift();
   }
   let dockerRepository = split.join('/');
-  if (!registryHost) {
-    registryHost = registryUrl.replace(
-      'https://docker.io',
-      'https://index.docker.io'
-    );
-  }
-  if (registryHost === 'docker.io') {
-    registryHost = 'index.docker.io';
-  }
-  if (!regEx(/^https?:\/\//).exec(registryHost)) {
+
+  if (!regEx(/^https?:\/\//).test(registryHost)) {
     registryHost = `https://${registryHost}`;
   }
+
+  const { path, base } =
+    regEx(/^(?<base>https:\/\/[^/]+)\/(?<path>.+)$/).exec(registryHost)
+      ?.groups ?? {};
+  if (base && path) {
+    registryHost = base;
+    dockerRepository = `${trimTrailingSlash(path)}/${dockerRepository}`;
+  }
+
+  registryHost = registryHost.replace(
+    'https://docker.io',
+    'https://index.docker.io'
+  );
+
   const opts = hostRules.find({
     hostType: dockerDatasourceId,
     url: registryHost,
diff --git a/lib/modules/datasource/docker/index.spec.ts b/lib/modules/datasource/docker/index.spec.ts
index 6750649a9d..3cf817a3aa 100644
--- a/lib/modules/datasource/docker/index.spec.ts
+++ b/lib/modules/datasource/docker/index.spec.ts
@@ -1140,6 +1140,31 @@ describe('modules/datasource/docker/index', () => {
       expect(res?.releases).toHaveLength(1);
     });
 
+    it('uses quay api 2', async () => {
+      const tags = [{ name: '5.0.12' }];
+      httpMock
+        .scope('https://quay.io')
+        .get(
+          '/api/v1/repository/bitnami/redis/tag/?limit=100&page=1&onlyActiveTags=true'
+        )
+        .reply(200, { tags, has_additional: true })
+        .get(
+          '/api/v1/repository/bitnami/redis/tag/?limit=100&page=2&onlyActiveTags=true'
+        )
+        .reply(200, { tags: [], has_additional: false })
+        .get('/v2/')
+        .reply(200, '', {})
+        .get('/v2/bitnami/redis/manifests/5.0.12')
+        .reply(200, '', {});
+      const config = {
+        datasource: DockerDatasource.id,
+        packageName: 'redis',
+        registryUrls: ['https://quay.io/bitnami'],
+      };
+      const res = await getPkgReleases(config);
+      expect(res?.releases).toHaveLength(1);
+    });
+
     it('uses quay api and test error', async () => {
       httpMock
         .scope('https://quay.io')
-- 
GitLab