Skip to content

Commit 711107c

Browse files
committed
Merge branch 'master' of github.com:dotnet/Docker.DotNet into ssh-transport
2 parents 2258100 + 4da1e18 commit 711107c

File tree

11 files changed

+381
-5
lines changed

11 files changed

+381
-5
lines changed

.gitattributes

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
# Autodetect text files
22
* text=auto
33

4-
# Definitively text files
5-
*.cs text

src/Docker.DotNet/DockerClient.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ internal DockerClient(DockerClientConfiguration configuration, Version requested
3939
System = new SystemOperations(this);
4040
Networks = new NetworkOperations(this);
4141
Secrets = new SecretsOperations(this);
42+
Configs = new ConfigOperations(this);
4243
Swarm = new SwarmOperations(this);
4344
Tasks = new TasksOperations(this);
4445
Volumes = new VolumeOperations(this);
@@ -152,6 +153,8 @@ await sock.ConnectAsync(new Microsoft.Net.Http.Client.UnixDomainSocketEndPoint(p
152153

153154
public ISecretsOperations Secrets { get; }
154155

156+
public IConfigOperations Configs { get; }
157+
155158
public ISwarmOperations Swarm { get; }
156159

157160
public ITasksOperations Tasks { get; }
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net.Http;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Docker.DotNet.Models;
7+
8+
namespace Docker.DotNet
9+
{
10+
internal class ConfigOperations : IConfigOperations
11+
{
12+
private readonly DockerClient _client;
13+
14+
internal ConfigOperations(DockerClient client)
15+
{
16+
this._client = client;
17+
}
18+
19+
async Task<IList<SwarmConfig>> IConfigOperations.ListConfigsAsync(CancellationToken cancellationToken)
20+
{
21+
var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Get, "configs", cancellationToken).ConfigureAwait(false);
22+
return this._client.JsonSerializer.DeserializeObject<IList<SwarmConfig>>(response.Body);
23+
}
24+
25+
async Task<SwarmCreateConfigResponse> IConfigOperations.CreateConfigAsync(SwarmCreateConfigParameters body, CancellationToken cancellationToken)
26+
{
27+
if (body == null)
28+
{
29+
throw new ArgumentNullException(nameof(body));
30+
}
31+
32+
var data = new JsonRequestContent<SwarmConfigSpec>(body.Config, this._client.JsonSerializer);
33+
var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Post, "configs/create", null, data, cancellationToken).ConfigureAwait(false);
34+
return this._client.JsonSerializer.DeserializeObject<SwarmCreateConfigResponse>(response.Body);
35+
}
36+
37+
async Task<SwarmConfig> IConfigOperations.InspectConfigAsync(string id, CancellationToken cancellationToken)
38+
{
39+
if (string.IsNullOrEmpty(id))
40+
{
41+
throw new ArgumentNullException(nameof(id));
42+
}
43+
44+
var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Get, $"configs/{id}", cancellationToken).ConfigureAwait(false);
45+
return this._client.JsonSerializer.DeserializeObject<SwarmConfig>(response.Body);
46+
}
47+
48+
Task IConfigOperations.RemoveConfigAsync(string id, CancellationToken cancellationToken)
49+
{
50+
if (string.IsNullOrEmpty(id))
51+
{
52+
throw new ArgumentNullException(nameof(id));
53+
}
54+
55+
return this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Delete, $"configs/{id}", cancellationToken);
56+
}
57+
}
58+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Collections.Generic;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Docker.DotNet.Models;
5+
6+
namespace Docker.DotNet
7+
{
8+
public interface IConfigOperations
9+
{
10+
/// <summary>
11+
/// List configs
12+
/// </summary>
13+
/// <remarks>
14+
/// 200 - No error.
15+
/// 500 - Server error.
16+
/// </remarks>
17+
Task<IList<SwarmConfig>> ListConfigsAsync(CancellationToken cancellationToken = default(CancellationToken));
18+
19+
/// <summary>
20+
/// Create a configs
21+
/// </summary>
22+
/// <remarks>
23+
/// 201 - No error.
24+
/// 406 - Server error or node is not part of a swarm.
25+
/// 409 - Name conflicts with an existing object.
26+
/// 500 - Server error.
27+
/// </remarks>
28+
Task<SwarmCreateConfigResponse> CreateConfigAsync(SwarmCreateConfigParameters body, CancellationToken cancellationToken = default(CancellationToken));
29+
30+
/// <summary>
31+
/// Inspect a configs
32+
/// </summary>
33+
/// <remarks>
34+
/// 200 - No error.
35+
/// 404 - Secret not found.
36+
/// 406 - Node is not part of a swarm.
37+
/// 500 - Server error.
38+
/// </remarks>
39+
/// <param name="id">ID of the config.</param>
40+
Task<SwarmConfig> InspectConfigAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
41+
42+
/// <summary>
43+
/// Remove a configs
44+
/// </summary>
45+
/// <remarks>
46+
/// 204 - No error.
47+
/// 404 - Secret not found.
48+
/// 500 - Server error.
49+
/// </remarks>
50+
/// <param name="id">ID of the config.</param>
51+
Task RemoveConfigAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
52+
}
53+
}

