diff --git a/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs b/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs index 6195b7a8..34267b64 100644 --- a/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs +++ b/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs @@ -1,68 +1,20 @@ namespace Docker.DotNet.BasicAuth; -public class BasicAuthCredentials : Credentials, IAuthProvider +public class BasicAuthCredentials : IAuthProvider { - private readonly bool _isTls; + private readonly string _username; - private readonly MaybeSecureString _username; + private readonly string _password; - private readonly MaybeSecureString _password; - - private bool _disposed; - - public BasicAuthCredentials(SecureString username, SecureString password, bool isTls = false) - : this(new MaybeSecureString(username), new MaybeSecureString(password), isTls) - { - } - - public BasicAuthCredentials(string username, string password, bool isTls = false) - : this(new MaybeSecureString(username), new MaybeSecureString(password), isTls) - { - } - - private BasicAuthCredentials(MaybeSecureString username, MaybeSecureString password, bool isTls) + private BasicAuthCredentials(string username, string password, bool tlsEnabled = false) { - _isTls = isTls; _username = username; _password = password; + TlsEnabled = tlsEnabled; } - public bool TlsEnabled => _isTls; - - public override void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public override bool IsTlsCredentials() - { - return _isTls; - } - - public override HttpMessageHandler GetHandler(HttpMessageHandler handler) - { - return new BasicAuthHandler(_username, _password, handler); - } + public bool TlsEnabled { get; } public HttpMessageHandler ConfigureHandler(HttpMessageHandler handler) - { - return GetHandler(handler); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _username.Dispose(); - _password.Dispose(); - } - - _disposed = true; - } + => new BasicAuthHandler(_username, _password, handler); } \ No newline at end of file diff --git a/src/Docker.DotNet.BasicAuth/BasicAuthHandler.cs b/src/Docker.DotNet.BasicAuth/BasicAuthHandler.cs index a9fbb57f..a3eb3cdf 100644 --- a/src/Docker.DotNet.BasicAuth/BasicAuthHandler.cs +++ b/src/Docker.DotNet.BasicAuth/BasicAuthHandler.cs @@ -1,22 +1,15 @@ namespace Docker.DotNet.BasicAuth; -internal class BasicAuthHandler : DelegatingHandler +internal sealed class BasicAuthHandler : DelegatingHandler { - private readonly MaybeSecureString _username; - - private readonly MaybeSecureString _password; - private readonly Lazy _authHeader; - public BasicAuthHandler(MaybeSecureString username, MaybeSecureString password, HttpMessageHandler httpMessageHandler) + public BasicAuthHandler(string username, string password, HttpMessageHandler httpMessageHandler) : base(httpMessageHandler) { - _username = username.Copy(); - _password = password.Copy(); - _authHeader = new Lazy(() => { - var credentials = $"{_username}:{_password}"; + var credentials = $"{username}:{password}"; var bytes = Encoding.ASCII.GetBytes(credentials); var base64 = Convert.ToBase64String(bytes); return new AuthenticationHeaderValue("Basic", base64); @@ -28,15 +21,4 @@ protected override Task SendAsync(HttpRequestMessage reques request.Headers.Authorization = _authHeader.Value; return base.SendAsync(request, cancellationToken); } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _username.Dispose(); - _password.Dispose(); - } - - base.Dispose(disposing); - } } \ No newline at end of file diff --git a/src/Docker.DotNet.Handler.Abstractions/Credentials.cs b/src/Docker.DotNet.Handler.Abstractions/Credentials.cs deleted file mode 100644 index 9623e570..00000000 --- a/src/Docker.DotNet.Handler.Abstractions/Credentials.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Docker.DotNet.Handler.Abstractions; - -[Obsolete("Use the IAuthProvider interface instead.")] -public abstract class Credentials : IDisposable -{ - public abstract void Dispose(); - - public abstract bool IsTlsCredentials(); - - public abstract HttpMessageHandler GetHandler(HttpMessageHandler handler); -} \ No newline at end of file diff --git a/src/Docker.DotNet.Handler.Abstractions/DelegateAuthProvider.cs b/src/Docker.DotNet.Handler.Abstractions/DelegateAuthProvider.cs deleted file mode 100644 index d5d85d01..00000000 --- a/src/Docker.DotNet.Handler.Abstractions/DelegateAuthProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Docker.DotNet.Handler.Abstractions; - -/// -/// An that delegates to the configured credentials. -/// -/// The client configuration. -public sealed class DelegateAuthProvider(IDockerClientConfiguration configuration) : IAuthProvider -{ - /// - public bool TlsEnabled - => configuration.Credentials.IsTlsCredentials(); - - /// - public HttpMessageHandler ConfigureHandler(HttpMessageHandler handler) - => configuration.Credentials.GetHandler(handler); -} \ No newline at end of file diff --git a/src/Docker.DotNet.Handler.Abstractions/IDockerClientConfiguration.cs b/src/Docker.DotNet.Handler.Abstractions/IDockerClientConfiguration.cs deleted file mode 100644 index 406e0063..00000000 --- a/src/Docker.DotNet.Handler.Abstractions/IDockerClientConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Docker.DotNet.Handler.Abstractions; - -[Obsolete("Use the DockerClientBuilder class instead: https://github.com/testcontainers/Docker.DotNet/blob/main/README.md#usage.")] -public interface IDockerClientConfiguration -{ - /// - /// Gets the collection of default HTTP request headers. - /// - public IReadOnlyDictionary DefaultHttpRequestHeaders { get; } - - public Uri EndpointBaseUri { get; } - - public Credentials Credentials { get; } - - public TimeSpan DefaultTimeout { get; } - - public TimeSpan NamedPipeConnectTimeout { get; } -} \ No newline at end of file diff --git a/src/Docker.DotNet.Handler.Abstractions/IDockerHandlerFactory.cs b/src/Docker.DotNet.Handler.Abstractions/IDockerHandlerFactory.cs index 23b2ad81..3a4203bf 100644 --- a/src/Docker.DotNet.Handler.Abstractions/IDockerHandlerFactory.cs +++ b/src/Docker.DotNet.Handler.Abstractions/IDockerHandlerFactory.cs @@ -5,16 +5,6 @@ namespace Docker.DotNet.Handler.Abstractions; /// public interface IDockerHandlerFactory : IStreamHijacker { - /// - /// Creates a handler and normalized endpoint URI for the provided endpoint and configuration. - /// - /// The endpoint URI. - /// The Docker client configuration. - /// The logger instance. - /// A tuple containing the configured handler and normalized endpoint URI. - [Obsolete("Use the CreateHandler(ClientOptions, ILogger) or CreateHandler(TTransportOptions, ClientOptions, ILogger) overload instead.")] - Tuple CreateHandler(Uri uri, IDockerClientConfiguration configuration, ILogger logger); - /// /// Creates a handler and normalized endpoint URI for the provided client options. /// diff --git a/src/Docker.DotNet.LegacyHttp/DockerHandlerFactory.cs b/src/Docker.DotNet.LegacyHttp/DockerHandlerFactory.cs index 6bf21154..53404f10 100644 --- a/src/Docker.DotNet.LegacyHttp/DockerHandlerFactory.cs +++ b/src/Docker.DotNet.LegacyHttp/DockerHandlerFactory.cs @@ -9,12 +9,6 @@ private DockerHandlerFactory() public static IDockerHandlerFactory Instance { get; } = new DockerHandlerFactory(); - public Tuple CreateHandler(Uri uri, IDockerClientConfiguration configuration, ILogger logger) - { - var clientOptions = new ClientOptions { Endpoint = uri, AuthProvider = new DelegateAuthProvider(configuration) }; - return CreateHandler(clientOptions, logger); - } - public Tuple CreateHandler(ClientOptions clientOptions, ILogger logger) { var transportOptions = new LegacyHttpTransportOptions(); diff --git a/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs b/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs index ea34d522..abe2002a 100644 --- a/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs +++ b/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs @@ -9,13 +9,6 @@ private DockerHandlerFactory() public static IDockerHandlerFactory Instance { get; } = new DockerHandlerFactory(); - public Tuple CreateHandler(Uri uri, IDockerClientConfiguration configuration, ILogger logger) - { - var transportOptions = new NPipeTransportOptions { ConnectTimeout = configuration.NamedPipeConnectTimeout }; - var clientOptions = new ClientOptions { Endpoint = uri, AuthProvider = new DelegateAuthProvider(configuration) }; - return CreateHandler(transportOptions, clientOptions, logger); - } - public Tuple CreateHandler(ClientOptions clientOptions, ILogger logger) { var transportOptions = new NPipeTransportOptions(); diff --git a/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs b/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs index 092f5e56..71343478 100644 --- a/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs +++ b/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs @@ -15,12 +15,6 @@ private DockerHandlerFactory() public static IDockerHandlerFactory Instance { get; } = new DockerHandlerFactory(); - public Tuple CreateHandler(Uri uri, IDockerClientConfiguration configuration, ILogger logger) - { - var clientOptions = new ClientOptions { Endpoint = uri, AuthProvider = new DelegateAuthProvider(configuration) }; - return CreateHandler(clientOptions, logger); - } - public Tuple CreateHandler(ClientOptions clientOptions, ILogger logger) { var transportOptions = new NativeHttpTransportOptions(); diff --git a/src/Docker.DotNet.Unix/DockerHandlerFactory.cs b/src/Docker.DotNet.Unix/DockerHandlerFactory.cs index f324bb05..91a1cd58 100644 --- a/src/Docker.DotNet.Unix/DockerHandlerFactory.cs +++ b/src/Docker.DotNet.Unix/DockerHandlerFactory.cs @@ -9,12 +9,6 @@ private DockerHandlerFactory() public static IDockerHandlerFactory Instance { get; } = new DockerHandlerFactory(); - public Tuple CreateHandler(Uri uri, IDockerClientConfiguration configuration, ILogger logger) - { - var clientOptions = new ClientOptions { Endpoint = uri }; - return CreateHandler(clientOptions, logger); - } - public Tuple CreateHandler(ClientOptions clientOptions, ILogger logger) { var transportOptions = new UnixSocketTransportOptions(); diff --git a/src/Docker.DotNet.X509/CertificateCredentials.cs b/src/Docker.DotNet.X509/CertificateCredentials.cs index 0241a7b0..a03bf796 100644 --- a/src/Docker.DotNet.X509/CertificateCredentials.cs +++ b/src/Docker.DotNet.X509/CertificateCredentials.cs @@ -1,6 +1,6 @@ namespace Docker.DotNet.X509; -public class CertificateCredentials : Credentials, IAuthProvider +public class CertificateCredentials : IAuthProvider { private readonly X509Certificate2 _certificate; @@ -13,18 +13,7 @@ public CertificateCredentials(X509Certificate2 certificate) public RemoteCertificateValidationCallback ServerCertificateValidationCallback { get; set; } - public override void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public override bool IsTlsCredentials() - { - return true; - } - - public override HttpMessageHandler GetHandler(HttpMessageHandler handler) + public HttpMessageHandler ConfigureHandler(HttpMessageHandler handler) { #if NET6_0_OR_GREATER if (handler is SocketsHttpHandler socketsHandler) @@ -56,13 +45,4 @@ public override HttpMessageHandler GetHandler(HttpMessageHandler handler) return handler; } - - public HttpMessageHandler ConfigureHandler(HttpMessageHandler handler) - { - return GetHandler(handler); - } - - protected virtual void Dispose(bool _) - { - } } \ No newline at end of file diff --git a/src/Docker.DotNet.X509/RSAUtil.cs b/src/Docker.DotNet.X509/RSAUtil.cs deleted file mode 100644 index 66d8064f..00000000 --- a/src/Docker.DotNet.X509/RSAUtil.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Docker.DotNet.X509; - -[Obsolete("RSAUtil is obsolete. Use DockerTlsCertificates instead.")] -public static class RSAUtil -{ - public static X509Certificate2 GetCertFromPFX(string pfxFilePath, string password) - { -#if NET9_0_OR_GREATER - return X509CertificateLoader.LoadPkcs12FromFile(pfxFilePath, password); -#else - return new X509Certificate2(pfxFilePath, password); -#endif - } - - public static X509Certificate2 GetCertFromPEM(string certFilePath, string keyFilePath) - { -#if NETSTANDARD - return Polyfills.X509Certificate2.CreateFromPemFile(certFilePath, keyFilePath); -#elif NET9_0_OR_GREATER - var certificate = X509Certificate2.CreateFromPemFile(certFilePath, keyFilePath); - return OperatingSystem.IsWindows() ? X509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pfx), null) : certificate; -#elif NET6_0_OR_GREATER - var certificate = X509Certificate2.CreateFromPemFile(certFilePath, keyFilePath); - return OperatingSystem.IsWindows() ? new X509Certificate2(certificate.Export(X509ContentType.Pfx)) : certificate; -#endif - } -} \ No newline at end of file diff --git a/src/Docker.DotNet.X509/X509Certificate2.cs b/src/Docker.DotNet.X509/X509Certificate2.cs index 26e30b4b..53c4b9a6 100644 --- a/src/Docker.DotNet.X509/X509Certificate2.cs +++ b/src/Docker.DotNet.X509/X509Certificate2.cs @@ -8,7 +8,7 @@ namespace Docker.DotNet.X509.Polyfills; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; -public static class X509Certificate2 +internal static class X509Certificate2 { private static readonly X509CertificateParser CertificateParser = new X509CertificateParser(); diff --git a/src/Docker.DotNet/AnonymousCredentials.cs b/src/Docker.DotNet/AnonymousCredentials.cs deleted file mode 100644 index 0375ca01..00000000 --- a/src/Docker.DotNet/AnonymousCredentials.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Docker.DotNet; - -public class AnonymousCredentials : Credentials -{ - public override void Dispose() - { - } - - public override bool IsTlsCredentials() - { - return false; - } - - public override HttpMessageHandler GetHandler(HttpMessageHandler handler) - { - return handler; - } -} \ No newline at end of file diff --git a/src/Docker.DotNet/DockerApiException.cs b/src/Docker.DotNet/DockerApiException.cs index 5d01417b..fd0b4e43 100644 --- a/src/Docker.DotNet/DockerApiException.cs +++ b/src/Docker.DotNet/DockerApiException.cs @@ -2,14 +2,14 @@ namespace Docker.DotNet; public class DockerApiException : Exception { - public HttpStatusCode StatusCode { get; private set; } - - public string ResponseBody { get; private set; } - - public DockerApiException(HttpStatusCode statusCode, string responseBody) - : base($"Docker API responded with status code={statusCode}, response={responseBody}") + public DockerApiException(HttpStatusCode statusCode, string? responseBody) + : base($"Docker API responded with status code='{statusCode}', response='{responseBody}'.") { StatusCode = statusCode; ResponseBody = responseBody; } + + public HttpStatusCode StatusCode { get; } + + public string? ResponseBody { get; } } \ No newline at end of file diff --git a/src/Docker.DotNet/DockerApiResponse.cs b/src/Docker.DotNet/DockerApiResponse.cs deleted file mode 100644 index 785045f5..00000000 --- a/src/Docker.DotNet/DockerApiResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Docker.DotNet; - -internal class DockerApiResponse -{ - public HttpStatusCode StatusCode { get; private set; } - - public string Body { get; private set; } - - public DockerApiResponse(HttpStatusCode statusCode, string body) - { - StatusCode = statusCode; - Body = body; - } -} \ No newline at end of file diff --git a/src/Docker.DotNet/DockerClient.cs b/src/Docker.DotNet/DockerClient.cs index 2c4e9379..ba96e162 100644 --- a/src/Docker.DotNet/DockerClient.cs +++ b/src/Docker.DotNet/DockerClient.cs @@ -10,83 +10,36 @@ public sealed class DockerClient : IDockerClient private readonly HttpClient _client; - private readonly Version? _requestedApiVersion; + private readonly ClientOptions _clientOptions; - private readonly Uri _endpointBaseUri; - - private readonly IReadOnlyDictionary _headers; - - private readonly IDockerHandlerFactory _handlerFactory; + private readonly IStreamHijacker _hijack; internal DockerClient( - IDockerHandlerFactory handlerFactory, HttpMessageHandler handler, ClientOptions clientOptions, - Uri endpoint, + IStreamHijacker hijack, ILogger logger) { - _ = logger; - - _requestedApiVersion = clientOptions.ApiVersion; - _endpointBaseUri = endpoint; - _headers = clientOptions.Headers; - _handlerFactory = handlerFactory; - - Configuration = null!; - DefaultTimeout = clientOptions.Timeout; - - Images = new ImageOperations(this); - Containers = new ContainerOperations(this); - System = new SystemOperations(this); - Networks = new NetworkOperations(this); - Secrets = new SecretsOperations(this); - Configs = new ConfigOperations(this); - Swarm = new SwarmOperations(this); - Tasks = new TasksOperations(this); - Volumes = new VolumeOperations(this); - Plugin = new PluginOperations(this); - Exec = new ExecOperations(this); - - _client = new HttpClient(clientOptions.AuthProvider.ConfigureHandler(handler), true); + _client = new HttpClient(handler, true); _client.Timeout = Timeout.InfiniteTimeSpan; - } - internal DockerClient(DockerClientConfiguration configuration, Version? requestedApiVersion, IDockerHandlerFactory handlerFactory, ILogger? logger = null) - { - if (handlerFactory == null) - { - throw new ArgumentNullException(nameof(handlerFactory)); - } - - _requestedApiVersion = requestedApiVersion; - _headers = configuration.DefaultHttpRequestHeaders; - _handlerFactory = handlerFactory; - - Configuration = configuration; - DefaultTimeout = configuration.DefaultTimeout; + _clientOptions = clientOptions; + _hijack = hijack; - Images = new ImageOperations(this); - Containers = new ContainerOperations(this); System = new SystemOperations(this); + Containers = new ContainerOperations(this); + Images = new ImageOperations(this); Networks = new NetworkOperations(this); + Volumes = new VolumeOperations(this); Secrets = new SecretsOperations(this); Configs = new ConfigOperations(this); Swarm = new SwarmOperations(this); Tasks = new TasksOperations(this); - Volumes = new VolumeOperations(this); Plugin = new PluginOperations(this); Exec = new ExecOperations(this); - - var (handler, endpoint) = _handlerFactory.CreateHandler(Configuration.EndpointBaseUri, Configuration, logger); - - _client = new HttpClient(Configuration.Credentials.GetHandler(handler), true); - _client.Timeout = Timeout.InfiniteTimeSpan; - _endpointBaseUri = endpoint; } - public DockerClientConfiguration Configuration { get; } - - public TimeSpan DefaultTimeout { get; set; } + public ISystemOperations System { get; } public IContainerOperations Containers { get; } @@ -104,8 +57,6 @@ internal DockerClient(DockerClientConfiguration configuration, Version? requeste public ITasksOperations Tasks { get; } - public ISystemOperations System { get; } - public IPluginOperations Plugin { get; } public IExecOperations Exec { get; } @@ -114,7 +65,6 @@ internal DockerClient(DockerClientConfiguration configuration, Version? requeste public void Dispose() { - Configuration?.Dispose(); _client.Dispose(); } @@ -187,7 +137,7 @@ internal Task MakeRequestAsync( IDictionary? headers, CancellationToken token) { - return MakeRequestAsync(errorHandlers, method, path, queryString, body, headers, DefaultTimeout, token); + return MakeRequestAsync(errorHandlers, method, path, queryString, body, headers, _clientOptions.Timeout, token); } internal Task MakeRequestAsync( @@ -221,7 +171,7 @@ await HandleIfErrorResponseAsync(response.StatusCode, response, errorHandlers) if (typeof(T) == typeof(NoContent)) { - return default; + return default!; } return await JsonSerializer.DeserializeAsync(response.Content, token) @@ -368,7 +318,7 @@ internal async Task MakeRequestForHijackedStreamAsync( await HandleIfErrorResponseAsync(response.StatusCode, response, errorHandlers) .ConfigureAwait(false); - return await _handlerFactory.HijackStreamAsync(response.Content) + return await _hijack.HijackStreamAsync(response.Content) .ConfigureAwait(false); } @@ -411,13 +361,13 @@ private HttpRequestMessage PrepareRequest(HttpMethod method, string path, IQuery throw new ArgumentNullException(nameof(path)); } - var request = new HttpRequestMessage(method, HttpUtility.BuildUri(_endpointBaseUri, _requestedApiVersion, path, queryString)); + var request = new HttpRequestMessage(method, HttpUtility.BuildUri(_clientOptions.Endpoint, _clientOptions.ApiVersion, path, queryString)); request.Version = new Version(1, 1); request.Headers.Add("User-Agent", UserAgent); var customHeaders = headers == null - ? _headers - : _headers.Concat(headers); + ? _clientOptions.Headers + : _clientOptions.Headers.Concat(headers); foreach (var header in customHeaders) { @@ -489,4 +439,4 @@ private async Task HandleIfErrorResponseAsync(HttpStatusCode statusCode, HttpRes private struct NoContent; } -internal delegate void ApiResponseErrorHandlingDelegate(HttpStatusCode statusCode, string responseBody); \ No newline at end of file +internal delegate void ApiResponseErrorHandlingDelegate(HttpStatusCode statusCode, string? responseBody); \ No newline at end of file diff --git a/src/Docker.DotNet/DockerClientBuilder.cs b/src/Docker.DotNet/DockerClientBuilder.cs index 5fda7460..67d8ea33 100644 --- a/src/Docker.DotNet/DockerClientBuilder.cs +++ b/src/Docker.DotNet/DockerClientBuilder.cs @@ -192,9 +192,25 @@ public DockerClientBuilder WithTransportOptionsA configured instance. public virtual DockerClient Build() { - var scheme = ClientOptions.Endpoint.Scheme; + var transportFactory = ResolveTransportFactory(ClientOptions.Endpoint.Scheme); - IDockerHandlerFactory transportFactory = scheme.ToLowerInvariant() switch + var (handler, endpoint) = transportFactory.CreateHandler(ClientOptions, Logger); + + var clientOptions = ClientOptions with { Endpoint = endpoint }; + + var authenticatedHandler = clientOptions.AuthProvider.ConfigureHandler(handler); + + return new DockerClient(authenticatedHandler, clientOptions, transportFactory, Logger); + } + + /// + /// Resolves the transport handler factory for the provided endpoint URI scheme. + /// + /// The endpoint URI scheme. + /// The selected transport handler factory. + protected virtual IDockerHandlerFactory ResolveTransportFactory(string scheme) + { + return scheme.ToLowerInvariant() switch { "npipe" => NPipe.DockerHandlerFactory.Instance, "unix" => Unix.DockerHandlerFactory.Instance, @@ -203,8 +219,5 @@ public virtual DockerClient Build() : LegacyHttp.DockerHandlerFactory.Instance, _ => throw new NotSupportedException($"The URI scheme '{scheme}' is not supported.") }; - - var (handler, endpoint) = transportFactory.CreateHandler(ClientOptions, Logger); - return new DockerClient(transportFactory, handler, ClientOptions, endpoint, Logger); } } \ No newline at end of file diff --git a/src/Docker.DotNet/DockerClientBuilder`1.cs b/src/Docker.DotNet/DockerClientBuilder`1.cs index f45c8e7d..20e212e5 100644 --- a/src/Docker.DotNet/DockerClientBuilder`1.cs +++ b/src/Docker.DotNet/DockerClientBuilder`1.cs @@ -30,6 +30,11 @@ public DockerClientBuilder( public override DockerClient Build() { var (handler, endpoint) = _transportFactory.CreateHandler(_transportOptions, ClientOptions, Logger); - return new DockerClient(_transportFactory, handler, ClientOptions, endpoint, Logger); + + var clientOptions = ClientOptions with { Endpoint = endpoint }; + + var authenticatedHandler = clientOptions.AuthProvider.ConfigureHandler(handler); + + return new DockerClient(authenticatedHandler, clientOptions, _transportFactory, Logger); } } \ No newline at end of file diff --git a/src/Docker.DotNet/DockerClientConfiguration.cs b/src/Docker.DotNet/DockerClientConfiguration.cs deleted file mode 100644 index a9e8608d..00000000 --- a/src/Docker.DotNet/DockerClientConfiguration.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace Docker.DotNet; - -using System; - -[Obsolete("Use the DockerClientBuilder class instead: https://github.com/testcontainers/Docker.DotNet/blob/main/README.md#usage.")] -public class DockerClientConfiguration : IDockerClientConfiguration, IDisposable -{ - private static readonly bool NativeHttpEnabled = Environment.GetEnvironmentVariable("DOCKER_DOTNET_NATIVE_HTTP_ENABLED") == "1"; - - public DockerClientConfiguration( - Credentials? credentials = null, - TimeSpan defaultTimeout = default, - TimeSpan namedPipeConnectTimeout = default, - IReadOnlyDictionary? defaultHttpRequestHeaders = null) - : this(GetLocalDockerEndpoint(), credentials, defaultTimeout, namedPipeConnectTimeout, defaultHttpRequestHeaders) - { - } - - public DockerClientConfiguration( - Uri endpoint, - Credentials? credentials = null, - TimeSpan defaultTimeout = default, - TimeSpan namedPipeConnectTimeout = default, - IReadOnlyDictionary? defaultHttpRequestHeaders = null) - { - if (endpoint == null) - { - throw new ArgumentNullException(nameof(endpoint)); - } - - if (defaultTimeout < Timeout.InfiniteTimeSpan) - { - throw new ArgumentException("Default timeout must be greater than -1", nameof(defaultTimeout)); - } - - EndpointBaseUri = endpoint; - Credentials = credentials ?? new AnonymousCredentials(); - DefaultTimeout = TimeSpan.Equals(TimeSpan.Zero, defaultTimeout) ? TimeSpan.FromSeconds(100) : defaultTimeout; - NamedPipeConnectTimeout = TimeSpan.Equals(TimeSpan.Zero, namedPipeConnectTimeout) ? TimeSpan.FromMilliseconds(100) : namedPipeConnectTimeout; - DefaultHttpRequestHeaders = defaultHttpRequestHeaders ?? new Dictionary(); - } - - /// - /// Gets the collection of default HTTP request headers. - /// - public IReadOnlyDictionary DefaultHttpRequestHeaders { get; } - - public Uri EndpointBaseUri { get; } - - public Credentials Credentials { get; } - - public TimeSpan DefaultTimeout { get; } - - public TimeSpan NamedPipeConnectTimeout { get; } - - public DockerClient CreateClient(Version? requestedApiVersion = null, ILogger? logger = null) - { - IDockerHandlerFactory handlerFactory = EndpointBaseUri.Scheme.ToLowerInvariant() switch - { - "npipe" => NPipe.DockerHandlerFactory.Instance, - "unix" => Unix.DockerHandlerFactory.Instance, - "tcp" or "http" or "https" => NativeHttpEnabled - ? NativeHttp.DockerHandlerFactory.Instance - : LegacyHttp.DockerHandlerFactory.Instance, - _ => throw new NotSupportedException($"The URI scheme '{EndpointBaseUri.Scheme}' is not supported.") - }; - - return CreateClient(requestedApiVersion, handlerFactory, logger); - } - - public DockerClient CreateClient(Version? requestedApiVersion, IDockerHandlerFactory handlerFactory, ILogger? logger = null) - { - if (handlerFactory == null) - { - throw new ArgumentNullException(nameof(handlerFactory)); - } - - return new DockerClient(this, requestedApiVersion, handlerFactory, logger); - } - - public void Dispose() - { - Credentials.Dispose(); - } - - private static Uri GetLocalDockerEndpoint() - { - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - return isWindows ? new Uri("npipe://./pipe/docker_engine") : new Uri("unix:/var/run/docker.sock"); - } -} \ No newline at end of file diff --git a/src/Docker.DotNet/Endpoints/ContainerOperations.cs b/src/Docker.DotNet/Endpoints/ContainerOperations.cs index 7c13ed0e..e6f2a811 100644 --- a/src/Docker.DotNet/Endpoints/ContainerOperations.cs +++ b/src/Docker.DotNet/Endpoints/ContainerOperations.cs @@ -38,7 +38,7 @@ public async Task> ListContainersAsync(ContainersLi public async Task CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default) { - IQueryString qs = null; + IQueryString? qs = null; if (parameters == null) { @@ -189,7 +189,7 @@ public Task ResizeContainerTtyAsync(string id, ContainerResizeParameters paramet return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/resize", queryParameters, cancellationToken); } - public async Task StartContainerAsync(string id, ContainerStartParameters parameters, CancellationToken cancellationToken = default) + public async Task StartContainerAsync(string id, ContainerStartParameters? parameters, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { @@ -349,6 +349,8 @@ public async Task GetArchiveFromContainerAsync(string throw new ArgumentNullException(nameof(parameters)); } + Stream? stream; + IQueryString queryParameters = new QueryString(parameters); var response = await _client.MakeRequestForStreamedResponseAsync(new[] { NoSuchContainerHandler }, statOnly ? HttpMethod.Head : HttpMethod.Get, $"containers/{id}/archive", queryParameters, cancellationToken); @@ -357,12 +359,22 @@ public async Task GetArchiveFromContainerAsync(string var bytes = Convert.FromBase64String(statHeader); - var pathStat = DockerClient.JsonSerializer.Deserialize(bytes); + var stat = DockerClient.JsonSerializer.Deserialize(bytes); + + if (statOnly) + { + response.Body.Dispose(); + stream = null; + } + else + { + stream = response.Body; + } return new ContainerArchiveResponse { - Stat = pathStat, - Stream = statOnly ? null : response.Body + Stat = stat, + Stream = stream }; } @@ -384,7 +396,7 @@ public Task ExtractArchiveToContainerAsync(string id, CopyToContainerParameters return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Put, $"containers/{id}/archive", queryParameters, data, cancellationToken); } - public async Task PruneContainersAsync(ContainersPruneParameters parameters, CancellationToken cancellationToken) + public async Task PruneContainersAsync(ContainersPruneParameters? parameters, CancellationToken cancellationToken) { var queryParameters = parameters == null ? null : new QueryString(parameters); return await _client.MakeRequestAsync(_client.NoErrorHandlers, HttpMethod.Post, "containers/prune", queryParameters, cancellationToken).ConfigureAwait(false); diff --git a/src/Docker.DotNet/Endpoints/DockerContainerNotFoundException.cs b/src/Docker.DotNet/Endpoints/DockerContainerNotFoundException.cs index 64bbf95a..856534d9 100644 --- a/src/Docker.DotNet/Endpoints/DockerContainerNotFoundException.cs +++ b/src/Docker.DotNet/Endpoints/DockerContainerNotFoundException.cs @@ -2,7 +2,8 @@ namespace Docker.DotNet; public class DockerContainerNotFoundException : DockerApiException { - public DockerContainerNotFoundException(HttpStatusCode statusCode, string responseBody) : base(statusCode, responseBody) + public DockerContainerNotFoundException(HttpStatusCode statusCode, string? responseBody) + : base(statusCode, responseBody) { } } \ No newline at end of file diff --git a/src/Docker.DotNet/Endpoints/DockerImageNotFoundException.cs b/src/Docker.DotNet/Endpoints/DockerImageNotFoundException.cs index 98d03e54..c1e0ff71 100644 --- a/src/Docker.DotNet/Endpoints/DockerImageNotFoundException.cs +++ b/src/Docker.DotNet/Endpoints/DockerImageNotFoundException.cs @@ -2,7 +2,8 @@ namespace Docker.DotNet; public class DockerImageNotFoundException : DockerApiException { - public DockerImageNotFoundException(HttpStatusCode statusCode, string body) : base(statusCode, body) + public DockerImageNotFoundException(HttpStatusCode statusCode, string? responseBody) + : base(statusCode, responseBody) { } } \ No newline at end of file diff --git a/src/Docker.DotNet/Endpoints/DockerNetworkNotFoundException.cs b/src/Docker.DotNet/Endpoints/DockerNetworkNotFoundException.cs index 7ea855b0..13ec8ff3 100644 --- a/src/Docker.DotNet/Endpoints/DockerNetworkNotFoundException.cs +++ b/src/Docker.DotNet/Endpoints/DockerNetworkNotFoundException.cs @@ -2,7 +2,8 @@ namespace Docker.DotNet; public class DockerNetworkNotFoundException : DockerApiException { - public DockerNetworkNotFoundException(HttpStatusCode statusCode, string responseBody) : base(statusCode, responseBody) + public DockerNetworkNotFoundException(HttpStatusCode statusCode, string? responseBody) + : base(statusCode, responseBody) { } } \ No newline at end of file diff --git a/src/Docker.DotNet/Endpoints/DockerPluginNotFoundException.cs b/src/Docker.DotNet/Endpoints/DockerPluginNotFoundException.cs index 1c443320..e8a851fd 100644 --- a/src/Docker.DotNet/Endpoints/DockerPluginNotFoundException.cs +++ b/src/Docker.DotNet/Endpoints/DockerPluginNotFoundException.cs @@ -2,7 +2,8 @@ namespace Docker.DotNet; public class DockerPluginNotFoundException : DockerApiException { - public DockerPluginNotFoundException(HttpStatusCode statusCode, string responseBody) : base(statusCode, responseBody) + public DockerPluginNotFoundException(HttpStatusCode statusCode, string? responseBody) + : base(statusCode, responseBody) { } } \ No newline at end of file diff --git a/src/Docker.DotNet/Endpoints/INetworkOperations.cs b/src/Docker.DotNet/Endpoints/INetworkOperations.cs index f389e8b1..ce6aa34a 100644 --- a/src/Docker.DotNet/Endpoints/INetworkOperations.cs +++ b/src/Docker.DotNet/Endpoints/INetworkOperations.cs @@ -93,7 +93,7 @@ public interface INetworkOperations /// 404 - Network or container not found. /// 500 - Server error. /// - [System.Obsolete("Use INetworkOperations.PruneNetworksAsync")] + [Obsolete("Use INetworkOperations.PruneNetworksAsync")] Task DeleteUnusedNetworksAsync(NetworksDeleteUnusedParameters parameters = null, CancellationToken cancellationToken = default(CancellationToken)); /// diff --git a/src/Docker.DotNet/IDockerClient.cs b/src/Docker.DotNet/IDockerClient.cs index b3b6bfcb..0b51b26b 100644 --- a/src/Docker.DotNet/IDockerClient.cs +++ b/src/Docker.DotNet/IDockerClient.cs @@ -2,11 +2,7 @@ namespace Docker.DotNet; public interface IDockerClient : IDisposable { - DockerClientConfiguration Configuration { get; } - - TimeSpan DefaultTimeout { get; set; } - - #region Endpoints + ISystemOperations System { get; } IContainerOperations Containers { get; } @@ -24,11 +20,7 @@ public interface IDockerClient : IDisposable ITasksOperations Tasks { get; } - ISystemOperations System { get; } - IPluginOperations Plugin { get; } IExecOperations Exec { get; } - - #endregion Endpoints } \ No newline at end of file diff --git a/src/Docker.DotNet/Models/ContainerArchiveResponse.cs b/src/Docker.DotNet/Models/ContainerArchiveResponse.cs index a2fa0a55..927dfb83 100644 --- a/src/Docker.DotNet/Models/ContainerArchiveResponse.cs +++ b/src/Docker.DotNet/Models/ContainerArchiveResponse.cs @@ -1,8 +1,8 @@ namespace Docker.DotNet.Models; -public class ContainerArchiveResponse +public sealed record ContainerArchiveResponse { - public ContainerPathStatResponse Stat { get; set; } + public ContainerPathStatResponse Stat { get; set; } = null!; - public Stream Stream { get; set; } + public Stream? Stream { get; set; } } \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringParameterAttribute.cs b/src/Docker.DotNet/QueryStringParameterAttribute.cs index 36925302..6b08d259 100644 --- a/src/Docker.DotNet/QueryStringParameterAttribute.cs +++ b/src/Docker.DotNet/QueryStringParameterAttribute.cs @@ -1,19 +1,15 @@ namespace Docker.DotNet; [AttributeUsage(AttributeTargets.Property)] -internal class QueryStringParameterAttribute : Attribute +internal sealed class QueryStringParameterAttribute : Attribute { public string Name { get; private set; } public bool IsRequired { get; private set; } - public Type ConverterType { get; private set; } + public Type? ConverterType { get; private set; } - public QueryStringParameterAttribute(string name, bool required) : this(name, required, null) - { - } - - public QueryStringParameterAttribute(string name, bool required, Type converterType) + public QueryStringParameterAttribute(string name, bool required, Type? converterType = null) { if (string.IsNullOrEmpty(name)) { @@ -22,7 +18,7 @@ public QueryStringParameterAttribute(string name, bool required, Type converterT if (converterType != null && !converterType.GetInterfaces().Contains(typeof (IQueryStringConverter))) { - throw new ArgumentException($"Provided query string converter type is not {typeof(IQueryStringConverter).FullName}", nameof(converterType)); + throw new ArgumentException($"Provided query string converter type is not '{typeof(IQueryStringConverter).FullName}'.", nameof(converterType)); } Name = name; diff --git a/src/Microsoft.Net.Http.Client/ContentLengthReadStream.cs b/src/Microsoft.Net.Http.Client/ContentLengthReadStream.cs index 2d38d85c..f86ab8d2 100644 --- a/src/Microsoft.Net.Http.Client/ContentLengthReadStream.cs +++ b/src/Microsoft.Net.Http.Client/ContentLengthReadStream.cs @@ -124,7 +124,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - // TODO: Sync drain with timeout if small number of bytes remaining? This will let us re-use the connection. + // TODO: Sync drain with timeout if small number of bytes remaining? This will let us re-use the connection. _inner.Dispose(); } } diff --git a/test/Docker.DotNet.TestsV2/DockerClientBuilderTests.cs b/test/Docker.DotNet.TestsV2/DockerClientBuilderTests.cs index a7232236..5df5352d 100644 --- a/test/Docker.DotNet.TestsV2/DockerClientBuilderTests.cs +++ b/test/Docker.DotNet.TestsV2/DockerClientBuilderTests.cs @@ -119,25 +119,21 @@ public void Build_WithUnsupportedScheme_ThrowsNotSupportedException() [Theory] [InlineData("npipe://./pipe/docker_engine", typeof(NPipe.DockerHandlerFactory))] [InlineData("unix:/var/run/docker.sock", typeof(Unix.DockerHandlerFactory))] - public void Build_UsesExpectedFactory_ForSocketSchemes(string endpoint, Type expectedFactoryType) + public void ResolveTransportFactory_UsesExpectedFactory_ForSocketSchemes(string endpoint, Type expectedFactoryType) { - var client = new DockerClientBuilder() - .WithEndpoint(new Uri(endpoint)) - .Build(); + var builder = new TestDockerClientBuilder(); - var actualFactory = GetPrivateField(client, "_handlerFactory"); + var actualFactory = builder.ResolveTransportFactory(new Uri(endpoint)); Assert.IsType(expectedFactoryType, actualFactory); } [Fact] - public void Build_WithHttpEndpoint_UsesConfiguredDefaultHttpFactory() + public void ResolveTransportFactory_WithHttpScheme_UsesConfiguredDefaultHttpFactory() { - var client = new DockerClientBuilder() - .WithEndpoint(new Uri("http://localhost:2375")) - .Build(); + var builder = new TestDockerClientBuilder(); - var actualFactory = GetPrivateField(client, "_handlerFactory"); + var actualFactory = builder.ResolveTransportFactory(new Uri("http://localhost:2375")); var expectedFactoryType = Environment.GetEnvironmentVariable("DOCKER_DOTNET_NATIVE_HTTP_ENABLED") == "1" ? typeof(NativeHttp.DockerHandlerFactory) @@ -153,7 +149,7 @@ public void Build_WithExplicitTransport_UsesProvidedFactoryAndOptions() var transportOptions = new FakeTransportOptions(); - var client = new DockerClientBuilder() + _ = new DockerClientBuilder() .WithTransportOptions(transportFactory, transportOptions) .WithEndpoint(new Uri("http://localhost:2375")) .WithApiVersion(new Version(1, 52)) @@ -167,9 +163,6 @@ public void Build_WithExplicitTransport_UsesProvidedFactoryAndOptions() Assert.Equal("value", transportFactory.LastClientOptions.Headers["x-test"]); Assert.Equal(new Version(1, 52), transportFactory.LastClientOptions.ApiVersion); Assert.Equal(TimeSpan.FromSeconds(1), transportFactory.LastClientOptions.Timeout); - - var actualFactory = GetPrivateField(client, "_handlerFactory"); - Assert.Same(transportFactory, actualFactory); } [Fact] @@ -190,22 +183,16 @@ public void WithTransportOptions_ReturnsTypedBuilder_ForBuiltInTransports() builder.WithTransportOptions(new UnixSocketTransportOptions())); } - private static T GetPrivateField(object instance, string fieldName) - { - var field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); - Assert.NotNull(field); - - var value = field!.GetValue(instance); - Assert.NotNull(value); - - return Assert.IsAssignableFrom(value); - } - private sealed class TestDockerClientBuilder : DockerClientBuilder { - public new ClientOptions ClientOptions => base.ClientOptions; + public new ClientOptions ClientOptions + => base.ClientOptions; + + public new ILogger Logger + => base.Logger; - public new ILogger Logger => base.Logger; + public IDockerHandlerFactory ResolveTransportFactory(Uri endpoint) + => base.ResolveTransportFactory(endpoint.Scheme); } private sealed class PassThroughAuthProvider(bool tlsEnabled) : IAuthProvider @@ -234,8 +221,6 @@ public Tuple CreateHandler(FakeTransportOptions transpo return new Tuple(new HttpClientHandler(), new Uri("http://localhost:2375")); } - public Tuple CreateHandler(Uri uri, IDockerClientConfiguration configuration, ILogger logger) - => throw new NotSupportedException(); public Tuple CreateHandler(ClientOptions clientOptions, ILogger logger) => throw new NotSupportedException();