Skip to content

Commit 5e6c4ec

Browse files
authored
Merge pull request #390 from serverlessworkflow/fix-runner-container-support
Fix the Runner to inject and configure an IContainerPlatform
2 parents caa5139 + 6436706 commit 5e6c4ec

File tree

20 files changed

+311
-54
lines changed

20 files changed

+311
-54
lines changed

deployments/docker-compose/docker-compose.build.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ services:
2929
CONNECTIONSTRINGS__REDIS: ${GARNET_URI}
3030
SYNAPSE_OPERATOR_NAMESPACE: default
3131
SYNAPSE_OPERATOR_NAME: operator-1
32-
SYNAPSE_OPERATOR_RUNNER_API: http://api:8080
32+
SYNAPSE_RUNNER_API: http://api:8080
33+
SYNAPSE_RUNNER_LIFECYCLE_EVENTS: true
3334
DOCKER_HOST: unix:///var/run/docker.sock
3435
extra_hosts:
3536
- "host.docker.internal:host-gateway"

deployments/docker-compose/docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ services:
2525
CONNECTIONSTRINGS__REDIS: ${GARNET_URI}
2626
SYNAPSE_OPERATOR_NAMESPACE: default
2727
SYNAPSE_OPERATOR_NAME: operator-1
28-
SYNAPSE_OPERATOR_RUNNER_API: http://api:8080
28+
SYNAPSE_RUNNER_API: http://api:8080
29+
SYNAPSE_RUNNER_LIFECYCLE_EVENTS: true
2930
DOCKER_HOST: unix:///var/run/docker.sock
3031
extra_hosts:
3132
- "host.docker.internal:host-gateway"

src/core/Synapse.Core.Infrastructure.Containers.Docker/Configuration/DockerContainerPlatformOptions.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14+
using Synapse.Resources;
15+
using System.Runtime.Serialization;
16+
using System.Text.Json.Serialization;
17+
using YamlDotNet.Serialization;
18+
1419
namespace Synapse.Core.Infrastructure.Containers.Configuration;
1520

1621
/// <summary>
@@ -20,8 +25,20 @@ public class DockerContainerPlatformOptions
2025
{
2126

2227
/// <summary>
23-
/// Gets/sets the Docker runtime host's network name
28+
/// Gets the default network to connect containers to
29+
/// </summary>
30+
public const string DefaultNetwork = "synapse";
31+
32+
/// <summary>
33+
/// Gets/sets the Docker API to use
34+
/// </summary>
35+
[DataMember(Order = 1, Name = "api"), JsonPropertyOrder(1), JsonPropertyName("api"), YamlMember(Order = 1, Alias = "api")]
36+
public virtual DockerApiConfiguration Api { get; set; } = new();
37+
38+
/// <summary>
39+
/// Gets/sets the network to connect containers to, if any
2440
/// </summary>
25-
public virtual string Network { get; set; } = "synapse";
41+
[DataMember(Order = 2, Name = "network"), JsonPropertyOrder(2), JsonPropertyName("network"), YamlMember(Order = 2, Alias = "network")]
42+
public virtual string? Network { get; set; } = DefaultNetwork;
2643

2744
}

