Creating Self Signed Certificates on Kubernetes

https://cshort.co/37Xreym Welcome to 2020. Creating self signed TLS certificates is still hard. Five (5) years ago I created a project on github …

Creating Self Signed Certificates on Kubernetes

Coverlet and Case-Sensitivity

If you are familiar with Linux, you already know that one of its differences from Windows is file systems case-sensitivity. Dev teams have to ensure casing is correct for building and testing on Linux; then Windows generally works.

As we containerize .NET Core code to run on Linux as well as Windows, we have encountered some interesting case-sensitivity issues. Recently, we realized that the open source code coverage tool for .NET Core, Coverlet, is always case-sensitive – even on Windows – with the Include parameter. Consider the following dotnet command:

dotnet test /p:CollectCoverage=true /p:Include="[J3DI*]*" /p:Exclude="[Test.J3DI*]*"  ./Test.J3DI.Domain

This command works correctly because the casing matches the path and file names on both OSs. (Cloning the Git repository, on either Linux or Windows, retains file and path casing.)

If we change the command just slightly (change Include filter specification to lower-case ‘i’):

dotnet test /p:CollectCoverage=true /p:Include="[J3Di*]*" /p:Exclude="[Test.J3DI*]*"  ./Test.J3DI.Domain

The tests run, but no code coverage will be recorded. Coverlet will output the standard coverage.json file, but it only contains an empty object. The file size is very small (just 2 in my case). Worse, Coverlet will report 100% coverage – very misleading.

+--------+------+--------+--------+
| Module | Line | Branch | Method |
+--------+------+--------+--------+

+---------+------+--------+--------+
|         | Line | Branch | Method |
+---------+------+--------+--------+
| Total   | 100% | 100%   | 100%   |
+---------+------+--------+--------+
| Average | NaN% | NaN%   | NaN%   |
+---------+------+--------+--------+

To remedy, double check casing in the file paths and Include / Exclude filters.

Snappy for .NET Core on Linux (AVOID!)

Snaps are very easy to install on most Linux OSs, are able to auto-update, etc. But, don’t use Snap to install .NET Core SDK on Linux. Although Snap allows for multiple versions of a snap package, only one is active at a time. Why is this a problem?

The specified framework 'Microsoft.NETCore.App', version '2.0.0' was not found.
  - The following frameworks were found:
      3.0.0 at [/usr/share/dotnet/shared/Microsoft.NETCore.App]

When the .NET Core SDK 3.0 snap package is active, down-level builds don’t work – e.g., using .NET Core SDK 3.0 to build for 2.2, 2.1, etc. Dev organizations of much size cannot simply upgrade everything to 3.0 en mass, so everyone’s config must support targeting netcoreapp2.0 with newer tooling.

Can this be remedied with some magical Snap tricks? Maybe. IMO, the easier route is to simply install all the SDKs you need using the official .NET Core instructions.

Bitbucket Pipelines vs .NET Core

There are many things to like about Atlassian’s Bitbucket, but .NET support in Pipelines is not on the list.

BitBucket’s Pipelines are for “Integrating CI/CD for Bitbucket Cloud,” and are “trivial to set up” using a YAML file for describing actions.

from 3/8/2019

Here’s an example of a pipeline file:

pipelines:
  default:
    - step:
        caches:
          - dotnetcore
        script: # Modify the comma`nds below to build your repository.
          - export PROJECT_NAME=hello.csproj
          - export TEST_NAME=hello-tests.csproj
          - dotnet restore
          - dotnet build $PROJECT_NAME
          - dotnet test $TEST_NAME

Pretty simple, right? Yep, nice and simple. Executing the pipeline is pretty easy, too. The problem is that this pipeline will fail, and the failure has little to do with the code.

Bitbucket Pipelines only accepts the JUnit XML format for test output. Since your standard .NET project does not use JUnit tooling, the Bitbucket Pipeline doesn’t find any test results.

You could fix this problem by using the JUnitTestLogger. Or you just use a hosted CI/CD solution which handles .NET better. Azure DevOps anyone?

CloudBlob Fails Function Indexing

When creating a new blob triggered Azure Function, the default type binding is Stream.

[FunctionName("Function1")]
public static void Run([BlobTrigger("{name}", 
    Connection="AzureWebJobsStorage")]Stream myBlob, ...)

Several binding types are available, such as CloudBlockBlob, 
CloudPageBlob, etc.  Both of these classes are derived from CloudBlob, yet it cannot be used as a binding type.  Although the compiler doesn’t mind, CloudBlob will at run-time during function indexing.

CloudBlob Fails Indexing

When the functions host indexes all the functions available, it fails if the binding type is CloudBlob.  If you know that the triggering blob will be a block or page blob, you can use CloudBlockBlob or CloudPageBlob, respectively.  For code that may be triggered by either, use ICloudBlob instead of CloudBlob.

Note that the error message is helpful in v2 of Azure Functions.  For the full list of types available for the triggering blob, see Microsoft’s documentation.

Avoid Shared Assemblies with Azure Functions

Keep Azure Function assemblies monolithic, or be prepared to work deal with mismatching Azure Storage assemblies.

Azure Function projects which attempt to share common code via other assemblies will fail to compile with:

