mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: Refactor legacy code to use pattern matching
Also add missing unit tests to cover more. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
parent
bba0759d27
commit
befbe62e63
@ -21,57 +21,9 @@ import { parseKubeApi } from "../kube-api-parse";
|
||||
type KubeApiParseTestData = [string, IKubeApiParsed];
|
||||
|
||||
const tests: KubeApiParseTestData[] = [
|
||||
["/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com", {
|
||||
apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "apiextensions.k8s.io",
|
||||
apiVersion: "v1beta1",
|
||||
apiVersionWithGroup: "apiextensions.k8s.io/v1beta1",
|
||||
namespace: undefined,
|
||||
resource: "customresourcedefinitions",
|
||||
name: "prometheuses.monitoring.coreos.com",
|
||||
}],
|
||||
["/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27", {
|
||||
apiBase: "/api/v1/pods",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
namespace: "kube-system",
|
||||
resource: "pods",
|
||||
name: "coredns-6955765f44-v8p27",
|
||||
}],
|
||||
["/apis/stable.example.com/foo1/crontabs", {
|
||||
apiBase: "/apis/stable.example.com/foo1/crontabs",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "stable.example.com",
|
||||
apiVersion: "foo1",
|
||||
apiVersionWithGroup: "stable.example.com/foo1",
|
||||
resource: "crontabs",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
}],
|
||||
["/apis/cluster.k8s.io/v1alpha1/clusters", {
|
||||
apiBase: "/apis/cluster.k8s.io/v1alpha1/clusters",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "cluster.k8s.io",
|
||||
apiVersion: "v1alpha1",
|
||||
apiVersionWithGroup: "cluster.k8s.io/v1alpha1",
|
||||
resource: "clusters",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
}],
|
||||
["/api/v1/namespaces", {
|
||||
apiBase: "/api/v1/namespaces",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
resource: "namespaces",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
}],
|
||||
["/api/v1/secrets", {
|
||||
[
|
||||
"http://some-irrelevant-domain/api/v1/secrets?some-irrelevant-parameter=some-irrelevant-value",
|
||||
{
|
||||
apiBase: "/api/v1/secrets",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
@ -80,8 +32,39 @@ const tests: KubeApiParseTestData[] = [
|
||||
resource: "secrets",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
}],
|
||||
["/api/v1/nodes/minikube", {
|
||||
},
|
||||
],
|
||||
[
|
||||
"/api/v1/secrets",
|
||||
{
|
||||
apiBase: "/api/v1/secrets",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
resource: "secrets",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/api/v1/namespaces",
|
||||
{
|
||||
apiBase: "/api/v1/namespaces",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
resource: "namespaces",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/api/v1/nodes/minikube",
|
||||
{
|
||||
apiBase: "/api/v1/nodes",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
@ -90,8 +73,12 @@ const tests: KubeApiParseTestData[] = [
|
||||
resource: "nodes",
|
||||
name: "minikube",
|
||||
namespace: undefined,
|
||||
}],
|
||||
["/api/foo-bar/nodes/minikube", {
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/api/foo-bar/nodes/minikube",
|
||||
{
|
||||
apiBase: "/api/foo-bar/nodes",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
@ -100,8 +87,12 @@ const tests: KubeApiParseTestData[] = [
|
||||
resource: "nodes",
|
||||
name: "minikube",
|
||||
namespace: undefined,
|
||||
}],
|
||||
["/api/v1/namespaces/kube-public", {
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/api/v1/namespaces/kube-public",
|
||||
{
|
||||
apiBase: "/api/v1/namespaces",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
@ -110,7 +101,50 @@ const tests: KubeApiParseTestData[] = [
|
||||
resource: "namespaces",
|
||||
name: "kube-public",
|
||||
namespace: undefined,
|
||||
}],
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/apis/stable.example.com/foo1/crontabs",
|
||||
{
|
||||
apiBase: "/apis/stable.example.com/foo1/crontabs",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "stable.example.com",
|
||||
apiVersion: "foo1",
|
||||
apiVersionWithGroup: "stable.example.com/foo1",
|
||||
resource: "crontabs",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/apis/cluster.k8s.io/v1alpha1/clusters",
|
||||
{
|
||||
apiBase: "/apis/cluster.k8s.io/v1alpha1/clusters",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "cluster.k8s.io",
|
||||
apiVersion: "v1alpha1",
|
||||
apiVersionWithGroup: "cluster.k8s.io/v1alpha1",
|
||||
resource: "clusters",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27",
|
||||
{
|
||||
apiBase: "/api/v1/pods",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
namespace: "kube-system",
|
||||
resource: "pods",
|
||||
name: "coredns-6955765f44-v8p27",
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/apis/apps/v1/namespaces/default/deployments/some-deployment",
|
||||
@ -125,20 +159,73 @@ const tests: KubeApiParseTestData[] = [
|
||||
resource: "deployments",
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
{
|
||||
apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "apiextensions.k8s.io",
|
||||
apiVersion: "v1beta1",
|
||||
apiVersionWithGroup: "apiextensions.k8s.io/v1beta1",
|
||||
namespace: undefined,
|
||||
resource: "customresourcedefinitions",
|
||||
name: "prometheuses.monitoring.coreos.com",
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/api/v1/namespaces/kube-system/pods",
|
||||
{
|
||||
apiBase: "/api/v1/pods",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
namespace: "kube-system",
|
||||
resource: "pods",
|
||||
name: undefined,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"/apis/cluster.k8s.io/v1/namespaces/kube-system/pods",
|
||||
{
|
||||
apiBase: "/apis/cluster.k8s.io/v1/pods",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "cluster.k8s.io",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "cluster.k8s.io/v1",
|
||||
namespace: "kube-system",
|
||||
resource: "pods",
|
||||
name: undefined,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
const invalidTests = [
|
||||
undefined,
|
||||
"",
|
||||
"ajklsmh",
|
||||
"some-invalid-path",
|
||||
"//apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
"/apis//v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
"/apis/apiextensions.k8s.io//customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
"/apis/apiextensions.k8s.io/v1beta1//prometheuses.monitoring.coreos.com",
|
||||
"/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/",
|
||||
|
||||
"//v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
"/api//v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
"/api//customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
"/api/v1beta1//prometheuses.monitoring.coreos.com",
|
||||
"/api/v1beta1/customresourcedefinitions/",
|
||||
];
|
||||
|
||||
describe("parseApi unit tests", () => {
|
||||
it.each(tests)("testing %j", (url, expected) => {
|
||||
it.each(tests)(`given path %j, parses as expected`, (url, expected) => {
|
||||
expect(parseKubeApi(url)).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it.each(invalidTests)("testing %j should throw", (url) => {
|
||||
it.each(invalidTests)(`given path %j, parses as undefined`, (url) => {
|
||||
expect(parseKubeApi(url as never)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,9 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Parse kube-api path and get api-version, group, etc.
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { compact, join } from "lodash/fp";
|
||||
import { getMatchFor } from "./get-match-for";
|
||||
import { prepend } from "./prepend";
|
||||
|
||||
import { array } from "@k8slens/utilities";
|
||||
// Parse kube-api path and get api-version, group, etc.
|
||||
|
||||
export interface IKubeApiLinkRef {
|
||||
apiPrefix?: string;
|
||||
@ -22,92 +25,46 @@ export interface IKubeApiParsed extends IKubeApiLinkRef {
|
||||
apiVersionWithGroup: string;
|
||||
}
|
||||
|
||||
export function parseKubeApi(path: string): IKubeApiParsed | undefined {
|
||||
const apiPath = new URL(path, "https://localhost").pathname;
|
||||
const [, prefix, ...parts] = apiPath.split("/");
|
||||
const apiPrefix = `/${prefix}`;
|
||||
const [left, right, namespaced] = array.split(parts, "namespaces");
|
||||
let apiGroup: string;
|
||||
let apiVersion: string | undefined;
|
||||
let namespace: string | undefined;
|
||||
let resource: string;
|
||||
let name: string | undefined;
|
||||
|
||||
if (namespaced) {
|
||||
switch (right.length) {
|
||||
case 1:
|
||||
name = right[0];
|
||||
// fallthrough
|
||||
case 0:
|
||||
resource = "namespaces"; // special case this due to `split` removing namespaces
|
||||
break;
|
||||
default:
|
||||
[namespace, resource, name] = right;
|
||||
break;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
apiVersion = left.at(-1)!;
|
||||
const rest = left.slice(0, -1);
|
||||
|
||||
apiGroup = rest.join("/");
|
||||
} else {
|
||||
if (left.length === 0) {
|
||||
export function parseKubeApi(
|
||||
path: string | undefined,
|
||||
): IKubeApiParsed | undefined {
|
||||
if (!path) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (left.length === 1 || left.length === 2) {
|
||||
[apiVersion, resource] = left;
|
||||
apiGroup = "";
|
||||
} else if (left.length === 4) {
|
||||
[apiGroup, apiVersion, resource, name] = left;
|
||||
} else {
|
||||
/**
|
||||
* Given that
|
||||
* - `apiVersion` is `GROUP/VERSION` and
|
||||
* - `VERSION` is `DNS_LABEL` which is /^[a-z0-9]((-[a-z0-9])|[a-z0-9])*$/i
|
||||
* where length <= 63
|
||||
* - `GROUP` is /^D(\.D)*$/ where D is `DNS_LABEL` and length <= 253
|
||||
*
|
||||
* There is no well defined selection from an array of items that were
|
||||
* separated by '/'
|
||||
*
|
||||
* Solution is to create a heuristic. Namely:
|
||||
* 1. if '.' in left[0] then apiGroup <- left[0]
|
||||
* 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1]
|
||||
* 3. otherwise assume apiVersion <- left[0]
|
||||
* 4. always resource, name <- left[(0 or 1)+1..]
|
||||
*/
|
||||
if (left[0].includes(".") || left[1].match(/^v[0-9]/)) {
|
||||
[apiGroup, apiVersion] = left;
|
||||
resource = left.slice(2).join("/");
|
||||
} else {
|
||||
apiGroup = "";
|
||||
apiVersion = left[0];
|
||||
[resource, name] = left.slice(1);
|
||||
}
|
||||
}
|
||||
const parsedPath = getParsedPath(path);
|
||||
|
||||
if (!parsedPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const apiVersionWithGroup = [apiGroup, apiVersion].filter(v => v).join("/");
|
||||
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
|
||||
const { apiOrApis, apiGroup, namespace, apiVersion, resource, name } =
|
||||
parsedPath;
|
||||
|
||||
return {
|
||||
apiBase,
|
||||
apiPrefix, apiGroup,
|
||||
apiVersion, apiVersionWithGroup,
|
||||
namespace, resource, name,
|
||||
apiBase: getApiBase(apiOrApis, apiGroup, apiVersion, resource),
|
||||
apiPrefix: getApiPrefix(apiOrApis),
|
||||
apiGroup: getApiGroup(apiGroup),
|
||||
apiVersion,
|
||||
apiVersionWithGroup: getApiVersionWithGroup(apiGroup, apiVersion),
|
||||
namespace,
|
||||
resource,
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
function isIKubeApiParsed(refOrParsed: IKubeApiLinkRef | IKubeApiParsed): refOrParsed is IKubeApiParsed {
|
||||
function isIKubeApiParsed(
|
||||
refOrParsed: IKubeApiLinkRef | IKubeApiParsed,
|
||||
): refOrParsed is IKubeApiParsed {
|
||||
return "apiGroup" in refOrParsed && !!refOrParsed.apiGroup;
|
||||
}
|
||||
|
||||
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
|
||||
export function createKubeApiURL(linkParsed: IKubeApiParsed): string;
|
||||
|
||||
export function createKubeApiURL(ref: IKubeApiLinkRef | IKubeApiParsed): string {
|
||||
export function createKubeApiURL(
|
||||
ref: IKubeApiLinkRef | IKubeApiParsed,
|
||||
): string {
|
||||
if (isIKubeApiParsed(ref)) {
|
||||
return createKubeApiURL({
|
||||
apiPrefix: ref.apiPrefix,
|
||||
@ -133,3 +90,42 @@ export function createKubeApiURL(ref: IKubeApiLinkRef | IKubeApiParsed): string
|
||||
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
const getKubeApiPathMatch = getMatchFor(
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
);
|
||||
|
||||
const getParsedPath = (path: string) =>
|
||||
pipeline(path, withoutDomainAddressOrParameters, getKubeApiPathMatch, (match) => match?.groups);
|
||||
|
||||
const joinTruthy = (delimiter: string) => (toBeJoined: string[]) =>
|
||||
pipeline(toBeJoined, compact, join(delimiter));
|
||||
|
||||
const getApiBase = (
|
||||
apiOrApis: string,
|
||||
apiGroup: string,
|
||||
apiVersion: string,
|
||||
resource: string,
|
||||
) =>
|
||||
pipeline(
|
||||
[apiOrApis, apiGroup, apiVersion, resource],
|
||||
joinTruthy("/"),
|
||||
prepend("/"),
|
||||
);
|
||||
|
||||
const getApiPrefix = prepend("/");
|
||||
|
||||
const getApiVersionWithGroup = (apiGroup: string, apiVersion: string) =>
|
||||
joinTruthy("/")([apiGroup, apiVersion]);
|
||||
|
||||
const getApiGroup = (apiGroup: string) => apiGroup || "";
|
||||
|
||||
const withoutDomainAddressOrParameters = (path: string) =>
|
||||
new URL(path, "http://irrelevant").pathname;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user