diff --git a/src/common/k8s-api/endpoints/pod.api.ts b/src/common/k8s-api/endpoints/pod.api.ts index 7b0bf55e9e..8e3aa51559 100644 --- a/src/common/k8s-api/endpoints/pod.api.ts +++ b/src/common/k8s-api/endpoints/pod.api.ts @@ -445,46 +445,62 @@ export interface PortworxVolumeSource { readOnly?: boolean; } +export interface KeyToPath { + key: string; + path: string; + mode?: number; +} + +export interface ConfigMapProjection { + name: string; + items?: KeyToPath[]; + optional?: boolean; +} + +export interface ObjectFieldSelector { + fieldPath: string; + apiVersion?: string; +} + +export interface ResourceFieldSelector { + resource: string; + containerName?: string; + divisor?: string; +} + +export interface DownwardAPIVolumeFile { + path: string; + fieldRef?: ObjectFieldSelector; + resourceFieldRef?: ResourceFieldSelector; + mode?: number; +} + +export interface DownwardAPIProjection { + items?: DownwardAPIVolumeFile[]; +} + +export interface SecretProjection { + name: string; + items?: KeyToPath[]; + optional?: boolean; +} + +export interface ServiceAccountTokenProjection { + audience?: string; + expirationSeconds?: number; + path: string; +} + +export interface VolumeProjection { + secret?: SecretProjection; + downwardAPI?: DownwardAPIProjection; + configMap?: ConfigMapProjection; + serviceAccountToken?: ServiceAccountTokenProjection; +} + export interface ProjectedSource { - sources: { - secret?: { - name: string; - items?: { - key: string; - path: string; - mode?: number; - }[]; - }; - downwardAPI?: { - items?: { - path: string; - fieldRef?: { - fieldPath: string; - apiVersion?: string; - }; - resourceFieldRef?: { - resource: string; - containerName?: string; - }; - mode?: number; - }[]; - }; - configMap?: { - name: string; - items?: { - key: string; - path: string; - mode?: number; - }[]; - optional?: boolean; - }; - serviceAccountToken?: { - audience?: string; - expirationSeconds?: number; - path: string; - }; - }[]; - defaultMode: number; + sources?: VolumeProjection[]; + defaultMode?: number; } export interface QuobyteSource { diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/__snapshots__/projected.test.tsx.snap b/src/renderer/components/+workloads-pods/details/volumes/variants/__snapshots__/projected.test.tsx.snap new file mode 100644 index 0000000000..ef0d3dd261 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/__snapshots__/projected.test.tsx.snap @@ -0,0 +1,229 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders 1`] = ` + +
+
+ + Sources + + +
+
+ +`; + +exports[` renders a secret source including overriding mode 1`] = ` + +
+
+ + Default Mount Mode + + + 0o777 + +
+
+ + Sources + + +
+ Secret +
+
+ + Name + + + my-projected-secret + +
+
+ + Items + + +
    +
  • + foo⇢/bar + (0o666) +
  • +
+
+
+
+
+
+ +`; + +exports[` renders a secret source, when provided 1`] = ` + +
+
+ + Default Mount Mode + + + 0o777 + +
+
+ + Sources + + +
+ Secret +
+
+ + Name + + + my-projected-secret + +
+
+ + Items + + +
    +
  • + foo⇢/bar +
  • +
+
+
+
+
+
+ +`; + +exports[` renders default mount mode in octal when provided 1`] = ` + +
+
+ + Default Mount Mode + + + 0o777 + +
+
+ + Sources + + +
+
+ +`; + +exports[` renders when no sources array provided 1`] = ` + +
+
+ + Default Mount Mode + + + 0o777 + +
+
+ + Sources + + +
+
+ +`; diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/projected.test.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/projected.test.tsx new file mode 100644 index 0000000000..24405bc1a9 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/projected.test.tsx @@ -0,0 +1,197 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { render } from "@testing-library/react"; +import React from "react"; +import type { ProjectedSource } from "../../../../../../common/k8s-api/endpoints"; +import { Pod } from "../../../../../../common/k8s-api/endpoints"; +import { Projected } from "./projected"; + +describe("", () => { + it("renders", () => { + const projectedVolume: ProjectedSource = { }; + const projectedVolumeName = "my-projected"; + const pod = new Pod({ + apiVersion: "v1", + kind: "Pod", + metadata: { + name: "my-pod", + namespace: "default", + resourceVersion: "1", + uid: "123", + selfLink: "/api/v1/pod/default/my-pod", + }, + spec: { + volumes: [{ + name: projectedVolumeName, + projected: projectedVolume, + }], + }, + }); + const result = render(( + + )); + + expect(result.baseElement).toMatchSnapshot(); + }); + + it("renders default mount mode in octal when provided", () => { + const projectedVolume: ProjectedSource = { + defaultMode: 0o777, + sources: [], + }; + const projectedVolumeName = "my-projected"; + const pod = new Pod({ + apiVersion: "v1", + kind: "Pod", + metadata: { + name: "my-pod", + namespace: "default", + resourceVersion: "1", + uid: "123", + selfLink: "/api/v1/pod/default/my-pod", + }, + spec: { + volumes: [{ + name: projectedVolumeName, + projected: projectedVolume, + }], + }, + }); + const result = render(( + + )); + + expect(result.baseElement).toMatchSnapshot(); + }); + + it("renders when no sources array provided", () => { + const projectedVolume: ProjectedSource = { + defaultMode: 0o777, + }; + const projectedVolumeName = "my-projected"; + const pod = new Pod({ + apiVersion: "v1", + kind: "Pod", + metadata: { + name: "my-pod", + namespace: "default", + resourceVersion: "1", + uid: "123", + selfLink: "/api/v1/pod/default/my-pod", + }, + spec: { + volumes: [{ + name: projectedVolumeName, + projected: projectedVolume, + }], + }, + }); + const result = render(( + + )); + + expect(result.baseElement).toMatchSnapshot(); + }); + + it("renders a secret source, when provided", () => { + const projectedVolume: ProjectedSource = { + defaultMode: 0o777, + sources: [{ + secret: { + name: "my-projected-secret", + items: [{ + key: "foo", + path: "/bar", + }], + }, + }], + }; + const projectedVolumeName = "my-projected"; + const pod = new Pod({ + apiVersion: "v1", + kind: "Pod", + metadata: { + name: "my-pod", + namespace: "default", + resourceVersion: "1", + uid: "123", + selfLink: "/api/v1/pod/default/my-pod", + }, + spec: { + volumes: [{ + name: projectedVolumeName, + projected: projectedVolume, + }], + }, + }); + const result = render(( + + )); + + expect(result.baseElement).toMatchSnapshot(); + expect(result.getByText("foo⇢/bar", { exact: false })).toBeTruthy(); + }); + + it("renders a secret source including overriding mode", () => { + const projectedVolume: ProjectedSource = { + defaultMode: 0o777, + sources: [{ + secret: { + name: "my-projected-secret", + items: [{ + key: "foo", + path: "/bar", + mode: 0o666, + }], + }, + }], + }; + const projectedVolumeName = "my-projected"; + const pod = new Pod({ + apiVersion: "v1", + kind: "Pod", + metadata: { + name: "my-pod", + namespace: "default", + resourceVersion: "1", + uid: "123", + selfLink: "/api/v1/pod/default/my-pod", + }, + spec: { + volumes: [{ + name: projectedVolumeName, + projected: projectedVolume, + }], + }, + }); + const result = render(( + + )); + + expect(result.baseElement).toMatchSnapshot(); + expect(result.getByText("(0o666)", { exact: false })).toBeTruthy(); + }); +}); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/projected.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/projected.tsx index cd725f1c67..c7ca22c87f 100644 --- a/src/renderer/components/+workloads-pods/details/volumes/variants/projected.tsx +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/projected.tsx @@ -4,18 +4,21 @@ */ import React from "react"; +import { displayMode } from "../../../../../utils"; import { DrawerItem, DrawerTitle } from "../../../../drawer"; import type { VolumeVariantComponent } from "../variant-helpers"; export const Projected: VolumeVariantComponent<"projected"> = ( ({ variant: { sources, defaultMode }}) => ( <> - - {`0o${defaultMode.toString(8)}`} - + {typeof defaultMode === "number" && ( + + {displayMode(defaultMode)} + + )} { - sources.map(({ secret, downwardAPI, configMap, serviceAccountToken }, index) => ( + sources?.map(({ secret, downwardAPI, configMap, serviceAccountToken }, index) => ( {secret && ( <> @@ -25,9 +28,12 @@ export const Projected: VolumeVariantComponent<"projected"> = (
    - {secret.items?.map(({ key, path }) => ( + {secret.items?.map(({ key, path, mode }) => (
  • {`${key}⇢${path}`} + {typeof mode === "number" && ( + ` (${displayMode(mode)})` + )}
  • ))}
diff --git a/src/renderer/utils/display-mode.ts b/src/renderer/utils/display-mode.ts new file mode 100644 index 0000000000..e0d2be5d88 --- /dev/null +++ b/src/renderer/utils/display-mode.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * Format `mode` in octal notation + */ +export function displayMode(mode: number): string { + return `0o${mode.toString(8)}`; +} diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts index 8f41c54e57..fee7dfc56d 100755 --- a/src/renderer/utils/index.ts +++ b/src/renderer/utils/index.ts @@ -10,6 +10,7 @@ export * from "../../common/event-emitter"; export * from "./cssNames"; export * from "./cssVar"; export * from "./display-booleans"; +export * from "./display-mode"; export * from "./interval"; export * from "./isMiddleClick"; export * from "./isReactNode";