/// <summary> /// Create a Kubernetes-style multiplexed connection over the WebSocket. /// </summary> /// <param name="websocket"> /// The <see cref="WebSocket"/>. /// </param> /// <param name="inputStreamIndexes"> /// An array of bytes containing the indexes of the expected input streams. /// </param> /// <param name="outputStreamIndexes"> /// An array of bytes containing the indexes of the expected output streams. /// </param> /// <param name="loggerFactory"> /// An optional <see cref="ILoggerFactory"/> used to create loggers for client components. /// </param> /// <returns> /// The configured <see cref="K8sMultiplexer"/>. /// </returns> public static K8sMultiplexer Multiplexed(this WebSocket websocket, byte[] inputStreamIndexes = null, byte[] outputStreamIndexes = null, ILoggerFactory loggerFactory = null) { if (websocket == null) { throw new ArgumentNullException(nameof(websocket)); } if (!(inputStreamIndexes?.Length > 0 || outputStreamIndexes?.Length > 0)) { throw new ArgumentException($"Must specify at least one of {nameof(inputStreamIndexes)} or {nameof(outputStreamIndexes)}."); } if (loggerFactory == null) { loggerFactory = new LoggerFactory(); } K8sMultiplexer multiplexer = null; try { multiplexer = new K8sMultiplexer(websocket, inputStreamIndexes, outputStreamIndexes, loggerFactory); multiplexer.Start(); return(multiplexer); } catch (Exception) { using (multiplexer) throw; } }
public async Task Exec_DefaultContainer_Multiplexed_AllStreams() { const string expectedPrompt = "/root # "; const string expectedCommand = "ls -l /root"; TestTimeout( TimeSpan.FromSeconds(5) ); await Host.StartAsync(TestCancellation); using (KubeApiClient client = CreateTestClient()) { K8sMultiplexer multiplexer = await client.PodsV1().ExecAndConnect( podName: "pod1", command: "/bin/bash", stdin: true, stdout: true, stderr: true ); using (multiplexer) { Stream stdin = multiplexer.GetStdIn(); Stream stdout = multiplexer.GetStdOut(); Log.LogInformation("Waiting for server-side WebSocket."); WebSocket serverSocket = await WebSocketTestAdapter.AcceptedPodExecV1Connection; Log.LogInformation("Server sends prompt."); await SendMultiplexed(serverSocket, K8sChannel.StdOut, expectedPrompt); Log.LogInformation("Server sent prompt."); Log.LogInformation("Client expects prompt."); byte[] receiveBuffer = new byte[2048]; int bytesReceived = await stdout.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, TestCancellation); string prompt = Encoding.ASCII.GetString(receiveBuffer, 0, bytesReceived); Assert.Equal(expectedPrompt, prompt); Log.LogInformation("Client got expected prompt."); Log.LogInformation("Client sends command."); byte[] sendBuffer = Encoding.ASCII.GetBytes(expectedCommand); await stdin.WriteAsync(sendBuffer, 0, sendBuffer.Length, TestCancellation); Log.LogInformation("Client sent command."); Log.LogInformation("Server expects command."); (string command, byte streamIndex, int totalBytes) = await ReceiveTextMultiplexed(serverSocket); Assert.Equal(K8sChannel.StdIn, streamIndex); Assert.Equal(expectedCommand, command); Log.LogInformation("Server got expected command."); Task closeServerSocket = WaitForClose(serverSocket, socketType: "server"); Log.LogInformation("Close enough; we're done."); await multiplexer.Shutdown(TestCancellation); await closeServerSocket; WebSocketTestAdapter.Done(); } } }
/// <summary> /// The main program entry-point. /// </summary> /// <param name="commandLineArguments"> /// The program's command-line arguments. /// </param> /// <returns> /// The program exit-code. /// </returns> static async Task <int> Main(string[] commandLineArguments) { // Show help if no arguments are specified. bool showHelp = commandLineArguments.Length == 0; if (showHelp) { commandLineArguments = new[] { "--help" } } ; ProgramOptions options = ProgramOptions.Parse(commandLineArguments); if (options == null) { return(showHelp ? ExitCodes.Success : ExitCodes.InvalidArguments); } ILoggerFactory loggers = ConfigureLogging(options); try { KubeClientOptions clientOptions = K8sConfig.Load().ToKubeClientOptions( kubeContextName: options.KubeContext, defaultKubeNamespace: options.KubeNamespace ); using (KubeApiClient client = KubeApiClient.Create(clientOptions, loggers)) { Log.LogInformation("Finding target pod..."); PodV1 targetPod = await client.PodsV1().Get(options.PodName, kubeNamespace: options.KubeNamespace ); if (targetPod == null) { Log.LogError("Pod '{PodName}' not found in namespace '{KubeNamespace}' on cluster ({KubeContextName}).", options.PodName, options.KubeNamespace, options.KubeContext ); return(ExitCodes.NotFound); } if (!String.IsNullOrWhiteSpace(options.ContainerName)) { ContainerStatusV1 targetContainer = targetPod.Status.ContainerStatuses.Find( container => container.Name == options.ContainerName ); if (targetContainer == null) { Log.LogError("Container '{ContainerName}' not found in Pod '{PodName}' in namespace '{KubeNamespace}' on cluster ({KubeContextName}).", options.ContainerName, options.PodName, options.KubeNamespace, options.KubeContext ); return(ExitCodes.NotFound); } } else if (targetPod.Status.ContainerStatuses.Count > 1) { Log.LogError("Pod '{PodName}' in namespace '{KubeNamespace}' on cluster ({KubeContextName}) has more than one container. Please specify the name of the target container", options.PodName, options.KubeNamespace, options.KubeContext ); return(ExitCodes.InvalidArguments); } Log.LogDebug("Connecting..."); K8sMultiplexer multiplexer = await client.PodsV1().ExecAndConnect( podName: options.PodName, container: options.ContainerName, command: options.Command, kubeNamespace: options.KubeContext, stdin: true, stdout: true, stderr: true, tty: true // Required for interactivity ); Log.LogInformation("Connected."); Task stdInPump, stdOutPump, stdErrPump; using (multiplexer) using (CancellationTokenSource pumpCancellation = new CancellationTokenSource()) using (Stream localStdIn = Console.OpenStandardInput()) using (Stream remoteStdIn = multiplexer.GetStdIn()) using (Stream localStdOut = Console.OpenStandardOutput()) using (Stream remoteStdOut = multiplexer.GetStdOut()) using (Stream localStdErr = Console.OpenStandardError()) using (Stream remoteStdErr = multiplexer.GetStdErr()) { stdInPump = localStdIn.CopyToAsync(remoteStdIn, pumpCancellation.Token); stdOutPump = remoteStdOut.CopyToAsync(localStdOut, pumpCancellation.Token); stdErrPump = remoteStdErr.CopyToAsync(localStdErr, pumpCancellation.Token); await multiplexer.WhenConnectionClosed; // Terminate stream pumps. pumpCancellation.Cancel(); } Log.LogInformation("Connection closed."); Log.LogInformation("Done."); } return(ExitCodes.Success); } catch (Exception unexpectedError) { Log.LogError(unexpectedError.ToString()); Log.LogError(unexpectedError, "Unexpected error."); return(ExitCodes.UnexpectedError); } }