Search code examples
dockerunit-testingnpmazure-pipelinescicd

Docker image was built but it's not found locally while running coverage test in Azure pipeline


I want to build and run coverage test and publish to sonar. Here's the Dockerfile:

FROM node:12.22.12-buster-slim as base

RUN apt update &&\
    apt install --yes --no-install-recommends \
    python2 \
    make \
    g++

WORKDIR /code

COPY .npmrc package.json package-lock.json ./

RUN npm install

ARG PUBLIC_URL="/web-frontend/"

FROM base as dev

RUN npm install --also=dev

COPY ./ ./

CMD ["npm", "start"]

FROM base as builder

COPY ./ ./

RUN PUBLIC_URL=${PUBLIC_URL} npm run build

FROM nginx:1.25-alpine3.18 AS dist

RUN apk add --no-cache \
    # CVE-2023-43787
    'libx11>=1.8.7-r0'

WORKDIR /app

COPY --from=builder /code/build/ ./

COPY st/nginx.conf /etc/nginx/nginx.conf
COPY st/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

and pipeline yaml:

trigger:
- none

pr:
- none

pool:
    vmImage: 'ubuntu-latest'

variables:
  acr_repository_name: 'webui'
  pkg_version: '0.0.1'
  sprint_number: '0001'
  branch_type: '$(Build.SourceBranchName)'
  build_version: '$(pkg_version)-d$(sprint_number).$(Build.BuildId)'  
  sonar_project_key: 'com.my_domain:$(acr_repository_name)-$(branch_type)'

jobs:
- job: Build
  displayName: 'Build'
  steps:
  - checkout: self
  - script: |
      echo "Build id: $(Build.BuildId)"
      echo "Branch name: $(Build.SourceBranchName)"
      echo "Branch type: $(branch_type)"
      echo "Build version: $(build_version)"
      echo "Souce directory: '$(Build.SourcesDirectory)"
      echo "Tag name: "my_domain.azurecr.io/dir/$(acr_repository_name):$(build_version)""
    displayName: 'Variables info'


  - task: Docker@2
    displayName: 'Build dev image'
    inputs:
      containerRegistry: 'my_domain Container Registry'
      repository: 'dir/$(acr_repository_name)'
      command: 'build'
      Dockerfile: '**/Dockerfile'
      target: 'dev'
      tags: '$(build_version)'

  - task: Docker@2
    displayName: 'Push dev image'
    inputs:
      containerRegistry: 'my_domain Container Registry'
      repository: 'dir/$(acr_repository_name)'
      command: 'push'
      tags: '$(build_version)'

  - script: |
      docker images
    displayName: 'List Docker Images'

  - script: |
      docker run --rm -v $(Build.SourcesDirectory)/coverage:/code/coverage dir/insightui:$(build_version) npm run test:coverage
    displayName: 'Run unit tests'

  - task: PublishPipelineArtifact@1
    inputs:
      targetPath: '$(Build.SourcesDirectory)/coverage'
      artifact: 'coverage'

Output from build step is:

#20 writing image sha256:2aab8942b077ded98d19adc30af6591d0c20c21f930ef4caef3a2fb5158f4abb done
#20 naming to ***/dir/web-ui:0.0.1-d001.001 done

Output from unit test step is:

Script contents:
docker run --rm -v /home/vsts/work/1/s/coverage:/code/coverage /dir/web-ui:0.0.1-d0001.001 npm run test:coverage
========================== Starting Command Output ===========================
/usr/bin/bash --noprofile --norc /home/vsts/work/_temp/4e631df5-e77e-4617-bdd3-8685a8f2a0a8.sh
Unable to find image '/dir/web-ui:0.0.1-d0001.001' locally
docker: Error response from daemon: pull access denied for dir/web-ui, repository does not exist or may require 'docker login': denied: requested access to the resource is denied.
See 'docker run --help'.

Output from docker images step is:

REPOSITORY                           TAG               IMAGE ID       CREATED         SIZE
***/dir/web-ui   0.0.1-d0001.001   2393648ffe25   5 seconds ago   63.9MB

I can't understand what's the issue here. The goal is to build image, do unit test and push to sonarcloud report. I can see some report in sonar for each run, but coverage is empty because there are no unit tests ran locally. Can I even run the npm run test:coverage against locally built image. Tried to change things plenty of times, but without any luck. Any help is appreciated.

// Answer to @Bright Ran-MSFT

Replaced in Dockerfile From base as builder to From builder as test and added these 2 lines:

FROM builder as test
ARG BuildId
LABEL test=$BuildId
RUN npm run test:coverage > coverage/lcov.info

