From 9e92712b7fd0e0c5db09254fb96bd4bed6ddfed7 Mon Sep 17 00:00:00 2001
From: Sebastian Poxhofer <secustor@users.noreply.github.com>
Date: Thu, 20 Jan 2022 10:31:16 +0100
Subject: [PATCH] fix(helmv3): adapt to new OCI format and allow aliases for
 OCI (#13603)

* feat(helmv3): adapt to new OCI format and refactor

* add whitespace to tests and null return value of `resolveAlias`

* docs(helmv3): use JSDocs for function documentation

Co-authored-by: Rhys Arkins <rhys@arkins.net>
---
 .../helmv3/__snapshots__/extract.spec.ts.snap | 16 +++---
 lib/manager/helmv3/extract.spec.ts            |  6 ++-
 lib/manager/helmv3/extract.ts                 | 53 ++++++------------
 lib/manager/helmv3/utils.spec.ts              | 43 +++++++++++++++
 lib/manager/helmv3/utils.ts                   | 54 +++++++++++++++++++
 5 files changed, 123 insertions(+), 49 deletions(-)
 create mode 100644 lib/manager/helmv3/utils.spec.ts
 create mode 100644 lib/manager/helmv3/utils.ts

diff --git a/lib/manager/helmv3/__snapshots__/extract.spec.ts.snap b/lib/manager/helmv3/__snapshots__/extract.spec.ts.snap
index 547524fa8d..0d6b4683f0 100644
--- a/lib/manager/helmv3/__snapshots__/extract.spec.ts.snap
+++ b/lib/manager/helmv3/__snapshots__/extract.spec.ts.snap
@@ -9,7 +9,6 @@ Object {
       "datasource": "docker",
       "depName": "library",
       "lookupName": "ghcr.io/ankitabhopatkar13/library",
-      "registryUrls": Array [],
     },
     Object {
       "currentValue": "0.8.1",
@@ -64,6 +63,12 @@ Object {
         "https://registry.example.com/",
       ],
     },
+    Object {
+      "currentValue": "2.2.0",
+      "datasource": "docker",
+      "depName": "oci-example",
+      "lookupName": "quay.example.com/organization/oci-example",
+    },
   ],
   "packageFileVersion": "0.1.0",
 }
