All posts by Brant Burnett

http://twitter.com/btburnett3

Remove Untagged Images From Docker for Windows

Here’s a quick note for Docker for Windows users. This is based on Jim Hoskin’s post Remove Untagged Images From Docker.

I’ve simply reformatted his two scripts for use on Docker for Windows via Powershell. To delete all stopped containers:

docker ps -a -q | % { docker rm $_ }

To delete all untagged local images:

docker images | ConvertFrom-String | where {$_.P2 -eq "<none>"} | % { docker rmi $_.P3 }

Restful API Integration Testing For .NET Core Using Docker

Overview

I love unit tests. There’s nothing quite like writing a class and feeling 100% confident it will work as described because the tests are all passing. But integration testing is also important. Sometimes I need to test the full stack and make sure it works as described.

For a recent project I have been creating .NET Core RESTful microservices. Along with these services I have been creating client SDKs that abstract away the RESTful requests. The client SDK is then published via an internal Nuget server. This makes it easy for services in the architecture to communicate with each other using the SDK rather than using HttpClient. For easy organization and version control, the SDK is located in the same solution as the service.

The question that followed quickly was “How do I test this SDK?” Unit tests can help cover some of the functionality, but they don’t test the full stack from an SDK consumer request through HTTP to the actual service. I could make a test application that uses the SDK to call the service, but this would be cumbersome.

What if I could write tests using a standard test framework like xUnit.net or NUnit? Such tests could be easily executed in Visual Studio or even in a continuous integration step. But if I use this kind of test framework, how do I easily make sure that the latest version of the service is up and running so my client SDK tests can use it? And what about other services called by the service under test?

Enter Docker

Docker is a great tool that serves many purposes in the development pipeline, and since the release of .NET Core I’ve starting to fall in love with it. Integration testing is another great use for Docker. Using Docker it’s possible to launch the service being tested in a container (along with any dependencies) as part of the client SDK integration tests. After the tests are complete, the containers are stopped and the resources are freed.

Preparing The Service

The first step is to make sure the service can be started as a Docker container. Here’s a brief summary of the steps I followed:

  1. Ensure that Hyper-V is enabled in Windows
  2. Install Docker for Windows
  3. Configure a Shared Drive in Docker for the drive where the application lives
  4. Install Visual Studio Tools for Docker
  5. Ensure that Docker is started (it can be configured to autostart on login)
  6. Right click on the project in Visual Studio and Add Docker Support
Add Docker Support in Visual Studio

Configuring Docker Compose

When running on a development machine from within Visual Studio, a Docker Compose file (docker-compose.yml) is used to control the Docker containers which are created. The default file is a great starting point, but it will require some tweaking:

version: '2'

services:
  myservice:
    image: user/myservice${TAG}
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80"

The first thing to do is change to a static port mapping. This will simplify accessing the service from the tests. By changing the port definition from “80″ to “8080:80″ the tests will be able to access the service on port 8080. Of course, any unused port will work.

version: '2'

services:
  myservice:
    image: user/myservice${TAG}
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:80"

Next, the file needs to be updated to deploy additional dependent services. This could get rather complicated if there are a lot of dependencies, but here’s an example of adding a single dependency with a link from the service.

version: '2'

services:
  myservice:
    image: user/myservice${TAG}
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    links:
      - mydependency
  mydependency:
    image: user/mydependency:latest
    expose:
      - "80"

Now when the service is started, it will also start the “mydependency” service. It will be accessible at http://mydependency/ from within “myservice”. Of course, how the dependencies communicate with each other can be adjusted depending on the architecture. The “image:” value should also be adjusted to refer to the correct Docker registry where the dependency is hosted.

Overriding With Test Specific Configuration

The settings in docker-compose.yml are generic and used to define the basic configuration for running the service. Additionally, docker-compose.dev.debug.yml and docker-compose.dev.release.yml provide overrides specific to running in Debug and Release mode in Visual Studio.

