Skip to content

Latest commit

Β 

History

History
578 lines (436 loc) Β· 16.3 KB

az-pipeline-vm.md

File metadata and controls

578 lines (436 loc) Β· 16.3 KB

Introduction to Azure Pipelines

You can use Azure pipelines to test, build and deploy your Python (or any other language) projects without needing to set up any insfrastructure of your own.

For this tutorial we will use the Microsoft-hosted agents with Python preinstalled - note that these can be Windows, Linux or MacOS based.

πŸ’» What you'll be doing

  1. Create a new Azure DevOps CI pipeline
  2. Create a basic CI pipeline that will run automated pytests for your bokeh apps
  3. Create a more complex pipelines that use Anaconda to test on Windows, Linux and MacOs
  4. Create a pipeline to work on an Azure notebook

πŸ›  Azure DevOps setup

  1. Head over to Azure DevOps click on Start for free (note you can directly link to your GitHub account).
  2. Once registered you need to create an organisation for your products. This will allow you to work with your collaborators in multiple shared-projects. When prompted to choose the location for your projects make sure to choose a close by region to you. For example, for this workshop we could use WestEurope.
  3. Once completed you can sign into your organisation at any time through http://dev.azure.com/{your_org}.
  4. We now need to create a new project. Click on the + Create project button

Make sure to give your project a meaningful name and add a sensible description.

Then click on Create

πŸ™‡πŸ»β€β™€οΈ Understanding the Azure Pipeline Build

A build can have multiple stages. Each stage can contain one or more jobs. For example you might have the following stages:

  • Test (my code using unittest)
  • Build (my awesome app)
  • Deploy

You can imagine a pipeline as a dependency graph:

You can find a list of all the available tasks in the Pipelines documentation. Plus you can define your own tasks using bash or PowerShell.


πŸ‘©πŸΏβ€πŸ’» Hands on

Clone this wokrshop's repo: https://github.com/trallard/ci-research to your own GitHub profile.

✨ Let's start by creating our .azure-pipelines/azure-pipelines.yml file in our repo.

# Python example Azure Pipeline

trigger:
- master

