From d20e2597b9f41e8dcf8925bdd383d34d444f9d44 Mon Sep 17 00:00:00 2001 From: Frederik Carlier Date: Sat, 31 Mar 2018 07:56:52 +0200 Subject: [PATCH] WebSocketNamespacedPodExecAsync: Support specifying command arguments (#123) * WebSocketNamespacedPodExecAsync: Support specifying command argumets * Address PR feedback * Address PR feedback * Fix unstable test --- src/IKubernetes.WebSocket.cs | 49 +++++++++++++++++++++++++++- src/Kubernetes.WebSocket.cs | 38 ++++++++++++++++----- tests/Kubernetes.Exec.Tests.cs | 2 +- tests/Kubernetes.WebSockets.Tests.cs | 4 +-- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/IKubernetes.WebSocket.cs b/src/IKubernetes.WebSocket.cs index cf6ff2e..6620571 100644 --- a/src/IKubernetes.WebSocket.cs +++ b/src/IKubernetes.WebSocket.cs @@ -52,7 +52,54 @@ namespace k8s /// /// A which can be used to communicate with the process running in the pod. /// - Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", string command = "/bin/bash", string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", string command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Executes a command in a pod. + /// + /// + /// name of the Pod + /// + /// + /// object name and auth scope, such as for teams and projects + /// + /// + /// Command is the remote command to execute. argv array. Not executed within a + /// shell. + /// + /// + /// Container in which to execute the command. Defaults to only container if + /// there is only one container in the pod. + /// + /// + /// Redirect the standard error stream of the pod for this call. Defaults to + /// . + /// + /// + /// Redirect the standard input stream of the pod for this call. Defaults to + /// . + /// + /// + /// Redirect the standard output stream of the pod for this call. Defaults to + /// . + /// + /// + /// TTY if true indicates that a tty will be allocated for the exec call. + /// Defaults to . + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A which can be used to communicate with the process running in the pod. + /// + Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", IEnumerable command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); /// /// Start port forwarding one or more ports of a pod. diff --git a/src/Kubernetes.WebSocket.cs b/src/Kubernetes.WebSocket.cs index b863f17..ee94e16 100644 --- a/src/Kubernetes.WebSocket.cs +++ b/src/Kubernetes.WebSocket.cs @@ -20,7 +20,13 @@ namespace k8s public Func CreateWebSocketBuilder { get; set; } = () => new WebSocketBuilder(); /// - public Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", string command = "/bin/sh", string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", string command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + return WebSocketNamespacedPodExecAsync(name, @namespace, new string[] { command }, container, stderr, stdin, stdout, tty, customHeaders, cancellationToken); + } + + /// + public Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", IEnumerable command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) { if (name == null) { @@ -37,6 +43,11 @@ namespace k8s throw new ArgumentNullException(nameof(command)); } + if (!command.Any()) + { + throw new ArgumentOutOfRangeException(nameof(command)); + } + // Tracing bool _shouldTrace = ServiceClientTracing.IsEnabled; string _invocationId = null; @@ -67,17 +78,28 @@ namespace k8s uriBuilder.Path += $"api/v1/namespaces/{@namespace}/pods/{name}/exec"; + var query = string.Empty; - uriBuilder.Query = QueryHelpers.AddQueryString(string.Empty, new Dictionary + foreach (var c in command) { - { "command", command}, - { "container", container}, - { "stderr", stderr ? "1": "0"}, - { "stdin", stdin ? "1": "0"}, - { "stdout", stdout ? "1": "0"}, - { "tty", tty ? "1": "0"} + query = QueryHelpers.AddQueryString(query, "command", c); + } + + if (container != null) + { + query = QueryHelpers.AddQueryString(query, "container", Uri.EscapeDataString(container)); + } + + query = QueryHelpers.AddQueryString(query, new Dictionary + { + {"stderr", stderr ? "1" : "0"}, + {"stdin", stdin ? "1" : "0"}, + {"stdout", stdout ? "1" : "0"}, + {"tty", tty ? "1" : "0"} }).TrimStart('?'); + uriBuilder.Query = query; + return this.StreamConnectAsync(uriBuilder.Uri, _invocationId, customHeaders, cancellationToken); } diff --git a/tests/Kubernetes.Exec.Tests.cs b/tests/Kubernetes.Exec.Tests.cs index 0f5e47f..46ad8c1 100644 --- a/tests/Kubernetes.Exec.Tests.cs +++ b/tests/Kubernetes.Exec.Tests.cs @@ -54,7 +54,7 @@ namespace k8s.Tests WebSocket clientSocket = await client.WebSocketNamespacedPodExecAsync( name: "mypod", @namespace: "mynamespace", - command: "/bin/bash", + command: new string[] { "/bin/bash" }, container: "mycontainer", stderr: false, stdin: false, diff --git a/tests/Kubernetes.WebSockets.Tests.cs b/tests/Kubernetes.WebSockets.Tests.cs index 48ce080..62a339f 100644 --- a/tests/Kubernetes.WebSockets.Tests.cs +++ b/tests/Kubernetes.WebSockets.Tests.cs @@ -39,7 +39,7 @@ namespace k8s.tests var webSocket = await client.WebSocketNamespacedPodExecAsync( name: "mypod", @namespace: "mynamespace", - command: "/bin/bash", + command: new string[] { "/bin/bash", "-c", $"echo Hello, World\nexit 0\n" }, container: "mycontainer", stderr: true, stdin: true, @@ -58,7 +58,7 @@ namespace k8s.tests }; Assert.Equal(mockWebSocketBuilder.PublicWebSocket, webSocket); // Did the method return the correct web socket? - Assert.Equal(new Uri("ws://localhost:80/api/v1/namespaces/mynamespace/pods/mypod/exec?command=%2Fbin%2Fbash&container=mycontainer&stderr=1&stdin=1&stdout=1&tty=1"), mockWebSocketBuilder.Uri); // Did we connect to the correct URL? + Assert.Equal(new Uri("ws://localhost/api/v1/namespaces/mynamespace/pods/mypod/exec?command=%2Fbin%2Fbash&command=-c&command=echo%20Hello,%20World%0Aexit%200%0A&container=mycontainer&stderr=1&stdin=1&stdout=1&tty=1"), mockWebSocketBuilder.Uri); // Did we connect to the correct URL? Assert.Empty(mockWebSocketBuilder.Certificates); // No certificates were used in this test Assert.Equal(expectedHeaders, mockWebSocketBuilder.RequestHeaders); // Did we use the expected headers }