/// <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();
                }
            }
        }
示例#3
0
        /// <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);
            }
        }