2
votes

I come from GitLab and its .gitlab-ci.yml and I am experimenting with Azure DevOps multi stage pipelines but I am quite confused about how it works and what's the best strategy even after reading several documentation articles at https://docs.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops

Please allow me to ask several related questions for the basic scenario I'm trying, which is compile, run unit tests, pack a nuget Package for the whole solution (it could contain multiple projects/nuGet packages) and publish the package to a nuGet feed (if the branch is master, a release version, otherwise a pre-release version). This is the repository I'm grabbing the code from: https://github.com/sasw-diego/sasw-test-support It would generate only a nuGet package but I've got other multiproject solutions which should generate many nuGet packages

This is my azure-pipelines.yml so far:

trigger:
- master
- feature/*

pool:
  vmImage: ubuntu-latest

variables:
  NUGET_FOLDER_NAME: nupkgs
  NUGET_REPOSITORY: https://whatever
  PRERELEASE_SUFFIX: $(Build.BuildId)
  PIPELINE_ARTIFACT_NAME: $(Build.BuildNumber)

stages:
- stage:
  displayName: 'Build'
  jobs:
  - job: 'Build'
    steps:
    - task: NuGetAuthenticate@0
      displayName: 'Authenticate in NuGet feed'
    - script: dotnet restore --no-cache --force
      displayName: 'Restore dependencies'
    - script: dotnet build --configuration Release --no-restore
      displayName: 'Build for Release'
    - script: ls $(System.DefaultWorkingDirectory)
      displayName: 'List content'
    - publish: $(System.DefaultWorkingDirectory)
      artifact: $(PIPELINE_ARTIFACT_NAME)
- stage:
  displayName: 'Automated Tests'
  condition: succeeded()
  jobs:
  - job:
    displayName: 'Unit Tests'
    steps:
    - download: current
      artifact: $(PIPELINE_ARTIFACT_NAME)
    - script: ls -a
      displayName: 'View'
    - script: ls ./test
      displayName: 'View test'
    - script: ls ./test/Sasw.TestSupport.UnitTests
      displayName: 'View folder'
    - script: dotnet vstest test/*UnitTests/bin/Release/**/*UnitTests.dll
      displayName: 'Run unit tests'
- stage:
  displayName: 'NuGet Package'
  condition: succeeded()
  jobs:
  - job:
    displayName: 'Pack Preview Version'
    condition: ne(variables['Build.SourceBranch'], 'refs/heads/master')
    steps:
    - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME)
      displayName: 'Pack'
  - job:
    displayName: 'Pack Stable Version'
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/master')
    steps:
    - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME) --version-suffix $(PRERELEASE_SUFFIX) --include-source --include-symbols -p:SymbolPackageFormat=snupkg
      displayName: 'Pack'

  1. Which is the "best" strategy for a multistage? I see Azure DevOps pipelines have the concept of Stage > Jobs > Task but they all look similar to me. So I decide to divide the process in stages such as Build - AutomatedTests - NuGet Package - Publish As you can see, it's a sequential process where each stage needs something from the previous one. The automated tests needs the built code (dll), the nuGet package needs also access to the built code, the publish needs access to the nupkg generated, etc. I don't know whether it's ok to follow this strategy or it's better to have a single stage with multiple jobs, or even a single job with multiple tasks. As I said I don't fully understand the benefit of having so many concepts and how do they fit in with my needs.
  2. Is Azure multistage pipelines supposed to replace the old Build and Release as separate concepts? It makes sense to me to have the CI/CD in one place with a multistage approach and scriptable to make it versioned in a source control repository. But I still see the Release concept as a separate one currently on Azure DevOps. So probably I should use the azure pipelines yml up until the package "step" and then with Release go grab this nupkg and publish it onto some feed. Not sure what the benefit would be though.
  3. I am having problems making the output of a stage as the input of the next one, and probably it's because I don't fully get it. In the yml above the build stage succeeds but the Automated Testing stage fails on its Run Unit Tests job with an error No test source files were specified. I verified this happens because there is no generated folder bin at all. It's seems strange since I am copying everything from the previous stage (and the previous stage generated bin folders with dll), but then the next stage, despite being able to download everything, it cannot find the tests.

This is the log for the stage that fails: https://gist.github.com/sasw-diego/df66eccf71bbfc044a4d72be96268c9a

It'd be very helpful if somebody spots what am I missing to be able to understand this process. Any link to clarify all these concepts would be much appreciated. Ta

