Step 2. Building a microservice

Some of the programming languages used in the Pip.Services Toolkit require a project to be built, yielding executable files. A separate stage is used for this, which builds a special “build” Docker image. The project’s source code is copied to the image, after which the container is run and the project is compiled from inside the container. If the project compiles successfully, the generated files will be copied from the container back to the project for further use.

Python is an interpreted language and does not require compilation. But to save an identical pipeline, a stub script is used.

To perform the build process for a Python project, we’ll be creating a Docker container build scenario in a file named Dockerfile.build. Copy the following into this file:

FROM node:8

# Install development tools
RUN npm install typescript -g

# set working directory
WORKDIR /app

# Copy project file
COPY package*.json ./

# install ALL node_modules, including 'devDependencies'
RUN npm install

# copy all project
COPY . .
# compile source code
RUN tsc

FROM mcr.microsoft.com/dotnet/core/sdk:3.1

# Set working directory
WORKDIR /app

# Restore
COPY src/Interface/Interface.csproj ./src/Interface/
RUN dotnet restore --disable-parallel src/Interface/Interface.csproj
COPY src/Service/Service.csproj ./src/Service/
RUN dotnet restore --disable-parallel src/Service/Service.csproj
COPY src/Client/Client.csproj ./src/Client/
RUN dotnet restore --disable-parallel src/Client/Client.csproj
COPY src/Process/Process.csproj ./src/Process/
RUN dotnet restore --disable-parallel src/Process/Process.csproj
COPY test/Service.Test/Service.Test.csproj ./test/Service.Test/
RUN dotnet restore --disable-parallel test/Service.Test/Service.Test.csproj
COPY test/Client.Test/Client.Test.csproj ./test/Client.Test/
RUN dotnet restore --disable-parallel test/Client.Test/Client.Test.csproj

# Copy src
COPY . .

# Publish
RUN dotnet build src/Process/Process.csproj
RUN dotnet publish src/Process/Process.csproj -o /obj

# Pack Nuget
RUN dotnet build src/Interface/Interface.csproj -c Release
RUN dotnet pack src/Interface/Interface.csproj -c Release -o ../../../dist
RUN dotnet build src/Service/Service.csproj -c Release
RUN dotnet pack src/Service/Service.csproj -c Release -o ../../../dist
RUN dotnet build src/Client/Client.csproj -c Release
RUN dotnet pack src/Client/Client.csproj -c Release -o ../../../dist

FROM golang:1.13

# Set environment variables for Go
ENV GO111MODULE=on \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOARCH=amd64

# Set a working directory
WORKDIR /app

# Copy the package files
COPY go.mod ./

# Install all go_modules
RUN go mod download

# Copy the package files
COPY . .

# Build the project
RUN go build -o /go/bin/run .


FROM google/dart

# set working directory
WORKDIR /app

# copy all project
COPY pubspec.* .

# Install all dependencies
RUN pub get
RUN pub get --offline

# copy all project
COPY . .

FROM python:3

# set working directory
WORKDIR /usr/src/app

# copy project file
COPY requirements.txt .

# install dependencies
RUN pip install -r requirements.txt

# copy all project
COPY . .

Not available

This file, along with the others we will be creating, should be placed in the docker folder at the root of the project.

Let’s have a look at what this Docker script will be doing. The standard Python v.3 image is going to be used as the base image, and Python is going to be installed on top of it. Next, /app is set as the working directory and our project’s requirements.txt file is copied there. This file contains a list of dependencies that are required to build the project, which are installed using the pip install -r requirements.txt command. The last steps of the script simply copies the rest of the project to the image.

Note that the file requirements.txt is copied first, then the dependencies are installed, and only after that do we copy the rest of the source code. This is done to speed up container creation during future runs, as the steps that haven’t changed from the last run are simply taken from Docker’s cache. In other words, unless we add or remove a dependency, Docker can use the cached image with all of the dependencies already installed, and only has to perform the “copy” steps when we change the project’s source code.

In our projects, we strive to make our scripts as universal as possible. Because of this, all variable values are defined in a separate file named component.json, which looks like this:

{
    "name":  "component-name",
    "registry":  "registry-name",
    "version":  "1.0.0",
    "build":    "1"
}

This file contains basic information about the component we are dealing with: its name, Docker Hub registry, version, and build number.

We’ve developed a special script called build.ps1 for building our projects. This script is written in PowerShell - a scripting language used for creating system scripts. Since version 6.0, PowerShell is supported by most platforms that are used for development, such as Windows, Mac, and Linux. If for some reason PowerShell can’t be used, you can rewrite the script using bash or any other scripting language.

#!/usr/bin/env pwsh

Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"

# Get component data and set necessary variables
$component = Get-Content -Path "component.json" | ConvertFrom-Json
$buildImage="$($component.registry)/$($component.name):$($component.version)-$(
$component.build)-build"
$container=$component.name

# Get build number from teamcity agent
$component.build = $env:BUILD_NUMBER
Set-Content -Path "component.json" -Value $($component | ConvertTo-Json)

# Copy private keys to access git repo
if (-not (Test-Path -Path "docker/id_rsa")) {
    if ($env:GIT_PRIVATE_KEY -ne $null) {
        Set-Content -Path "docker/id_rsa" -Value $env:GIT_PRIVATE_KEY
    } else {
        Copy-Item -Path "~/.ssh/id_rsa" -Destination "docker"
    }
}

# Build docker image
docker build -f docker/Dockerfile.build -t $buildImage .

# Create container, then destroy
docker create --name $container $buildImage
docker rm $container

This script generates a name for the image using the data in the component.json file, cleans the project of files from previous compilations, runs the Docker build scenario, and then copies the compiled files from the image back into the project. Once the files are copied, the container is deleted, and the script outputs the results of the build to the console.

This finishes up the build process. Continue on to Step 3 to dockerize the testing process.

Step 3. Running automated tests.