However, these configurations don’t start the service in the way most containers are started. They start the container as an executable that does nothing and never exits: “tail -f /dev/null”. Then the service is run out of band using “docker exec”. The container doesn’t even contain the service, it’s just reading the files from the host hard drive using Docker volumes. This is great for debugging in Visual Studio, but I found it problematic for running automated integration tests.

To address this, create an additional YAML file, docker-compose.test.yml, in the root of the service project. This file overrides the build context path so that it collects the service from the publication directory (which is created by using “dotnet publish”). It can also configure environment variables within the container which can override the default ASP.NET Core configuration from appsettings.json.

version: '2'

services:
  myservice:
    build:
      context: bin/${CONFIGURATION}/netcoreapp1.0/publish
    environment:
      - ASPNETCORE_ENVIRONMENT=Development

Starting The Service When Running Tests

To start the container, some commands should be run during test startup. How these commands are run will vary depending on the test framework, but the basic list is:

  1. Run “dotnet publish” against the service to build and publish the application
  2. Run “docker-compose build” to build an up-to-date image for the application
  3. Run “docker-compose up” to start the containers
  4. After tests are complete, run “docker-compose down” to shutdown and remove the containers

If xUnit.net is being used as the test framework, this can be done using a collection fixture. First, define a test collection:

using System;
using Xunit;

namespace IntegrationTests
{
    [CollectionDefinition("ClientTests")]
    public class ClientTestCollection : ICollectionFixture<ServiceContainersFixture>
    {
    }
}

Note above the ClientTestCollection class is implementing ICollectionFixture. There must also be a definition for the referenced ServiceContainersFixture. This class will be created once for all tests in the test collection, and then disposed when they are complete.

Note: The example below assumes that “dotnet” and “docker-compose” are accessible in the system PATH. They should be by default.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace IntegrationTests
{
    public class ServiceContainersFixture : IDisposable
    {
        // Name of the service
        private const string ServiceName = "myservice";

        // Relative path to the root folder of the service project.
        // The path is relative to the target folder for the test DLL,
        // i.e. /test/MyTests/bin/Debug
        private const string ServicePath = "../../../../src/MyService";

        // Tag used for ${TAG} in docker-compose.yml
        private const string Tag = "test";

        // This URL should return 200 once the service is up and running
        private const string TestUrl = "http://localhost:8080/myservice/ping";

        // How long to wait for the test URL to return 200 before giving up
        private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(60);

#if DEBUG
        private const string Configuration = "Debug";
#else
        private const string Configuration = "Release";
#endif

        public ApplicationFixture()
        {
            Build();

            StartContainers();

            var started = WaitForService().Result;

            if (!started)
            {
                throw new Exception($"Startup failed, could not get '{TestUrl}' after trying for '{TestTimeout}'");
            }
        }

        private void Build()
        {
            var process = Process.Start(new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = $"publish {ServicePath} --configuration {Configuration}"
            });

            process.WaitForExit();
            Assert.Equal(0, process.ExitCode);
        }

        private void StartContainers()
        {
            // First build the Docker container image

            var processStartInfo = new ProcessStartInfo
            {
                FileName = "docker-compose",
                Arguments =
                    $"-f {ServicePath}/docker-compose.yml -f {ServicePath}/docker-compose.test.yml build"
            };
            AddEnvironmentVariables(processStartInfo);

            var process = Process.Start(processStartInfo);

            process.WaitForExit();
            Assert.Equal(0, process.ExitCode);

            // Now start the docker containers

            processStartInfo = new ProcessStartInfo
            {
                FileName = "docker-compose",
                Arguments =
                    $"-f {ServicePath}/docker-compose.yml -f {ServicePath}/docker-compose.test.yml -p {ServiceName} up -d"
            };
            AddEnvironmentVariables(processStartInfo);

            process = Process.Start(processStartInfo);

            process.WaitForExit();
            Assert.Equal(0, process.ExitCode);
        }

        private void StopContainers()
        {
            // Run docker-compose down to stop the containers
            // Note that "--rmi local" deletes the images as well to keep the machine clean
            // But it does so by deleting all untagged images, which may not be desired in all cases

            var processStartInfo = new ProcessStartInfo
            {
                FileName = "docker-compose",
                Arguments =
                    $"-f {ServicePath}/docker-compose.yml -f {ServicePath}/docker-compose.test.yml -p {ServiceName} down --rmi local"
            };
            AddEnvironmentVariables(processStartInfo);

            var process = Process.Start(processStartInfo);

            process.WaitForExit();
            Assert.Equal(0, process.ExitCode);
        }

        private void AddEnvironmentVariables(ProcessStartInfo processStartInfo)
        {
            processStartInfo.Environment["TAG"] = Tag;
            processStartInfo.Environment["CONFIGURATION"] = Configuration;
            processStartInfo.Environment["COMPUTERNAME"] = Environment.MachineName;
        }

        private async Task<bool> WaitForService()
        {
            using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(1)})
            {
                var startTime = DateTime.Now;
                while (DateTime.Now - startTime < TestTimeout)
                {
                    try
                    {
                        var response = await client.GetAsync(new Uri(TestUrl)).ConfigureAwait(false);
                        if (response.IsSuccessStatusCode)
                        {
                            return true;
                        }
                    }
                    catch
                    {
                        // Ignore exceptions, just retry
                    }

                    await Task.Delay(1000).ConfigureAwait(false);
                }
            }

            return false;
        }

        public void Dispose()
        {
            StopContainers();
        }
    }
}

