Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions src/Docker.DotNet.BasicAuth/BasicAuthHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,24 @@ namespace Docker.DotNet.BasicAuth;
internal class BasicAuthHandler : DelegatingHandler
{
private readonly MaybeSecureString _username;

private readonly MaybeSecureString _password;

public BasicAuthHandler(MaybeSecureString username, MaybeSecureString password, HttpMessageHandler innerHandler) : base(innerHandler)
public BasicAuthHandler(MaybeSecureString username, MaybeSecureString password, HttpMessageHandler httpMessageHandler)
: base(httpMessageHandler)
{
_username = username.Copy();
_password = password.Copy();
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", BuildParameters());

return base.SendAsync(request, cancellationToken);
}

private string BuildParameters()
{
var authInfo = $"{_username}:{_password}";
return Convert.ToBase64String(Encoding.UTF8.GetBytes(authInfo));
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
_username.Dispose();
_password.Dispose();
}

base.Dispose(disposing);
}
}
58 changes: 12 additions & 46 deletions src/Docker.DotNet/Endpoints/ExecOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Docker.DotNet;

internal class ExecOperations : IExecOperations
{
internal static readonly ApiResponseErrorHandlingDelegate NoSuchContainerHandler = (statusCode, responseBody) =>
private static readonly ApiResponseErrorHandlingDelegate NoSuchContainerHandler = (statusCode, responseBody) =>
{
if (statusCode == HttpStatusCode.NotFound)
{
Expand All @@ -17,33 +17,18 @@ internal ExecOperations(DockerClient client)
_client = client;
}

public async Task<ContainerExecCreateResponse> ExecCreateContainerAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
public async Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}

if (parameters == null)
{
throw new ArgumentNullException(nameof(parameters));
}

var data = new JsonRequestContent<ContainerExecCreateParameters>(parameters, DockerClient.JsonSerializer);
return await _client.MakeRequestAsync<ContainerExecCreateResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/exec", null, data, cancellationToken).ConfigureAwait(false);
}

public async Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}

return await _client.MakeRequestAsync<ContainerExecInspectResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"exec/{id}/json", null, cancellationToken).ConfigureAwait(false);
return await _client.MakeRequestAsync<ContainerExecInspectResponse>([NoSuchContainerHandler], HttpMethod.Get, $"exec/{id}/json", null, cancellationToken)
.ConfigureAwait(false);
}

public Task ResizeContainerExecTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken)
public async Task<ContainerExecCreateResponse> CreateContainerExecAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
Expand All @@ -55,43 +40,24 @@ public Task ResizeContainerExecTtyAsync(string id, ContainerResizeParameters par
throw new ArgumentNullException(nameof(parameters));
}

var queryParameters = new QueryString<ContainerResizeParameters>(parameters);
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/resize", queryParameters, cancellationToken);
var data = new JsonRequestContent<ContainerExecCreateParameters>(parameters, DockerClient.JsonSerializer);

return await _client.MakeRequestAsync<ContainerExecCreateResponse>([NoSuchContainerHandler], HttpMethod.Post, $"containers/{id}/exec", null, data, cancellationToken)
.ConfigureAwait(false);
}

// StartContainerExecAsync will start the process specified by id in detach mode with no connected
// stdin, stdout, or stderr pipes.
public Task StartContainerExecAsync(string id, CancellationToken cancellationToken)
public async Task<MultiplexedStream> StartContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}

var parameters = new ContainerExecStartParameters
{
Detach = true,
};
var data = new JsonRequestContent<ContainerExecStartParameters>(parameters, DockerClient.JsonSerializer);
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/start", null, data, cancellationToken);
}

// StartAndAttachContainerExecAsync will start the process specified by id with stdin, stdout, stderr
// connected, and optionally using terminal emulation if tty is true.
public async Task<MultiplexedStream> StartAndAttachContainerExecAsync(string id, bool tty, CancellationToken cancellationToken)
{
return await StartWithConfigContainerExecAsync(id, new ContainerExecStartParameters() { AttachStdin = true, AttachStderr = true, AttachStdout = true, Tty = tty }, cancellationToken);
}
var result = await _client.MakeRequestForStreamAsync([NoSuchContainerHandler], HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken)
.ConfigureAwait(false);

public async Task<MultiplexedStream> StartWithConfigContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}

