Skip to content

Commit

Permalink
node-shell: Control namespace and allow to disable node-shell
Browse files Browse the repository at this point in the history
  • Loading branch information
farodin91 committed Dec 29, 2024
1 parent 637e6b2 commit 44a383a
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 18 deletions.
6 changes: 6 additions & 0 deletions node-shell/src/components/NodeShellAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import { ActionButton } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import { Node } from '@kinvolk/headlamp-plugin/lib';
import { useState } from 'react';
import { NodeShellTerminal } from './NodeShellTerminal';
import { getCluster } from '@kinvolk/headlamp-plugin/lib/Utils';
import { isEnabled } from '../util';

export function NodeShellAction({ item }) {
const [showShell, setShowShell] = useState(false);
const cluster = getCluster();
function isLinux(item: Node | null): boolean {
return item?.status?.nodeInfo?.operatingSystem === 'linux';
}
if(!isEnabled(cluster)) {
return (<></>);
}
return (
<>
<ActionButton
Expand Down
24 changes: 14 additions & 10 deletions node-shell/src/components/NodeShellTerminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
import { Dialog, DialogProps } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import DialogContent from '@mui/material/DialogContent';
import { Box } from '@mui/material';
import { Node } from '@kinvolk/headlamp-plugin/lib';
import Node from '@kinvolk/headlamp-plugin/lib/K8s/node';
import { useEffect, useRef, useState } from 'react';
import { Terminal as XTerminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import Pod, { KubePod } from '@kinvolk/headlamp-plugin/lib/lib/k8s/pod';
import Pod, { KubePod } from '@kinvolk/headlamp-plugin/lib/K8s/pod';
import { apply, stream, StreamResultsCb } from '@kinvolk/headlamp-plugin/lib/ApiProxy';
import { DEFAULT_NODE_SHELL_LINUX_IMAGE } from './Settings';
import { DEFAULT_NODE_SHELL_LINUX_IMAGE, DEFAULT_NODE_SHELL_NAMESPACE } from './Settings';
import { getClusterConfig } from '../util';
import { getCluster } from '@kinvolk/headlamp-plugin/lib/Utils';
import _ from 'lodash';

const decoder = new TextDecoder('utf-8');
const encoder = new TextEncoder();
Expand Down Expand Up @@ -38,13 +39,13 @@ interface XTerminalConnected {
}


const shellPod = (name: string, nodeName: string, nodeShellImage: string) => {
const shellPod = (name: string, namespace: string, nodeName: string, nodeShellImage: string) => {
return {
kind: 'Pod',
apiVersion: 'v1',
metadata: {
name,
namespace: 'kube-system',
namespace,
},
spec: {
nodeName,
Expand Down Expand Up @@ -89,11 +90,15 @@ async function shell(item: Node, onExec: StreamResultsCb) {
//const clusterSettings = helpers.loadClusterSettings(cluster);
const config = getClusterConfig(cluster)
let image = config?.image || '';
let namespace = config?.namespace || '';
const podName = `node-shell-${item.getName()}-${uniqueString()}`;
if (image === '') {
image = DEFAULT_NODE_SHELL_LINUX_IMAGE;
}
const kubePod = shellPod(podName, item.getName(), image!!);
if (namespace === '') {
namespace = DEFAULT_NODE_SHELL_NAMESPACE;
}
const kubePod = shellPod(podName, namespace, item.getName(), image!!);
try {
await apply(kubePod);
} catch (e) {
Expand Down Expand Up @@ -135,7 +140,6 @@ export function NodeShellTerminal(props: NodeShellTerminalProps) {
const fitAddonRef = useRef<FitAddon | null>(null);
const streamRef = useRef<any | null>(null);


const wrappedOnClose = () => {
if (!!onClose) {
onClose();
Expand All @@ -146,7 +150,6 @@ export function NodeShellTerminal(props: NodeShellTerminalProps) {
}
};


// @todo: Give the real exec type when we have it.
function setupTerminal(containerRef: HTMLElement, xterm: XTerminal, fitAddon: FitAddon) {
if (!containerRef) {
Expand Down Expand Up @@ -316,8 +319,9 @@ export function NodeShellTerminal(props: NodeShellTerminalProps) {

(async function () {
xtermRef?.current?.xterm.writeln('Trying to open a shell');
const { stream, onClose } = await shell(item, (items: ArrayBuffer) =>
onData(xtermRef.current!, items)
const { stream, onClose } = await shell(
item,
(items: ArrayBuffer) => onData(xtermRef.current!, items)
);
streamRef.current = stream;
xtermRef.current!.onClose = onClose;
Expand Down
47 changes: 45 additions & 2 deletions node-shell/src/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@ import Select from '@mui/material/Select';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { useEffect, useState } from 'react';
import Switch from '@mui/material/Switch';

export const DEFAULT_NODE_SHELL_LINUX_IMAGE = 'docker.io/library/alpine:latest'
export const DEFAULT_NODE_SHELL_NAMESPACE = 'kube-system'

/**
* Props for the Settings component.
* @interface SettingsProps
* @property {Object.<string, {isMetricsEnabled?: boolean, autoDetect?: boolean, address?: string, defaultTimespan?: string}>} data - Configuration data for each cluster
* @property {Object.<string, {isEnabled?: boolean, namespace?: string, image?: string}>} data - Configuration data for each cluster
* @property {Function} onDataChange - Callback function when data changes
*/
interface SettingsProps {
data: Record<
string,
{
image?: string;
namespace?: string;
isEnabled?: boolean;
}
>;
onDataChange: (newData: SettingsProps['data']) => void;
}

/**
* Settings component for configuring Prometheus metrics.
* Settings component for configuring Node-Shell Action.
*/
export function Settings(props: SettingsProps) {
const { data, onDataChange } = props;
Expand All @@ -50,8 +54,27 @@ export function Settings(props: SettingsProps) {
}, [selectedCluster, data, onDataChange]);

const selectedClusterData = data?.[selectedCluster] || {};
const isEnabled = selectedClusterData.isEnabled ?? true;

const settingsRows = [
{
name: 'Enable Node Shell',
value: (
<Switch
checked={isEnabled}
onChange={e => {
const newEnabled = e.target.checked;
onDataChange({
...(data || {}),
[selectedCluster]: {
...((data || {})[selectedCluster] || {}),
isEnabled: newEnabled,
},
});
}}
/>
),
},
{
name: 'Node Shell Linux Image',
value: (
Expand All @@ -72,6 +95,26 @@ export function Settings(props: SettingsProps) {
/>
),
},
{
name: 'Namespace',
value: (
<TextField
value={selectedClusterData.namespace || ''}
onChange={e => {
const newNamespace = e.target.value;
onDataChange({
...(data || {}),
[selectedCluster]: {
...((data || {})[selectedCluster] || {}),
namespace: newNamespace,
},
});
}}
placeholder={DEFAULT_NODE_SHELL_NAMESPACE}
helperText={'The default namespace is kube-system.'}
/>
),
},
];

return (
Expand Down
23 changes: 17 additions & 6 deletions node-shell/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,36 @@ export const PLUGIN_NAME = 'node-shell';

/**
* ClusterData type represents the configuration data for a cluster.
* @property {boolean} autoDetect - Whether to auto-detect Prometheus metrics.
* @property {boolean} isMetricsEnabled - Whether metrics are enabled for the cluster.
* @property {string} address - The address of the Prometheus service.
* @property {string} defaultTimespan - The default timespan for metrics.
* @property {boolean} isEnabled - Whether node-shell is enabled for the cluster.
* @property {string} image - Image to create the node shell.
* @property {string} namespace - The namespace to spawn the pod to create a node shell.
*/
type ClusterData = {
image?: string;
namespace?: string;
isEnabled?: boolean
};

/**
* Conf type represents the configuration data for the prometheus plugin.
* Conf type represents the configuration data for the node-shell plugin.
* @property {[cluster: string]: ClusterData} - The configuration data for each cluster.
*/
type Conf = {
[cluster: string]: ClusterData;
};

/**
* getConfigStore returns the config store for the prometheus plugin.
* isEnabled checks if node-shell is enabled for a specific cluster.
* @param {string} cluster - The name of the cluster.
* @returns {boolean} True or null if node-shell is enabled, false otherwise.
*/
export function isEnabled(cluster: string): boolean {
const clusterData = getClusterConfig(cluster);
return clusterData?.isEnabled ?? true;
}

/**
* getConfigStore returns the config store for the node-shell plugin.
* @returns {ConfigStore<Conf>} The config store.
*/
export function getConfigStore(): ConfigStore<Conf> {
Expand Down

0 comments on commit 44a383a

Please sign in to comment.