Continuous integration

Note

This is an advanced topic that is closely tied with the further development and customization of the nRF Asset Tracker for your purposes. See the GitHub project page of the nRF Asset Tracker for Azure for an implementation of the process outlined in this section.

Continuous integration involves the following actions:

  • Every change to the project is tested against an Azure account, which must be manually prepared.

  • A Behavior Driven Development (BDD) test suite of end-to-end tests is run. The test suite is written in Gherkin, which describes the tests in English.

In this way, the tests are not tied to the implementation and you cannot accidentally drop tests during refactoring. Tests written for test runners like Jest tend to be closely tied to the API of the source code implementation. In the case of a larger refactoring, the tests often need to be refactored as well. Since the BDD tests are purely testing based on the public API of the project (which is a mix of the native Azure API and a custom REST API), they can be kept unchanged during refactoring.

Prepare your Azure account

Note

The setup process in Azure is more complicated when compared to the AWS continuous integration setup since it involves many manual steps, which cannot be automated. If you have ideas to simplify the process, provide your input.

Log in and create the resource group

To create the resource group for the CI resources, complete the following steps:

  1. Log in to the shell:

    az login
    
  2. Export the identifier of the subscription that contains the nRF Asset Tracker resources:

    export SUBSCRIPTION_ID="subscription id"
  3. Make sure that you have enabled the correct subscription by using the following commands:

    az account set --subscription $SUBSCRIPTION_ID
    # Verify that it is set to default
    az account list --output table
    
  4. Choose a resource group name for the solution and export it as RESOURCE_GROUP. In this example, nrfassettrackerci is used as the resource group name.

    export RESOURCE_GROUP="nrfassettrackerci"
    
  5. Choose a name for the solution and export it as APP_NAME. Use a short name (not more than 16 characters) composed of numbers and lowercase letters only. In this example, nrfassettrackerci is used as the application name.

    export APP_NAME="nrfassettrackerci"
    
  6. Configure your preferred location (you can list the locations using az account list-locations) and export it on the environment variable LOCATION. In this example, northeurope is used as the location name.

    export LOCATION="northeurope"
    
  7. Create a resource group for the CI resources:

    az group create --name ${RESOURCE_GROUP:-nrfassettrackerci} --location ${LOCATION:-northeurope}
    

Create a secondary tenant (Azure Active Directory B2C)

  1. Create an Azure Active Directory B2C. Currently, it is not possible to create an Active Directory B2C and application through the ARM template (see GitHub issue).

    1. Follow the instructions in the tutorial for creating an Azure Active Directory B2C tenant and create a B2C tenant.

    2. Follow the instructions in the tutorial for setting up a resource owner password credentials flow in Azure Active Directory B2C and register an application. Make sure to set the following parameters:

      • Set the Supported account types to All users

      • Update the Azure Active Directory app manifest and allow implicit grant flow for OAuth2:

        {"oauth2AllowImplicitFlow": true}
        
  2. Export the initial domain name that you used:

    export B2C_TENANT="Initial domain name" # For example, "nrfassettrackerciusers"
  3. Export the Application (client) ID and the Directory (tenant) ID of the created Active Directory B2C App:

    export APP_REG_CLIENT_ID="Application (client) id"
    export B2C_TENANT_ID="Directory (tenant) ID"
  4. For enabling the test-runner to programmatically log in users, enable the resource owner password credentials (ROPC) flow with the following settings on the Active Directory B2C:

    1. Name - B2C_1_developer.

    2. Click Application claims, select Show more … and then mark Email Addresses as a return claim.

  5. Grant the B2C directory API permissions for authenticating users:

    1. In the left menu, under Manage, select API permissions. Add the permission to manage user accounts (Microsoft Graph -> Application permission -> User.ReadWrite.All).

  6. Grant the B2C directory API permissions for the function app:

    1. Click Expose an API and set the Application ID URI field to api.

    2. Click + Add a scope and create a new scope with the following values and click Add a scope:

      • Scope name - nrfassettracker.admin

      • Admin consent display name - Administrator access to the nRF Asset Tracker API

      • Admin consent description - Allows administrator access to all resources exposed through the nRF Asset Tracker API

    3. Click API permissions and then click + Add a permission. Under My APIs, select the app registration.

    4. Enable the nrfassettracker.admin permission and click Add permission.

  7. Click Grant admin consent for <your B2C directory>.

  8. Create a new client secret for the App registration (for example, 12OzW72ie-U.vlmzik-eO5gX.x26jLTI6U) and note it down.

    export B2C_CLIENT_SECRET="client secret"
  9. Link this Azure AD B2C tenant to the subscription for CI by following the Billing guide.

Fork the nRF Asset Tracker repositories

To enable continuous deployment, complete the following steps:

  1. Fork the nRF Asset Tracker for Azure repository.

  2. Fork the nRF Asset Tracker web application repository.

  3. Update the deploy.webApp.repository in the package.json file of your nRF Asset Tracker for Azure fork. It must point to the repository URL of your fork of the nRF Asset Tracker web application.

Set up continuous integration on GitHub