Azure pipelines edit:

    - task: Docker@2
    displayName: 'Build dev image'
    inputs:
      containerRegistry: 'my_domain Container Registry'
      repository: 'dor/$(acr_repository_name)'
      command: 'build'
      Dockerfile: '**/Dockerfile'
      target: 'dev'
      tags: '$(build_version)'
      arguments: '--build-arg BuildId=$(Build.BuildId)'
  # - script: |
  #     npm run test:coverage > coverage/lcov.info
  #   displayName: 'Run tests and generate coverage'

   - task: PowerShell@2
     displayName: 'Copy test results'
     inputs:
      pwsh: true
      targetType: inline
      script: |
        $images = docker images 
        echo "images" $images 
        $id=docker images --filter "label=test=$(Build.BuildId)" -q | Select-Object -First 1 
        echo "image id:" $id 
        docker create --name copytestresult $id 
        docker cp copytestresult:/testresults ./testresults 
        docker rm copytestresult

  # - task: PublishCodeCoverageResults@1
  #   displayName: 'Publish code coverage results'
  #   inputs:
  #     codeCoverageTool: 'lcov'
  #     summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/lcov.info'


- script: |
      echo ">> Running SonarQube analysis"
      docker run --rm --volume $(System.DefaultWorkingDirectory):/code --workdir /code --env SONAR_SCANNER_OPTS=-Xmx512m sonarsource/sonar-scanner-cli:4.8 \
        sonar-scanner \
        -Dsonar.host.url=https://global-sonar.my-domain.com \
        -Dsonar.projectKey=$(sonar_project_key) \
        -Dsonar.projectVersion=$(build_version) \
        -Dsonar.sources=src
      echo ">> Waiting for SonarQube analysis to complete"
    displayName: 'Run SonarQube analysis'
    continueOnError: true
  

Adding package.json scripts block also:

"scripts": {
    "start": "npm run lint && react-scripts start ",
    "watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive",
    "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/",
    "test": "jest",
    "test:coverage": "jest --collectCoverage --coverageReporters=lcov",
    "coverage": "jest --collectCoverage",
    "eject": "react-scripts eject",
    "build": "react-scripts build",
    "lint": "eslint src/**/*.js src/**/*.jsx"

Solution

  • In your pipeline, the step 'Run unit tests' just starts the container in a separate session might get cleaned when this step is completed. The subsequent steps (include PublishPipelineArtifact@1) will be still running on the agent machine instead of in the container started by above steps.

      - script: |
          docker run --rm -v $(Build.SourcesDirectory)/coverage:/code/coverage dir/insightui:$(build_version) npm run test:coverage
        displayName: 'Run unit tests'
    
      - task: PublishPipelineArtifact@1
        inputs:
          targetPath: '$(Build.SourcesDirectory)/coverage'
          artifact: 'coverage'
    

    For your case, I recommend you configure the Dockerfile to execute both build and test process when build the Docker image, and generate the report file of test result. Then copy the generated report file to outside of the built Docker image.

    To publish the code coverage results, you can use the PublishTestResults@2 or PublishCodeCoverageResults@2 task. If you want to publish the test result to Sonar, you need to use tasks provided by the "SonarCloud" or "SonarQube" extension.

    1. The Dockerfile.
    FROM node:12.22.12-buster-slim as base
    . . .
    
    FROM base as dev
    . . .
    
    FROM dev as builder
    . . .
    
    FROM builder as test
    ARG BuildId
    LABEL test=$BuildId
    // Commands to run test and generate the test result to a specified path.
    
    FROM nginx:1.25-alpine3.18 AS dist
    . . .
    
    1. The pipeline.
    variables: 
      DOCKER_BUILDKIT: 0
      . . .
    
    jobs:
    - job: Build
      displayName: 'Build'
      steps:
      . . .
    
      - task: Docker@2
        displayName: 'Build dev image'
        inputs:
          containerRegistry: 'my_domain Container Registry'
          repository: 'dir/$(acr_repository_name)'
          command: 'build'
          Dockerfile: '**/Dockerfile'
          target: 'dev'
          tags: '$(build_version)'
          arguments: '--build-arg BuildId=$(Build.BuildId)'
    
      . . .
    
      - task: PowerShell@2
        displayName: 'Copy test results'
        inputs:
          pwsh: true
          targetType: inline
          script: |
            $images = docker images 
            echo "images" $images 
            $id=docker images --filter "label=test=$(Build.BuildId)" -q | Select-Object -First 1 
            echo "image id:" $id 
            docker create --name copytestresult $id 
            docker cp copytestresult:/testresults ./testresults 
            docker rm copytestresult
    
        - task: PublishTestResults@2 
          displayName: 'Pusblish test results'
          . . .
    

    EDIT:

    For the error "unknown flag: --build-arg BuildId", try to check the whitespaces in the following lines. Ensure they are using the normal whitespace of and not containing other types of whitespaces:

    1. In the Dockerfile.
    . . .
    FROM builder as test
    ARG BuildId
    LABEL test=$BuildId
    . . .
    
    1. On the Docker@2 task in pipeline.
    arguments: '--build-arg BuildId=$(Build.BuildId)'
    

    You can try to delete and re-enter the whitespaces in these lines.