src/core/Synapse.Core.Infrastructure.Containers.Docker/DockerContainerPlatform.cs

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ namespace Synapse.Core.Infrastructure.Containers;
2424
/// </summary>
2525
/// <param name="logger">The service used to perform logging</param>
2626
/// <param name="hostEnvironment">The current <see cref="IHostEnvironment"/></param>
27-
/// <param name="dockerClient">The service used to interact with the Docker API</param>
2827
/// <param name="options">The current <see cref="DockerContainerPlatformOptions"/></param>
29-
public class DockerContainerPlatform(ILogger<DockerContainerPlatform> logger, IHostEnvironment hostEnvironment, IDockerClient dockerClient, IOptions<DockerContainerPlatformOptions> options)
30-
: IHostedService, IContainerPlatform
28+
public class DockerContainerPlatform(ILogger<DockerContainerPlatform> logger, IHostEnvironment hostEnvironment, IOptions<DockerContainerPlatformOptions> options)
29+
: IHostedService, IContainerPlatform, IDisposable, IAsyncDisposable
3130
{
3231

32+
bool _disposed;
33+
3334
/// <summary>
3435
/// Gets the service used to perform logging
3536
/// </summary>
@@ -43,7 +44,7 @@ public class DockerContainerPlatform(ILogger<DockerContainerPlatform> logger, IH
4344
/// <summary>
4445
/// Gets the service used to interact with the Docker API
4546
/// </summary>
46-
protected IDockerClient DockerClient { get; } = dockerClient;
47+
protected IDockerClient? Docker { get; set; }
4748

4849
/// <summary>
4950
/// Gets the current <see cref="DockerContainerPlatformOptions"/>
@@ -53,40 +54,42 @@ public class DockerContainerPlatform(ILogger<DockerContainerPlatform> logger, IH
5354
/// <inheritdoc/>
5455
public virtual async Task StartAsync(CancellationToken cancellationToken)
5556
{
57+
var dockerConfiguration = new DockerClientConfiguration(this.Options.Api.Endpoint);
58+
this.Docker = dockerConfiguration.CreateClient(string.IsNullOrWhiteSpace(this.Options.Api.Version) ? null : System.Version.Parse(this.Options.Api.Version!));
5659
if (!this.Environment.RunsInDocker()) return;
5760
var containerShortId = System.Environment.MachineName;
58-
var containerId = (await this.DockerClient.Containers.InspectContainerAsync(containerShortId, cancellationToken)).ID;
61+
var containerId = (await this.Docker.Containers.InspectContainerAsync(containerShortId, cancellationToken)).ID;
5962
var response = null as NetworkResponse;
6063
try
6164
{
62-
response = await this.DockerClient.Networks.InspectNetworkAsync(this.Options.Network, cancellationToken);
65+
response = await this.Docker.Networks.InspectNetworkAsync(this.Options.Network, cancellationToken);
6366
}
6467
catch (DockerNetworkNotFoundException)
6568
{
66-
await this.DockerClient.Networks.CreateNetworkAsync(new() { Name = this.Options.Network }, cancellationToken);
69+
await this.Docker.Networks.CreateNetworkAsync(new() { Name = this.Options.Network }, cancellationToken);
6770
}
6871
finally
6972
{
70-
if (response == null || !response!.Containers.ContainsKey(containerId))
71-
await this.DockerClient.Networks.ConnectNetworkAsync(this.Options.Network, new NetworkConnectParameters() { Container = containerId }, cancellationToken);
73+
if (response == null || !response!.Containers.ContainsKey(containerId)) await this.Docker.Networks.ConnectNetworkAsync(this.Options.Network, new NetworkConnectParameters() { Container = containerId }, cancellationToken);
7274
}
7375
}
7476

7577
/// <inheritdoc/>
7678
public virtual async Task<IContainer> CreateAsync(ContainerProcessDefinition definition, CancellationToken cancellationToken = default)
7779
{
7880
ArgumentNullException.ThrowIfNull(definition);
81+
if (this.Docker == null) throw new NullReferenceException("The DockerContainerPlatform has not been properly initialized");
7982
try
8083
{
81-
await this.DockerClient.Images.InspectImageAsync(definition.Image, cancellationToken).ConfigureAwait(false);
84+
await this.Docker.Images.InspectImageAsync(definition.Image, cancellationToken).ConfigureAwait(false);
8285
}
8386
catch (DockerApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
8487
{
8588
var downloadProgress = new Progress<JSONMessage>();
8689
var imageComponents = definition.Image.Split(':');
8790
var imageName = imageComponents[0];
8891
var imageTag = imageComponents.Length > 1 ? imageComponents[1] : null;
89-
await this.DockerClient.Images.CreateImageAsync(new() { FromImage = imageName, Tag = imageTag }, new(), downloadProgress, cancellationToken).ConfigureAwait(false);
92+
await this.Docker.Images.CreateImageAsync(new() { FromImage = imageName, Tag = imageTag }, new(), downloadProgress, cancellationToken).ConfigureAwait(false);
9093
}
9194
var parameters = new CreateContainerParameters()
9295
{
@@ -99,16 +102,60 @@ public virtual async Task<IContainer> CreateAsync(ContainerProcessDefinition def
99102
Binds = definition.Volumes?.Select(e => $"{e.Key}={e.Value}")?.ToList() ?? []
100103
}
101104
};
102-
var response = await this.DockerClient.Containers.CreateContainerAsync(parameters, cancellationToken).ConfigureAwait(false);
103-
if (this.Environment.RunsInDocker()) await this.DockerClient.Networks.ConnectNetworkAsync(this.Options.Network, new NetworkConnectParameters() { Container = response.ID }, cancellationToken);
105+
var response = await this.Docker.Containers.CreateContainerAsync(parameters, cancellationToken).ConfigureAwait(false);
106+
if (this.Environment.RunsInDocker()) await this.Docker.Networks.ConnectNetworkAsync(this.Options.Network, new NetworkConnectParameters() { Container = response.ID }, cancellationToken);
104107
foreach (var warning in response.Warnings)
105108
{
106109
this.Logger.LogWarning(warning);
107110
}
108-
return new DockerContainer(response.ID, this.DockerClient);
111+
return new DockerContainer(response.ID, this.Docker);
109112
}
110113

111114
/// <inheritdoc/>
112115
public virtual Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
113116

117+
/// <summary>
118+
/// Disposes of the <see cref="DockerContainerPlatform"/>
119+
/// </summary>
120+
/// <param name="disposing">A boolean indicating whether or not the <see cref="DockerContainerPlatform"/> is being disposed of</param>
121+
/// <returns>A new <see cref="ValueTask"/></returns>
122+
protected virtual async ValueTask DisposeAsync(bool disposing)
123+
{
124+
if (this._disposed) return;
125+
if (disposing)
126+
{
127+
this.Docker?.Dispose();
128+
}
129+
this._disposed = true;
130+
await Task.CompletedTask.ConfigureAwait(false);
131+
}
132+
133+
/// <inheritdoc/>
134+
public async ValueTask DisposeAsync()
135+
{
136+
await this.DisposeAsync(disposing: true).ConfigureAwait(false);
137+
GC.SuppressFinalize(this);
138+
}
139+
140+
/// <summary>
141+
/// Disposes of the <see cref="DockerContainerPlatform"/>
142+
/// </summary>
143+
/// <param name="disposing">A boolean indicating whether or not the <see cref="DockerContainerPlatform"/> is being disposed of</param>
144+
protected virtual void Dispose(bool disposing)
145+
{
146+
if (this._disposed) return;
147+
if (disposing)
148+
{
149+
this.Docker?.Dispose();
150+
}
151+
this._disposed = true;
152+
}
153+
154+
/// <inheritdoc/>
155+
public void Dispose()
156+
{
157+
this.Dispose(disposing: true);
158+
GC.SuppressFinalize(this);
159+
}
160+
114161
}

src/core/Synapse.Core.Infrastructure.Containers.Docker/Extensions/DockerContainerServiceCollectionExtensions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,9 @@ public static class DockerContainerServiceCollectionExtensions
2828
/// Adds and configures a new <see cref="DockerContainerPlatform"/>
2929
/// </summary>
3030
/// <param name="services">The <see cref="IServiceCollection"/> to configure</param>
31-
/// <param name="dockerClientConfiguration">The <see cref="DockerClientConfiguration"/> used to configure the <see cref="IDockerClient"/> to use, if any</param>
3231
/// <returns>The configured <see cref="IServiceCollection"/></returns>
33-
public static IServiceCollection AddDockerContainerPlatform(this IServiceCollection services, DockerClientConfiguration? dockerClientConfiguration = null)
32+
public static IServiceCollection AddDockerContainerPlatform(this IServiceCollection services)
3433
{
35-
dockerClientConfiguration ??= new DockerClientConfiguration();
36-
services.TryAddSingleton<IDockerClient>(dockerClientConfiguration.CreateClient());
3734
services.TryAddSingleton<DockerContainerPlatform>();
3835
services.AddSingleton<IContainerPlatform>(provider => provider.GetRequiredService<DockerContainerPlatform>());
3936
services.AddSingleton<IHostedService>(provider => provider.GetRequiredService<DockerContainerPlatform>());
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright © 2024-Present The Synapse Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"),
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
namespace Synapse;
15+
16+
/// <summary>
17+
/// Enumerates all supported container platforms
18+
/// </summary>
19+
public static class ContainerPlatform
20+
{
21+
22+
/// <summary>
23+
/// Gets the Docker container platform
24+
/// </summary>
25+
public const string Docker = "docker";
26+
/// <summary>
27+
/// Gets the Kubernetes container platform
28+
/// </summary>
29+
public const string Kubernetes = "kubernetes";
30+
31+
/// <summary>
32+
/// Gets a new <see cref="IEnumerable{T}"/> containing all default container platforms
33+
/// </summary>
34+
/// <returns>A new <see cref="IEnumerable{T}"/> containing all default container platforms</returns>
35+
public static IEnumerable<string> AsEnumerable()
36+
{
37+
yield return Docker;
38+
yield return Kubernetes;
39+
}
40+
41+
}

src/core/Synapse.Core/OperatorRuntimeMode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ public static IEnumerable<string> AsEnumerable()
3838
yield return Containerized;
3939
}
4040

41-
}
41+
}

src/core/Synapse.Core/Resources/CorrelationLifetime.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ public static IEnumerable<string> AsEnumerable()
3939
yield return Ephemeral;
4040
}
4141