To allow the continuous deployment GitHub Action workflow to authenticate against Azure with short-lived credentials using a service principal, complete the following steps:

  1. Follow the instructions to Configure a service principal with a Federated Credential to use OIDC based authentication. Use https://nrfassettracker.invalid/ci as the name.

    From the command line this can be achieved using:

    az ad app create --display-name 'https://nrfassettracker.invalid/ci'
    export APPLICATION_OBJECT_ID=`az ad app list | jq -r '.[] | select(.displayName=="https://nrfassettracker.invalid/ci") | .id' | tr -d '\n'`
    az rest --method POST --uri "https://graph.microsoft.com/beta/applications/${APPLICATION_OBJECT_ID}/federatedIdentityCredentials" --body '{"name":"GitHubActions","issuer":"https://token.actions.githubusercontent.com","subject":"repo:NordicSemiconductor/asset-tracker-cloud-azure-js:environment:ci","description":"Allow GitHub Actions to modify Azure resources","audiences":["api://AzureADTokenExchange"]}'
    

    Make sure to use the organization and repository name of your fork instead of NordicSemiconductor/asset-tracker-cloud-azure-js in the command above.

  2. Set the secrets:

    • Set the secrets using the GitHub UI:

      Set the following secrets through the GitHub UI to an environment called production in your fork of the nRF Asset Tracker for Azure:

      • AZURE_CLIENT_ID - Store the application (client) ID of the service principal app registration created in step in the above step.

      • AZURE_TENANT_ID - Store the directory (tenant) ID of the service principal app registration created in step in the above step.

      • AZURE_SUBSCRIPTION_ID - Store the ID of the subscription which contains the nRF Asset Tracker resources.

      Set the following following values from your .envrc file as secrets as well:

      • RESOURCE_GROUP

      • LOCATION

      • APP_NAME

      • B2C_TENANT

      • APP_REG_CLIENT_ID

      If you have enabled the Unwired Labs Cell Geolocation, add your API key UNWIRED_LABS_API_KEY as a secret as well.

    • Alternatively, set the secrets using the GitHub CLI:

      You can use the GitHub CLI with the environment settings from above (make sure to create the ci deployment environment in your repository first):

    export AZURE_CLIENT_ID=`az ad app list | jq -r '.[] | select(.displayName=="https://nrfassettracker.invalid/ci") | .appId' | tr -d '\n'`
    export AZURE_TENANT_ID=`az ad sp show --id ${AZURE_CLIENT_ID} | jq -r '.appOwnerOrganizationId' | tr -d '\n'`
    gh secret set AZURE_CLIENT_ID --env ci --body "${AZURE_CLIENT_ID}"
    gh secret set AZURE_TENANT_ID --env ci --body "${AZURE_TENANT_ID}"
    gh secret set AZURE_SUBSCRIPTION_ID --env ci --body "${SUBSCRIPTION_ID}"
    gh secret set RESOURCE_GROUP --env ci --body "${RESOURCE_GROUP}"
    gh secret set LOCATION --env ci --body "${LOCATION}"
    gh secret set APP_NAME --env ci --body "${APP_NAME}"
    gh secret set B2C_TENANT --env ci --body "${B2C_TENANT}"
    gh secret set APP_REG_CLIENT_ID --env ci --body "${APP_REG_CLIENT_ID}"
    
  3. Grant the application created in step 1 Owner permissions for your subscription:

    export AZURE_CLIENT_ID=`az ad app list | jq -r '.[] | select(.displayName=="https://nrfassettracker.invalid/ci") | .appId' | tr -d '\n'`
    az role assignment create --role Owner \
       --assignee ${AZURE_CLIENT_ID} \
       --scope /subscriptions/${SUBSCRIPTION_ID}
    
  4. Grant the application created in step 1 “Key Vault Secrets Officer” to the KeyVault:

    export AZURE_CLIENT_ID=`az ad app list | jq -r '.[] | select(.displayName=="https://nrfassettracker.invalid/ci") | .appId' | tr -d '\n'`
    az role assignment create --role "Key Vault Secrets Officer" \
       --assignee ${AZURE_CLIENT_ID} \
       --scope /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP:-nrfassettrackerci}/providers/Microsoft.KeyVault/vaults/${APP_NAME:-nrfassettrackerci}
    

Commit and push a change

Now, commit and push a change to your repository. This will trigger the CI run.

You can also manually trigger a deployment on the Test and Release workflow.

Running the solution during development

To run the end-to-end tests against the solution during development, run the following commands:

# Install dependencies
npm ci

# One time operation: create an intermediate CA certificate
./cli.sh create-ca-root
./cli.sh proof-ca-root-possession
./cli.sh create-ca-intermediate

# Run tests
npm run test:e2e

Note

Azure functions allow only one Client ID and Issuer URL in the Active Directory authentication configuration. You cannot interact with this instance from the end-to-end tests and the web application, because the user flow names are different (B2C_1_developer for end-to-end tests and B2C_1_signup_signin for the web application) and it is part of the Issuer URL (for example, https://${TENANT_DOMAIN}.b2clogin.com/${TENANT_DOMAIN}.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_developer).

More information

You can read more about how GitHub Actions uses OIDC on About security hardening with OpenID Connect in the GitHub Actions documentation.