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 6a10a5cf7bdf25..00000000000000 --- 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 6d2231e3e8a586..0f6744daac5e84 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 878f5001de73ec..9ac56edc663fe4 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 00000000000000..274094c80ae39c --- /dev/null +++ b/lib/modules/manager/kubernetes/types.ts @@ -0,0 +1,4 @@ +export interface KubernetesConfiguration { + apiVersion: string; + kind: string; +}