From 98b19615fa23e1dbe84045b799bd0d2d44938b6a Mon Sep 17 00:00:00 2001
From: Jamie Magee <jamie.magee@gmail.com>
Date: Wed, 13 Jul 2022 12:09:15 -0700
Subject: [PATCH] feat(manager/kubernetes): extract kubernetes api versions
 (#16556)

* feat(manager/kubernetes): extract kubernetes api versions

* Update lib/modules/manager/kubernetes/extract.ts

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>

* coverage

* inline small fixture

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 .../__snapshots__/extract.spec.ts.snap        | 35 ----------
 .../manager/kubernetes/extract.spec.ts        | 70 +++++++++++++++++--
 lib/modules/manager/kubernetes/extract.ts     | 35 ++++++++--
 lib/modules/manager/kubernetes/types.ts       |  4 ++
 4 files changed, 98 insertions(+), 46 deletions(-)
 delete mode 100644 lib/modules/manager/kubernetes/__snapshots__/extract.spec.ts.snap
 create mode 100644 lib/modules/manager/kubernetes/types.ts

diff --git a/lib/modules/manager/kubernetes/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/kubernetes/__snapshots__/extract.spec.ts.snap
deleted file mode 100644
index 6a10a5cf7b..0000000000
--- a/lib/modules/manager/kubernetes/__snapshots__/extract.spec.ts.snap
+++ /dev/null
@@ -1,35 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`modules/manager/kubernetes/extract extractPackageFile() extracts image line in a YAML array 1`] = `
-Array [
-  Object {
-    "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
-    "currentDigest": undefined,
-    "currentValue": "v2.1.0",
-    "datasource": "docker",
-    "depName": "quay.io/external_storage/local-volume-provisioner",
-    "replaceString": "quay.io/external_storage/local-volume-provisioner:v2.1.0",
-  },
-]
-`;
-
-exports[`modules/manager/kubernetes/extract extractPackageFile() extracts multiple image lines 1`] = `
-Array [
-  Object {
-    "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
-    "currentDigest": undefined,
-    "currentValue": "1.7.9",
-    "datasource": "docker",
-    "depName": "nginx",
-    "replaceString": "nginx:1.7.9",
-  },
-  Object {
-    "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
-    "currentDigest": undefined,
-    "currentValue": "v1.11.1",
-    "datasource": "docker",
-    "depName": "k8s.gcr.io/kube-proxy-amd64",
-    "replaceString": "k8s.gcr.io/kube-proxy-amd64:v1.11.1",
-  },
-]
-`;
diff --git a/lib/modules/manager/kubernetes/extract.spec.ts b/lib/modules/manager/kubernetes/extract.spec.ts
index 6d2231e3e8..0f6744daac 100644
--- a/lib/modules/manager/kubernetes/extract.spec.ts
+++ b/lib/modules/manager/kubernetes/extract.spec.ts
@@ -9,23 +9,81 @@ const otherYamlFile = Fixtures.get('gitlab-ci.yaml');
 describe('modules/manager/kubernetes/extract', () => {
   describe('extractPackageFile()', () => {
     it('returns null for empty', () => {
-      expect(extractPackageFile(kubernetesConfigMapFile)).toBeNull();
+      expect(extractPackageFile('')).toBeNull();
     });
 
-    it('extracts multiple image lines', () => {
+    it('returns only API version', () => {
+      const res = extractPackageFile(kubernetesConfigMapFile);
+      expect(res?.deps).toStrictEqual([
+        {
+          currentValue: 'v1',
+          depName: 'ConfigMap',
+        },
+      ]);
+    });
+
+    it('extracts multiple Kubernetes configurations', () => {
       const res = extractPackageFile(kubernetesImagesFile);
-      expect(res?.deps).toMatchSnapshot();
-      expect(res?.deps).toHaveLength(2);
+      expect(res?.deps).toStrictEqual([
+        {
+          autoReplaceStringTemplate:
+            '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
+          currentDigest: undefined,
+          currentValue: '1.7.9',
+          datasource: 'docker',
+          depName: 'nginx',
+          replaceString: 'nginx:1.7.9',
+        },
+        {
+          autoReplaceStringTemplate:
+            '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
+          currentDigest: undefined,
+          currentValue: 'v1.11.1',
+          datasource: 'docker',
+          depName: 'k8s.gcr.io/kube-proxy-amd64',
+          replaceString: 'k8s.gcr.io/kube-proxy-amd64:v1.11.1',
+        },
+        {
+          currentValue: 'apps/v1',
+          depName: 'Deployment',
+        },
+        {
+          currentValue: 'extensions/v1beta1',
+          depName: 'DaemonSet',
+        },
+      ]);
     });
 
     it('extracts image line in a YAML array', () => {
       const res = extractPackageFile(kubernetesArraySyntaxFile);
-      expect(res?.deps).toMatchSnapshot();
-      expect(res?.deps).toHaveLength(1);
+      expect(res?.deps).toStrictEqual([
+        {
+          autoReplaceStringTemplate:
+            '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
+          currentDigest: undefined,
+          currentValue: 'v2.1.0',
+          datasource: 'docker',
+          depName: 'quay.io/external_storage/local-volume-provisioner',
+          replaceString:
+            'quay.io/external_storage/local-volume-provisioner:v2.1.0',
+        },
+        {
+          currentValue: 'apps/v1',
+          depName: 'DaemonSet',
+        },
+      ]);
     });
 
     it('ignores non-Kubernetes YAML files', () => {
       expect(extractPackageFile(otherYamlFile)).toBeNull();
     });
+
+    it('handles invalid YAML files', () => {
+      const invalidYaml = `apiVersion: v1
+kind: ConfigMap
+<
+`;
+      expect(extractPackageFile(invalidYaml)).toBeNull();
+    });
   });
 });
diff --git a/lib/modules/manager/kubernetes/extract.ts b/lib/modules/manager/kubernetes/extract.ts
index 878f5001de..9ac56edc66 100644
--- a/lib/modules/manager/kubernetes/extract.ts
+++ b/lib/modules/manager/kubernetes/extract.ts
@@ -1,11 +1,12 @@
+import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
 import { newlineRegex, regEx } from '../../../util/regex';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency, PackageFile } from '../types';
+import type { KubernetesConfiguration } from './types';
 
 export function extractPackageFile(content: string): PackageFile | null {
   logger.trace('kubernetes.extractPackageFile()');
-  let deps: PackageDependency[] = [];
 
   const isKubernetesManifest =
     regEx(/\s*apiVersion\s*:/).test(content) &&
@@ -14,6 +15,17 @@ export function extractPackageFile(content: string): PackageFile | null {
     return null;
   }
 
+  const deps: PackageDependency[] = [
+    ...extractImages(content),
+    ...extractApis(content),
+  ];
+
+  return deps.length ? { deps } : null;
+}
+
+function extractImages(content: string): PackageDependency[] {
+  const deps: PackageDependency[] = [];
+
   for (const line of content.split(newlineRegex)) {
     const match = regEx(/^\s*-?\s*image:\s*'?"?([^\s'"]+)'?"?\s*$/).exec(line);
     if (match) {
@@ -30,9 +42,22 @@ export function extractPackageFile(content: string): PackageFile | null {
       deps.push(dep);
     }
   }
-  deps = deps.filter((dep) => !dep.currentValue?.includes('${'));
-  if (!deps.length) {
-    return null;
+
+  return deps.filter((dep) => !dep.currentValue?.includes('${'));
+}
+
+function extractApis(content: string): PackageDependency[] {
+  let doc: KubernetesConfiguration[] | undefined;
+
+  try {
+    doc = loadAll(content) as KubernetesConfiguration[];
+  } catch (err) {
+    logger.debug({ err, content }, 'Failed to parse Kubernetes configuration.');
+    return [];
   }
-  return { deps };
+
+  return doc.map((configuration) => ({
+    depName: configuration.kind,
+    currentValue: configuration.apiVersion,
+  }));
 }
diff --git a/lib/modules/manager/kubernetes/types.ts b/lib/modules/manager/kubernetes/types.ts
new file mode 100644
index 0000000000..274094c80a
--- /dev/null
+++ b/lib/modules/manager/kubernetes/types.ts
@@ -0,0 +1,4 @@
+export interface KubernetesConfiguration {
+  apiVersion: string;
+  kind: string;
+}
-- 
GitLab