Versions
- Docker Engine CE v19.03.8
- dotnet sdk v3.1.301
- Docker.DotNet v3.125.2
- xUnit v2.4.0
Setup
I'm using the Docker.DotNet package to manage container dependencies for the Functional Tests in my ASP.NET Core API project
I've used the code from this blog (https://danieldonbavand.com/dockerdotnet/), to create a class to manage the interactions with the Docker API from within my test project - I've added this code to the end of the post because it's relatively long.
Problem
When I run the dotnet test command from my terminal everything works as expected. However, I want to run these tests when I build my Dockerfile.
When I run docker build -t my-project . I get the following error:
System.Net.Http.HttpRequestException : Connection failed ---- System.Net.Internals.SocketExceptionFactory+ExtendedSocketException : Cannot assign requested address /var/run/docker.sock Stack Trace: at Microsoft.Net.Http.Client.ManagedHandler.ProcessRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Microsoft.Net.Http.Client.ManagedHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task
1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at Docker.DotNet.DockerClient.PrivateMakeRequestAsync(TimeSpan timeout, HttpCompletionOption completionOption, HttpMethod method, String path, IQueryString queryString, IDictionary2 headers, IRequestContent data, CancellationToken cancellationToken) at Docker.DotNet.DockerClient.PrivateMakeRequestAsync(TimeSpan timeout, HttpCompletionOption completionOption, HttpMethod method, String path, IQueryString queryString, IDictionary2 headers, IRequestContent data, CancellationToken cancellationToken) at Docker.DotNet.DockerClient.MakeRequestAsync(IEnumerable1 errorHandlers, HttpMethod method, String path, IQueryString queryString, IRequestContent body, IDictionary2 headers, TimeSpan timeout, CancellationToken token) at Docker.DotNet.ContainerOperations.CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken) at My.FunctionalTests.Helpers.DockerSetup.StartContainerAsync(String image, String tag, String hostPort, String containerPort, IList1 environmentVariables) in /src/My.FunctionalTests/Helpers/Docker/DockerSetup.cs:line 34 at My.FunctionalTests.Helpers.DockerSetupExtensions.StartLocalStackAsync(DockerSetup docker, String services) in /src/My.FunctionalTests/Helpers/Docker/DockerSetupExtentions.cs:line 12 at My.FunctionalTests.MyTests.GetAsync_Valid_Success(String url) in /src/My.FunctionalTests/MyTests.cs:line 36
Which after doing some googling it is possible this is caused by a localhost conflict. I'm a bit out of my depth with this one so was wondering
- Why does this error throw?
- What can I do to solve it?
Below is the relevant code:
Dockerfile
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
# Copy csproj and restore as distinct layers
COPY ./My.sln .
COPY ./My.API/My.API.csproj ./My.API/My.API.csproj
COPY ./My.FunctionalTests/My.FunctionalTests.csproj ./My.FunctionalTests/My.FunctionalTests.csproj
RUN dotnet restore
# Copy everything else and build
COPY . .
RUN dotnet publish -c Release -o /app
# Functional tests
FROM build as functionaltest
WORKDIR /src/My.FunctionalTests
RUN dotnet test
DockerSetup.cs
public class DockerSetup
{
private readonly DockerClient _dockerClient;
public DockerSetup()
{
_dockerClient = new DockerClientConfiguration(new Uri(DockerApiUri())).CreateClient();
}
private static string DockerApiUri()
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
return "npipe://./pipe/docker_engine";
}
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
if (isLinux || isMacOS)
{
return "unix:/var/run/docker.sock";
}
throw new Exception("Was unable to determine what OS this is running on, does not appear to be Windows, MacOS or Linux!?");
}
public async Task PullImageAsync(string image, string tag)
{
await _dockerClient.Images
.CreateImageAsync(new ImagesCreateParameters
{
FromImage = image,
Tag = tag
},
new AuthConfig(),
new Progress<JSONMessage>());
}
public async Task<string> StartContainerAsync(string image, string tag, string hostPort, string containerPort, IList<string> environmentVariables)
{
var response = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters
{
Image = $"{image}:{tag}",
ExposedPorts = new Dictionary<string, EmptyStruct>
{
{
containerPort, default(EmptyStruct)
}
},
HostConfig = new HostConfig
{
PortBindings = new Dictionary<string, IList<PortBinding>>
{
{containerPort, new List<PortBinding> {new PortBinding {HostPort = hostPort}}}
},
PublishAllPorts = false
},
Env = environmentVariables
});
await _dockerClient.Containers.StartContainerAsync(response.ID, null);
return response.ID;
}
public async Task StopContainerAsync(string containerId)
{
if (containerId != null)
{
await _dockerClient.Containers.KillContainerAsync(containerId, new ContainerKillParameters());
}
}
}
I then use this code in the test as so:
MyTests.cs
public class MyTests
: IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
private readonly HttpClient _client;
public DockerSetup DockerSetup { get; private set; }
public MyTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient();
DockerSetup = new DockerSetup();
}
[Theory]
[InlineData("s3")]
public async Task GetAsync_Valid_Success(string url)
{
// Assemble
string containerId = await DockerSetup.StartContainerAsync(
"localstack/localstack",
"latest",
"4566",
"4566",
new List<string> { $"SERVICES=s3" }
);
// Act
var response = await _client.GetAsync(url);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json; charset=utf-8",
response.Content.Headers.ContentType.ToString());
// Cleanup
await DockerSetup.StopContainerAsync(containerId);
}
}