Multitargeting in DotNetCore and Linux and Mac builds

One of the most important features of DotNetStandard is the ability to run on Linux and Mac, but if you need to use a DotNetStandard compiled library in a project that uses full .NET framework, sometimes you can have little problems. Actually you can reference a dll compiled for DotNetCore from a project that uses full Framework, but in a couple of project we experienced some trouble with some assemblies.

Thanks to multitargeting you can simply instruct DotNet compiler to produce libraries compiled against different versions of frameworks, so you can simply tell the compiler that you want both DotNetStandard 2.0 and full framework 4.6.1, you just need to modify the project file to use TargetFrameworks tag to request compilation for different framework.

netstandard2.0;net461

Et voilà, now if you run dotnet build command you will find in the output folder both the versions of the assembly.

image

Figure 1: Multiple version of the same library, compiled for different versions of the assembly.

Multitargeting allows you to produce libraries compiled for different version of the framework in a single build.

The nice aspect of MultiTargeting is that you can use dotnet pack command to request the creation of Nuget Packages: generated packages contain libraries for every version of the framework you choose.

image

Figure 2: Package published in MyGet contains both version of the framework.

The only problem of this approach is when you try to compile multitargeted project in Linux on in Macintosh, because the compiler is unable to compile for the Full Framework. that can be installed only on Windows machines. To solve this problem you should remember that .csproj files of DotNetCore projects are really similar to standard MsBuild project files so you can use conditional options. This is how I defined multitargeting in a project

image

Figure 3: Conditional multitargeting

The Condition attribute is used to instruct the compiler to consider that XML node only if the condition is true, and with Dollar syntax you can reference environment variables. The above example can be read in this way: if  DOTNETCORE_MULTITARGET environment variable is defined and equal to true, the compiler will generate netstandard2.0 and net461 libraries, otherwise (no variable defined or defined with false value) the compiler will generate only netstandard2.0 libraries.

Using the Condition attribute you can specify different target framework with an Environment Variable

All the people with Windows machines will define this variable to true and all projects that uses this syntax, automatically have the project compiled for both the framework. On the contrary, all the people that uses Linux or Macintosh can work perfectly with only netstandard2.0 version simply avoiding defining this variable.

The risk of this solution is: if you always work in Linux, you can potentially introduce code that compiles for netstandard2.0 and not for net461. Even if this situation cannot happen now, working with Linux or Mac actually does not compile and test the code against the full framework. The solution to this problem is simple, just create a build in VSTS that is executed on a Windows agent and remember to set DOTNETCORE_MULTITARGET to true, to be sure that the build will target all desired framework.

image

Figure 4: Use Build variables to setup environment variables during the build

Thanks to VSTS / TFS build system it is super easy to define the DOTNETCORE_MULTITARGET at build level, and you can decide at each build if the value is true or false (and you are able to trigger a build that publish packages only for netstandard2.0). In this build I usually automatically publish NuGet package in MyGet feed, thanks to GitVersion numbering is automatic.

image

Figure 5: Package published in pre-release.

This will publish a pre-release package at each commit, so I can test immediately. Everything is done automatically and is run in parallel with the build running in Linux, so I’m always 100% sure that the code compile both in Windows an Linux and tests are 100% green in each operating system.

Gian Maria.

Configure a VSTS Linux agent with docker in minutes

It is really simple to create a build agent for VSTS that runs in Linux and is capable of building and packaging your DotNetCore project, I’ve explained everything in a previous post, but I want to remind you that, with docker, the whole process is really simple.

Anyone knows that setting up a build machine often takes time. VSTS makes it super simple to install the Agent , just download a zip, call a script to configure the agent and the game is done. But this is only one side of the story. Once the agent is up, if you fire a build, it will fail if you did not install all the tools to compile your project (.NET Framework) and often you need to install the whole Visual Studio environment because you have specific dependencies. I have also code that needs MongoDB and Sql Server to run tests against those two databases, this will usually require more manual work to setup everything.