Note: Before each call to docker-compose, AddEnvironmentVariables is being called to set some environment variables in ProcessStartInfo. These are used by docker-compose to perform substitutions in the YAML file. For example, ${COMPUTERNAME} will be replaced with the name of the development computer. This could be easily extended with other environment variables as needed.

To specify that a test class needs the containers to be running, apply the Collection attribute to the test class. Note how the string “ClientTests” matches the CollectionDefinition attribute used earlier on ClientTestCollection.

[Collection("ClientTests")]
public class MyTests
{
    // Tests here...
}

Conclusion

It’s now possible to run .NET Core integration tests that depend on running services directly in Visual Studio using your test runner of choice (I use Resharper). It will build the .NET Core service, start it and its dependencies in Docker, run the integration tests, and then perform cleanup. This could also be done as part of a continuous integration process, so long as the test machines have Docker installed and access to any required Docker registries. Best of all, this simultaneously tests both the service and the client SDK, exposing problems in either.

Cancelling Long Running Couchbase N1QL Queries

Overview

The recent release of the Couchbase .NET SDK 2.4.0 has added many new features.  There is a minor feature, however, that is worth a mention.  It’s now possible to cancel long-running N1QL queries.

For example, in a web application a user might browse away from the page in impatience.  When they do, you don’t want  the query to keep executing pointlessly.  Instead, you can cancel the query, freeing web server and Couchbase  server resources for other requests.

How To Cancel

Using this new feature is very easy, when executing your query simply supply a CancellationToken.  For web applications, this can be acquired by including a CancellationToken as a parameter on an asynchronous action method.

public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
    var bucket = ClusterHelper.GetBucket("my-bucket");
    var query = new QueryRequest("SELECT * FROM `my-bucket` WHERE type = 'docType' LIMIT 1000");

    var result = await bucket.QueryAsync<Document>(query, cancellationToken);
    if (result.Success) {
        return Json(result.Rows);
    }
}

Compatibility

Note: Documentation on what versions of ASP.NET MVC and Web API support CancellationToken is a bit sparse.  Apparently some versions only use it for timeouts (using  AsyncTimeout), while some versions have support for cancellations from the browser.  There is also a way to add support for browser cancellation using CreateLinkedTokenSource.

