Continuous Integration in GitHub Actions, deploy in AzureDevops

My dear friend Matteo just published an interesting article on integration between GitHub actions and Azure Devops Pipeline here. I have a different scenario where I’ve already published a GitHub release from a GitHub action, but I have nothing ready in GitHub to release in my machines.

While GitHub is really fantastic for source code and starts having a good support for CI with Actions, in the release part it still miss a solution. Usually this is not a problem, because we have Azure DevOps or other products that can fill the gap.

This specific project is a firewall that closes every port in a machine and listen on UDP ports for a specific message to open other ports, thus, a machine where the service is installed has no way to be contacted unless you use the appropriate client to ask for port opening. I want the deploy to be automatic, no way I’m going to all my machines, login in RDP and then update the service, everything should happen automatically.

The really nice aspect of Azure DevOps release pipelines, is that, once you installed agents in one or more machines, those machines will contact Azure DevOps and pull works to do without the need for the machine to be contacted from outside world.

This is a key point of Azure DevOps release pipeline, you do not need to have any special setup in deploy target, you should simply let the target to be able to contact Azure DevOps site (https://dev.azure.com).

Another nice aspect of Azure DevOps release pipeline, is that it can use many sources for artifacts, not only those produced by Azure DevOps CI pipeline. When you add an artifacts, you can choose a GitHub release as well as Jenkins and other CI providers like Azure Artifacts (check Matteo’s article to see how to publish in azure artifacts from a GitHub Action)

image

Figure 1: Choose GitHub release as artifact source

To use GitHub as source you should have already connected your azure DevOps to GitHub with a service connection, another cool feature of Azure DevOps. As an administrator you can connect Azure DevOps account to GitHub, then give permission to specific people to use that service connection, without requiring them to know the real credentials to connect to the service (Github in this example). Once you have one or more connection active you can simply choose the repository to use. In Figure 2 You can see the configuration I choose for my project.

image

Figure 2: Configure GitHub release as artifact source.

Settings are: Repository (1), default version of the release to use (2) and finally alias you use for that specific artifact in your release (3). Remember that a release can have more than a single artifact as source, if you have a simple project like this, probably you have a single artifact.

Now you have the full power of Azure DevOps pipeline at your fingertips, in this specific example I just need to deploy a Windows Service and this is the pipeline to release in my stages.

image

Figure 3: Release pipeline for a Windows Service

This is a standard  four phase for a service release, first step is needed to stop the service if it is running, then I extract the artifacts coming from GitHub as 7zipped files, then I overwrite the directory where I’ve installed the service and finally I install the service if needed and restart it.

Before launching the release, you need to be sure that you have at least one release associated to that repository, in this example I have release 0.4.1 and others available.

image

Figure 4: Available releases for my GitHub repository

When you create a release (if the release is launched manually) you can choose GitHub release you want to use (if the release is automatic it will use release configured in the artifact configuration, usually latest one), the connection is done by Azure DevOps for you, no need to know credentials of GitHub, just choose the version you want to install and Bam, you are ready.

image

Figure 5: Choose the release you want to use directly from Azure DevOps

When the release starts, your target will download the workflow, it will instruct the agent to download artifacts from GitHub and then your scripts will run releasing the software.

image

Figure 6: Release completed, version 0.4.1 is now released on my machines.

As you can verify from detail page, artifacts are indeed downloaded by a GitHub standard release.

image

Figure 7: Artifacts downloaded directly from GitHub.

If everything runs successfully, you will have the new version installed on all machines part of deployment group used.

image

Figure 8: All steps executed successfully.

As you can see, Azure DevOps has a real powerful way to connect other services like GitHub and this is ideal to compensate the gap that other tools has at the moment. This leaves you free to compose your tooling chain, using the service that is best for the specific part.

Gian Maria.

Azure DevOps pipeline template for build and release .NET core project

Some days ago I’ve blogged on how to release projects on GitHub with actions, now it is time to understand how to do a similar thing in Azure DevOps to build / test / publish a .NET core library with nuget. The purpose is to create a generic template that can be reused on every general that needs to build an utility dll, run test and publish to a Nuget feed.

The ability to create template pipeline in Azure DevOps is a great opportunity to define a standard way to build / test /  deploy projects in your organization

Everything starts with a dedicated repository where I store a single build template file to create a MultiStage pipeline, where the first stage is a .NET core build test, and the second stage is publishing with nuget. Such simple build could be done with a single stage, but creating it with MultiStage gives me the opportunity to explain some interesting aspect of Azure DevOps pipelines.

Everything starts with parameters declaration.

parameters:
  buildName: 'Specify name'
  solution: ''
  buildPlatform: 'ANY CPU'
  buildConfiguration: 'Release'
  nugetProject: ''
  nugetProjectDir: ''
  dotNetCoreVersion: '2.2.301'
  pool: 'Default'
  nugetPublish: true

Every single parameter can have a default option, and can be overridden, after parameter first stage starts, build and test .NET core project.

jobs:

- job: 'Build_and_Test'
  pool:
    name: ${{parameters.pool}}

  steps:
  - task: DotNetCoreInstaller@2
    displayName: 'Use .NET Core sdk ${{parameters.dotNetCoreVersion}}'
    inputs:
      version: ${{parameters.dotNetCoreVersion}}

  - task: DotNetCoreCLI@2
    displayName: 'install if needed dotnet gitversion tool'
    inputs:
      command: 'custom'
      custom: 'tool'
      arguments: 'update GitVersion.Tool --tool-path $(Agent.ToolsDirectory)/gitversion/5.1.3 --version 5.1.3'
  
  - script: |
      $(Agent.ToolsDirectory)/gitversion/5.1.3/dotnet-gitversion $(Build.Repository.LocalPath) /output buildserver

  - powershell: |
      Write-Host "##vso[build.updatebuildnumber]${{parameters.buildName}}-$env:GITVERSION_FULLSEMVER"

      $var = (gci env:*).GetEnumerator() | Sort-Object Name
      $out = ""
      Foreach ($v in $var) {$out = $out + "`t{0,-28} = {1,-28}`n" -f $v.Name, $v.Value}

      write-output "dump variables on $env:BUILD_ARTIFACTSTAGINGDIRECTORY\test.md"
      $fileName = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY\test.md"
      set-content $fileName $out

      write-output "##vso[task.addattachment type=Distributedtask.Core.Summary;name=Environment Variables;]$fileName"

  - task: DotNetCoreCLI@2
    displayName: 'dotnet restore'
    inputs:
      command: restore
      projects: '${{parameters.solution}}'
      feedsToUse: config
      nugetConfigPath: src/NuGet.Config

  - task: DotNetCoreCLI@2
    displayName: 'dotnet build'
    inputs:
      command: build
      projects: '${{parameters.solution}}'
      configuration: '$(BuildConfiguration)'
      arguments: /p:AssemblyVersion=$(GITVERSION.ASSEMBLYSEMVER) /p:FileVersion=$(GITVERSION.ASSEMBLYSEMFILEVER) /p:InformationalVersion=$(GITVERSION.SHA)

  - task: DotNetCoreCLI@2
    displayName: 'dotnet test'
    inputs:
      command: test
      nobuild: true
      projects: '${{parameters.solution}}'
    continueOnError: true

  - powershell: |
      echo "[task.setvariable variable=GITVERSION_ASSEMBLYSEMVER;isOutput=true]$(GITVERSION.ASSEMBLYSEMVER)"
      echo "[task.setvariable variable=GITVERSION_ASSEMBLYSEMFILEVER;isOutput=true]$(GITVERSION.ASSEMBLYSEMFILEVER)"
      echo "[task.setvariable variable=GITVERSION_SHA;isOutput=true]$(GITVERSION.SHA)"
      echo "[task.setvariable variable=GITVERSION_FULLSEMVER;isOutput=true]$(GITVERSION.FULLSEMVER)"
      echo "##vso[task.setvariable variable=GITVERSION_ASSEMBLYSEMVER;isOutput=true]$(GITVERSION.ASSEMBLYSEMVER)"
      echo "##vso[task.setvariable variable=GITVERSION_ASSEMBLYSEMFILEVER;isOutput=true]$(GITVERSION.ASSEMBLYSEMFILEVER)"
      echo "##vso[task.setvariable variable=GITVERSION_SHA;isOutput=true]$(GITVERSION.SHA)"
      echo "##vso[task.setvariable variable=GITVERSION_FULLSEMVER;isOutput=true]$(GITVERSION.FULLSEMVER)"
    name: 'SetGitVersionVariables'

You can recognize in this script many of the techniques already discussed in previous GitHub Action post, it is just declined for Azure DevOps. The main difference is that Actions are directed toward a simple way to execute a “script” composed by a series of commandline istruction and tasks while Pipelines are more structured to create a workflow, but everything is really similar.

Since this pipeline will run on Windows, I can simply use PowerShell task to execute inline script. The most peculiar part of the script is the last PowerShell script, that contains a series of Pipeline commands echoing ##vso in the output stream. The purpose of that step is to save some variable values to reuse in subsequent stages. This is a killer feature, in this example I runs GitVersion on first stage only, then pass all the output to be reused by subsequent Stages.

The ability to pass variable values between stages opens a wide range of opportunities, where you can run special tools on special agents, then reuse output in all other stages.

This is really handy if you need to execute subsequent stages, in different operating system / environment and you want to simply reuse some variable values that was calculate in some previous stage. Suppose you have a tool that runs only on windows, you can run in a stage, then reuse output in subsequent stages that runs in linux.

Publish stage is really simple, the only really interesting part is the declaration.

- job: 'Pack_Nuget'
  dependsOn: 'Build_and_Test'
  condition: eq(${{parameters.nugetPublish}}, true)

  pool:
    name: ${{parameters.pool}}

  variables:
    GITVERSION_ASSEMBLYSEMVER: $[ dependencies.Build_and_Test.outputs['SetGitVersionVariables.GITVERSION_ASSEMBLYSEMVER'] ]
    GITVERSION_ASSEMBLYSEMFILEVER: $[ dependencies.Build_and_Test.outputs['SetGitVersionVariables.GITVERSION_ASSEMBLYSEMFILEVER'] ]
    GITVERSION_SHA: $[ dependencies.Build_and_Test.outputs['SetGitVersionVariables.GITVERSION_SHA'] ]
    GITVERSION_FULLSEMVER: $[ dependencies.Build_and_Test.outputs['SetGitVersionVariables.GITVERSION_FULLSEMVER'] ]

The stage starts with a name and a dependency declaration on previous Build_and_test stage, this implies that this stage can run only if the previous stage run successfully. The execution is also dependent on a parameter called nugetPublish, that should be true for this stage to execute. This allows the pipeline that uses this template to choose if publish stage should run.

The ability to conditionally execute stages allows for complex workflow execution, where each stage can decide on following stages execution.

Following the declaration we can find a variables section, where I actually load variable from previous stage in this stage. In this specific example I’m retrieving all GitVersion output value that I need to build NugetPackage.

The stage ends with standard pack and publish of NugetPackage, using SemVer numbers that were passsed from previous stage.

steps:

  - task: DotNetCoreInstaller@2
    displayName: 'Use .NET Core sdk ${{parameters.dotNetCoreVersion}}'
    inputs:
      version: ${{parameters.dotNetCoreVersion}}

  - powershell: |
      echo "GITVERSION_ASSEMBLYSEMVER $(GITVERSION_ASSEMBLYSEMVER)"
      echo "GITVERSION_ASSEMBLYSEMFILEVER $(GITVERSION_ASSEMBLYSEMFILEVER)"
      echo "GITVERSION_SHA $(GITVERSION_SHA)"
      echo "GITVERSION_FULLSEMVER $(GITVERSION_FULLSEMVER)"
    name: 'Dumpvariables'

  - task: DotNetCoreCLI@2
    displayName: NuGet Pack
    inputs:
      command: custom
      custom: pack
      projects: ${{parameters.nugetProject}}
      arguments: -o "$(Build.ArtifactStagingDirectory)\NuGet" -c ${{parameters.BuildConfiguration}} /p:PackageVersion=$(GITVERSION_FULLSEMVER) /p:AssemblyVersion=$(GITVERSION_ASSEMBLYSEMVER) /p:FileVersion=$(GITVERSION_ASSEMBLYSEMFILEVER) /p:InformationalVersion=$(GITVERSION_SHA)

  - task: NuGetCommand@2
    displayName: NuGet Push
    inputs:
      command: push
      packagesToPush: '$(Build.ArtifactStagingDirectory)\NuGet\*.nupkg'
      nuGetFeedType: internal
      publishVstsFeed: '95a01998-aa90-433c-8077-41da981289aa'
    continueOnError: true

Once this template file is checked in in AzureDevOps repository, you can refer it from another project in the same Organization. This is the real power of templates, I wrote the definition one time in a dedicated repository and every other project that needs to declare a pipeline to build / test / publish can simply refers to this template. With a few lines of YAML you can create a pipeline for your new project.

trigger:
  branches:
    include:
      - master
      - develop
      - release/*
      - hotfix/*
      - feature/*

resources:
  repositories:
  - repository: templatesRepository
    type: git
    name: Jarvis/BuildScripts
    ref: refs/heads/develop

jobs:

- template: 'NetStandardTestAndNuget.yaml@templatesRepository'
    
  parameters:
    buildName: 'LicenseManager'
    solution: 'src/LicenseManager.sln'
    pool: '$(pool)'
    dotNetCoreVersion: '3.1.100'
    nugetPublish: true
    nugetProject: 'src/LicenseManager.Core/LicenseManager.Core.csproj'

Look at how simple it is, just define triggers, add repository that contains the build script in the resources section, and simple populate the parameters, and, BAM, your project has a pipeline for build / test / publish.

The ref parameter of reference section allows you to choose which branch use to grab script template, in this project I want the latest trunk version, so I’ve choose develop, other project can stay on master to have a better stable version.

The template once uses an old version of task of mine to perform GitVersion, that is become really obsolete and it is not useful to maintain anymore. I’ve decided to upgrade the template to use dotnet-gitversion command line tool, I’ve upgraded the template in a feature branch, using a project as test, then I’ve merged in develop and when I’ll finally close it on master, every project that uses this template, will use the new definition, without any user intervention.

Thanks to template I can upgrade the template in dedicated branch, test with actual project, then promote the upgrade through standard develop, release and master branch to automatically upgrade pipeline of all projects that uses this template.

How cool is that.

It is actually superfluous telling you how important  is to have an automatic build / test pipeline, as an example it seems that yesterday night I’ve broke the test Smile, shame on me.

image

Figure 1: Build results showing me that some test fails

The nice aspect of Azure DevOps pipeline is that they have a dedicated section to examine test failures, that gives me immediate insight on what went wrong. It seems that I’ve messed something in exception handling.

image

Figure 2: Dedicated pane to view test result.

Actually Azure DevOps pipelines are more complex than GitHub actions, but they can also solve more complex problems and are (at date of today) probably better suited for an enterprise, especially with the ability to define template to make a standard in how to build projects of the company. Another key value is the ability to immediately explore failed tests and code coverage for your build, not to mention multi stage pipeline to create complex build / release workflows.

Actually we have a superposition between Azure DevOps and GitHub pipelines, both of them now owned by Microsoft. My advice is just look at what are the capabilities as today and choose what better suites you.

Remember also that you can easily use Azure DevOps pipeline to build a GitHub project, just point the build to a GitHub repository after you connected your GitHub organization  / account to Azure DevOps. The only limitation is that build template file should still reside on AzureDevOps account (a limitation that probably will expire soon).

Remember that you can freely use Azure DevOps pipeline to build GitHub projects without any problem, just choose the product that better suites your need.

Gian Maria.

Strange error disallow my .NET core application to start

Today I cloned in my workstation a .NET core application that works perfectly on my laptop, but when I started it I got this error

An attempt was made to access a socket in a way forbidden by its access permissions

I’ve spent almost 10 minutes to find why my netsh rule is not working (I work with a user that is not administrator of the machine) and finally, by frustration I opened Visual Studio with administrator user, just to verify that the error is still there.

For whatever reason (I’ve not time to investigate right now) port 21000 is somewhat blocked by some firewall rule apparently, because it is free, but kesterel is continuing giving me that error even if runs as admin.

Changing port solved the issue, but left me puzzled :/

Gian Maria.

Release software with GitHub actions and GitVersion

One of the nice aspect of GitHub actions is that you can automate stuff simply with command line tools. If you want to do continuous release of your software and you want to use GitVersion tool to determine a unique SemVer version from the history, here is the sequence of steps.

1) Iinstall/update gitversion tool with commandline tools
2) Run GitVersio to determine SemVer numbers
3) Issue a standard build/test using SemVerNumbers of step 2
4) If tests are ok, use dotnet publish command (with SemVer numbers) to publish software
5) Zip and upload publish result
6) It the branch is Master publish a GitHub release of your software.

Automating your CI pipeline using only CommandLine tools makes your build not dependent on the Engine you are using.

I’ve done such small exercise on a public project you can found here (https://github.com/AlkampferOpenSource/StupidFirewallManager) and it is composed by two command line tools, one is server version the other is the client.

Complete workflow definition can be found here (https://github.com/AlkampferOpenSource/StupidFirewallManager/blob/master/.github/workflows/dotnetcore.yml).

In this example I’m simply using some open source tasks to automate the whole process with GitHub Actions. The first step is being sure that right version of DotNetCore Sdk is used, and that GitVersion tool is installed.

    steps:
    - uses: actions/checkout@v1
      
    - name: Fetch all history for all tags and branches
      run: git fetch --prune
    
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: '5.0.100-preview.1.20155.7'  

    - name: Install GitVersion
      uses: gittools/actions/gitversion/setup@v0.9
      with:
          versionSpec: '5.1.x'

The cool part is that I do not need to have anything preinstalled on the machine that runs the action, everything will be downloaded and installed by action definition. In this specific example my software is build with a pre-release of .NET Sdk (5.0.100-preview), a tooling that probably is not preinstalled in the action machine, but thanks to the capability of GitHub actions I can simply require installation on the fly before compiling everything. 

Being able to install required toolchain directly from Action definition allows you to run your action on public available GitHub agents.

Point 2 is achieved simply running GitVersion tool (installed from point 1) with a dedicated action. Another cool part of GitHub action is that you can simply refer custom action directly from your definition, no need to install anything.

- name: Use GitVersion
      id: gitversion # step id used as reference for output values
      uses: gittools/actions/gitversion/execute@v0.9
    - run: |
        echo "Major: ${{ steps.gitversion.outputs.major }}"
        echo "Minor: ${{ steps.gitversion.outputs.minor }}"
        echo "Patch: ${{ steps.gitversion.outputs.patch }}"
        echo "PreReleaseTag: ${{ steps.gitversion.outputs.preReleaseTag }}"
        echo "PreReleaseTagWithDash: ${{ steps.gitversion.outputs.preReleaseTagWithDash }}"
        echo "PreReleaseLabel: ${{ steps.gitversion.outputs.preReleaseLabel }}"
        echo "PreReleaseNumber: ${{ steps.gitversion.outputs.preReleaseNumber }}"
        echo "WeightedPreReleaseNumber: ${{ steps.gitversion.outputs.weightedPreReleaseNumber }}"
        echo "BuildMetaData: ${{ steps.gitversion.outputs.buildMetaData }}"
        echo "BuildMetaDataPadded: ${{ steps.gitversion.outputs.buildMetaDataPadded }}"
        echo "FullBuildMetaData: ${{ steps.gitversion.outputs.fullBuildMetaData }}"
        echo "MajorMinorPatch: ${{ steps.gitversion.outputs.majorMinorPatch }}"
        echo "SemVer: ${{ steps.gitversion.outputs.semVer }}"
        echo "LegacySemVer: ${{ steps.gitversion.outputs.legacySemVer }}"
        echo "LegacySemVerPadded: ${{ steps.gitversion.outputs.legacySemVerPadded }}"
        echo "AssemblySemVer: ${{ steps.gitversion.outputs.assemblySemVer }}"
        echo "AssemblySemFileVer: ${{ steps.gitversion.outputs.assemblySemFileVer }}"
        echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}"
        echo "InformationalVersion: ${{ steps.gitversion.outputs.informationalVersion }}"
        echo "BranchName: ${{ steps.gitversion.outputs.branchName }}"
        echo "Sha: ${{ steps.gitversion.outputs.sha }}"
        echo "ShortSha: ${{ steps.gitversion.outputs.shortSha }}"
        echo "NuGetVersionV2: ${{ steps.gitversion.outputs.nuGetVersionV2 }}"
        echo "NuGetVersion: ${{ steps.gitversion.outputs.nuGetVersion }}"
        echo "NuGetPreReleaseTagV2: ${{ steps.gitversion.outputs.nuGetPreReleaseTagV2 }}"
        echo "NuGetPreReleaseTag: ${{ steps.gitversion.outputs.nuGetPreReleaseTag }}"
        echo "VersionSourceSha: ${{ steps.gitversion.outputs.versionSourceSha }}"
        echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.commitsSinceVersionSource }}"
        echo "CommitsSinceVersionSourcePadded: ${{ steps.gitversion.outputs.commitsSinceVersionSourcePadded }}"
        echo "CommitDate: ${{ steps.gitversion.outputs.commitDate }}"

Point 3 requires only a run of dotnet restore followed by a dotnet test, to verify that code compiles and all tests are green.

    - name: Restore nuget with dotnet
      run: dotnet restore src/StupidFirewallManager.sln
           
    - name: dotnet tests
      run: dotnet test src/StupidFirewallManager.sln /p:AssemblyVersion=${{ steps.gitversion.outputs.assemblySemFileVer }} /p:FileVersion=${{ steps.gitversion.outputs.assemblySemFileVer }} /p:InformationalVersion=${{ steps.gitversion.outputs.Sha }}

A special mention is made to dotnet test parameters, where I specified AssemblyVersion, FileVersion and InformationalVersion directly from command line, using the SemVer number of GitVersion as assembly and file version, but full SHA for informational version.

You have various method to choose version number of the software during build phase, with full Framework you are forced to modify attributes in AssemblyInfo.cs (or AssemblyInfo.vb) before issuing the build. That options is available in GitVersion with UpdateAssemblyInfo options, but it is really clumsy because it does not gave you the ability to choose exactly what number goes in InformationalVersion, that, in my opinion, is most important of all the three. This is why in the past I’ve always use a PowerShell version to update AssemblyInfo.cs.

To be fair, GitVersion does an excellent work in updating AssemblyInfo.cs, but I’ve found a couple of problems, the first is that it actually only Updates the attributes, so you should not forget to insert an AssemblyInformationalVersion in your source code. The second problem is that it uses a too long name for that value, something like 0.5.0-alpha.11+Branch.develop.Sha.21fa38932256d7b64661e6363982dda39eb48b23. While it contains the Sha of the repository, that in my opinion is the most important value, it should be at first position of the string, due to limiting space you have when you look at file property in windows (Figure 5).

Using full Git Sha as informational version gives you the exact version of the code used to produce that artifacts. No way to alter the code and have the same Git Sha makes that number really important and usually it is the only information you need as Informational Version.

Points 4 and 5 are a simple set of Publish/compress/upload artifacts steps, made easy by the presence of 7zip in all machines (thanks Giulio for pointing me in the right direction).

    - name: dotnet publish server
      run: dotnet publish src/StupidFirewallManager/StupidFirewallManager.csproj --configuration release /p:AssemblyVersion=${{ steps.gitversion.outputs.assemblySemFileVer }} /p:FileVersion=${{ steps.gitversion.outputs.assemblySemFileVer }} /p:InformationalVersion=${{ steps.gitversion.outputs.Sha }}

    - name: dotnet publish client
      run: dotnet publish src/StupidFirewallManager.Client/StupidFirewallManager.Client.csproj --configuration release /p:AssemblyVersion=${{ steps.gitversion.outputs.assemblySemFileVer }} /p:FileVersion=${{ steps.gitversion.outputs.assemblySemFileVer }} /p:InformationalVersion=${{ steps.gitversion.outputs.Sha }}
    
    - name: 7Zip client
      run: 7z a -t7z -mx=9 client.7z ./src/StupidFirewallManager.Client/bin/release/netcoreapp5.0/publish/
    
    - name: 7Zip server
      run: 7z a -t7z -mx=9 server.7z ./src/StupidFirewallManager/bin/release/netcoreapp5.0/publish/

    - uses: actions/upload-artifact@v1
      with:
        name: "StupidFirewallManagerServer-${{ steps.gitversion.outputs.fullSemVer }}"
        path: server.7z

    - uses: actions/upload-artifact@v1
      with:
        name: "StupidFirewallManagerClient-${{ steps.gitversion.outputs.fullSemVer }}"
        path: client.7z

I’ve chosen to always create 7zip file with the same name (client.7z and server.7z) but when it is time to upload to pipeline as artifacts, it is nice to change name and give a full SemVer number. As you can verify, since we are in .NET Core, I was able to specify Assembly, File and Informational version directly in command line, without any need to have attributes in AssemblyInfo.cs files. An approach that I really like and prefer (no file to search and modify before the build).

In Figure 1 you can finally find how the artifacts are named in Action run summary page.

Artifacts uploaded by action run, you can notice the full semver.

Figure 1: Artifacts uploaded by action run, you can notice the full semver.

For final step (release) I’ve chosen to use a custom action to publish file on GitHub release and to automatically create release if no one with that name was created. I know that there are now official actions (from GitHub team) but this custom action worked perfectly in the past and it is still perfectly working today, so why change.

    - name: Upload server binaries to release
      if: contains(github.ref, 'master')
      uses: svenstaro/upload-release-action@v1-release
      with:
        repo_token: ${{ secrets.GITHUB_TOKEN }}
        file: server.7z
        asset_name: Server.7z
        tag: "${{ steps.gitversion.outputs.fullSemVer }}"
        overwrite: true
        
    - name: Upload client binaries to release
      if: contains(github.ref, 'master')
      uses: svenstaro/upload-release-action@v1-release
      with:
        repo_token: ${{ secrets.GITHUB_TOKEN }}
        file: client.7z
        asset_name: Client.7z
        tag: "${{ steps.gitversion.outputs.fullSemVer }}"
        overwrite: true

The only peculiarity of these two steps is the if clause, that allows me to ask Action engine to run this step only if the the ref is master. If you look at execution steps of a branch different from master, those two steps are not executed.

Action ran on branch different from master, upload release steps are skipped

Figure 2: Action ran on branch different from master, upload release steps are skipped

If you instead look at logs from an execution of master branch, the two steps are executed. As usual the name of the release is taken from SemVer numbers returned by GitVersion.

image

Figure 3: Publish artifacts to a standard GitHub release output

After the action ran on master, you should see a brand new release of your Repository, containing not only source code, but also published artifacts. As you can see, I’ve decided to upload artifacts with current name (server.7z and client.7z) because number was already present in Release Number

image

Figure 4: Release created by action run for branch master.

You can choose to release whatever branch you like, usually you release hotfix, release and master branch, but you can always grab the artifacts directly from action run output, so I’ve opted to create a real release only from master and stable version.

As final check you should download the release and verify that AssemblyVersion, FileVersion and InformationalVersion were correctly set in the assembly. As you can see in Figure 5 my released software was correctly marked with the correct version.

Versioning of the assembly is present and correct

Figure 5: Versioning of the assembly is present and correct

This last check is especially important, not only for software that will be release with Nuget Packages, but also for executable, because it can immediately tells you the exact version and the exact source code that was used to compile that specific version of the software.

Gian Maria.

One Team Project to rule them all

A similar post was made lots of time ago, but since this is always an hot topic, it is probably the time to refresh with new UI and new concepts of Azure DevOps.

The subject is, how can I apply security to backlogs if I adopt the strategy one single Team Project subdivided by teams?

The approach One Team Project to rule them all is still valid as today, because, once you have a team project, you can divide it with Teams, where each team has its own backlog (or share a single backlog between teams) making everything more manageable.

If you adopt the Single Team Project approach, usually a question of security arise, what if I need people of Team A being able to view only backlog of Team A and Team B are able to view backlog of Team B? Clearly if you create more than on Team Project the solution is obvious, if John is in Team Project A  and Jane is in Team Project B, each one will see only the backlog of Team Projects he/she belongs. This happens because each Team Project has its own user and you can see code, work items, pipeline of a Team Project only if you belong to that Team Project.

Team Project is useful if you need to segregate information between various member, so members of one Team Project cannot see information of other Team Projects.

If you create a single Team Project ad then create Team A and Team B, with the default option you will have two sub areas called TeamProject\Team A and TeamProject\Team B, and two security groups, one for each team. The problem is: is you put John in Team A and Jane in Team B both of them can see Work Items belonging to both teams.

This happens because once you are added in a Team Project, you are usually added also to a security group of the corresponding team, that in turn belongs to a special group called Team Project contributors, that in turn can see all Work Items and work with code etc etc.

Lets recap: this is the dialog you got when you are creating a new team.

image

Figure 1: Interface of new Team Creation

Two are the important points in Figure 1, the first is that the security behind that group is [Team Project Name]\Contributors, the second is that a new area path with the name of the team will be created. After you press create, a new security group with the name of the team will be created, and that group will be part of [Team Project Name]\Contributors default group. This imply that each person that is added to Team A will be alos part of Contributors, a special group that has access to all Work Items, Code and other resources in the project.

This is the default behavior, you can remove group in (1), but if you already created the team, this is the situation you got.

Now you need to solve your original problem: member of Team A should see only Work Items of Team A and members of Team B should see only Work Item of Team B. An obvious solution is to change security of corresponding area. Just go to project administration page where you configure areas as shown in Figure 2.

image

Figure 2: Administration of Area for security of Team A,

This shows actual permission for Work Items in area of Team A, as you can see from Figure 3 Contributors group can edit everything. From that figure you can see (3) that you have also an option that allows this area to inherit all permissions from parent areas.

image

Figure 3: Contributors permission for Work Item of area belonging to Team A.

First step is to remove inheritance and remove contributors from permission list as shown in Figure 4. To remove the group you can simply press the trash bin icon.

image

Figure 4: Remove permissions for Contributors Group and disable inheritance for the area.

Now only administrators, readers and Build administrators can access Work Item, and it is time to add corresponding team to current area as shown in Figure 5.

image

Figure 5: Just start typing in search textbox to find the team corresponding to current area. PAY ATTENTION to choose the group that belongs to the right Team Project because you could have multiple Team A in different Team Projects

Since you are configuring Team A area, select corresponding team called Team A (pay attention at team project name if you have more team called Team A in different Team Project). Now you should give permission to see and edit Work Items in that area as shown in Figure 6.

image

Figure 6: Give all permission to Team A to access work item in area Team A

Now people that are only in Team B, cannot access Work Item that are in area Team A, they cannot even find Work Item with a query. If you want to explicitly check permission level of another team, just search Team B in the same UI and check effective permission. As you can see in Figure 7 Team B has no permission in this area.

image

Figure 7: Permission of Team B

It is important that permission are “not set”  and not deny because a deny wins over all other settings.

Now you should repeat this configuration for each area and for each team. To recap

1) Choose settings for the corresponding area
2) Remove contributors
3) Stop inheritance
4) add the corresponding team group and give it permission

Enjoy.

Gian Maria.