Search code examples
dockerasp.net-coredocker-composemicroservicesazure-pipelines

Is it possible to use Docker.DotNet to start a container programmatically on Azure Pipelines?


I have a microservices application and I wan't to start the whole stack to run integration tests. The idea is to be able to run the tests from Test Explorer within Visual Studio. So I don't want to create a shell script to be able to accomplish this.

The idea is to start all external microservice dependencies in containers and then run a TestServer of the system under test to receive the test requests so that the system can call other microservices.

I found this library and liked the idea of start containers programmatically to achieve the afore mentioned.

What I don't know, is if this would work running on an Azure Pipeline. What would be the correct docker engine address on Azure to initialize Docker.DotNet?

// Default Docker Engine on Windows
using Docker.DotNet;
DockerClient client = new DockerClientConfiguration(
    new Uri("npipe://./pipe/docker_engine"))
     .CreateClient();
// Default Docker Engine on Linux
using Docker.DotNet;
DockerClient client = new DockerClientConfiguration(
    new Uri("unix:///var/run/docker.sock"))
     .CreateClient();

Solution

  • I did a little research and found that both Windows and Linux images for Ms-hosted agents have Docker and Docker-Compose pre-installed, so it is possible to start containers using these agents.

    My problem was that I didn't realize that the Ops team configured a Linux agent. I was trying to execute using a Windows named pipe and not a Unix socket.

    I changed the Docker Engine API address accordingly and it worked.

    Here is my code in case someone is interested in creating Integration Tests that start containers for microservices dependencies:

    using Docker.DotNet;
    using System;
    using System.Runtime.InteropServices;
    
    namespace Relationship.Promotion.Applier.IntegrationTest.Helpers
    {
        public class DockerClientFactory
        {
            private static readonly Uri defaultWindowsDockerEngineUri = new Uri("npipe://./pipe/docker_engine");
            private static readonly Uri defaultLinuxDockerEngineUri = new Uri("unix:///var/run/docker.sock");
    
            public static DockerClient CreateInstance()
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    return new DockerClientConfiguration(defaultWindowsDockerEngineUri).CreateClient();
                else
                    return new DockerClientConfiguration(defaultLinuxDockerEngineUri).CreateClient();
            }
        }
    }
    
    namespace Relationship.Promotion.Applier.IntegrationTest
    {
        public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
        {
            private readonly DockerClient dockerClient;
            private readonly PromotionContainer promotionContainer;
            private readonly StoreContainer storeContainer;
            private readonly ConversionContainer conversionContainer;
    
            public CustomWebApplicationFactory()
            {
                try
                {
                    // Alternative to Docker.DotNet
                    // https://github.com/Deffiss/testenvironment-docker
                    dockerClient = DockerClientFactory.CreateInstance();
    
                    DockerContainerBase.CleanupOrphanedContainers(dockerClient).Wait(300.Seconds());
    
                    if (!dockerClient.Networks.ListNetworksAsync().Result.Select(n => n.Name).Contains("boti-network"))
                    {
                        var result = dockerClient.Networks.CreateNetworkAsync(new Docker.DotNet.Models.NetworksCreateParameters()
                        {
                            Name = "boti-network",
                            Driver = "bridge"
                        }).Result;
                    }
    
                    promotionContainer = new PromotionContainer(imageName: "promotion-engine-promotion-api");
                    promotionContainer.Start(dockerClient).Wait(300.Seconds());
    
                    conversionContainer = new ConversionContainer(imageName: "promotion-engine-conversion-api");
                    conversionContainer.Start(dockerClient).Wait(300.Seconds());
    
                    storeContainer = new StoreContainer("promotion-engine-store-api");
                    storeContainer.Start(dockerClient).Wait(300.Seconds());
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
    
                    throw;
                }
            }
    
            protected override IWebHostBuilder CreateWebHostBuilder()
            {
                return base.CreateWebHostBuilder().UseEnvironment("Docker");
            }
    
            protected override void Dispose(bool itIsSafeToAlsoFreeManagedObjects)
            {
                if (!itIsSafeToAlsoFreeManagedObjects) return;
    
                promotionContainer.Remove(dockerClient).Wait(300.Seconds());
                conversionContainer.Remove(dockerClient).Wait(300.Seconds());
                storeContainer.Remove(dockerClient).Wait(300.Seconds());
    
                dockerClient.Dispose();
    
                base.Dispose(itIsSafeToAlsoFreeManagedObjects);
            }
        }
    }