The behavior may also depend on the web server you’re using (i.e. IIS versus Kestrel, and Kestrel version). For example, this change to Kestrel appears to cause client disconnects to do a better job of triggering the CancellationTokken. If anyone knows more details about version support, please let me know and I’ll update the post.

Docker Login For Amazon AWS ECR Using Windows Powershell

My recent studies in .Net Core have lead me to the new world of Docker (new for .Net developers, anyway).  The idea of developing low-cost microservices while still working using  my favorite development platform is very exciting.  In the process, I began using the Amazon AWS Docker platform, Elastic Container Services (ECS).

I quickly found that documentation for using ECS from Windows is a bit scarce.  In particular, the Powershell tools are missing a pretty key helper method, get-login.  Calling “aws ecr get-login” on a Linux box delivers you a complete “docker login” command for authenticating to the Elastic Container Registry (ECR).  There is currently no such helper for Windows.  At least, not that I can find, someone correct me if I’m just missing  it.

Instead, I’ve done a bit of digging and found how to authenticate programatically.  From that, I’ve created the helper code below for reuse.

# Get the authorization token
$token = Get-ECRAuthorizationToken -Region us-east-1 -AccessKey your-access-key -SecretKey your-secret-key
# Split the token into username and password segments
$tokenSegments = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($token.AuthorizationToken)).Split(":")
# Get the host name without https, as this can confuse some Windows machines 
$hostName = (New-Object System.Uri $token.ProxyEndpoint).DnsSafeHost
# Perform login
docker login -u $($tokenSegments[0]) -p $($tokenSegments[1]) -e none $hostName

This login should then be valid for 12 hours. Note that if you use your own Region, AccessKey, and SecretKey on the first line. Alternatively, you could use Set-DefaultAWSRegion and Set-AWSCredentials to store them in your Powershell session. If you’re on a build server running in AWS you could also use IAM roles to grant access directly to the build server.

Update 2/8/2017

You can add the section below to your PowerShell profile to add an easy to use cmdlet. To install:

  1. Run “notepad $PROFILE”
  2. Paste the code below into the file and save
  3. Run “. $PROFILE” or restart Powershell
  4. Run “Auth-ECR your-access-key your-secretkey”.
