Skip to content

Commit

Permalink
TCP Dump (#281)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Tatsinnit <[email protected]>
  • Loading branch information
peterbom and Tatsinnit authored Nov 1, 2023
1 parent 46f7be8 commit 0cbf90e
Show file tree
Hide file tree
Showing 15 changed files with 1,244 additions and 12 deletions.
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@
{
"command": "aks.aksRunKubectlCommands",
"title": "Run Kubectl Commands"
},
{
"command": "aks.aksTCPDump",
"title": "Collect TCP Dumps"
}
],
"menus": {
Expand Down Expand Up @@ -305,6 +309,11 @@
"command": "aks.aksInspektorGadgetShow",
"when": "view == kubernetes.cloudExplorer && viewItem =~ /aks\\.cluster/i || view == extension.vsKubernetesExplorer && viewItem =~ /vsKubernetes\\.\\w*cluster$/i",
"group": "9@4"
},
{
"command": "aks.aksTCPDump",
"when": "view == kubernetes.cloudExplorer && viewItem =~ /aks\\.cluster/i || view == extension.vsKubernetesExplorer && viewItem =~ /vsKubernetes\\.\\w*cluster$/i",
"group": "9@5"
}
],
"aks.createClusterSubMenu": [
Expand Down Expand Up @@ -396,6 +405,10 @@
{
"id": "aks.createClusterSubMenu",
"label": "Create Cluster"
},
{
"id": "aks.tcpDataCollectionSubMenu",
"label": "Collect TCP Dump"
}
]
},
Expand Down
66 changes: 66 additions & 0 deletions src/commands/aksTCPCollection/tcpDumpCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as vscode from 'vscode';
import * as k8s from 'vscode-kubernetes-tools-api';
import { IActionContext } from "@microsoft/vscode-azext-utils";
import { getKubernetesClusterInfo } from '../utils/clusters';
import { getExtension } from '../utils/host';
import { Errorable, failed, map as errmap } from '../utils/errorable';
import * as tmpfile from '../utils/tempfile';
import { TcpDumpDataProvider, TcpDumpPanel } from '../../panels/TcpDumpPanel';
import { getVersion, invokeKubectlCommand } from '../utils/kubectl';

export async function aksTCPDump(_context: IActionContext, target: any) {
const kubectl = await k8s.extension.kubectl.v1;
const cloudExplorer = await k8s.extension.cloudExplorer.v1;
const clusterExplorer = await k8s.extension.clusterExplorer.v1;

if (!kubectl.available) {
vscode.window.showWarningMessage(`Kubectl is unavailable.`);
return;
}

if (!cloudExplorer.available) {
vscode.window.showWarningMessage(`Cloud explorer is unavailable.`);
return;
}

if (!clusterExplorer.available) {
vscode.window.showWarningMessage(`Cluster explorer is unavailable.`);
return;
}

const clusterInfo = await getKubernetesClusterInfo(target, cloudExplorer, clusterExplorer);
if (failed(clusterInfo)) {
vscode.window.showErrorMessage(clusterInfo.error);
return;
}

const extension = getExtension();
if (failed(extension)) {
vscode.window.showErrorMessage(extension.error);
return;
}

const kubeConfigFile = await tmpfile.createTempFile(clusterInfo.result.kubeconfigYaml, "yaml");
const linuxNodesList = await getLinuxNodes(kubectl, kubeConfigFile.filePath);
if (failed(linuxNodesList)) {
vscode.window.showErrorMessage(linuxNodesList.error);
return;
}

const kubectlVersion = await getVersion(kubectl, kubeConfigFile.filePath);
if (failed(kubectlVersion)) {
vscode.window.showErrorMessage(kubectlVersion.error);
return;
}

const dataProvider = new TcpDumpDataProvider(kubectl, kubeConfigFile.filePath, kubectlVersion.result, clusterInfo.result.name, linuxNodesList.result);
const panel = new TcpDumpPanel(extension.result.extensionUri);

panel.show(dataProvider, kubeConfigFile);
}

async function getLinuxNodes(kubectl: k8s.APIAvailable<k8s.KubectlV1>, kubeConfigFile: string): Promise<Errorable<string[]>> {
const command = `get node -l kubernetes.io/os=linux --no-headers -o custom-columns=":metadata.name"`;
const commandResult = await invokeKubectlCommand(kubectl, kubeConfigFile, command);
return errmap(commandResult, sr => sr.stdout.trim().split("\n"));
}
60 changes: 52 additions & 8 deletions src/commands/utils/kubectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,66 @@ export enum NonZeroExitCodeBehaviour {
Fail
}