var data = new JsonRequestContent<ContainerExecStartParameters>(parameters, DockerClient.JsonSerializer);
var result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken).ConfigureAwait(false);
return new MultiplexedStream(result, !parameters.Tty);
}
}
63 changes: 3 additions & 60 deletions src/Docker.DotNet/Endpoints/IExecOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,9 @@ namespace Docker.DotNet;

public interface IExecOperations
{
/// <summary>
/// Create an exec instance.
///
/// Runs a command inside a running container.
/// </summary>
/// <remarks>
/// docker exec
/// docker container exec
///
/// 201 - No error.
/// 404 - No such container.
/// 409 - Container is paused.
/// 500 - Server error.
/// </remarks>
/// <param name="id">ID or name of the container.</param>
Task<ContainerExecCreateResponse> ExecCreateContainerAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken = default);

/// <summary>
/// Start an exec instance.
///
/// Starts a previously set up exec instance. If detach is true, this endpoint returns immediately after starting
/// the command. Otherwise, it sets up an interactive session with the command.
/// </summary>
/// <remarks>
/// 204 - No error.
/// 404 - No such exec instance.
/// 500 - Server error.
/// </remarks>
/// <param name="id">Exec instance ID.</param>
Task StartContainerExecAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
Task<ContainerExecCreateResponse> CreateContainerExecAsync(string id, ContainerExecCreateParameters parameters, CancellationToken cancellationToken = default);

Task<MultiplexedStream> StartAndAttachContainerExecAsync(string id, bool tty, CancellationToken cancellationToken = default(CancellationToken));

Task<MultiplexedStream> StartWithConfigContainerExecAsync(string id, ContainerExecStartParameters eConfig, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Resize an exec instance.
///
/// Resize the TTY session used by an exec instance. This endpoint only works if {tty} was specified as part of
/// creating and starting the exec instance.
/// </summary>
/// <remarks>
/// 201 - No error.
/// 404 - No such exec instance.
/// </remarks>
/// <param name="id">Exec instance ID.</param>
Task ResizeContainerExecTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Inspect an exec instance.
///
/// Return low-level information about an exec instance.
/// </summary>
/// <remarks>
/// docker inspect
///
/// 200 - No error.
/// 404 - No such exec instance.
/// 500 - Server error.
/// </remarks>
/// <param name="id">Exec instance ID.</param>
Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
Task<MultiplexedStream> StartContainerExecAsync(string id, ContainerExecStartParameters parameters, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,17 @@ public override long Position

protected override void Dispose(bool disposing)
{
// TODO: Why does disposing break the implementation, see the other chunked streams too.
// base.Dispose(disposing);

if (disposing)
{
_inner.Dispose();

if (Interlocked.Decrement(ref _bufferRefCount) == 0)
{
ArrayPool<byte>.Shared.Return(_buffer);
}

_inner.Dispose();
}

base.Dispose(disposing);
}

public override long Seek(long offset, SeekOrigin origin)
Expand Down
10 changes: 0 additions & 10 deletions src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedReadStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,6 @@ public override int WriteTimeout
}
}

protected override void Dispose(bool disposing)
{
// base.Dispose(disposing);

if (disposing)
{
// _inner.Dispose();
}
}

public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
Expand Down
34 changes: 18 additions & 16 deletions src/Docker.DotNet/Microsoft.Net.Http.Client/ChunkedWriteStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Microsoft.Net.Http.Client;

internal sealed class ChunkedWriteStream : Stream
{
private static readonly byte[] s_EndContentBytes = Encoding.ASCII.GetBytes("0\r\n\r\n");
private static readonly byte[] EndOfContentBytes = Encoding.ASCII.GetBytes("0\r\n\r\n");

private readonly Stream _inner;

Expand All @@ -28,16 +28,6 @@ public override long Position
set { throw new NotImplementedException(); }
}

protected override void Dispose(bool disposing)
{
// base.Dispose(disposing);

if (disposing)
{
// _inner.Dispose();
}
}

public override void Flush()
{
_inner.Flush();
Expand Down Expand Up @@ -75,14 +65,26 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
return;
}

var chunkSize = Encoding.ASCII.GetBytes(count.ToString("x") + "\r\n");
await _inner.WriteAsync(chunkSize, 0, chunkSize.Length, cancellationToken).ConfigureAwait(false);
await _inner.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
await _inner.WriteAsync(chunkSize, chunkSize.Length - 2, 2, cancellationToken).ConfigureAwait(false);
const string crlf = "\r\n";