42-
}
42+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright © 2024-Present The Synapse Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"),
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
using System.Runtime.InteropServices;
15+
16+
namespace Synapse.Resources;
17+
18+
/// <summary>
19+
/// Represents an object used to configure the Docker API to use
20+
/// </summary>
21+
[DataContract]
22+
public record DockerApiConfiguration
23+
{
24+
25+
/// <summary>
26+
/// Gets/sets the endpoint of the Docker API to use
27+
/// </summary>
28+
[DataMember(Order = 1, Name = "endpoint"), JsonPropertyOrder(1), JsonPropertyName("endpoint"), YamlMember(Order = 1, Alias = "endpoint")]
29+
public virtual Uri Endpoint { get; set; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new Uri("npipe://./pipe/docker_engine") : new Uri("unix:/var/run/docker.sock");
30+
31+
/// <summary>
32+
/// Gets/sets the version of the Docker API to use
33+
/// </summary>
34+
[DataMember(Order = 2, Name = "version"), JsonPropertyOrder(2), JsonPropertyName("version"), YamlMember(Order = 2, Alias = "version")]
35+
public virtual string? Version { get; set; }
36+
37+
}

src/core/Synapse.Core/Resources/RunnerDefinition.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,22 @@ public record RunnerDefinition
4040
}
4141
};
4242