export async function invokeKubectlCommand(kubectl: APIAvailable<KubectlV1>, kubeConfigFile: string, command: string, exitCodeBehaviour?: NonZeroExitCodeBehaviour): Promise<Errorable<KubectlV1.ShellResult>> {
try {
type KubeconfigCommandConfig = {
plainCommand: string,
commandWithKubeconfig: string,
exitCodeBehaviour: NonZeroExitCodeBehaviour
};

export type K8sVersion = {
major: string,
minor: string,
gitVersion: string,
buildDate: string
};

export type KubectlVersion = {
clientVersion: K8sVersion,
serverVersion: K8sVersion
};

export function getVersion(kubectl: APIAvailable<KubectlV1>, kubeConfigFile: string): Promise<Errorable<KubectlVersion>> {
return getKubectlJsonResult(kubectl, kubeConfigFile, "version -o json");
}

export async function getExecOutput(kubectl: APIAvailable<KubectlV1>, kubeConfigFile: string, namespace: string, pod: string, podCommand: string): Promise<Errorable<KubectlV1.ShellResult>> {
const plainCommand = `exec -n ${namespace} ${pod} -- ${podCommand}`;
const config: KubeconfigCommandConfig = {
plainCommand,
// Note: kubeconfig is the first argument because it needs to be part of the kubectl args, not the exec command's args.
commandWithKubeconfig: `--kubeconfig="${kubeConfigFile}" ${plainCommand}`,
// Always fail for non-zero exit code.
exitCodeBehaviour: NonZeroExitCodeBehaviour.Fail
};

return invokeKubectlCommandInternal(kubectl, config);
}

export function invokeKubectlCommand(kubectl: APIAvailable<KubectlV1>, kubeConfigFile: string, command: string, exitCodeBehaviour?: NonZeroExitCodeBehaviour): Promise<Errorable<KubectlV1.ShellResult>> {
const config: KubeconfigCommandConfig = {
plainCommand: command,
// Note: kubeconfig is the last argument because kubectl plugins will not work with kubeconfig in start.
const shellResult = await kubectl.api.invokeCommand(`${command} --kubeconfig="${kubeConfigFile}"`);
commandWithKubeconfig: `${command} --kubeconfig="${kubeConfigFile}"`,
exitCodeBehaviour: (exitCodeBehaviour === undefined) ? NonZeroExitCodeBehaviour.Fail : NonZeroExitCodeBehaviour.Succeed
};

return invokeKubectlCommandInternal(kubectl, config);
}

async function invokeKubectlCommandInternal(kubectl: APIAvailable<KubectlV1>, config: KubeconfigCommandConfig): Promise<Errorable<KubectlV1.ShellResult>> {
try {
const shellResult = await kubectl.api.invokeCommand(config.commandWithKubeconfig);
if (shellResult === undefined) {
return { succeeded: false, error: `Failed to run kubectl command "${command}"` };
return { succeeded: false, error: `Failed to run command "kubectl ${config.plainCommand}"` };
}

exitCodeBehaviour = (exitCodeBehaviour === undefined) ? NonZeroExitCodeBehaviour.Fail : NonZeroExitCodeBehaviour.Succeed;
if (shellResult.code !== 0 && exitCodeBehaviour === NonZeroExitCodeBehaviour.Fail) {
return { succeeded: false, error: `Kubectl returned error ${shellResult.code} for ${command}\nError: ${shellResult.stderr}` };
if (shellResult.code !== 0 && config.exitCodeBehaviour === NonZeroExitCodeBehaviour.Fail) {
return { succeeded: false, error: `The command "kubectl ${config.plainCommand}" returned status code ${shellResult.code}\nError: ${shellResult.stderr}` };
}

return { succeeded: true, result: shellResult };
} catch (e) {
return { succeeded: false, error: `Error running kubectl command "${command}": ${getErrorMessage(e)}` };
return { succeeded: false, error: `Error running "kubectl ${config.plainCommand}":\n${getErrorMessage(e)}` };
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { aksInspektorGadgetShow } from './commands/aksInspektorGadget/aksInspekt
import aksCreateCluster from './commands/aksCreateCluster/aksCreateCluster';
import aksAbortLastOperation from './commands/aksAbortLastOperation/aksAbortLastOperation';
import aksReconcileCluster from './commands/aksReconcileCluster/aksReconcileCluster';
import { aksTCPDump } from './commands/aksTCPCollection/tcpDumpCollection';

export async function activate(context: vscode.ExtensionContext) {
const cloudExplorer = await k8s.extension.cloudExplorer.v1;
Expand Down Expand Up @@ -77,6 +78,7 @@ export async function activate(context: vscode.ExtensionContext) {
registerCommandWithTelemetry('aks.aksReconcileCluster', aksReconcileCluster);
registerCommandWithTelemetry('aks.aksInspektorGadgetShow', aksInspektorGadgetShow);
registerCommandWithTelemetry('aks.createCluster', aksCreateCluster);
registerCommandWithTelemetry('aks.aksTCPDump', aksTCPDump);

await registerAzureServiceNodes(context);

Expand Down
Loading

0 comments on commit 0cbf90e

Please sign in to comment.