function Auth-ECR {
	[CmdletBinding()]
	param (
		[parameter(Mandatory=$true, Position=0)]
		[string]
		$AccessKey,
		
		[parameter(Mandatory=$true, Position=1)]
		[string]
		$SecretKey,
		
		[parameter()]
		[string]
		$Region = "us-east-1"
	)
	
	# Get the authorization token
	$token = Get-ECRAuthorizationToken -AccessKey $AccessKey -SecretKey $SecretKey -Region $Region `
		-ErrorAction Stop
	
	# Split the token into username and password segments
	$tokenSegments = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($token.AuthorizationToken)).Split(":")
	
	# Get the host name without https, as this can confuse some Windows machines 
	$hostName = (New-Object System.Uri $token.ProxyEndpoint).DnsSafeHost
	
	# Perform login
	docker login -u $($tokenSegments[0]) -p $($tokenSegments[1]) -e none $hostName
}

Note that this script defaults to using the us-east-1 region. You can change the default in your profile, or use “-Region” on the command line.

Positron – HTML 5 UI For .Net Desktop Applications

At CenterEdge Software, the development department recently had management walk into a meeting and drop a bombshell on us. They wanted us to completely rebuild the UI from the ground up and “make it sexy”. Oh, and “make it last until 2025″. Don’t you just love wild management direction?

Well, we scratched our collective heads for a while and tried to come up with a solution. Preferably one that didn’t leave us clawing out our eyeballs, nor made management come asking about how we blew the budget. We came up with three key criteria we had to meet.

  1. We need to leverage our existing .Net code as much as possible.
  2. We need to be able to deliver the UI redesign in phases over time, rather than operate in a completely independent branch for a couple of years.
  3. We need to support the current hardware infrastructure at our 600+ clients, so that hardware upgrades and networking redesigns are not required before upgrading.

What We Aren’t Doing (And Why)

Based on these criteria, our first thought was WPF. We could continue writing .Net desktop client/server applications to operate our Point of Sale and other local applications. This would allow us to easily maintain our current low-level hardware integrations (read: lots of serial ports and USB serial emulation – ugh). It would also allow us to easily phase in portions of our application as WPF while other portions remain WinForms.

The downside to WPF is the size of the developer pool. There just aren’t that many WPF developers out there, especially great ones, and they tend to be expensive. But what about HTML5 and Javascript? There’s all sorts of great work happening surrounding web technologies and user interfaces. And there’s a much larger pool of developers with these skills for us to draw on.

Looking at HTML5 UI options, we looked at and discarded the two obvious solutions:

  • Convert to a cloud application. We already have cloud components to our suite. But the core on-premise system, if converted, would become too reliant on stable internet connections, and too far from our current architecture to convert easily. This would also be very difficult to implement in a phased manner.
  • Operate an on-premises web server. For our larger clients this wouldn’t be an issue. But a significant portion of our client base are smaller businesses that use workstations as their servers. IIS isn’t an option, and a computer with a couple extra GB of RAM might work for running SQL Express, but not for running the whole application for a half dozen stations.

Can We Have Our Cake And Eat It, Too?

Is it possible to have the best of both worlds? Can we run a desktop client/server application like WPF, but yet build our UI using HTML5? Well, this question stumped us for a bit. But there are systems that do this, like Electron, just not for .Net.

Enter Positron! Positron is a solution for building a .Net desktop application using HTML5 user interface components (rendered using Chromium). It hosts an in-process version of MVC 6 (yes, that’s the new .Net Core flavor), which is then wired in-process to Chromium running within a WPF window.

Fun Facts

  • All requests from Chromium to MVC are handled in-process, there’s no network stack or HTTP involved. This keeps performance and security very high.
  • The window itself is the only WPF involved, the entire window content is the Chromium browser.
  • ASP.Net Core MVC, despite having “Core” in it’s name, isn’t actually .Net Core specific. You can use it in traditional .Net Framework applications as well.
  • All resources (images, views, CSS, JS, etc) are embedded in the DLL, making application distribution easy.
  • Positron even supports Chromium developer tools (via a separate Chrome window).
  • Positron is agnostic about how you build your HTML5 application. We’re currently using using React and TypeScript at CenterEdge. We’re even using Browserify and Gulp to build the JS files. But any web technology will work, pick your favorite flavor.
  • There is currently one significant issue, the Razor view editor in Visual Studio doesn’t recognize the fact we’re working with MVC, so it’s a squiggle fest. I’m sure support for this will be forthcoming, and it works fine after you compile and run. If there are any Visual Studio experts out there, we could use some help with this!

What About Automated Testing?

All the QA and QE people out there are yelling at me now. They’re saying their test frameworks won’t work with Positron. We have a solution for that, too. You’re building an MVC application, just hosting it in-process. There’s nothing stopping you from spinning up a Kestrel server instead to support automated testing over HTTP. Just use Chrome as the test browser for parity with Chromium.

It’s also possible to install plugins in Chromium, so it might be possible to get some of the testing frameworks up and running directly against Positron if their plugin is installed. But we haven’t vetted this out yet.

Open Source

Our specific use case is probably not common. But at CenterEdge we do feel like there is a need for desktop applications with HTML UI. There are many .Net desktop applications that could benefit from the plethora of great UI tools and frameworks available in the web development community. Therefore, we’ve decided to make Positron open source. It’s available on NuGet (there are 4 packages), and the code is available on GitHub.

At this point it’s an early version (0.2.0), and there’s lots of room for improvement. We are already working on integrating this into our application suite, and then learning the pain points to make improvements

We’d also welcome community feedback. And feel free to fork the repo and send back pull requests.