diff --git a/examples/cp/Cp.cs b/examples/cp/Cp.cs new file mode 100644 index 0000000..896310b --- /dev/null +++ b/examples/cp/Cp.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using k8s; +using ICSharpCode.SharpZipLib.Tar; +using System.Threading; +using System.Linq; +using System.Text; + +namespace cp +{ + internal class Cp + { + private static IKubernetes client; + + private static async Task Main(string[] args) + { + var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(); + client = new Kubernetes(config); + + + var pods = client.CoreV1.ListNamespacedPod("default", null, null, null, $"job-name=upload-demo"); + var pod = pods.Items.First(); + + await CopyFileToPodAsync(pod.Metadata.Name, "default", "upload-demo", args[0], $"home/{args[1]}"); + + } + + + + + private static void ValidatePathParameters(string sourcePath, string destinationPath) + { + if (string.IsNullOrWhiteSpace(sourcePath)) + { + throw new ArgumentException($"{nameof(sourcePath)} cannot be null or whitespace"); + } + + if (string.IsNullOrWhiteSpace(destinationPath)) + { + throw new ArgumentException($"{nameof(destinationPath)} cannot be null or whitespace"); + } + + } + + public static async Task CopyFileToPodAsync(string name, string @namespace, string container, string sourceFilePath, string destinationFilePath, CancellationToken cancellationToken = default(CancellationToken)) + { + // All other parameters are being validated by MuxedStreamNamespacedPodExecAsync called by NamespacedPodExecAsync + ValidatePathParameters(sourceFilePath, destinationFilePath); + + // The callback which processes the standard input, standard output and standard error of exec method + var handler = new ExecAsyncCallback(async (stdIn, stdOut, stdError) => + { + var fileInfo = new FileInfo(destinationFilePath); + try + { + using (var memoryStream = new MemoryStream()) + { + using (var inputFileStream = File.OpenRead(sourceFilePath)) + using (var tarOutputStream = new TarOutputStream(memoryStream, Encoding.Default)) + { + tarOutputStream.IsStreamOwner = false; + + var fileSize = inputFileStream.Length; + var entry = TarEntry.CreateTarEntry(fileInfo.Name); + + entry.Size = fileSize; + + tarOutputStream.PutNextEntry(entry); + await inputFileStream.CopyToAsync(tarOutputStream); + tarOutputStream.CloseEntry(); + } + + memoryStream.Position = 0; + + const int bufferSize = 31 * 1024 * 1024; // must be lower than 32 * 1024 * 1024 + byte[] localBuffer = new byte[bufferSize]; + while (true) + { + int numRead = await memoryStream.ReadAsync(localBuffer, 0, localBuffer.Length); + if (numRead <= 0) + { + break; + } + await stdIn.WriteAsync(localBuffer, 0, numRead); + } + await stdIn.FlushAsync(); + } + + } + catch (Exception ex) + { + throw new IOException($"Copy command failed: {ex.Message}"); + } + + using StreamReader streamReader = new StreamReader(stdError); + while (streamReader.EndOfStream == false) + { + string error = await streamReader.ReadToEndAsync(); + throw new IOException($"Copy command failed: {error}"); + } + }); + + string destinationFolder = GetFolderName(destinationFilePath); + + return await client.NamespacedPodExecAsync( + name, + @namespace, + container, + new string[] { "sh", "-c", $"tar xmf - -C {destinationFolder}" }, + false, + handler, + cancellationToken); + } + + + private static string GetFolderName(string filePath) + { + var folderName = Path.GetDirectoryName(filePath); + + return string.IsNullOrEmpty(folderName) ? "." : folderName; + } + + + } +} diff --git a/examples/cp/cp.csproj b/examples/cp/cp.csproj new file mode 100644 index 0000000..27cff03 --- /dev/null +++ b/examples/cp/cp.csproj @@ -0,0 +1,11 @@ + + + + Exe + + + + + + +