Skip to content

Commit b660ea7

Browse files
authored
FtpServer provider support + runtime variables (#104)
* Added ftpserver implementation
1 parent 39360f2 commit b660ea7

19 files changed

+647
-6
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ To get more detailed information about Squadron, go to the [Squadron Docs](https
2626
- [x] MariaDB
2727
- [x] Neo4j
2828
- [x] S3 with [Minio](https://github.com/minio/minio)
29+
- [x] FtpServer with [fauria/vsftpd](https://github.com/fauria/docker-vsftpd)
2930

3031
### Cloud Providers
3132

src/Core/ContainerPortMapping.cs

+10
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,22 @@ public class ContainerPortMapping
1010
/// </summary>
1111
public int InternalPort { get; internal set; }
1212

13+
/// <summary>
14+
/// Internal port that value will be resolved from variable
15+
/// </summary>
16+
public string InternalPortVariableName { get; internal set; }
17+
1318
/// <summary>
1419
/// Gets the external port (Static).
1520
/// </summary>
1621
/// <value>
1722
/// The external port.
1823
/// </value>
1924
public int ExternalPort { get; internal set; }
25+
26+
/// <summary>
27+
/// External port that value will be resolved from variable
28+
/// </summary>
29+
public string ExternalPortVariableName { get; internal set; }
2030
}
2131
}

src/Core/ContainerResourceBuilder.cs

+105-1
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,115 @@ public ContainerResourceBuilder AddPortMapping(int internalPort, int externalPor
181181
_options.AdditionalPortMappings.Add(
182182
new ContainerPortMapping()
183183
{
184-
ExternalPort = externalPort, InternalPort = internalPort
184+
ExternalPort = externalPort,
185+
InternalPort = internalPort
185186
});
186187
return this;
187188
}
188189

190+
/// <summary>
191+
/// If you only want to expose one port, please use <see cref="InternalPort"/> and
192+
/// <see cref="ExternalPort"/> to so do!
193+
/// Exposes additional port mappings for this container.
194+
/// </summary>
195+
/// <param name="internalPort">
196+
/// The internal port of a container that shall be exposed.
197+
/// </param>
198+
/// <param name="externalPortVariableName">
199+
/// The external port number will be resolved before the container creation and stored in
200+
/// variable.
201+
/// Only provide an external port if a static external port is required.
202+
/// When the given external port is already in use by a container, the creation will fail.
203+
/// </param>
204+
/// <returns></returns>
205+
public ContainerResourceBuilder AddPortMapping(
206+
int internalPort,
207+
string externalPortVariableName)
208+
{
209+
_options.AdditionalPortMappings.Add(
210+
new ContainerPortMapping()
211+
{
212+
InternalPort = internalPort,
213+
ExternalPortVariableName = externalPortVariableName,
214+
});
215+
return this;
216+
}
217+
218+
219+
/// <summary>
220+
/// If you only want to expose one port, please use <see cref="InternalPort"/> and
221+
/// <see cref="ExternalPort"/> to so do!
222+
/// Exposes additional port mappings for this container.
223+
/// </summary>
224+
/// <param name="internalPortVariableName">
225+
/// The internal port number will be resolved before the container creation and stored
226+
/// in variable.
227+
/// </param>
228+
/// <param name="externalPort">
229+
/// The external static port of a container that the internal port will be mapped to.
230+
/// Defaults to 0, which will let the OS choose a free port for you.
231+
///
232+
/// Only provide an external port if a static external port is required.
233+
/// When the given external port is already in use by a container, the creation will fail.
234+
/// </param>
235+
/// <returns></returns>
236+
public ContainerResourceBuilder AddPortMapping(
237+
string internalPortVariableName,
238+
int externalPort = 0)
239+
{
240+
_options.AdditionalPortMappings.Add(
241+
new ContainerPortMapping()
242+
{
243+
InternalPortVariableName = internalPortVariableName,
244+
ExternalPort = externalPort,
245+
});
246+
return this;
247+
}
248+
249+
/// <summary>
250+
/// If you only want to expose one port, please use <see cref="InternalPort"/> and
251+
/// <see cref="ExternalPort"/> to so do!
252+
/// Exposes additional port mappings for this container.
253+
/// </summary>
254+
/// <param name="internalPortVariableName">
255+
/// The internal port number will be resolved before the container creation and stored
256+
/// in variable.
257+
/// </param>
258+
/// <param name="externalPortVariableName">
259+
/// The external port number will be resolved before the container creation and stored in
260+
/// variable.
261+
/// Only provide an external port if a static external port is required.
262+
/// When the given external port is already in use by a container, the creation will fail.
263+
/// </param>
264+
/// <returns></returns>
265+
public ContainerResourceBuilder AddPortMapping(
266+
string internalPortVariableName,
267+
string externalPortVariableName)
268+
{
269+
_options.AdditionalPortMappings.Add(
270+
new ContainerPortMapping()
271+
{
272+
InternalPortVariableName = internalPortVariableName,
273+
ExternalPortVariableName = externalPortVariableName
274+
});
275+
return this;
276+
}
277+
278+
/// <summary>
279+
/// Variables that will be resolved before container is created. They can be then
280+
/// used in port AdditionalPortMappings and EnvironmentVariables (referenced between
281+
/// '{}' brackets eg. {RUNTIME_VARIABLE_1}.)
282+
/// </summary>
283+
/// <param name="name"></param>
284+
/// <param name="type"></param>
285+
/// <returns></returns>
286+
public ContainerResourceBuilder AddVariable(string name, VariableType type)
287+
{
288+
_options.Variables.Add(new Variable(name, type));
289+
290+
return this;
291+
}
292+
189293
/// <summary>
190294
/// Wait timeout in seconds
191295
/// </summary>

src/Core/ContainerResourceSettings.cs

+6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ public class ContainerResourceSettings
3737
public IList<ContainerPortMapping> AdditionalPortMappings { get; internal set; } =
3838
new List<ContainerPortMapping>();
3939

40+
/// <summary>
41+
/// A list of variables resolved dynamically
42+
/// </summary>
43+
public IList<Variable> Variables { get; internal set; } =
44+
new List<Variable>();
45+
4046
/// <summary>
4147
/// Docker image tag
4248
/// </summary>

src/Core/DockerContainerManager.cs

+43-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public class DockerContainerManager : IDockerContainerManager
3434
.Handle<TimeoutException>()
3535
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(2));
3636

37+
private readonly VariableResolver _variableResolver;
38+
3739
/// <summary>
3840
/// Initializes a new instance of the <see cref="DockerContainerManager"/> class.
3941
/// </summary>
@@ -51,6 +53,7 @@ public DockerContainerManager(ContainerResourceSettings settings,
5153
)
5254
.CreateClient(Version.Parse("1.25"));
5355
_authConfig = GetAuthConfig();
56+
_variableResolver = new VariableResolver(_settings.Variables);
5457
}
5558

