diff --git a/src/commands/aksTCPCollection/tcpDumpCollection.ts b/src/commands/aksTCPCollection/tcpDumpCollection.ts index ee8435423..fb45aa9bf 100644 --- a/src/commands/aksTCPCollection/tcpDumpCollection.ts +++ b/src/commands/aksTCPCollection/tcpDumpCollection.ts @@ -6,7 +6,7 @@ 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 { invokeKubectlCommand } from '../utils/kubectl'; +import { getVersion, invokeKubectlCommand } from '../utils/kubectl'; export async function aksTCPDump(_context: IActionContext, target: any) { const kubectl = await k8s.extension.kubectl.v1; @@ -47,7 +47,13 @@ export async function aksTCPDump(_context: IActionContext, target: any) { return; } - const dataProvider = new TcpDumpDataProvider(kubectl, kubeConfigFile.filePath, clusterInfo.result.name, linuxNodesList.result); + 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); diff --git a/src/commands/utils/kubectl.ts b/src/commands/utils/kubectl.ts index 38057b878..88417899a 100644 --- a/src/commands/utils/kubectl.ts +++ b/src/commands/utils/kubectl.ts @@ -14,6 +14,22 @@ type KubeconfigCommandConfig = { 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, kubeConfigFile: string): Promise> { + return getKubectlJsonResult(kubectl, kubeConfigFile, "version -o json"); +} + export async function getExecOutput(kubectl: APIAvailable, kubeConfigFile: string, namespace: string, pod: string, podCommand: string): Promise> { const plainCommand = `exec -n ${namespace} ${pod} -- ${podCommand}`; const config: KubeconfigCommandConfig = { diff --git a/src/panels/TcpDumpPanel.ts b/src/panels/TcpDumpPanel.ts index 43ec397b3..b37fdc7ae 100644 --- a/src/panels/TcpDumpPanel.ts +++ b/src/panels/TcpDumpPanel.ts @@ -2,10 +2,11 @@ import { platform } from "os"; import { relative } from "path"; import { Uri, commands, window, workspace } from "vscode"; import * as k8s from 'vscode-kubernetes-tools-api'; +import * as semver from 'semver'; import { failed, map as errmap, Errorable } from "../commands/utils/errorable"; import { MessageHandler, MessageSink } from "../webview-contract/messaging"; import { BasePanel, PanelDataProvider } from "./BasePanel"; -import { getExecOutput, invokeKubectlCommand } from "../commands/utils/kubectl"; +import { KubectlVersion, getExecOutput, invokeKubectlCommand } from "../commands/utils/kubectl"; import { CompletedCapture, InitialState, ToVsCodeMsgDef, ToWebViewMsgDef } from "../webview-contract/webviewDefinitions/tcpDump"; import { withOptionalTempFile } from "../commands/utils/tempfile"; @@ -54,6 +55,7 @@ export class TcpDumpDataProvider implements PanelDataProvider<"tcpDump"> { constructor( readonly kubectl: k8s.APIAvailable, readonly kubeConfigFilePath: string, + readonly kubectlVersion: KubectlVersion, readonly clusterName: string, readonly linuxNodesList: string[] ) { } @@ -350,7 +352,15 @@ spec: } const localCpPath = getLocalKubectlCpPath(localCaptureUri); - const command = `cp -n ${debugPodNamespace} ${getPodName(node)}:${captureFileBasePath}${captureName}.cap ${localCpPath}`; + + // `kubectl cp` can fail with an EOF error for large files, and there's currently no good workaround: + // See: https://github.com/kubernetes/kubernetes/issues/60140 + // The best advice I can see is to use the 'retries' option if it is supported, and the + // 'request-timeout' option otherwise. + const clientVersion = this.kubectlVersion.clientVersion.gitVersion.replace(/^v/, ""); + const isRetriesOptionSupported = semver.parse(clientVersion) && semver.gte(clientVersion, "1.23.0"); + const cpEOFAvoidanceFlag = isRetriesOptionSupported ? "--retries 99" : "--request-timeout=10m"; + const command = `cp -n ${debugPodNamespace} ${getPodName(node)}:${captureFileBasePath}${captureName}.cap ${localCpPath} ${cpEOFAvoidanceFlag}`; const output = await invokeKubectlCommand(this.kubectl, this.kubeConfigFilePath, command); if (failed(output)) { webview.postDownloadCaptureFileResponse({