src/Docker.DotNet/Endpoints/ISwarmOperations.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Threading.Tasks;
33
using Docker.DotNet.Models;
44
using System.Threading;
5+
using System.IO;
56

67
namespace Docker.DotNet
78
{
@@ -154,6 +155,42 @@ public interface ISwarmOperations
154155
/// <param name="id">ID or name of service.</param>
155156
Task RemoveServiceAsync(string id, CancellationToken cancellationToken = default(CancellationToken));
156157

158+
/// <summary>
159+
/// Gets <c>stdout</c> and <c>stderr</c> logs from services.
160+
/// </summary>
161+
/// <param name="id">The ID or name of the service.</param>
162+
/// <param name="parameters">Specifics of how to perform the operation.</param>
163+
/// <param name="cancellationToken">When triggered, the operation will stop at the next available time, if possible.</param>
164+
/// <returns>A <see cref="Task"/> that will complete once all log lines have been read.</returns>
165+
/// <remarks>
166+
/// This method is only suited for services with the <c>json-file</c> or <c>journald</c> logging driver.
167+
///
168+
/// HTTP GET /services/(id)/logs
169+
///
170+
/// 101 - Logs returned as a stream.
171+
/// 200 - Logs returned as a string in response body.
172+
/// 404 - No such service.
173+
/// 500 - Server error.
174+
/// 503 - Node is not part of a swarm.
175+
/// </remarks>
176+
Task<Stream> GetServiceLogsAsync(string id, ServiceLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
177+
178+
/// <summary>
179+
/// Gets <c>stdout</c> and <c>stderr</c> logs from services.
180+
/// </summary>
181+
/// <param name="id">The ID or name of the service.</param>
182+
/// <param name="tty">Indicates whether the service was created with a TTY. If <see langword="false"/>, the returned stream is multiplexed.</param>
183+
/// <param name="parameters">Specifics of how to perform the operation.</param>
184+
/// <param name="cancellationToken">When triggered, the operation will stop at the next available time, if possible.</param>
185+
/// <returns>
186+
/// A <see cref="Task{TResult}"/> that resolves to a <see cref="MultiplexedStream"/>, which provides the log information.
187+
/// If the service wasn't created with a TTY, this stream is multiplexed.
188+
/// </returns>
189+
/// <remarks>
190+
/// This method is only suited for services with the <c>json-file</c> or <c>journald</c> logging driver.
191+
/// </remarks>
192+
Task<MultiplexedStream> GetServiceLogsAsync(string id, bool tty, ServiceLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken));
193+
157194
#endregion Services
158195

159196
#region Nodes

src/Docker.DotNet/Endpoints/SwarmOperations.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Docker.DotNet
88
using System.Threading.Tasks;
99
using System.Threading;
1010
using Models;
11+
using System.IO;
1112

1213
internal class SwarmOperations : ISwarmOperations
1314
{
@@ -156,7 +157,42 @@ async Task<ServiceUpdateResponse> ISwarmOperations.UpdateServiceAsync(string id,
156157
return this._client.JsonSerializer.DeserializeObject<ServiceUpdateResponse>(response.Body);
157158
}
158159

159-
async Task ISwarmOperations.UpdateSwarmAsync(SwarmUpdateParameters parameters, CancellationToken cancellationToken)
160+
public Task<Stream> GetServiceLogsAsync(string id, ServiceLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
161+
{
162+
if (string.IsNullOrEmpty(id))
163+
{
164+
throw new ArgumentNullException(nameof(id));
165+
}
166+
167+
if (parameters == null)
168+
{
169+
throw new ArgumentNullException(nameof(parameters));
170+
}
171+
172+
IQueryString queryParameters = new QueryString<ServiceLogsParameters>(parameters);
173+
return this._client.MakeRequestForStreamAsync(new[] { SwarmResponseHandler }, HttpMethod.Get, $"services/{id}/logs", queryParameters, cancellationToken);
174+
}
175+
176+
public async Task<MultiplexedStream> GetServiceLogsAsync(string id, bool tty, ServiceLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
177+
{
178+
if (string.IsNullOrEmpty(id))
179+
{
180+
throw new ArgumentNullException(nameof(id));
181+
}
182+
183+
if (parameters == null)
184+
{
185+
throw new ArgumentNullException(nameof(parameters));
186+
}
187+
188+
IQueryString queryParameters = new QueryString<ServiceLogsParameters>(parameters);
189+
190+
Stream result = await this._client.MakeRequestForStreamAsync(new[] { SwarmResponseHandler }, HttpMethod.Get, $"services/{id}/logs", queryParameters, cancellationToken).ConfigureAwait(false);
191+
192+
return new MultiplexedStream(result, !tty);
193+
}
194+
195+
async Task ISwarmOperations.UpdateSwarmAsync(SwarmUpdateParameters parameters, CancellationToken cancellationToken)
160196
{
161197
var query = new QueryString<SwarmUpdateParameters>(parameters ?? throw new ArgumentNullException(nameof(parameters)));
162198
var body = new JsonRequestContent<Spec>(parameters.Spec ?? throw new ArgumentNullException(nameof(parameters.Spec)), this._client.JsonSerializer);
@@ -211,7 +247,7 @@ async Task<NodeListResponse> ISwarmOperations.InspectNodeAsync(string id, Cancel
211247
async Task ISwarmOperations.RemoveNodeAsync(string id, bool force, CancellationToken cancellationToken)
212248
{
213249
if (string.IsNullOrEmpty(id)) throw new ArgumentNullException(nameof(id));
214-
var parameters = new NodeRemoveParameters {Force = force};
250+
var parameters = new NodeRemoveParameters { Force = force };
215251
var query = new QueryString<NodeRemoveParameters>(parameters);
216252
await this._client.MakeRequestAsync(new[] { SwarmResponseHandler }, HttpMethod.Delete, $"nodes/{id}", query, cancellationToken).ConfigureAwait(false);
217253
}

src/Docker.DotNet/IDockerClient.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public interface IDockerClient : IDisposable
2020

2121
ISecretsOperations Secrets { get; }
2222

23+
IConfigOperations Configs { get; }
24+
2325
ISwarmOperations Swarm { get; }
2426

2527
ITasksOperations Tasks { get; }

src/Docker.DotNet/Microsoft.Net.Http.Client/ContentLengthReadStream.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ public override int Read(byte[] buffer, int offset, int count)
9393
{
9494
return 0;
9595
}
96+
97+
if (_bytesRemaining == 0)
98+
{
99+
return 0;
100+
}
101+
96102
int toRead = (int)Math.Min(count, _bytesRemaining);
97103
int read = _inner.Read(buffer, offset, toRead);
98104
UpdateBytesRemaining(read);
@@ -106,6 +112,12 @@ public async override Task<int> ReadAsync(byte[] buffer, int offset, int count,
106112
{
107113
return 0;
108114
}
115+
116+
if (_bytesRemaining == 0)
117+
{
118+
return 0;
119+
}
120+
109121
cancellationToken.ThrowIfCancellationRequested();
110122
int toRead = (int)Math.Min(count, _bytesRemaining);
111123
int read = await _inner.ReadAsync(buffer, offset, toRead, cancellationToken);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Docker.DotNet.Models;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace Docker.DotNet.Tests
8+
{
9+
[Collection(nameof(TestCollection))]
10+
public class IConfigOperationsTests
11+
{
12+
private readonly DockerClientConfiguration _dockerClientConfiguration;
13+
private readonly DockerClient _dockerClient;
14+
private readonly TestOutput _output;
15+
public IConfigOperationsTests(TestFixture testFixture, ITestOutputHelper outputHelper)
16+
{
17+
_dockerClientConfiguration = testFixture.DockerClientConfiguration;
18+
_dockerClient = _dockerClientConfiguration.CreateClient();
19+
_output = new TestOutput(outputHelper);
20+
}
21+
22+
[Fact]
23+
public async void SwarmConfig_CanCreateAndRead()
24+
{
25+
var currentConfigs = await _dockerClient.Configs.ListConfigsAsync();
26+
27+
_output.WriteLine($"Current Configs: {currentConfigs.Count}");
28+
29+
var testConfigSpec = new SwarmConfigSpec
30+
{
31+
Name = $"Config-{Guid.NewGuid().ToString().Substring(1, 10)}",
32+
Labels = new Dictionary<string, string> { { "key", "value" } },
33+
Data = new List<byte> { 1, 2, 3, 4, 5 }
34+
};
35+
36+
var configParameters = new SwarmCreateConfigParameters
37+
{
38+
Config = testConfigSpec
39+
};
40+
41+
var createdConfig = await _dockerClient.Configs.CreateConfigAsync(configParameters);
42+
Assert.NotNull(createdConfig.ID);
43+
_output.WriteLine($"Config created: {createdConfig.ID}");
44+
45+
var configs = await _dockerClient.Configs.ListConfigsAsync();
46+
Assert.Contains(configs, c => c.ID == createdConfig.ID);
47+
_output.WriteLine($"Current Configs: {configs.Count}");
48+
49+
var configResponse = await _dockerClient.Configs.InspectConfigAsync(createdConfig.ID);
50+
51+
Assert.NotNull(configResponse);
52+
53+
Assert.Equal(configResponse.Spec.Name, testConfigSpec.Name);
54+
Assert.Equal(configResponse.Spec.Data, testConfigSpec.Data);
55+
Assert.Equal(configResponse.Spec.Labels, testConfigSpec.Labels);
56+
Assert.Equal(configResponse.Spec.Templating, testConfigSpec.Templating);
57+
58+
59+
_output.WriteLine($"Config created is the same.");
60+
61+
await _dockerClient.Configs.RemoveConfigAsync(createdConfig.ID);
62+
63+
await Assert.ThrowsAsync<Docker.DotNet.DockerApiException>(() => _dockerClient.Configs.InspectConfigAsync(createdConfig.ID));
64+
65+
66+
67+
}
68+
}
69+
}
70+

0 commit comments

Comments
 (0)