From acdfc894e6cc4e106b88231d74fbbae3821ce5ed Mon Sep 17 00:00:00 2001 From: Gideon de Swardt Date: Thu, 6 Apr 2023 18:25:46 +0100 Subject: [PATCH] Automate build, publish and releases This change set automates the build, test, package, and publish process for our .NET project using GitVersion to obtain the semantic version. The CD pipeline is triggered when a new release branch is created in the format of release/v{MAJOR.MINOR.PATCH}. It will publish a pre-release package to NuGet first, and only on approval will it publish the final version, create a tag that matches the version number, and generate the GitHub release with release notes. To ensure the version is calculated correctly, a tag was added to the previous commit that was released. The CD pipeline uses this tag to calculate the version number. The implementation details include: - Using GitVersion plugin to obtain the semantic version - Automating the build, test, package, and publish process - Creating a tag on the previous commit that was released to ensure correct version calculation - Publishing a pre-release package to NuGet first and only on approval publishing the final version, creating a tag that matches the version number, and generating the GitHub release with release notes - Create a pull request to ensure that any changes that were committed to the `release` branch is also merged back into `master` This change set simplifies the release process and ensures that all our artifacts are versioned correctly, making it easier for other developers to understand and track our what has changed in each release. --- .github/actions/dotnet/build/action.yml | 19 +++++++ .github/actions/dotnet/nuget/action.yml | 32 +++++++++++ .github/actions/dotnet/pack/action.yml | 33 +++++++++++ .github/actions/dotnet/test/action.yml | 36 ++++++++++++ .github/actions/dotnet/version/action.yml | 51 +++++++++++++++++ .github/workflows/build.yml | 36 ++++++++++++ .github/workflows/cd.yml | 68 +++++++++++++++++++++++ .github/workflows/ci.yml | 23 ++++++++ .github/workflows/dotnet-core.yml | 27 --------- .github/workflows/publish.yml | 28 ++++++++++ GitVersion.yml | 11 ++++ 11 files changed, 337 insertions(+), 27 deletions(-) create mode 100644 .github/actions/dotnet/build/action.yml create mode 100644 .github/actions/dotnet/nuget/action.yml create mode 100644 .github/actions/dotnet/pack/action.yml create mode 100644 .github/actions/dotnet/test/action.yml create mode 100644 .github/actions/dotnet/version/action.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/dotnet-core.yml create mode 100644 .github/workflows/publish.yml create mode 100644 GitVersion.yml diff --git a/.github/actions/dotnet/build/action.yml b/.github/actions/dotnet/build/action.yml new file mode 100644 index 00000000..2dae253b --- /dev/null +++ b/.github/actions/dotnet/build/action.yml @@ -0,0 +1,19 @@ +name: build +description: 'Restore and build the dotnet solution' + +inputs: + dotnet-build-configuration: + default: Release + description: 'Defines the build configuration. The default for most projects is Release.' + required: true + +runs: + using: "composite" + steps: + - name: Restore dependencies + shell: bash + run: dotnet restore + + - name: Build solutions + shell: bash + run: dotnet build --no-restore --configuration ${{ inputs.dotnet-build-configuration }} \ No newline at end of file diff --git a/.github/actions/dotnet/nuget/action.yml b/.github/actions/dotnet/nuget/action.yml new file mode 100644 index 00000000..c7626f68 --- /dev/null +++ b/.github/actions/dotnet/nuget/action.yml @@ -0,0 +1,32 @@ +name: nuget +description: 'Publish NuGet packages' + +inputs: + dotnet-version: + description: 'The dotnet framework and SDK version.' + default: 7.0.x + required: true + nuget-api-key: + description: 'The NuGet API key to publish artifacts' + default: 'nuget-api-key' + required: true + packages-directory: + description: 'The root directory of the NuGet packages that should be published' + server-url: + description: 'Specifies the NuGet server URL' + required: true + default: 'https://api.nuget.org/v3/index.json' + +runs: + using: "composite" + steps: + - name: Setup dotnet environment + uses: actions/setup-dotnet@v3 + id: setup + with: + dotnet-version: ${{ inputs.dotnet-version }} + + - name: Publish to NuGet + working-directory: ${{ inputs.packages-directory }} + shell: bash + run: 'dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${{ inputs.nuget-api-key }} --source ${{ inputs.server-url }}' \ No newline at end of file diff --git a/.github/actions/dotnet/pack/action.yml b/.github/actions/dotnet/pack/action.yml new file mode 100644 index 00000000..151a01f5 --- /dev/null +++ b/.github/actions/dotnet/pack/action.yml @@ -0,0 +1,33 @@ +name: pack +description: 'Packs and sign the code into a NuGet package' + +inputs: + dotnet-build-configuration: + default: Release + description: 'Defines the build configuration. The default for most projects is Release.' + required: true + pre-release-version: + description: 'The semantic version to be used for pre-release artifacts' + required: true + release-version: + description: 'The semantic version to be used for release artifacts' + required: true + +runs: + using: "composite" + steps: + - name: Pack pre-release version ${{steps.version.outputs.nuGetVersionV2}} + shell: bash + run: dotnet pack --configuration ${{ inputs.dotnet-build-configuration }} --verbosity normal --no-restore -p:PackageVersion=${{ inputs.pre-release-version }} --output ./packages/preview + + - name: Pack release version ${{steps.version.outputs.majorMinorPatch}} + shell: bash + run: dotnet pack --configuration ${{ inputs.dotnet-build-configuration }} --verbosity normal --no-restore -p:PackageVersion=${{ inputs.release-version }} --output ./packages/release + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: packages + path: | + ./packages/preview + ./packages/release \ No newline at end of file diff --git a/.github/actions/dotnet/test/action.yml b/.github/actions/dotnet/test/action.yml new file mode 100644 index 00000000..e2bd59e7 --- /dev/null +++ b/.github/actions/dotnet/test/action.yml @@ -0,0 +1,36 @@ +name: test +description: 'Execute the unit tests and parse results report' + +inputs: + dotnet-build-configuration: + default: Release + description: 'Defines the build configuration. The default for most projects is Release.' + required: true + results-directory: + description: 'The test results output directory' + default: './test-results' + github-token: + description: 'GitHub personal access token' + required: true + +runs: + using: "composite" + steps: + + - name: Execute unit tests + shell: bash + run: dotnet test --logger trx --results-directory ${{ inputs.results-directory }} --no-build --configuration ${{ inputs.dotnet-build-configuration }} --verbosity normal + + - name: Parse the unit test files + uses: nasamin/trx-parser@v0.2.0 + with: + TRX_PATH: ${{ github.workspace }}/test-results + REPO_TOKEN: ${{ inputs.github-token }} + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: unit-test-logs + path: | + ./test-logs diff --git a/.github/actions/dotnet/version/action.yml b/.github/actions/dotnet/version/action.yml new file mode 100644 index 00000000..8ac50acf --- /dev/null +++ b/.github/actions/dotnet/version/action.yml @@ -0,0 +1,51 @@ +name: version +description: 'Use the git tools to calculate the semantic version and update the dotnet project files' +inputs: + dotnet-version: + description: 'The dotnet framework and SDK version.' + default: 7.0.x + required: true + git-tools-version: + description: 'The version specification to be used of the git tools ' + default: 5.x + required: true + config-file-path: + description: 'Tha path of the git version configuration file' + default: GitVersion.yml + required: true + +runs: + using: "composite" + steps: + - name: Setup dotnet environment + uses: actions/setup-dotnet@v3 + id: setup + with: + dotnet-version: ${{ inputs.dotnet-version }} + + - name: Setup the git version tools + uses: gittools/actions/gitversion/setup@v0.9.15 + with: + versionSpec: ${{ inputs.git-tools-version }} + + - name: Update the semantic version of the projects + uses: gittools/actions/gitversion/execute@v0.9.15 + id: version + with: + useConfigFile: true + configFilePath: ${{ inputs.config-file-path }} + additionalArguments: '/updateprojectfiles' + +outputs: + dotnet-version: + description: 'The dotnet framework and sdk version' + value: ${{ steps.setup.outputs.dotnet-version }} + pre-release-version: + description: 'The semantic version to be used for pre-release artifacts' + value: ${{steps.version.outputs.nuGetVersionV2}} + release-version: + description: 'The semantic version to be used for release artifacts' + value: ${{steps.version.outputs.majorMinorPatch}} + + + \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..e014acf0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,36 @@ +name: build +on: + workflow_call: + inputs: + pack: + default: false + required: true + type: boolean + +jobs: + build-and-sign: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Semantic version + uses: './.github/actions/dotnet/version' + id: version + + - name: Restore and build + uses: './.github/actions/dotnet/build' + + - name: Execute unit test and publish reports + uses: './.github/actions/dotnet/test' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Pack and sign NuGet packages + uses: './.github/actions/dotnet/pack' + if: ${{ inputs.pack }} + with: + pre-release-version: ${{steps.version.outputs.pre-release-version}} + release-version: ${{steps.version.outputs.release-version}} \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..642c457d --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,68 @@ +name: continuous-delivery +run-name: Publish pipeline after ${{github.event_name}} by @${{ github.actor }} + +on: + push: + branches: + - release/** + +jobs: + continuous-integration: + uses: ./.github/workflows/build.yml + with: + pack: true + secrets: inherit + + publish-preview-packages: + uses: ./.github/workflows/publish.yml + needs: [ continuous-integration ] + with: + environment: preview + secrets: inherit + + publish-release-packages: + uses: ./.github/workflows/publish.yml + needs: [ publish-preview-packages ] + with: + environment: release + secrets: inherit + + tag-and-release: + runs-on: ubuntu-latest + needs: [ publish-release-packages ] + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Semantic version + uses: './.github/actions/dotnet/version' + id: version + + - name: Create tag + uses: rickstaa/action-create-tag@v1 + id: tag + with: + tag: 'v${{steps.version.outputs.release-version}}' + tag_exists_error: true + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create pull request + uses: devops-infra/action-pull-request@v0.5.5 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + target_branch: master + title: Release v${{steps.version.outputs.release-version}} + draft: false + get_diff: true + ignore_users: "dependabot" + allow_no_diff: true + + - name: Create the release + uses: ncipollo/release-action@v1 + with: + allowUpdates: true + generateReleaseNotes: true + name: 'v${{steps.version.outputs.release-version}}' + tag: 'v${{steps.version.outputs.release-version}}' \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..c28db285 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: continuous-integration +run-name: Build pipeline after ${{github.event_name}} by @${{ github.actor }} + +on: + workflow_dispatch: + + push: + branches: + - master + - feature/** + - bugfix/** + + pull_request: + branches: + - feature/** + - bugfix/** + +jobs: + continuous-integration: + uses: ./.github/workflows/build.yml + with: + pack: false + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml deleted file mode 100644 index 651f9611..00000000 --- a/.github/workflows/dotnet-core.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: .NET Core - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - name: setup-msbuild - uses: microsoft/setup-msbuild@v1 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '7.0.x' - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - name: Test - run: dotnet test --no-restore --verbosity normal diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..cdadd226 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,28 @@ +name: publish +on: + workflow_call: + inputs: + environment: + type: string + required: true + +jobs: + publish: + environment: ${{ inputs.environment }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Download artifacts + uses: actions/download-artifact@v3 + id: download + with: + name: 'packages' + path: './packages' + + - name: Publish packages + uses: './.github/actions/dotnet/nuget' + with: + nuget-api-key: ${{ secrets.NUGET_API_KEY }} + packages-directory: '${{steps.download.outputs.download-path}}/${{ inputs.environment }}' \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..a8566560 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,11 @@ +mode: ContinuousDeployment +branches: + master: + prevent-increment-of-merged-branch-version: false + source-branches: ['feature'] + tag: master + feature: + tag: 'feature.{BranchName}' + source-branches: ['master'] + release: + tag: 'release' \ No newline at end of file