# specific branch build
pr:
  branches:
    include:
    - master
    exclude:
    - feature/*  # regex wildcard (or any other regex)

First we specify what triggers the pipeline, in this case pushing to the master branch. Equally, the pr entry determines which cases of Pull Requests will trigger the pipeline as well.

For example, you might not want any pr to build so you can set this to pr:none you can also use regex to define the builds or triggers.

πŸ‘‰πŸΌ Read more about triggers

The next step us defining the pool we want to use. Basically this is the OS for your project:. We are going to start with a basic Ubuntu runner:

pool:
    vmImage: 'Ubuntu-16.04'

And add steps to your pipeline:

steps:
- script: echo "Hello World!"
  displayName: "Run AZ pipelines Hello World"

Commit your changes and push to your repo.

git add azure-pipelines.yml
git push

πŸ›  Setting your pipeline

Back in Azure Devops click on Pipelines > New pipelines and then select GitHub from the options presented:

DO NOT click on the "Use the clasic editor" .

Select the azure-pipelines.yml file in your repo.

Click on save and then run. You should see your first pipeline run and the logs being displayed.

🐍 Python specific pipelines

Replace your steps:

trigger:
  - azure-pipelines
  - master

pool:
  vmImage: "ubuntu-16.04"

steps:
- task: UsePythonVersion@0
  displayName: 'Use Python 3.6'
  inputs:
    versionSpec: '3.6'
    architecture: 'x64'

The steps: element can contain children like:

- task:, which runs a specific task that's defined in Azure DevOps (see the full Task reference)

- script:, which runs an arbitrary set of commands as you see in a moment. The task in the code above is UsePythonVersion, which specifies the version of Python to use on the build agent. The @<n> suffix indicates the version of the task; @0 indicates "preview".

Adding additional steps as if they were bash commands:

- script: python -m pip install --upgrade pip
          displayName: "Upgrade pip"

- script: |
    # commands run within the step
    pip install -r requirements.txt
  displayName: 'Install dependencies'

- script: |
    python -m pip install pylint --quiet
    pylint boston/*.py
    pylint iris/*.py
  displayName: 'Run lint tests'

- script: |
    pip install pytest
    python -m pytest tests/
  displayName: 'Test with pytest'

Save, commit and see your pipeline run!

🐍 Multiple Python versions

You can modify your steps to use a matrix specification: (πŸ‘‰πŸΌ see reference here)

trigger:
  - azure-pipelines
  - master

pool:
  vmImage: 'Ubuntu-16.04'
strategy:
  matrix:
    Python35:
      python.version: '3.5'
    Python36:
      python.version: '3.6'
    Python37:
      python.version: '3.7'

steps:
- task: UsePythonVersion@0
  inputs:
    versionSpec: '$(python.version)'
  displayName: 'Use Python $(python.version)'

- script: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
  displayName: 'Install dependencies'

- script: |
    pip install pytest pytest-azurepipelines
    pytest
  displayName: 'pytest'

Commit your changes and you should be able to see the pipeline run.

πŸš₯ Add a step script for linting using pylint:

1. install pylint from pip
2. lint the boston/*.py and iris/*.py files

πŸ–₯πŸ‘Ύ Adding multi OS support

πŸ‘‡πŸ½ Click to expand!

You can add Windows, Mac OS and Ubuntu runners to your environment, this allows you to have a more extensive set of tests within one single CI provider.

trigger:
  - azure-pipelines
  - master

jobs:
  - job: Ubuntu_testing
    # https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema#pool
    pool:
      vmImage: 'Ubuntu-16.04'
    strategy:
      matrix:
        Python36:
          python.version: '3.6'
        Python37:
          python.version: '3.7'

    steps:
      - task: UsePythonVersion@0
        displayName: 'Get Python version $(python.version)'
        inputs:
          versionSpec: '$(python.version)'
          architecture: 'x64'
      - script: python -m pip install --upgrade pip
        displayName: "Upgrade pip"
      - script: |
          # commands run within the step
          pip install -r requirements.txt
        displayName: 'Install dependencies'
      - script: |
          python -m pip install pylint --quiet
          pylint boston/*.py
          pylint iris/*.py
        displayName: 'Run lint tests'
      - script: |
          pip install pytest pytest-azurepipelines
          python -m pytest tests/
        displayName: 'Test with pytest'

  - job: Windows_unit_test
      pool:
        vmImage: 'windows-2019'

      strategy:
        matrix:
          Python36:
            python.version: '3.6'
          Python37:
            python.version: '3.7'

      steps:
        - task: UsePythonVersion@0
          displayName: 'Get Python version $(python.version)' 
          inputs:
            versionSpec: '$(python.version)'
            architecture: 'x64'
          
        - script: python -m pip install --upgrade pip
          displayName: "Upgrade pip"

        - script: |
            # commands run within the step
            pip install -r requirements.txt
          displayName: 'Install dependencies'

        - script: |
            pip install pytest pytest-azurepipelines
            python -m pytest tests/ 
          displayName: 'Test with pytest'

πŸ–₯🐍 Using conda environments

πŸ‘‡πŸ½ Click to expand!

You can also use and create conda environments within your builds. Let's create a .azure-pipelines/conda.yml file:

trigger:
  - pipelines
  - master

jobs:
  - job:
    displayName: Anaconda_ubuntu
    pool:
      vmImage: "ubuntu-16.04"
    strategy:
      matrix:
        Python36:
          python.version: "3.6"
        Python37:
          python.version: "3.7"
    steps:
      - bash: echo "##vso[task.prependpath]$CONDA/bin"
        displayName: Add conda to PATH

      - bash: conda create --yes --quiet --name testingEnv
        displayName: Create Anaconda environment

      - bash: |
          source activate testingEnv
          conda install --yes --quiet --name testingEnv python=$PYTHON_VERSION --file requirements.txt
        displayName: Install Anaconda packages

      - bash: |
          source activate testingEnv
          pip install pytest pytest-azurepipelines
          python -m pytest tests/
        displayName: pytest

Now we can create a new pipeline. Go to Pipelines > + new > GitHub > Existing yaml file.

This will add a whole new pipeline to your CI, which can have different triggers, environments, tests and builds.

🀯 Complex pipelines: using templates

πŸ‘‡πŸ½ Click to expand!

Sometimes you want to be able to use complex setups without your yaml becoming too convoluted. For these cases you can create "templates" and import them within your yaml file.

Let's create a .azure-pipelines/ci.yml file:

#  using templates for the pipeline
trigger:
  - azure-pipelines
  - master

jobs:
  - job:
    displayName: Anaconda_ubuntu
    pool:
      vmImage: "ubuntu-16.04"
    strategy:
      matrix:
        Python36:
          python.version: "3.6"
        Python37:
          python.version: "3.7"

    # using template
    steps:
      - template: ./anaconda.yml

  # testing in MacOS
  - job:
    displayName: MacOS_test
    pool:
      vmImage: "macOS-10.13"
    strategy:
      matrix:
        Python37:
          python.version: "3.7"

    steps:
      - template: ./anaconda.yml

  #  using TOX for the tests
  - job:
    displayName: TOX_tests
    pool:
      vmImage: "ubuntu-16.04"
    strategy:
      matrix:
        py36:
          python.version: "3.6"
          tox.env: py36
        py37:
          python.version: "3.7"
          tox.env: py37
        py36-black:
          python.version: "3.6"
          tox.env: py36-black

    steps:
      - task: UsePythonVersion@0
        displayName: "Use Python $(python.version) for tests"
        inputs:
          versionSpec: "$(python.version)"
          architecture: "x64"

      - script: pip install -U pip
        displayName: "Upgrade pip"

      - script: pip install tox
        displayName: "Install tox"

      - script: tox -e $(tox.env)
        displayName: "Run tox -e $(tox.env)"

And an anaconda template .azure-pipelines/anaconda.yml

steps:
  - bash: echo "##vso[task.prependpath]$CONDA/bin"
    displayName: Add conda to PATH

  - bash: conda create --yes --quiet --name testingEnv
    displayName: Create Anaconda environment

  - bash: |
      source activate testingEnv
      conda install --yes --quiet --name testingEnv python=$PYTHON_VERSION --file requirements.txt
    displayName: Install Anaconda packages

  - bash: |
      source activate testingEnv
      pip install pytest pytest-azurepipelines
      python -m pytest tests/
    displayName: pytest

🐳 Bonus: Docker with Azure pipeliens

Note that you need a DockerHub account to complete all of this section. Though you can omit the pushing your image.

First you need to create a connection service to access DockerHub. This will help us to keep our password secret.

To do this click on project settings > service connection > new service connection > docker registry

service conn

Give your connection a name and fill in with your Docker details:

service conn

πŸ“Using a Dockerfile

πŸ‘‡πŸ½ Click to expand!

If you already have a Dockerfile in place we can straight away create a new pipeline ./azure-pipelines/dockerfile.yml

# Docker
# Build a Docker image
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker

trigger:
  - master
  
resources:
  - repo: self

variables:
  tag: "$(Build.BuildId)"
  imageName: "trallard/ci-research"

stages:
  - stage: Build
    displayName: Build image
    jobs:
      - job: Build
        displayName: Build
        pool:
          vmImage: "Ubuntu-18.04"
        steps:
          - task: Docker@2
            displayName: Login DockerHub
            inputs:
              command: login
              containerRegistry: trallard-docker
          - task: Docker@2
            displayName: Build an image
            inputs:
              command: buildAndPush
              dockerfile: "**/Dockerfile"
              repository: $(imageName)
              tags: |
                $(tag)

