From 0ca479474594de7e798cd8631a06c6c9aa77bdff Mon Sep 17 00:00:00 2001
From: Jamie Magee <jamie.magee@gmail.com>
Date: Wed, 24 Aug 2022 22:10:35 -0700
Subject: [PATCH] feat(versioning): add kubernetes-api versioning (#16570)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 lib/modules/versioning/api.ts                 |   2 +
 .../versioning/kubernetes-api/index.spec.ts   | 145 ++++++++++++++++++
 .../versioning/kubernetes-api/index.ts        |  22 +++
 3 files changed, 169 insertions(+)
 create mode 100644 lib/modules/versioning/kubernetes-api/index.spec.ts
 create mode 100644 lib/modules/versioning/kubernetes-api/index.ts

diff --git a/lib/modules/versioning/api.ts b/lib/modules/versioning/api.ts
index 7dfb74d219..7329e25b87 100644
--- a/lib/modules/versioning/api.ts
+++ b/lib/modules/versioning/api.ts
@@ -11,6 +11,7 @@ import * as helm from './helm';
 import * as hermit from './hermit';
 import * as hex from './hex';
 import * as ivy from './ivy';
+import * as kubernetesApi from './kubernetes-api';
 import * as loose from './loose';
 import * as maven from './maven';
 import * as node from './node';
@@ -46,6 +47,7 @@ api.set(helm.id, helm.api);
 api.set(hermit.id, hermit.api);
 api.set(hex.id, hex.api);
 api.set(ivy.id, ivy.api);
+api.set(kubernetesApi.id, kubernetesApi.api);
 api.set(loose.id, loose.api);
 api.set(maven.id, maven.api);
 api.set(node.id, node.api);
diff --git a/lib/modules/versioning/kubernetes-api/index.spec.ts b/lib/modules/versioning/kubernetes-api/index.spec.ts
new file mode 100644
index 0000000000..82130abcb7
--- /dev/null
+++ b/lib/modules/versioning/kubernetes-api/index.spec.ts
@@ -0,0 +1,145 @@
+import { KubernetesApiVersioningApi } from './index';
+
+describe('modules/versioning/kubernetes-api/index', () => {
+  const versioning = new KubernetesApiVersioningApi();
+
+  test.each`
+    version       | expected
+    ${'v1'}       | ${true}
+    ${'v2'}       | ${true}
+    ${'v1alpha1'} | ${false}
+    ${'v1beta11'} | ${false}
+  `('isStable("$version") === $expected', ({ version, expected }) => {
+    expect(versioning.isStable(version)).toBe(expected);
+  });
+
+  test.each`
+    version                          | expected
+    ${'v1'}                          | ${true}
+    ${'v2'}                          | ${true}
+    ${'v10'}                         | ${true}
+    ${'v1.0'}                        | ${false}
+    ${'1'}                           | ${false}
+    ${'2'}                           | ${false}
+    ${'10'}                          | ${false}
+    ${'1.0'}                         | ${false}
+    ${'apps/v1'}                     | ${true}
+    ${'telemetry.istio.io/v1alpha1'} | ${true}
+    ${'k3d.io/v1alpha2'}             | ${true}
+    ${'extensions/v1beta1'}          | ${true}
+    ${'apps/v1beta2'}                | ${true}
+    ${'autoscaling/v2'}              | ${true}
+    ${'acme.cert-manager.io/v1'}     | ${true}
+  `('isValid("$version") === $expected', ({ version, expected }) => {
+    expect(versioning.isValid(version)).toBe(expected);
+  });
+
+  test.each`
+    version       | major | minor | patch
+    ${'v1'}       | ${1}  | ${0}  | ${0}
+    ${'v2'}       | ${2}  | ${0}  | ${0}
+    ${'v1alpha1'} | ${1}  | ${0}  | ${0}
+    ${'v1alpha2'} | ${1}  | ${0}  | ${0}
+    ${'v1beta1'}  | ${1}  | ${0}  | ${0}
+    ${'v1beta2'}  | ${1}  | ${0}  | ${0}
+  `(
+    'getMajor, getMinor, getPatch for "$version"',
+    ({ version, major, minor, patch }) => {
+      expect(versioning.getMajor(version)).toBe(major);
+      expect(versioning.getMinor(version)).toBe(minor);
+      expect(versioning.getPatch(version)).toBe(patch);
+    }
+  );
+
+  test.each`
+    version       | other                     | expected
+    ${'v1'}       | ${'v1'}                   | ${true}
+    ${'v1'}       | ${'v2'}                   | ${false}
+    ${'v1'}       | ${'v1alpha1'}             | ${false}
+    ${'v1'}       | ${'v1alpha2'}             | ${false}
+    ${'v1'}       | ${'v1beta1'}              | ${false}
+    ${'v1'}       | ${'v1beta2'}              | ${false}
+    ${'v1alpha1'} | ${'v1alpha1'}             | ${true}
+    ${'v1alpha1'} | ${'v1alpha2'}             | ${false}
+    ${'v1alpha1'} | ${'v1beta1'}              | ${false}
+    ${'v1alpha1'} | ${'v1beta2'}              | ${false}
+    ${'apps/v1'}  | ${'apps/v1'}              | ${true}
+    ${'apps/v1'}  | ${'apps/v2'}              | ${false}
+    ${'apps/v1'}  | ${'apps/v1alpha1'}        | ${false}
+    ${'apps/v1'}  | ${'apps/v1beta1'}         | ${false}
+    ${'apps/v1'}  | ${'autoscaling/v1'}       | ${true}
+    ${'apps/v1'}  | ${'autoscaling/v2'}       | ${false}
+    ${'apps/v1'}  | ${'autoscaling/v1alpha1'} | ${false}
+    ${'apps/v1'}  | ${'autoscaling/v1beta1'}  | ${false}
+  `(
+    'equals("$version", "$other") === $expected',
+    ({ version, other, expected }) => {
+      expect(versioning.equals(version, other)).toBe(expected);
+    }
+  );
+
+  test.each`
+    version       | other         | expected
+    ${'v1'}       | ${'v1'}       | ${true}
+    ${'v1'}       | ${'v2'}       | ${false}
+    ${'v1'}       | ${'v1alpha1'} | ${false}
+    ${'v1'}       | ${'v1alpha2'} | ${false}
+    ${'v1'}       | ${'v1beta1'}  | ${false}
+    ${'v1'}       | ${'v1beta2'}  | ${false}
+    ${'v1alpha1'} | ${'v1alpha1'} | ${true}
+    ${'v1alpha1'} | ${'v1alpha2'} | ${false}
+    ${'v1alpha1'} | ${'v1beta1'}  | ${false}
+    ${'v1alpha1'} | ${'v1beta2'}  | ${false}
+  `(
+    'matches("$version", "$other") === $expected',
+    ({ version, other, expected }) => {
+      expect(versioning.matches(version, other)).toBe(expected);
+    }
+  );
+
+  test.each`
+    version       | other         | expected
+    ${'v1'}       | ${'v2'}       | ${false}
+    ${'v1'}       | ${'v1alpha1'} | ${true}
+    ${'v1'}       | ${'v1beta1'}  | ${true}
+    ${'v2'}       | ${'v1beta1'}  | ${true}
+    ${'v1alpha1'} | ${'v1alpha2'} | ${false}
+    ${'v1beta1'}  | ${'v1alpha1'} | ${true}
+    ${'v1beta2'}  | ${'v1beta1'}  | ${true}
+  `(
+    'isGreaterThan("$version", "$other") === $expected',
+    ({ version, other, expected }) => {
+      expect(versioning.isGreaterThan(version, other)).toBe(expected);
+    }
+  );
+
+  it('sorts versions in an ascending order', () => {
+    expect(
+      [
+        'v10',
+        'v2',
+        'v2beta2',
+        'v2beta1',
+        'v2alpha2',
+        'v2alpha1',
+        'v1',
+        'v1beta2',
+        'v1beta1',
+        'v1alpha2',
+        'v1alpha1',
+      ].sort((a, b) => versioning.sortVersions(a, b))
+    ).toEqual([
+      'v1alpha1',
+      'v1alpha2',
+      'v1beta1',
+      'v1beta2',
+      'v1',
+      'v2alpha1',
+      'v2alpha2',
+      'v2beta1',
+      'v2beta2',
+      'v2',
+      'v10',
+    ]);
+  });
+});
diff --git a/lib/modules/versioning/kubernetes-api/index.ts b/lib/modules/versioning/kubernetes-api/index.ts
new file mode 100644
index 0000000000..c620991813
--- /dev/null
+++ b/lib/modules/versioning/kubernetes-api/index.ts
@@ -0,0 +1,22 @@
+import { RegExpVersioningApi } from '../regex';
+import type { VersioningApi } from '../types';
+
+export const id = 'kubernetes-api';
+export const displayName = 'Kubernetes API';
+export const urls = [
+  'https://kubernetes.io/docs/reference/using-api/#api-versioning',
+];
+export const supportsRanges = false;
+
+export class KubernetesApiVersioningApi extends RegExpVersioningApi {
+  private static readonly versionRegex =
+    '^(?:(?<compatibility>\\S+)\\/)?v(?<major>\\d+)(?<prerelease>(?:alpha|beta)\\d+)?$';
+
+  public constructor() {
+    super(KubernetesApiVersioningApi.versionRegex);
+  }
+}
+
+export const api: VersioningApi = new KubernetesApiVersioningApi();
+
+export default api;
-- 
GitLab