43+
/// <summary>
44+
/// Gets/sets the container platform used by runners to spawn containers
45+
/// </summary>
46+
[Required]
47+
[DataMember(Order = 3, Name = "containerPlatform"), JsonPropertyOrder(3), JsonPropertyName("containerPlatform"), YamlMember(Order = 3, Alias = "containerPlatform")]
48+
public virtual string ContainerPlatform { get; set; } = Synapse.ContainerPlatform.Docker;
49+
4350
/// <summary>
4451
/// Gets/sets the endpoint that references the base address and authentication policy for the Synapse API used by runners
4552
/// </summary>
46-
[DataMember(Order = 3, Name = "certificates"), JsonPropertyOrder(3), JsonPropertyName("certificates"), YamlMember(Order = 3, Alias = "certificates")]
53+
[DataMember(Order = 4, Name = "certificates"), JsonPropertyOrder(4), JsonPropertyName("certificates"), YamlMember(Order = 4, Alias = "certificates")]
4754
public virtual CertificateValidationStrategyDefinition? Certificates { get; set; }
48-
55+
56+
/// <summary>
57+
/// Gets/sets a boolean indicating whether or not runners spawned by the configured Synapse Operators should publish lifecycle events
58+
/// </summary>
59+
[DataMember(Order = 5, Name = "publishLifecycleEvents"), JsonPropertyOrder(5), JsonPropertyName("publishLifecycleEvents"), YamlMember(Order = 5, Alias = "publishLifecycleEvents")]
60+
public virtual bool? PublishLifecycleEvents { get; set; } = true;
4961
}

0 commit comments

Comments
 (0)