Skip to content

Commit

Permalink
feat: implement copy file to container
Browse files Browse the repository at this point in the history
Signed-off-by: Lenin Mehedy <lenin.mehedy@swirldslabs.com>
  • Loading branch information
leninmehedy committed Jan 15, 2024
1 parent bfd72cf commit df76d00
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 10 deletions.
151 changes: 143 additions & 8 deletions fullstack-network-manager/src/core/kubectl2.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import {expect} from "@jest/globals";
import * as k8s from '@kubernetes/client-node'
import fs from "fs";
import path from "path";
import {FullstackTestingError, MissingArgumentError, ResourceNotFoundError} from "./errors.mjs";
import * as stream_buffer from 'stream-buffers'

/**
* A kubectl wrapper class providing custom functionalities required by fsnetman
Expand Down Expand Up @@ -240,25 +244,156 @@ export class Kubectl2 {
}

return contexts

}

parseLsOutput(output) {
if (!output) return []

const items = []
const lines = output.split("\n")
for (let line of lines) {
line = line.replace(/\s+/g, "|");
const parts = line.split('|')
if (parts.length === 9) {
const name = parts[parts.length - 1]
if (name !== '.' && name !== '..') {
const permission = parts[0]
const item = {
directory: permission[0] === 'd',
owner: parts[2],
group: parts[3],
size: parts[4],
modifiedAt: `${parts[5]} ${parts[6]} ${parts[7]}`,
name: name
}

items.push(item)
}
}
}

return items
}

/**
* List files and directories in a container
*
* It runs ls -la on the specified path and returns a list of object containing the entries.
* For example:
* [{
* directory: false,
* owner: hedera,
* group: hedera,
* size: 121,
* modifiedAt: Jan 15 13:50
* name: config.txt
* }]
*
* @param podName pod name
* @param containerName container name
* @param destPath path inside the container
* @return {Promise<{}>}
*/
async listDir(podName, containerName, destPath) {
try {
// verify that file is copied correctly
const self = this
const execInstance = new k8s.Exec(this._kubeConfig)
const command = ['ls', '-la', destPath]
const writerStream = new stream_buffer.WritableStreamBuffer()
const errStream = new stream_buffer.WritableStreamBuffer()
return new Promise(async (resolve, reject) => {
await execInstance.exec(
this.getCurrentNamespace(),
podName,
containerName,
command,
writerStream,
errStream,
null,
false,
async ({status}) => {
const items = []
if (status === 'Failure' || errStream.size()) {
reject(new FullstackTestingError(`Error - details: \n ${errStream.getContentsAsString()}`))
}

const output = writerStream.getContentsAsString()
resolve(self.parseLsOutput(output))
});
})
} catch (e) {

}
}

/**
* Check if a file path exists in the container
* @param podName pod name
* @param containerName container name
* @param destPath path inside the container
* @return {Promise<boolean>}
*/
async hasPath(podName, containerName, destPath) {
const entries = await this.listDir(podName, containerName, destPath)

for (const item of entries) {
if (item.name === destPath) {
return true
}
}

return false
}

/**
* Copy file into a containerName
* Copy a file into a container
*
* It overwrites any existing file inside the container at the destination directory
*
* FIXME: currently it fails to catch error if incorrect containerName or invalid destination path is specified
*
* @param podName podName name
* @param containerName container name
* @param srcPath
* @param destPath
* @param tmpDir temp directory to be used
* @param srcPath source file path in the local
* @param destDir destination directory in the container
* @returns {Promise<boolean>}
*/
async copy(podName, containerName, srcPath, destPath, tmpDir = undefined) {
async copyTo(podName, containerName, srcPath, destDir) {
try {
await this._initKubeCopy().cpToPod(this.getCurrentNamespace(), podName, containerName, srcPath, destPath, tmpDir)
const srcFile = path.basename(srcPath)
const srcDir = path.dirname(srcPath)
await this._initKubeCopy().cpToPod(this.getCurrentNamespace(), podName, containerName, srcFile, destDir, srcDir)
return true
} catch (e) {
throw new FullstackTestingError(`failed to copy file: ${podName} -c ${containerName} ${srcPath}:${destPath}: ${e.message}`, e)
throw new FullstackTestingError(`failed to copy file to container [pod: ${podName} container:${containerName}]: ${srcPath} -> ${destDir}: ${e.message}`, e)
}
}

return true
/**
* Copy a file from a container
*
* It overwrites any existing file at the destination directory
*
* FIXME: currently it fails to catch error if incorrect containerName or invalid destination path is specified
*
* @param podName podName name
* @param containerName container name
* @param srcPath source file path in the container
* @param destDir destination directory in the local
* @returns {Promise<boolean>}
*/
async copyFrom(podName, containerName, srcPath, destDir) {
try {
const srcFile = path.basename(srcPath)
const srcDir = path.dirname(srcPath)
const destPath = `${destDir}/${srcFile}`
await this._initKubeCopy().cpFromPod(this.getCurrentNamespace(), podName, containerName, srcFile, destDir, srcDir)
return true
} catch (e) {
throw new FullstackTestingError(`failed to copy file from container [pod: ${podName} container:${containerName}]: ${srcPath} -> ${destDir}: ${e.message}`, e)
}
}

/**
Expand Down
12 changes: 10 additions & 2 deletions fullstack-network-manager/test/e2e/core/kubectl_e2e.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,23 @@ describe('Kubectl', () => {
await expect(kubectl.getClusterIP('INVALID')).rejects.toThrow(FullstackTestingError)
})

it('should be able to copy a file into a container', async () => {
it('should be able to copy a file to and from a container', async () => {
const podName = Templates.renderNetworkPodName('node0')
const containerName = constants.ROOT_CONTAINER
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kubectl-'))
const tmpDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'kubectl-'))
const tmpFile = path.join(tmpDir, 'test.txt')
const destDir = constants.HEDERA_HAPI_PATH
const destPath = `${destDir}/test.txt`
fs.writeFileSync(tmpFile, "TEST")

await expect(kubectl.copy(podName, containerName, tmpFile, `/tmp/dummy.txt`, tmpDir)).resolves.toBeTruthy()
await expect(kubectl.copyTo(podName, containerName, tmpFile, destDir)).resolves.toBeTruthy()
await expect(kubectl.hasPath(podName, containerName, destPath)).resolves.toBeTruthy()

await expect(kubectl.copyFrom(podName, containerName, destPath, tmpDir2)).resolves.toBeTruthy()
expect(fs.existsSync(`${tmpDir2}/test.txt`))

fs.rmdirSync(tmpDir, {recursive: true})
fs.rmdirSync(tmpDir2, {recursive: true})
})
})
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"stream-buffers": "^3.0.2"
}
}

0 comments on commit df76d00

Please sign in to comment.