In this situation Docker is your lifesaver, because it allowed me to setup a build agent in linux in less than one minute.

Here are the steps: first of all unit tests use an Environment Variable to grab the connection string to Mongodb, MsSql and every external service they need. This is a key part, because each build agent can setup those environment variable to point to the right server. You can think that 99% of the time the connection are something like mongodb://localhost:27017/, because the build agent usually have mongodb installed locally to speedup the tests, but you cannot be sure so it is better to leave to each agent the ability to change those variables.

With this prerequisite, I installed a simple Ubuntu machine and then install Docker . Once Docker is up and running I just fire up three Docker environment, first one is the mongo database

sudo docker run -d -p 27017:27017 --restart unless-stopped --name mongommapv1 mongo

Than, thanks to Microsoft, I can run Sql Server in linux in a container, here is the second Docker container to run MSSQL

sudo docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=my_password' -p 1433:1433 --name msssql --restart=unless-stopped -d microsoft/mssql-server-linux

This will start a container with Microsoft Sql Server, listening on standard port 1433 and with sa user and password my_password. Finally I start the docker agent for VSTS

sudo docker run \
  -e VSTS_ACCOUNT=prxm \
  -e VSTS_TOKEN=xxx\
  -e TEST_MONGODB=mongodb://172.17.0.1 \
  -e TEST_MSSQL='Server=172.17.0.1;user id=sa;password=my_password' \
  -e VSTS_AGENT='schismatrix' \
  -e VSTS_POOL=linux \
  --restart unless-stopped \
  --name vsts-agent-prxm \
  -it microsoft/vsts-agent

Thanks to the –e option I can specify any environment variable I want, this allows me to specify TEST_MSSQL and TEST_MONGODB variables for the third docker container, the VSTS Agent. The ip of mongodb and MSSql are on a special interface called docker0, that is a virtual network interfaces shared by docker containers.

image

Figure 1: configuration of docker0 interface on the host machine

Since I’ve configured the container to bridge mongo and SQL port on the same port of the host, I can access MongoDB and MSSQL directly using the docker0 interface ip address of the host. You can use docker inspect to know the exact ip of the docker container on this subnet but you can just use the ip of the host.

image

Figure 2: Connecting to mongodb instance

With just three lines of code my agent is up and running and is capable of executing build that require external databases engine to verify the code.

This is the perfect technique to spinup a new build server in minutes (except the time needed for my network to download Docker images 🙂 ) with few lines of code and on a machine that has no UI (clearly you want to do a minimum linux installation to have only the thing you need).

Gian Maria.

How to security expose my test TFS on the internet

I’m not a security expert, but I have a basic knowledge on the argument, so when it is time to expose my test TFS on the outside world I took some precautions. First of all this is a test TFS instance that is running in my test network, it is not a production instance and I need to access it only sometimes when I’m outside my network.

Instead of mapping 8080 port on my firewall I’ve deployed a Linux machine, enabled SSH and added google two factor authentication, then I expose port 22 on another external port. Thanks to this, the only port that is exposed on my router is a port that remap on port 22 on my linux instance.

Now when I’m on external network, I use putty to connect in SSH to that machine, and I setup tunneling as for Figure 1.

image

Figure 1: Tunneling to access my TFS machine

Tunneling allows me to remap the 8080 port of the 10.0.0.116 machine (my local tfs) on my local 8080 port. Now from a machine external on my network I can login to that linux machine.

image

Figure 2: Login screen with verification code.

This is on a raspberry linux pi, I simply use pi as username, then use verification code from my cellphone (google authenticator app) and finally the password of my account.

Once I’m connected to the raspberry machine I can simply browse http://localhost:8080 and everything is redirected through a secure SSH tunnel to the 10.0.0.116 machine. Et voilà I can access any machine, any port in my network just using SSH tunneling.

image