var chunkHeader = count.ToString("X") + crlf;
var headerBytes = Encoding.ASCII.GetBytes(chunkHeader);

// Write the chunk header
await _inner.WriteAsync(headerBytes, 0, headerBytes.Length, cancellationToken)
.ConfigureAwait(false);

// Write the chunk data
await _inner.WriteAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);

// Write the chunk footer (CRLF)
await _inner.WriteAsync(headerBytes, headerBytes.Length - 2, 2, cancellationToken)
.ConfigureAwait(false);
}

public Task EndContentAsync(CancellationToken cancellationToken)
{
return _inner.WriteAsync(s_EndContentBytes, 0, s_EndContentBytes.Length, cancellationToken);
return _inner.WriteAsync(EndOfContentBytes, 0, EndOfContentBytes.Length, cancellationToken);
}
}
32 changes: 2 additions & 30 deletions src/Docker.DotNet/Models/ContainerExecStartParameters.Generated.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,16 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Docker.DotNet.Models
{
public class ContainerExecStartParameters // (main.ContainerExecStartParameters)
{
[JsonPropertyName("User")]
public string User { get; set; }

[JsonPropertyName("Privileged")]
public bool Privileged { get; set; }
[JsonPropertyName("Detach")]
public bool Detach { get; set; }

[JsonPropertyName("Tty")]
public bool Tty { get; set; }

[JsonPropertyName("ConsoleSize")]
public ulong[] ConsoleSize { get; set; }

[JsonPropertyName("AttachStdin")]
public bool AttachStdin { get; set; }

[JsonPropertyName("AttachStderr")]
public bool AttachStderr { get; set; }

[JsonPropertyName("AttachStdout")]
public bool AttachStdout { get; set; }

[JsonPropertyName("Detach")]
public bool Detach { get; set; }

[JsonPropertyName("DetachKeys")]
public string DetachKeys { get; set; }

[JsonPropertyName("Env")]
public IList<string> Env { get; set; }

[JsonPropertyName("WorkingDir")]
public string WorkingDir { get; set; }

[JsonPropertyName("Cmd")]
public IList<string> Cmd { get; set; }
}
}
4 changes: 2 additions & 2 deletions test/Docker.DotNet.Tests/IContainerOperationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -728,8 +728,8 @@ public async Task MultiplexedStreamWriteAsync_DoesNotThrowAnException()
_ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters());

// When
var containerExecCreateResponse = await _testFixture.DockerClient.Exec.ExecCreateContainerAsync(createContainerResponse.ID, containerExecCreateParameters);
using var stream = await _testFixture.DockerClient.Exec.StartWithConfigContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters);
var containerExecCreateResponse = await _testFixture.DockerClient.Exec.CreateContainerExecAsync(createContainerResponse.ID, containerExecCreateParameters);
using var stream = await _testFixture.DockerClient.Exec.StartContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters);

var buffer = new byte[] { 10 };
var exception = await Record.ExceptionAsync(() => stream.WriteAsync(buffer, 0, buffer.Length, _testFixture.Cts.Token));
Expand Down
4 changes: 2 additions & 2 deletions test/Docker.DotNet.Tests/ISystemOperations.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ await _testFixture.DockerClient.Images.DeleteImageAsync(

await cts.CancelAsync();

await Assert.ThrowsAsync<OperationCanceledException>(() => task);
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => task);

Assert.True(wasProgressCalled);
}
Expand Down Expand Up @@ -239,7 +239,7 @@ await _testFixture.DockerClient.Images.TagImageAsync(
await Task.Delay(TimeSpan.FromSeconds(1));
await cts.CancelAsync();

await Assert.ThrowsAsync<OperationCanceledException>(() => task);
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => task);

Assert.Equal(2, progressCalledCounter);
Assert.True(task.IsCanceled);
Expand Down
2 changes: 1 addition & 1 deletion tools/specgen/modeldefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ type ContainerExecCreateParameters container.ExecOptions
type ContainerExecCreateResponse types.IDResponse

// ContainerExecStartParameters for POST /exec/(id)/start
type ContainerExecStartParameters container.ExecOptions
type ContainerExecStartParameters container.ExecStartOptions

// ImagesCreateParameters for POST /images/create
type ImagesCreateParameters struct {
Expand Down
Loading