You can learn more about the Docker tasks πŸ‘‰πŸΌhere

Add your pipeline as we did with the Conda an Python ones.

πŸ›  Using repo2docker

We can also use repo2docker to simplify the creation of your Docker images.

πŸ‘‡πŸ½ Click to expand!

You can learn more about repo2docker πŸ‘‰πŸΌ here.

We start by creating a new file: .azure-pipelines/repo2docker.yml

trigger:
  - master
  - pipelines

stages:
  - stage: Build
    displayName: Build image with repo2docker
    jobs:
      - job: Build
        displayName: Build
        pool:
          vmImage: "Ubuntu-18.04"
        steps:
          - task: UsePythonVersion@0
            displayName: 'Get Python version' 
            inputs:
              versionSpec: '3.7'
          - task: Docker@2
            displayName: Login DockerHub
            inputs:
              command: login
              containerRegistry: trallard-docker
          - script: python -m pip install --upgrade pip
            displayName: "Upgrade pip"
          - script: python -m pip install jupyter-repo2docker
            displayName: "Install repo2docker"
          - script: |
              jupyter-repo2docker --debug --user-name jovyan --user-id 1000 --no-run --image-name "trallard/ci-research-r2d:latest" https://github.com/trallard/ci-research.git
            displayName: "Create Docker image"
          - task: Docker@2
            displayName: Push image
            inputs: 
              command: push
              imageName: trallard/ci-research-r2d:latest