This subject is split into five parts:

Part 3: Create a docker linter

For this second decorator, we would like to build a task responsible for analyzing a Dockerfile and checking that it follows good practices. There are plenty of tools to do like dockle, hadolint and in our case dockerfilelint chosen for its simplicity.

Build our linter task

Technically, we need to create a task that:

  • checks if a Dockerfile exists in the source code
  • if any file is found, install dockerfileint
  • scan any Dockerfile and report the results
  • stops the workflow if issues have been found

Our script within our decorator could look like this:

steps:
  - task: CmdLine@2
    displayName: '(Injected) Docker linter'
    inputs:
      script: |
        
        echo 'looking for DockerFile'
        filePath=$(find -type f -name Dockerfile)
        if [ -z "$filePath" ]
        then
              echo "No Dockerfile was found"
        else
              echo "Dockerfile(s) found: $filePath"

              echo 'installing dockerfilelint'
              npm install -g dockerfilelint --silent
              echo 'Installed version: '
              dockerfilelint -v
              
              echo 'running dockerfilelint'
              find -type f -name 'Dockerfile' -exec dockerfilelint {} \;
              # explanation of find+exec: https://stackoverflow.com/questions/9612090/how-to-loop-through-file-names-returned-by-find

              if echo "$result" | grep -q "None found"; then
                  echo "no issues found"
              else
                  echo "issues found"
                  exit 1 # throw an error to stop the workflow
              fi
        fi

Once the YAML file, is ready, we just need to create the vss-extension file:

{
    "manifestVersion": 1,
    "id": "dockerlinter-by-lgmorand",
    "name": "A docker linter decorator",
    "version": "1.0.0",
    "publisher": "lgmorand",
    "targets": [
        {
            "id": "Microsoft.VisualStudio.Services"
        }
    ],    
    "description": "A docker linter based on dockerfilelint.",
    "categories": [
        "Azure Pipelines"
    ],
    "icons": {
        "default": "images/extension-icon.png"        
    },
    "content": {
        "details": {
            "path": "overview.md"
        }
    },
    "contributions": [
        {
            "id": "docker-linter-decorator",
            "type": "ms.azure-pipelines.pipeline-decorator",
            "targets": [
                "ms.azure-pipelines-agent-job.pre-job-tasks"
            ],
            "properties": {
                "template": "docker-linter-decorator.yml",
            }
        }
    ],
    "files": [
        {
            "path": "docker-linter-decorator.yml",
            "addressable": true,
            "contentType": "text/plain"
        }
    ]
}

We can now package it, publish it and deploy it to our organization.

My decorator is injected too often

If we evaluate it, our decorator is working perfectly but it is also injected into all workflows of our organization, including those who don’t use Docker technology. This could be an issue as it will increase the time of each pipeline execution (especially if the source code contains numerous files) and if you have several decorators in your organization, each pipeline could get polluted by irrelevant decorators.

We need to find a way to target only pipelines that are using Dockerfile. We could restrict the decorator to run for specific projects by adding a condition, but it would require to hard-code the GUID of each project like this (there are other ways to filter by project):

steps:
- $

Another way of doing it is to target the presence of specific tasks in the pipelines. In our case, we want to analyze any Dockerfile with the tool dockerfilelint before we use the file to build a Docker image. We just need to find pipelines where we use the Docker file. The simplest way to do it is to target pipelines that contains the built-in Docker task:

Docker task

In our case, the ID of the Docker task is: “e28912f1-0114-4464-802a-a3a35437fd16”

To find the ID of a task, you can either check this repository if the task is a built-in task, and open the task.json file. If the task is a custom task from the marketplace, you just need to download its VSIX file and unzip it to find the task.json file. To obtain the VSIX from a custom task, just go on the marketplace and try to install it. Instead of installing it on your organization, choose the Azure DevOps Server option and download the file: Download VSIX

Once we have the task’s ID, we need to alter the target of our decorator to say, “inject it before each occurrence of a specific task” and we can do it using the target ms.azure-pipelines-agent-job.pre-task-tasks and by adding the property targettask with the GUID of our task.

Our final vss-extension file looks like this:

"contributions": [
    {
        "id": "docker-linter-decorator",
        "type": "ms.azure-pipelines.pipeline-decorator",
        "targets": [
            "ms.azure-pipelines-agent-job.pre-task-tasks"
        ],
        "properties": {
            "template": "docker-linter-decorator.yml",
            "targettask": "e28912f1-0114-4464-802a-a3a35437fd16"
        }
    }
]

Note: if the users use directly the docker command within a script or a simple CmdLine task, our decorator would not work as we can’t parse the pipeline file to detect a keyword. There is no solution that I’m aware of for such use case.

Let’s package a new version of our decorator and deploy it. If we run any workflow which uses the Docker task, then our decorator is injected as seen in this example :

Linter found issues in Dockerfile

It’s not perfect but it does the job and injecting a decorator before specific tasks is very useful to target specific workflows.

In the last part of this guide, we will build a more complex decorator and see some tips.