From c2fe56a1265cc946de1a50f97e6f3dfec9e1a131 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nikolai=20R=C3=B8ed=20Kristiansen?=
 <nikolai.kristiansen@remarkable.no>
Date: Sun, 19 Mar 2023 09:15:15 +0100
Subject: [PATCH] feat(manager/helmfile): Support kustomize (#20782)

---
 .../helmfile/__fixtures__/multidoc.yaml       | 23 ++++++++++++
 .../__snapshots__/extract.spec.ts.snap        | 10 +++++
 .../manager/helmfile/artifacts.spec.ts        | 23 ++++++++----
 lib/modules/manager/helmfile/artifacts.ts     | 37 ++++++++++++-------
 lib/modules/manager/helmfile/extract.spec.ts  |  1 +
 lib/modules/manager/helmfile/extract.ts       |  5 ++-
 lib/modules/manager/helmfile/types.ts         |  5 ++-
 lib/modules/manager/helmfile/utils.ts         | 10 +++++
 lib/util/exec/containerbase.ts                |  6 +++
 lib/util/exec/types.ts                        |  1 +
 10 files changed, 98 insertions(+), 23 deletions(-)
 create mode 100644 lib/modules/manager/helmfile/utils.ts

diff --git a/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml b/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml
index 69d3c85447..d42bd70e57 100644
--- a/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml
+++ b/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml
@@ -25,6 +25,8 @@ repositories:
   url: https://charts.bitnami.com/bitnami
 - name: prometheus-community
   url: https://prometheus-community.github.io/helm-charts
+- name: incubator
+  url: https://charts.helm.sh/incubator/
 
 templates:
   external-chart: &external-chart
@@ -72,3 +74,24 @@ releases:
     {{`{{ range .Alerts }}
       *Alert:* {{ .Annotations.summary }}
     {{ end }}`}}
+
+- name: raw1
+  chart: incubator/raw
+  version: 0.1.0
+  values:
+  - resources:
+    - apiVersion: v1
+      kind: ConfigMap
+      metadata:
+        name: raw1
+        namespace: default
+      data:
+        foo: FOO
+  strategicMergePatches:
+    - apiVersion: v1
+      kind: ConfigMap
+      metadata:
+        name: raw1
+        namespace: default
+      data:
+        bar: BAR
diff --git a/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap
index dbe1c07f50..49b560fae8 100644
--- a/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap
+++ b/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap
@@ -30,6 +30,16 @@ exports[`modules/manager/helmfile/extract extractPackageFile() parses multidoc y
       "depName": "external-dns",
       "skipReason": "invalid-version",
     },
+    {
+      "currentValue": "0.1.0",
+      "depName": "raw",
+      "managerData": {
+        "needKustomize": true,
+      },
+      "registryUrls": [
+        "https://charts.helm.sh/incubator/",
+      ],
+    },
   ],
 }
 `;
diff --git a/lib/modules/manager/helmfile/artifacts.spec.ts b/lib/modules/manager/helmfile/artifacts.spec.ts
index d2213cbb15..f4c448451a 100644
--- a/lib/modules/manager/helmfile/artifacts.spec.ts
+++ b/lib/modules/manager/helmfile/artifacts.spec.ts
@@ -42,8 +42,9 @@ releases:
     chart: oauth2-proxy/oauth2-proxy
     version: 6.8.0
 `;
+
 const lockFile = codeBlock`
-version: 0.150.0
+version: 0.151.0
 dependencies:
 - name: backstage
   repository: https://backstage.github.io/charts
@@ -51,11 +52,11 @@ dependencies:
 - name: oauth2-proxy
   repository: https://oauth2-proxy.github.io/manifests
   version: 6.2.1
-digest: sha256:98c605fc3de51960ad1eb022f01dfae3bb0a1a06549e56fa39ec86db2a9a072d
-generated: "2023-01-23T12:13:46.487247+01:00"
+digest: sha256:e284706b71f37b757a536703da4cb148d67901afbf1ab431f7d60a9852ca6eef
+generated: "2023-03-08T21:32:06.122276997+01:00"
 `;
 const lockFileTwo = codeBlock`
-version: 0.150.0
+version: 0.151.0
 dependencies:
 - name: backstage
   repository: https://backstage.github.io/charts
@@ -63,8 +64,8 @@ dependencies:
 - name: oauth2-proxy
   repository: https://oauth2-proxy.github.io/manifests
   version: 6.8.0
-digest: sha256:8ceea14d17c0f3c108a26ba341c63380e2426db66484d2b2876ab6e636e52af4
-generated: "2023-01-23T12:16:41.881988+01:00"
+digest: sha256:9d83889176d005effb86041d30c20361625561cbfb439cbd16d7243225bac17c
+generated: "2023-03-08T21:30:48.273709455+01:00"
 `;
 
 describe('modules/manager/helmfile/artifacts', () => {
@@ -171,6 +172,8 @@ describe('modules/manager/helmfile/artifacts', () => {
             ' && ' +
             'install-tool helmfile v0.129.0' +
             ' && ' +
+            'install-tool kustomize 5.0.0' +
+            ' && ' +
             'helmfile deps -f helmfile.yaml' +
             '"',
         },
@@ -181,6 +184,7 @@ describe('modules/manager/helmfile/artifacts', () => {
       expectedCommands: [
         { cmd: 'install-tool helm v3.7.2' },
         { cmd: 'install-tool helmfile v0.129.0' },
+        { cmd: 'install-tool kustomize 5.0.0' },
         { cmd: 'helmfile deps -f helmfile.yaml' },
       ],
     },
@@ -203,7 +207,12 @@ describe('modules/manager/helmfile/artifacts', () => {
       datasource.getPkgReleases.mockResolvedValueOnce({
         releases: [{ version: 'v0.129.0' }],
       });
-      const updatedDeps = [{ depName: 'dep1' }];
+      datasource.getPkgReleases.mockResolvedValueOnce({
+        releases: [{ version: '5.0.0' }],
+      });
+      const updatedDeps = [
+        { depName: 'dep1', managerData: { needKustomize: true } },
+      ];
       expect(
         await helmfile.updateArtifacts({
           packageFileName: 'helmfile.yaml',
diff --git a/lib/modules/manager/helmfile/artifacts.ts b/lib/modules/manager/helmfile/artifacts.ts
index c7a9f23ae5..578372e106 100644
--- a/lib/modules/manager/helmfile/artifacts.ts
+++ b/lib/modules/manager/helmfile/artifacts.ts
@@ -3,7 +3,7 @@ import { quote } from 'shlex';
 import { TEMPORARY_ERROR } from '../../../constants/error-messages';
 import { logger } from '../../../logger';
 import { exec } from '../../../util/exec';
-import type { ExecOptions } from '../../../util/exec/types';
+import type { ToolConstraint } from '../../../util/exec/types';
 import {
   getSiblingFileName,
   readLocalFile,
@@ -40,21 +40,30 @@ export async function updateArtifacts({
   try {
     await writeLocalFile(packageFileName, newPackageFileContent);
 
-    const execOptions: ExecOptions = {
+    const toolConstraints: ToolConstraint[] = [
+      {
+        toolName: 'helm',
+        constraint: config.constraints?.helm,
+      },
+      {
+        toolName: 'helmfile',
+        constraint: config.constraints?.helmfile,
+      },
+    ];
+    const needKustomize = updatedDeps.some(
+      (dep) => dep.managerData?.needKustomize
+    );
+    if (needKustomize) {
+      toolConstraints.push({
+        toolName: 'kustomize',
+        constraint: config.constraints?.kustomize,
+      });
+    }
+    await exec(`helmfile deps -f ${quote(packageFileName)}`, {
       docker: {},
       extraEnv: {},
-      toolConstraints: [
-        {
-          toolName: 'helm',
-          constraint: config.constraints?.helm,
-        },
-        {
-          toolName: 'helmfile',
-          constraint: config.constraints?.helmfile,
-        },
-      ],
-    };
-    await exec(`helmfile deps -f ${quote(packageFileName)}`, execOptions);
+      toolConstraints,
+    });
 
     const newHelmLockContent = await readLocalFile(lockFileName, 'utf8');
     if (existingLockFileContent === newHelmLockContent) {
diff --git a/lib/modules/manager/helmfile/extract.spec.ts b/lib/modules/manager/helmfile/extract.spec.ts
index f3f535df14..6b312da7b7 100644
--- a/lib/modules/manager/helmfile/extract.spec.ts
+++ b/lib/modules/manager/helmfile/extract.spec.ts
@@ -199,6 +199,7 @@ describe('modules/manager/helmfile/extract', () => {
           { depName: 'kube-prometheus-stack', currentValue: '13.7' },
           { depName: 'invalid', skipReason: 'invalid-name' },
           { depName: 'external-dns', skipReason: 'invalid-version' },
+          { depName: 'raw', managerData: { needKustomize: true } },
         ],
       });
     });
diff --git a/lib/modules/manager/helmfile/extract.ts b/lib/modules/manager/helmfile/extract.ts
index 5477a52dc4..70b79c7200 100644
--- a/lib/modules/manager/helmfile/extract.ts
+++ b/lib/modules/manager/helmfile/extract.ts
@@ -10,6 +10,7 @@ import type {
   PackageFileContent,
 } from '../types';
 import type { Doc } from './types';
+import { areKustomizationsUsed } from './utils';
 
 const isValidChartName = (name: string | undefined): boolean =>
   !!name && !regEx(/[!@#$%^&*(),.?":{}/|<>A-Z]/).test(name);
@@ -92,7 +93,9 @@ export function extractPackageFile(
           .concat([config.registryAliases?.[repoName]] as string[])
           .filter(is.string),
       };
-
+      if (areKustomizationsUsed(dep)) {
+        res.managerData = { needKustomize: true };
+      }
       // in case of OCI repository, we need a PackageDependency with a DockerDatasource and a packageName
       const repository = doc.repositories?.find(
         (repo) => repo.name === repoName
diff --git a/lib/modules/manager/helmfile/types.ts b/lib/modules/manager/helmfile/types.ts
index f2a9593e7c..d38263d102 100644
--- a/lib/modules/manager/helmfile/types.ts
+++ b/lib/modules/manager/helmfile/types.ts
@@ -1,7 +1,10 @@
-interface Release {
+export interface Release {
   name: string;
   chart: string;
   version: string;
+  strategicMergePatches?: unknown;
+  jsonPatches?: unknown;
+  transformers?: unknown;
 }
 
 interface Repository {
diff --git a/lib/modules/manager/helmfile/utils.ts b/lib/modules/manager/helmfile/utils.ts
new file mode 100644
index 0000000000..415925b9d1
--- /dev/null
+++ b/lib/modules/manager/helmfile/utils.ts
@@ -0,0 +1,10 @@
+import type { Release } from './types';
+
+/** Returns true if kustomize specific keys exist in a helmfile release */
+export function areKustomizationsUsed(release: Release): boolean {
+  return (
+    release.strategicMergePatches !== undefined ||
+    release.jsonPatches !== undefined ||
+    release.transformers !== undefined
+  );
+}
diff --git a/lib/util/exec/containerbase.ts b/lib/util/exec/containerbase.ts
index 3c3f5c1909..ed55be68f8 100644
--- a/lib/util/exec/containerbase.ts
+++ b/lib/util/exec/containerbase.ts
@@ -80,6 +80,12 @@ const allToolConfig: Record<string, ToolConfig> = {
     packageName: 'jsonnet-bundler/jsonnet-bundler',
     versioning: semverVersioningId,
   },
+  kustomize: {
+    datasource: 'github-releases',
+    packageName: 'kubernetes-sigs/kustomize',
+    extractVersion: '^kustomize/v(?<version>.*)$',
+    versioning: semverVersioningId,
+  },
   lerna: {
     datasource: 'npm',
     packageName: 'lerna',
diff --git a/lib/util/exec/types.ts b/lib/util/exec/types.ts
index ffe7949910..80cedbe66d 100644
--- a/lib/util/exec/types.ts
+++ b/lib/util/exec/types.ts
@@ -7,6 +7,7 @@ export interface ToolConstraint {
 
 export interface ToolConfig {
   datasource: string;
+  extractVersion?: string;
   packageName: string;
   hash?: boolean;
   versioning: string;
-- 
GitLab