Figure 3: My local TFS instance now accessible from external machine

This is surely not a tutorial on how to expose a production TFS instance (please use https), but instead is a simple tutorial on how you can access every machine in your local lab, without the need to expose directly the port on your home router. If you are a security expert you will probably find flaws in this approach, but surely it is better than directly map ports on the router.

Gian Maria.

Using certificate for SSH in Azure Linux VM

If you like to use certificate to connect via SSH to your Linux machine you will probably use that technique to access all of your VMs, even those one hosted on Azure.

This operation is really simple, because Azure Portal allow you to specify the public key during VM creation and everything else is managed by VM Creation Scripts. In the same blade where you specify username and password you can opt in to use a certificate instead of a password. You should open the file with .pub extension you’ve created previously (with ssh-keygen) and paste full content in appropriate textbox.

image

Figure 1: Specifying ssh public key during VM Creation

As you can see from Figure 1 the portal will validate the key with a little green sign at the right of the textbox, informing you that the public key is valid. Once the VM is created you can use Putty or your favourite ssh client to access your machine with the certificate.

Thanks to Azure Portal you can choose to use an existing certificate to access your machine

If you already created your vm using standard username and password, you can easily connect to that machine and add public key to .ssh/authorized_keys file as showed in previous blog post, or you can use Azure CLI to configure SSH on an existing VM. First of all you need to convert the file generated with ssh-keygen in a format that can be understood by Azure CLI.

Unfortunately you cannot use the .pub file as you can when you are creating the machine;  Command Line Interface tool require a file with .pem extension. You can convert your file easily with openssl utility in a Linux VM.

openssl req -x509 -new -days 365 -key id_rsa_nopwd -out id_rsa_nopwd.pem

Thanks to this command, my RSA private key file, generated with ssh-keygen is converted to a pem file. Now you can use it to configure your VM from Azure CLI.

azure vm 
	reset-access 
	--reset-ssh --ssh-key-file z:\Secure\Rsa\id_rsa_nopwd.pem 
	--user-name gianmaria 
	--password xxxxxx

You will be prompted for Resource Group and VM Name (you can specify those two parameter from command line), then the CLI will update your Virtual Machine for you.

image

Figure 2: Result of the reset-access command

Now you can access your VM using certificate, and if you check your .ssh/authorized_keys file, you can check that the public key was correctly added by the Azure CLI utility.

image

Figure 3: I can now connect to my VM using certificate

Gian Maria.

Where is my DNS name for Azure VM with new Resource manager?

Azure is changing management mode for resources, as you can read from this article and this is the reason why, in the new portal, you can see two different entry for some of the resources, ex: Virtual Machines.

image

Figure 1: Classic and new resource management in action

Since the new model has more control over resources, I’ve create a linux machine with the new model to do some testing. After the machine was created I opened the blade for the machine (machine create with the new model are visible only on the new portal) and I noticed that I have no DNS name setting.

image

Figure 2: Summary of my new virtual machine, computer name is not a valid DNS address

Compare Figure 2 with Figure 3, that represents the summary of a VM created with the old resource management. As you can see the computer name is a valid address in domain cloudapp.net

image

Figure 3: Summary of VM created with old resource management, it has a valid DNS name.

Since these are test VM that are off most of the time, the IP is chaning at each reboot, and I really want a stable friendly name to store my connection in Putty/MremoteNG.

From the home page of the portal, you should see the resource manager group where the machine was created into. If you open it, you can see all the resources that belongs to that group. In the list you should see your virtual machine as well as the IP Address resource (point 2 in Figure 4) that can be configured to have a DNS name label. The name is optional, so it is not automatically setup for you during machine creation, but you can specify it later.

SNAGHTML280a6d

Figure 4: Managing ip addresses of network attached to the Virtual Machine

Now I set the name of my machine to docker.westeurope.cloudapp.azure.com to have a friendly DNS for my connection.

Enjoy.