CS0433: The type 'CloudBlob' exists in both 'Microsoft.Azure.Storage.Blob, Version=9.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' and 'Microsoft.WindowsAzure.Storage, Version=9.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' <Assembly with Azure Functions and including shared assembly which includes Azure Storage assembly>
AzFnTrials Solution Hierarchy

The mismatch occurs due to the Azure Storage versions included in the Azure WebJobs extensions and more general Azure Storage.  The nearby AsFnTrials Solution Hierarchy screenshot highlights the mismatched versions.  
The easiest way to avoid this compilation error is to keep all of the code in a single assembly – e.g., move BlobHelper.cs to SharedAsmb and remove CommonLib (or remove it from dependencies).

The downside with this approach is that sharing code gets more complicated, particularly when existing shared code exists.

Breaking Azure Functions with local.settings.json

Because local.settings.json is finicky!

At some point you’re going to want to add an object to the Values section of local.settings.json. Don’t! Declaring an object (or array) in the Values breaks the configuration, resulting in Missing value for AzureWebJobsStorage in local.settings.json.  Notice the customObj declaration below.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobsDashboard": "UseDevelopmentStorage=true",
    "customKey": "customValue",
    "customObj": {
      "customObjKey": "customObjValue"
    }
  }
}

Just having customObj declared will breaks things.  Yes, it’s valid JSON; no, your functions won’t be able to retrieve settings from Value.

A simple key-value pair works – like customKey above. After removing customObj, retrieving the setting for customKey from Values is straight-forward

var customVal = System.Environment.GetEnvironmentVariable("customKey");

Beyond this simple case for local.settings.json, use app configs, and remember that Azure Functions v2 switched to ASP.NET Core Configuration, and does not support ConfigurationManager.  See Jon Gallant’s post for details.

Controlling .NET Core tool versions by global.json

If you ever need to use older versions of .NET Core tooling, an easy option is setting the sdk version in global.json.  For example, create an empty directory and execute dotnet –info. Since I’m using .NET Core 2.0, my output is:

.NET Command Line Tools (2.0.0)

Product Information:
 Version: 2.0.0
 Commit SHA-1 hash: cdcd1928c9

Runtime Environment:
 OS Name: ubuntu
 OS Version: 16.04
 OS Platform: Linux
 RID: ubuntu.16.04-x64
 Base Path: /usr/share/dotnet/sdk/2.0.0/

Microsoft .NET Core Shared Framework Host

Version : 2.0.0
 Build : e8b8861ac7faf042c87a5c2f9f2d04c98b69f28d

Now add this global.json file to the directory:

{
  "sdk": {
    "version": "1.0.0"
  }
}

and run dotnet –info again.  Now the output will look like:

.NET Command Line Tools (1.0.1)

Product Information:
 Version: 1.0.1
 Commit SHA-1 hash: 005db40cd1

Runtime Environment:
 OS Name: ubuntu
 OS Version: 16.04
 OS Platform: Linux
 RID: ubuntu.16.04-x64
 Base Path: /usr/share/dotnet/sdk/1.0.1

Microsoft .NET Core Shared Framework Host

Version : 2.0.0
 Build : e8b8861ac7faf042c87a5c2f9f2d04c98b69f28d

The highlight shows that the older tools are used.  Now, use the older tools to create a new project by executing dotnet new console.  Notice the TargetFramework value in the project file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
</Project>

The .NET Core 1.x tooling created a console project targeting NetCoreApp1.1.  By comparison, .NET Core 2.x tooling will create the project targeting NetCoreApp2.0.

Other tools are affected, too.  For example, dotnet build invokes MSBuild version 15.1.548.43366 for sdk version 1.0.0, and version 15.3.409.57025 for sdk version 2.0.

Beyond manual creation, this capability seems useful for automation scenarios which need to specify which tooling to use.  I was even able to force 1.0.0-preview2-1-003177 tooling to be used.  Will you use this capability?

How To Use Mocha for Node Testing in Windows (2017)

Using Mocha on Windows is so much easier these days!  The previous post on this topic showed how to get past the Linux-specific instructions in Mocha’s Getting Started.  That approach is outdated, and using Mocha on Windows is now a breeze.  So, let’s see how easy it is:

After creating directory for your project, use npm to create a package.json file. (You can skip this step if adding mocha to an existing project)

npm init

You’ll be presented with several options. For this example, accept defaults for all except test command which you’ll type mocha.  Accept the resulting json displayed and your package.json will be written to disk.

If you started with an existing project, i.e., package.json already existed, you just need to set test to mocha in the scripts section.

Once the package.json exists, install mocha.  The save-dev option puts mocha in the devDependencies section rather dependencies.

npm install –save-dev mocha

Now you’re ready to create a test!  Create a subdir test and use your favorite editor to create test.js there.

var assert = require('assert');
describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1,2,3].indexOf(4));
    });
  });
});

After saving the file, ensure all packages are up to date.  Go back to the project directory (above test) and run:

npm update –dev

The dev option causes devDependencies to be processed, too.

That’s it!  Now just run the test:

npm test

The output will look like this:

  Array
  #indexOf()
    ✓ should return -1 when the value is not present

 1 passing (9ms)