From 4686c53b6e4eebaba3040119ce80ea0540630175 Mon Sep 17 00:00:00 2001 From: Daniel Herbolt Date: Fri, 26 Sep 2025 15:17:52 +0200 Subject: [PATCH 1/7] feat: allow to set the cwd in copyFromPod for use in tar --- src/cp.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cp.ts b/src/cp.ts index d84d34cf86..39f8e4213b 100644 --- a/src/cp.ts +++ b/src/cp.ts @@ -18,6 +18,7 @@ export class Cp { * @param {string} containerName - The name of the container in the pod to exec the command inside. * @param {string} srcPath - The source path in the pod * @param {string} tgtPath - The target path in local + * @param {string} cwd - The directory that is used as the parent in the pod when downloading */ public async cpFromPod( namespace: string, @@ -25,10 +26,15 @@ export class Cp { containerName: string, srcPath: string, tgtPath: string, + cwd?: string, ): Promise { const tmpFile = tmp.fileSync(); const tmpFileName = tmpFile.name; - const command = ['tar', 'zcf', '-', srcPath]; + const command = ['tar', 'zcf', '-']; + if (cwd) { + command.push('-C', cwd); + } + command.push(srcPath); const writerStream = fs.createWriteStream(tmpFileName); const errStream = new WritableStreamBuffer(); this.execInstance.exec( From f20560a2b37f1244f7c8612b8366c193d7e196f9 Mon Sep 17 00:00:00 2001 From: Daniel Herbolt Date: Sat, 27 Sep 2025 13:46:18 +0200 Subject: [PATCH 2/7] added simple test --- src/cp_test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/cp_test.ts b/src/cp_test.ts index 7cfcafa7d4..0782106cba 100644 --- a/src/cp_test.ts +++ b/src/cp_test.ts @@ -38,6 +38,35 @@ describe('Cp', () => { await cp.cpFromPod(namespace, pod, container, srcPath, tgtPath); verify(fakeWebSocket.connect(`${path}?${queryStr}`, null, anyFunction())).called(); }); + + it('should run create tar command to a url with cwd', async () => { + const kc = new KubeConfig(); + const fakeWebSocket: WebSocketInterface = mock(WebSocketHandler); + const exec = new Exec(kc, instance(fakeWebSocket)); + const cp = new Cp(kc, exec); + + const namespace = 'somenamespace'; + const pod = 'somepod'; + const container = 'container'; + const srcPath = '/'; + const tgtPath = '/'; + const cwd = '/abc'; + const cmdArray = ['tar', 'zcf', '-', '-C', cwd, srcPath]; + const path = `/api/v1/namespaces/${namespace}/pods/${pod}/exec`; + + const query = { + stdout: true, + stderr: true, + stdin: false, + tty: false, + command: cmdArray, + container, + }; + const queryStr = querystring.stringify(query); + + await cp.cpFromPod(namespace, pod, container, srcPath, tgtPath, cwd); + verify(fakeWebSocket.connect(`${path}?${queryStr}`, null, anyFunction())).called(); + }); }); describe('cpToPod', () => { From 4cffe9fc6fa6838f86273ce5b5b78fa152853343 Mon Sep 17 00:00:00 2001 From: Daniel Herbolt Date: Mon, 29 Sep 2025 15:12:06 +0200 Subject: [PATCH 3/7] cwd is optional, reset unwanted changes --- src/cp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cp.ts b/src/cp.ts index 39f8e4213b..44ff4b63fd 100644 --- a/src/cp.ts +++ b/src/cp.ts @@ -18,7 +18,7 @@ export class Cp { * @param {string} containerName - The name of the container in the pod to exec the command inside. * @param {string} srcPath - The source path in the pod * @param {string} tgtPath - The target path in local - * @param {string} cwd - The directory that is used as the parent in the pod when downloading + * @param {string} [cwd] - The directory that is used as the parent in the pod when downloading */ public async cpFromPod( namespace: string, From 973de59011e7f48ee9cfb1792771913ba0b327f6 Mon Sep 17 00:00:00 2001 From: Sachin Iyer Date: Thu, 16 Oct 2025 21:10:48 -0700 Subject: [PATCH 4/7] fix: cp from pod was broken --- src/test/integration/cpFromPod.ts | 78 +++++++++++++++++++++++++++++++ src/test/integration/index.ts | 2 + 2 files changed, 80 insertions(+) create mode 100644 src/test/integration/cpFromPod.ts diff --git a/src/test/integration/cpFromPod.ts b/src/test/integration/cpFromPod.ts new file mode 100644 index 0000000000..f4bf61a91b --- /dev/null +++ b/src/test/integration/cpFromPod.ts @@ -0,0 +1,78 @@ +import assert from 'node:assert'; +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; +import { CoreV1Api, KubeConfig, V1Pod } from '../../index.js'; +import { Cp } from '../../cp.js'; +import { generateName } from './name.js'; + +export default async function cpFromPod() { + const kc = new KubeConfig(); + kc.loadFromDefault(); + + const coreV1Client = kc.makeApiClient(CoreV1Api); + const cp = new Cp(kc); + + const testPodName = generateName('cp-test-pod'); + const namespace = 'default'; + + const pod = new V1Pod(); + pod.metadata = { name: testPodName }; + pod.spec = { + containers: [ + { + name: 'test-container', + image: 'busybox', + command: ['sh', '-c', 'echo "test content" > /tmp/test.txt && sleep 3600'], + }, + ], + restartPolicy: 'Never', + }; + + console.log(`Creating pod ${testPodName}`); + await coreV1Client.createNamespacedPod({ namespace, body: pod }); + + console.log('Waiting for pod to be ready...'); + let podReady = false; + for (let i = 0; i < 30; i++) { + const currentPod = await coreV1Client.readNamespacedPod({ name: testPodName, namespace }); + if (currentPod.status?.phase === 'Running') { + podReady = true; + break; + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + assert.strictEqual(podReady, true, 'Pod did not become ready in time'); + console.log('Pod is ready'); + + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'k8s-cp-test-')); + + try { + console.log('Copying file from pod...'); + + cp.cpFromPod(namespace, testPodName, 'test-container', 'test.txt', tempDir, '/tmp'); + + // Wait for file to appear + const copiedFilePath = path.join(tempDir, 'test.txt'); + let fileExists = false; + for (let i = 0; i < 20; i++) { + if (fs.existsSync(copiedFilePath)) { + fileExists = true; + break; + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + + assert.strictEqual(fileExists, true, 'File was not copied'); + + const content = fs.readFileSync(copiedFilePath, 'utf-8'); + assert.strictEqual(content.trim(), 'test content', 'File content does not match'); + + console.log('cpFromPod test passed!'); + } finally { + console.log('Cleaning up...'); + await coreV1Client.deleteNamespacedPod({ name: testPodName, namespace }); + fs.rmSync(tempDir, { recursive: true, force: true }); + } +} diff --git a/src/test/integration/index.ts b/src/test/integration/index.ts index 5065dcd429..ee26fa03df 100644 --- a/src/test/integration/index.ts +++ b/src/test/integration/index.ts @@ -1,5 +1,7 @@ import patchNamespace from './patchNamespace.js'; +import cpFromPod from './cpFromPod.js'; console.log('Integration testing'); await patchNamespace(); +await cpFromPod(); From 2d16e0b4aab6e5fc6e09ce574171c4d4fcf21455 Mon Sep 17 00:00:00 2001 From: Sachin Iyer Date: Thu, 16 Oct 2025 21:10:48 -0700 Subject: [PATCH 5/7] fix: cp from pod was broken --- src/test/integration/cpFromPod.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/integration/cpFromPod.ts b/src/test/integration/cpFromPod.ts index f4bf61a91b..001368dfdf 100644 --- a/src/test/integration/cpFromPod.ts +++ b/src/test/integration/cpFromPod.ts @@ -2,6 +2,7 @@ import assert from 'node:assert'; import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; +import { setTimeout } from 'node:timers/promises'; import { CoreV1Api, KubeConfig, V1Pod } from '../../index.js'; import { Cp } from '../../cp.js'; import { generateName } from './name.js'; @@ -40,7 +41,7 @@ export default async function cpFromPod() { podReady = true; break; } - await new Promise((resolve) => setTimeout(resolve, 1000)); + await setTimeout(1000); } assert.strictEqual(podReady, true, 'Pod did not become ready in time'); @@ -61,7 +62,7 @@ export default async function cpFromPod() { fileExists = true; break; } - await new Promise((resolve) => setTimeout(resolve, 500)); + await setTimeout(500); } assert.strictEqual(fileExists, true, 'File was not copied'); From d387d3794915487fc69c136a6c461e6a9b4a61a9 Mon Sep 17 00:00:00 2001 From: Sachin Iyer Date: Thu, 16 Oct 2025 21:10:48 -0700 Subject: [PATCH 6/7] fix: cp from pod was broken --- src/cp_test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cp_test.ts b/src/cp_test.ts index 0782106cba..fb38247df3 100644 --- a/src/cp_test.ts +++ b/src/cp_test.ts @@ -22,7 +22,7 @@ describe('Cp', () => { const container = 'container'; const srcPath = '/'; const tgtPath = '/'; - const cmdArray = ['tar', 'zcf', '-', srcPath]; + const cmdArray = ['tar', 'cf', '-', srcPath]; const path = `/api/v1/namespaces/${namespace}/pods/${pod}/exec`; const query = { @@ -51,7 +51,7 @@ describe('Cp', () => { const srcPath = '/'; const tgtPath = '/'; const cwd = '/abc'; - const cmdArray = ['tar', 'zcf', '-', '-C', cwd, srcPath]; + const cmdArray = ['tar', 'cf', '-', '-C', cwd, srcPath]; const path = `/api/v1/namespaces/${namespace}/pods/${pod}/exec`; const query = { From f3c8c2c97ea5175242c835a4ada6cf974178205d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Str=C3=BCbing?= Date: Tue, 25 Nov 2025 11:33:56 +0000 Subject: [PATCH 7/7] fix: cp from pod was broken --- src/cp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cp.ts b/src/cp.ts index 44ff4b63fd..52e3bdd72c 100644 --- a/src/cp.ts +++ b/src/cp.ts @@ -30,7 +30,7 @@ export class Cp { ): Promise { const tmpFile = tmp.fileSync(); const tmpFileName = tmpFile.name; - const command = ['tar', 'zcf', '-']; + const command = ['tar', 'cf', '-']; if (cwd) { command.push('-C', cwd); }