5659
private AuthConfig GetAuthConfig()
@@ -278,6 +281,8 @@ await _retryPolicy
278281

279282
private async Task CreateContainerAsync()
280283
{
284+
ResolveAndReplaceVariables();
285+
281286
var hostConfig = new HostConfig
282287
{
283288
PublishAllPorts = true,
@@ -325,7 +330,8 @@ private async Task CreateContainerAsync()
325330
Tty = false,
326331
HostConfig = hostConfig,
327332
Env = _settings.EnvironmentVariables,
328-
Cmd = _settings.Cmd
333+
Cmd = _settings.Cmd,
334+
ExposedPorts = allPorts.ToDictionary(k => $"{k.InternalPort}/tcp", v => new EmptyStruct()),
329335
};
330336

331337
try
@@ -362,6 +368,42 @@ await _retryPolicy
362368
}
363369
}
364370

371+
private void ResolveAndReplaceVariables()
372+
{
373+
ResolveAdditionalPortsVariables();
374+
ReplaceVariablesInEnvironmentalVariables();
375+
}
376+
377+
private void ReplaceVariablesInEnvironmentalVariables()
378+
{
379+
foreach (Variable variable in _settings.Variables)
380+
{
381+
_settings.EnvironmentVariables = _settings.EnvironmentVariables
382+
.Select(p => p.Replace(
383+
$"{{{variable.Name}}}",
384+
_variableResolver.Resolve<string>(variable.Name)))
385+
.ToList();
386+
}
387+
}
388+
389+
private void ResolveAdditionalPortsVariables()
390+
{
391+
foreach (ContainerPortMapping additionalPort in _settings.AdditionalPortMappings)
392+
{
393+
if (!string.IsNullOrEmpty(additionalPort.InternalPortVariableName))
394+
{
395+
additionalPort.InternalPort = _variableResolver.Resolve<int>(
396+
additionalPort.InternalPortVariableName);
397+
}
398+
399+
if (!string.IsNullOrEmpty(additionalPort.ExternalPortVariableName))
400+
{
401+
additionalPort.ExternalPort = _variableResolver.Resolve<int>(
402+
additionalPort.ExternalPortVariableName);
403+
}
404+
}
405+
}
406+
365407
public async Task<bool> ImageExists()
366408
{
367409
try
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
using System.Net.Sockets;
5+
6+
namespace Squadron
7+
{
8+
internal class DynamicPortVariableResolver: IVariableResolver
9+
{
10+
private static readonly IPEndPoint DefaultLoopbackEndpoint =
11+
new IPEndPoint(IPAddress.Loopback, port: 0);
12+
13+
public IDictionary<string, int> _resolvedPorts;
14+
15+
public DynamicPortVariableResolver()
16+
{
17+
_resolvedPorts = new Dictionary<string, int>();
18+
}
19+
20+
public bool CanHandle(VariableType type)
21+
{
22+
return type == VariableType.DynamicPort;
23+
}
24+
25+
public T Resolve<T>(string dynamicVariableName)
26+
{
27+
if (typeof(T) != typeof(int) && typeof(T) != typeof(string))
28+
{
29+
throw new NotSupportedException(
30+
$"The resolver {nameof(DynamicPortVariableResolver)} cannot " +
31+
$"resolve the type '{nameof(T)}'. Only the type 'int' is supported");
32+
}
33+
34+
int resolvedPort = GetOrResolvePort(dynamicVariableName);
35+
36+
return (T)Convert.ChangeType(resolvedPort, typeof(T));
37+
}
38+
39+
private int GetOrResolvePort(string dynamicVariableName)
40+
{
41+
if (_resolvedPorts.ContainsKey(dynamicVariableName))
42+
{
43+
return _resolvedPorts[dynamicVariableName];
44+
45+
}
46+
47+
var resolvedPort = GetAvailablePort();
48+
49+
_resolvedPorts.Add(dynamicVariableName, resolvedPort);
50+
51+
return resolvedPort;
52+
}
53+
54+
public static int GetAvailablePort()
55+
{
56+
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
57+
{
58+
socket.Bind(DefaultLoopbackEndpoint);
59+
return ((IPEndPoint)socket.LocalEndPoint).Port;
60+
}
61+
}
62+
}
63+
}

src/Core/IRuntimeVariableResolver.cs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Squadron
6+
{
7+
internal interface IVariableResolver
8+
{
9+
T Resolve<T>(string dynamicVariableName);
10+
bool CanHandle(VariableType type);
11+
}
12+
}

src/Core/RuntimeVariable.cs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Squadron
6+
{
7+
public class Variable
8+
{
9+
public Variable
10+
(
11+
string name,
12+
VariableType type)
13+
{
14+
Name = name;
15+
Type = type;
16+
}
17+
18+
public string Name { get; }
19+
public VariableType Type { get; }
20+
}
21+
}

src/Core/RuntimeVariableType.cs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Squadron
2+
{
3+
public enum VariableType
4+
{
5+
DynamicPort
6+
}
7+
}

0 commit comments

Comments
 (0)