PS: This is a similar generic CI/CD I had in GitLab to upload 1 or many nuGets to a feed: https://gist.github.com/sasw-diego/bf46258cb1ad0aa5241e8d1866b53f48


UPDATE: Thanks for the answer. I successfully created a CI/CD yml with multi-stage pipelines that restores, builds, executes tests, runs a container (e.g: an eventStore host) to run integration tests against it, and releases the nuGet in artifacts. So mission accomplished! I've separated it into different stages and jobs to probe some points

trigger:
- master
- feature/*

pool:
  vmImage: ubuntu-18.04

variables:
  - group: sasw-common-variables
  - name: NUGET_FOLDER_NAME
    value: nupkgs
  - name: PIPELINE_ARTIFACT_NAME
    value: $(Build.BuildNumber)
  - name: PATH_PIPELINE_ARTIFACT_NAME
    value: $(Pipeline.Workspace)/$(PIPELINE_ARTIFACT_NAME)
  - name: NUGET_API_KEY
    value: $(nuget-api-key)
  - name: NUGET_FEED
    value: $(nuget-feed)
  - name: PRERELEASE_SUFFIX
    value: $(nuget-prerelease-suffix)

resources:
  containers:
    - container: eventstore
      image: eventstore/eventstore:release-5.0.2
      ports:
      - 1113:1113
      env:
        EVENTSTORE_INT_TCP_PORT: 1113
        EVENTSTORE_EXT_TCP_PORT: 1113
        EVENTSTORE_INT_HTTP_PORT: 2113
        EVENTSTORE_EXT_HTTP_PORT: 2113
        EVENTSTORE_EXT_HTTP_PREFIXES: http://*:2113/

stages:
  - stage:
    displayName: 'Build'
    jobs:
      - job: 'Build'
        displayName: 'Build & Create nuGet Package'
        services: 
            eventstore: eventstore
        steps:
          - task: NuGetAuthenticate@0
            displayName: 'Authenticate in NuGet feed'
          - script: dotnet restore --no-cache --force
            displayName: 'Restore dependencies'
          - script: dotnet build --configuration Release --no-restore
            displayName: 'Build with Release Configuration'
          - script: dotnet vstest test/*UnitTests/bin/Release/**/*UnitTests.dll
            displayName: 'Run unit tests'
          - script: dotnet vstest test/*IntegrationTests/bin/Release/**/*IntegrationTests.dll
            displayName: 'Run integration tests'
          - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME)
            condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
            displayName: 'Create release nuGet'
          - script: dotnet pack *.sln --configuration Release --output $(NUGET_FOLDER_NAME) --version-suffix $(PRERELEASE_SUFFIX) --include-source --include-symbols -p:SymbolPackageFormat=snupkg
            condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master'))
            displayName: 'Create pre-release nuGet'
          - publish: $(System.DefaultWorkingDirectory)/$(NUGET_FOLDER_NAME)
            artifact: $(PIPELINE_ARTIFACT_NAME)
            displayName: 'Publish pipeline artifact'
  - stage:
    displayName: 'Release'
    condition: succeeded()
    jobs:
      - job: 'Publish'
        displayName: 'Publish nuGet Package'
        steps:
          - download: current
            artifact: $(PIPELINE_ARTIFACT_NAME)
            displayName: 'Download pipeline artifact'
          - script: ls $(PATH_PIPELINE_ARTIFACT_NAME)
            displayName: 'Display contents of downloaded articacts path'
          - task: NuGetAuthenticate@0
            displayName: 'Authenticate in NuGet feed'
          - task: UseDotNet@2
            displayName: 'Use latest .NET Core sdk 3.x'
            inputs:
              packageType: sdk
              version: 3.x
              includePreviewVersions: true
              installationPath: $(Agent.ToolsDirectory)/dotnet
          - script: dotnet nuget push $(PATH_PIPELINE_ARTIFACT_NAME)/**/*.nupkg --source $(NUGET_FEED) --api-key $(NUGET_API_KEY) --skip-duplicate
            displayName: 'Uploads nuGet packages'
1

1 Answers

3
votes
  1. your whole build definition should be a single "stage". Those Build - AutomatedTests - NuGet Package - Publish are not stages, those are logical parts of the build.
  2. probably sometime in the future that will\might happen
  3. that is because you are using stages when you are not supposed to. each stage runs on a different agent. you should have a single stage and all the tasks should run inside that stage.