Skip to content

Commit d20e259

Browse files
qmfrederikbrendandburns
authored andcommitted
WebSocketNamespacedPodExecAsync: Support specifying command arguments (#123)
* WebSocketNamespacedPodExecAsync: Support specifying command argumets * Address PR feedback * Address PR feedback * Fix unstable test
1 parent cf1c995 commit d20e259

File tree

4 files changed

+81
-12
lines changed

4 files changed

+81
-12
lines changed

src/IKubernetes.WebSocket.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,54 @@ public partial interface IKubernetes
5252
/// <return>
5353
/// A <see cref="ClientWebSocket"/> which can be used to communicate with the process running in the pod.
5454
/// </return>
55-
Task<WebSocket> 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<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken));
55+
Task<WebSocket> 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<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken));
56+
57+
/// <summary>
58+
/// Executes a command in a pod.
59+
/// </summary>
60+
/// <param name='name'>
61+
/// name of the Pod
62+
/// </param>
63+
/// <param name='namespace'>
64+
/// object name and auth scope, such as for teams and projects
65+
/// </param>
66+
/// <param name='command'>
67+
/// Command is the remote command to execute. argv array. Not executed within a
68+
/// shell.
69+
/// </param>
70+
/// <param name='container'>
71+
/// Container in which to execute the command. Defaults to only container if
72+
/// there is only one container in the pod.
73+
/// </param>
74+
/// <param name='stderr'>
75+
/// Redirect the standard error stream of the pod for this call. Defaults to
76+
/// <see langword="true"/>.
77+
/// </param>
78+
/// <param name='stdin'>
79+
/// Redirect the standard input stream of the pod for this call. Defaults to
80+
/// <see langword="true"/>.
81+
/// </param>
82+
/// <param name='stdout'>
83+
/// Redirect the standard output stream of the pod for this call. Defaults to
84+
/// <see langword="true"/>.
85+
/// </param>
86+
/// <param name='tty'>
87+
/// TTY if true indicates that a tty will be allocated for the exec call.
88+
/// Defaults to <see langword="true"/>.
89+
/// </param>
90+
/// <param name='customHeaders'>
91+
/// Headers that will be added to request.
92+
/// </param>
93+
/// <param name='cancellationToken'>
94+
/// The cancellation token.
95+
/// </param>
96+
/// <exception cref="ArgumentNullException">
97+
/// Thrown when a required parameter is null
98+
/// </exception>
99+
/// <return>
100+
/// A <see cref="ClientWebSocket"/> which can be used to communicate with the process running in the pod.
101+
/// </return>
102+
Task<WebSocket> WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", IEnumerable<string> command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken));
56103

57104
/// <summary>
58105
/// Start port forwarding one or more ports of a pod.

src/Kubernetes.WebSocket.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ public partial class Kubernetes
2020
public Func<WebSocketBuilder> CreateWebSocketBuilder { get; set; } = () => new WebSocketBuilder();
2121

2222
/// <inheritdoc/>
23-
public Task<WebSocket> 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<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
23+
public Task<WebSocket> 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<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
24+
{
25+
return WebSocketNamespacedPodExecAsync(name, @namespace, new string[] { command }, container, stderr, stdin, stdout, tty, customHeaders, cancellationToken);
26+
}
27+
28+
/// <inheritdoc/>
29+
public Task<WebSocket> WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", IEnumerable<string> command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
2430
{
2531
if (name == null)
2632
{
@@ -37,6 +43,11 @@ public partial class Kubernetes
3743
throw new ArgumentNullException(nameof(command));
3844
}
3945

46+
if (!command.Any())
47+
{
48+
throw new ArgumentOutOfRangeException(nameof(command));
49+
}
50+
4051
// Tracing
4152
bool _shouldTrace = ServiceClientTracing.IsEnabled;
4253
string _invocationId = null;
@@ -67,17 +78,28 @@ public partial class Kubernetes
6778

6879
uriBuilder.Path += $"api/v1/namespaces/{@namespace}/pods/{name}/exec";
6980

81+
var query = string.Empty;
7082

71-
uriBuilder.Query = QueryHelpers.AddQueryString(string.Empty, new Dictionary<string, string>
83+
foreach (var c in command)
7284
{
73-
{ "command", command},
74-
{ "container", container},
75-
{ "stderr", stderr ? "1": "0"},
76-
{ "stdin", stdin ? "1": "0"},
77-
{ "stdout", stdout ? "1": "0"},
78-
{ "tty", tty ? "1": "0"}
85+
query = QueryHelpers.AddQueryString(query, "command", c);
86+
}
87+
88+
if (container != null)
89+
{
90+
query = QueryHelpers.AddQueryString(query, "container", Uri.EscapeDataString(container));
91+
}
92+
93+
query = QueryHelpers.AddQueryString(query, new Dictionary<string, string>
94+
{
95+
{"stderr", stderr ? "1" : "0"},
96+
{"stdin", stdin ? "1" : "0"},
97+
{"stdout", stdout ? "1" : "0"},
98+
{"tty", tty ? "1" : "0"}
7999
}).TrimStart('?');
80100

101+
uriBuilder.Query = query;
102+
81103
return this.StreamConnectAsync(uriBuilder.Uri, _invocationId, customHeaders, cancellationToken);
82104
}
83105

tests/Kubernetes.Exec.Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public async Task Exec_DefaultContainer_StdOut()
5454
WebSocket clientSocket = await client.WebSocketNamespacedPodExecAsync(
5555
name: "mypod",
5656
@namespace: "mynamespace",
57-
command: "/bin/bash",
57+
command: new string[] { "/bin/bash" },
5858
container: "mycontainer",
5959
stderr: false,
6060
stdin: false,

tests/Kubernetes.WebSockets.Tests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public async Task WebSocketNamespacedPodExecAsync()
3939
var webSocket = await client.WebSocketNamespacedPodExecAsync(
4040
name: "mypod",
4141
@namespace: "mynamespace",
42-
command: "/bin/bash",
42+
command: new string[] { "/bin/bash", "-c", $"echo Hello, World\nexit 0\n" },
4343
container: "mycontainer",
4444
stderr: true,
4545
stdin: true,
@@ -58,7 +58,7 @@ public async Task WebSocketNamespacedPodExecAsync()
5858
};
5959

6060
Assert.Equal(mockWebSocketBuilder.PublicWebSocket, webSocket); // Did the method return the correct web socket?
61-
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?
61+
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?
6262
Assert.Empty(mockWebSocketBuilder.Certificates); // No certificates were used in this test
6363
Assert.Equal(expectedHeaders, mockWebSocketBuilder.RequestHeaders); // Did we use the expected headers
6464
}

0 commit comments

Comments
 (0)