@@ -76,17 +81,11 @@ Object {
     Object {
       "currentValue": "0.9.0",
       "depName": "redis",
-      "registryUrls": Array [
-        "@placeholder",
-      ],
       "skipReason": "placeholder-url",
     },
     Object {
       "currentValue": "0.8.1",
       "depName": "postgresql",
-      "registryUrls": Array [
-        "nope",
-      ],
       "skipReason": "invalid-url",
     },
     Object {
@@ -113,9 +112,6 @@ Object {
     Object {
       "currentValue": "0.8.1",
       "depName": "postgresql",
-      "registryUrls": Array [
-        "file:///some/local/path/",
-      ],
       "skipReason": "local-dependency",
     },
   ],
diff --git a/lib/manager/helmv3/extract.spec.ts b/lib/manager/helmv3/extract.spec.ts
index fdee21c756..1261309886 100644
--- a/lib/manager/helmv3/extract.spec.ts
+++ b/lib/manager/helmv3/extract.spec.ts
@@ -79,7 +79,7 @@ describe('manager/helmv3/extract', () => {
       dependencies:
       - name: library
         version: 0.1.0
-        repository: oci://ghcr.io/ankitabhopatkar13/library
+        repository: oci://ghcr.io/ankitabhopatkar13
         import-values:
           - defaults
       - name: postgresql
@@ -119,12 +119,16 @@ describe('manager/helmv3/extract', () => {
         - name: example
           version: 1.0.0
           repository: alias:longalias
+        - name: oci-example
+          version: 2.2.0
+          repository: alias:ociRegistry
       `;
       const fileName = 'Chart.yaml';
       const result = await extractPackageFile(content, fileName, {
         aliases: {
           placeholder: 'https://my-registry.gcr.io/',
           longalias: 'https://registry.example.com/',
+          ociRegistry: 'oci://quay.example.com/organization',
         },
       });
       expect(result).not.toBeNull();
diff --git a/lib/manager/helmv3/extract.ts b/lib/manager/helmv3/extract.ts
index bc0266ca3b..fc385c93ae 100644
--- a/lib/manager/helmv3/extract.ts
+++ b/lib/manager/helmv3/extract.ts
@@ -1,11 +1,11 @@
 import is from '@sindresorhus/is';
 import { load } from 'js-yaml';
-import * as datasourceDocker from '../../datasource/docker';
 import { HelmDatasource } from '../../datasource/helm';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
 import { getSiblingFileName, localPathExists } from '../../util/fs';
 import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
+import { parseRepository, resolveAlias } from './utils';
 
 export async function extractPackageFile(
   content: string,
@@ -57,45 +57,22 @@ export async function extractPackageFile(
       depName: dep.name,
       currentValue: dep.version,
     };
-    if (dep.repository) {
-      res.registryUrls = [dep.repository];
-      if (
-        dep.repository.startsWith('@') ||
-        dep.repository.startsWith('alias:')
-      ) {
-        const repoWithPrefixRemoved = dep.repository.slice(
-          dep.repository[0] === '@' ? 1 : 6
-        );
-        const alias = config.aliases[repoWithPrefixRemoved];
-        if (alias) {
-          res.registryUrls = [alias];
-          return res;
-        }
-
-        res.skipReason = SkipReason.PlaceholderUrl;
-      } else {
-        try {
-          const url = new URL(dep.repository);
-          switch (url.protocol) {
-            case 'oci:':
-              res.datasource = datasourceDocker.id;
-              res.lookupName = dep.repository.replace('oci://', '');
-              res.registryUrls = [];
-              break;
-            case 'file:':
-              res.skipReason = SkipReason.LocalDependency;
-              break;
-            default:
-          }
-        } catch (err) {
-          logger.debug({ err }, 'Error parsing url');
-          res.skipReason = SkipReason.InvalidUrl;
-        }
-      }
-    } else {
+    if (!dep.repository) {
       res.skipReason = SkipReason.NoRepository;
+      return res;
+    }
+
+    const repository = resolveAlias(dep.repository, config.aliases);
+    if (!repository) {
+      res.skipReason = SkipReason.PlaceholderUrl;
+      return res;
     }
-    return res;
+
+    const result: PackageDependency = {
+      ...res,
+      ...parseRepository(dep.name, repository),
+    };
+    return result;
   });
   const res: PackageFile = {
     deps,
diff --git a/lib/manager/helmv3/utils.spec.ts b/lib/manager/helmv3/utils.spec.ts
new file mode 100644
index 0000000000..5dc2a8aaf9
--- /dev/null
+++ b/lib/manager/helmv3/utils.spec.ts
@@ -0,0 +1,43 @@
+import { resolveAlias } from './utils';
+
+describe('manager/helmv3/utils', () => {
+  describe('.resolveAlias()', () => {
+    it('return alias with "alias:"', () => {
+      const repoUrl = 'https://charts.helm.sh/stable';
+      const repository = resolveAlias('alias:testRepo', {
+        testRepo: repoUrl,
+      });
+      expect(repository).toBe(repoUrl);
+    });
+
+    it('return alias with "@"', () => {
+      const repoUrl = 'https://charts.helm.sh/stable';
+      const repository = resolveAlias('@testRepo', {
+        testRepo: repoUrl,
+      });
+      expect(repository).toBe(repoUrl);
+    });
+
+    it('return null if alias repo is not defined', () => {
+      const repository = resolveAlias('alias:testRepo', {
+        anotherRepository: 'https://charts.helm.sh/stable',
+      });
+      expect(repository).toBeNull();
+    });
+
+    it('return resolved repository on OCI registries', () => {
+      const repository = resolveAlias('alias:artifactory', {
+        artifactory: 'oci://artifactory.example.com',
+      });
+      expect(repository).toBe('oci://artifactory.example.com');
+    });
+
+    it('return repository parameter if it is not an alias', () => {
+      const repoUrl = 'https://registry.example.com';
+      const repository = resolveAlias(repoUrl, {
+        anotherRepository: 'https://charts.helm.sh/stable',
+      });
+      expect(repository).toBe(repoUrl);
+    });
+  });
+});
diff --git a/lib/manager/helmv3/utils.ts b/lib/manager/helmv3/utils.ts
new file mode 100644
index 0000000000..729faca436
--- /dev/null
+++ b/lib/manager/helmv3/utils.ts
@@ -0,0 +1,54 @@
+import * as datasourceDocker from '../../datasource/docker';
+import { logger } from '../../logger';
+import { SkipReason } from '../../types';
+import type { PackageDependency } from '../types';
+
+export function parseRepository(
+  depName: string,
+  repositoryURL: string
+): PackageDependency {
+  const res: PackageDependency = {};
+
+  try {
+    const url = new URL(repositoryURL);
+    switch (url.protocol) {
+      case 'oci:':
+        res.datasource = datasourceDocker.id;
+        res.lookupName = `${repositoryURL.replace('oci://', '')}/${depName}`;
+        break;
+      case 'file:':
+        res.skipReason = SkipReason.LocalDependency;
+        break;
+      default:
+        res.registryUrls = [repositoryURL];
+    }
+  } catch (err) {
+    logger.debug({ err }, 'Error parsing url');
+    res.skipReason = SkipReason.InvalidUrl;
+  }
+  return res;
+}
+
+/**
+ * Resolves alias in repository string.
+ *
+ * @param repository to be resolved string
+ * @param aliases Records containing aliases as key and to be resolved URLs as values
+ *
+ * @returns  resolved alias. If repository does not contain an alias the repository string will be returned. Should it contain an alias which can not be resolved using `aliases`, null will be returned
+ */
+export function resolveAlias(
+  repository: string,
+  aliases: Record<string, string>
+): string | null {
+  if (!(repository.startsWith('@') || repository.startsWith('alias:'))) {
+    return repository;
+  }
+
+  const repoWithPrefixRemoved = repository.slice(repository[0] === '@' ? 1 : 6);
+  const alias = aliases[repoWithPrefixRemoved];
+  if (alias) {
+    return alias;
+  }
+  return null;
+}
-- 
GitLab