GitHub - Building out a release pipeline

Creating release pipelines for Oh-My-Posh Profile

Oh-My-Posh Profile Update Improvements

Since I last posted about my Oh-My-Posh Mark Two Profile. Much has changed and improved. We are now the Mark Three release/iteration of the profile and recently while showing it to some colleagues at Intercept. Someone asked about improving the update cycles, in some form of a function like Update-PSProfile.

Prior to the Update-PSProfile function being created, You had to pull the complete GitHub repository and run the New-OhMyPoshProfile.ps1 for. every. single. change. Which was not ideal and it meant you had to sometimes leave the “active terminal” to get the latest updates. - Not ideal!

For those of you who do not know about my pet Powershell profile project, Check out the repository: https://github.com/smoonlee/oh-my-posh-profile

Why GitHub Releases

GitHub Releases are a powerful feature on GitHub that allows developers to manage software releases effectively. It provides a structured way to package, distribute, and document software versions, ensuring that users have access to stable and reliable versions of your project.

Key Features

  1. Versioning and Tagging: Each release is tied to a specific Git tag, making it easy to track changes and rollback if necessary. This helps maintain a clear version history.

  2. Release Notes: Developers can attach release notes to each version, detailing the changes, enhancements, and bug fixes. This is crucial for users to understand what has been updated.

  3. Binary Attachments: GitHub Releases supports uploading binary files, such as compiled executables, libraries, or other assets. This allows users to download the necessary files without building the project from source.

  4. Pre-releases: For testing and feedback purposes, developers can create pre-releases. These are versions that may not be fully stable but are made available for early access and testing.

  5. Automated Releases: Integrating with Continuous Integration (CI) tools, developers can automate the creation of releases, ensuring consistency and saving time.

Benefits

  1. Centralized Distribution: All releases are available in one place, making it easy for users to find and download the latest stable version of the software.

  2. Enhanced Transparency: By providing detailed release notes and changelogs, developers can keep users informed about what has changed in each version, enhancing transparency and trust.

  3. Improved User Experience: Users can download pre-built binaries and installers, reducing the complexity of setting up and using the software.

  4. Streamlined Development Workflow: With integration into CI/CD pipelines, releases can be managed more efficiently, ensuring that new features and fixes are delivered promptly.

Best Practices

  1. Semantic Versioning: Follow semantic versioning (MAJOR.MINOR.PATCH) to communicate the nature of changes in each release clearly.

  2. Detailed Changelogs: Maintain comprehensive and clear changelogs for each release to keep users informed about new features, improvements, and bug fixes.

  3. Automate Where Possible: Use CI tools like GitHub Actions to automate the release process, ensuring consistency and reducing manual errors.

  4. Engage with Users: Use pre-releases to gather feedback from users and make necessary adjustments before the official release.

Enabling GitHub Releases

From your GitHub Repository, click on the settings cog next to About and tick [x] Releases and Save changes.

Update-PSProfile Overview

For this to work out, I figured any good project needs a prod and dev release branches right? This would allow for semi private testing and then official release which my friends and colleges would be able to pull after much testing had been completed right. My aim was to be able fully automate the release cycles for dev and prod and figured this would be do able using GitHub actions and some form of tagging. Because everyone likes a tag 😄 So the two tags I opted for were:

  • updated-profile-release-prod
  • updated-profile-release-dev

Based on these tags the release pipeline would either publish a latest release or a pre-release development build.

to be able to call the various releases using the powershell function you can call either

Production

1
Update-PSProfile

Development

Update-PSProfile -devMode

Action - Auto Tag Pull Request

So first I looked into the feasibility of creating something which would trigger as soon a I Opened/Created a pull request. this was pretty straight forward. Using GitHub Action you can use a trigger based on:

1
2
3
on:
  pull_request:
    types: [opened, edited, reopened]

This action is always triggered when a PR is opened. I wrote the action to check for the pull request branch name for a suffix of either [prod] or [dev] and based on this logic apply one of the two release tags.

Then taking advantage of the GitHub API, Merge and close the PR.

Please ensure you create a secret AUTH_TOKEN for a PAT/Fine-Grained Token

GitHub Docs - Creating Personal Access Token

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
name: 'Action - Auto Tag Pull Request'

on:
  pull_request:
    types: [opened, edited, reopened]

jobs:
  add_tag:
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repository
        uses: actions/checkout@v4

      - name: Add tags to PR based on the branch name
        env:
          GITHUB_TOKEN: ${{ secrets.AUTH_TOKEN }}
        run: |
          PR_NUMBER=${{ github.event.pull_request.number }}
          BRANCH_NAME=${{ github.event.pull_request.head.ref }}

          if [[ "$BRANCH_NAME" == *"dev"* ]]; then
            TAG="updated-profile-release-dev"
          elif [[ "$BRANCH_NAME" == *"prod"* ]]; then
            TAG="updated-profile-release-prod"
          fi

          if [[ -n "$TAG" ]]; then
            curl -s -H "Authorization: token $GITHUB_TOKEN" \
                 -X POST \
                 -H "Accept: application/vnd.github.v3+json" \
                 https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/labels \
                 -d "{\"labels\":[\"$TAG\"]}"
          fi          

      - name: Merge Pull Request
        env:
          GITHUB_TOKEN: ${{ secrets.AUTH_TOKEN }}
        run: |
          PR_NUMBER=${{ github.event.pull_request.number }}
          MERGE_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
                 -X PUT \
                 -H "Accept: application/vnd.github.v3+json" \
                 https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER/merge \
                 -d '{"commit_message":"Executing Release Build","merge_method":"merge"}')

          echo "Merge response: $MERGE_RESPONSE"

          if [[ "$(echo "$MERGE_RESPONSE" | jq -r .merged)" != "true" ]]; then
            echo "Merge failed: $(echo "$MERGE_RESPONSE" | jq -r .message)"
            exit 1
          fi          

Action - Auto Release Scheduler

Next was the release pipeline, This required a little troubleshooting and some ChatGPT to get some improved logic. The release pipeline takes advantage of this marketplace offering by svenstaro - upload-release-action. this allowed me to ensure that I just uploaded the updated Microsoft.PowerShell_profile.ps1 file and the release notes (if I remember to fill them in) so end-users will know what the latest changes are.

Please ensure you create a secret AUTH_TOKEN for a PAT/Fine-Grained Token

GitHub Docs - Creating Personal Access Token

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
name: 'Action - Auto Release Scheduler'

on:
  pull_request:
    types: [closed]

jobs:
  create_release:
    if: ${{ github.event.pull_request.merged }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          sparse-checkout: |
            Microsoft.PowerShell_profile.ps1            
          sparse-checkout-cone-mode: false

      - name: Check Directory Contents
        run: |
          pwd
          ls          

      - name: Extract profileVersion
        id: extract_version
        run: |
          profileVersion=$(grep -o "\$profileVersion = '[^']*'" Microsoft.PowerShell_profile.ps1 | sed "s/\$profileVersion = '//; s/'//")
          echo "profileVersion=${profileVersion}"
          echo "profileVersion=${profileVersion}" >> $GITHUB_ENV          

      - name: Upload binaries to release
        if: contains(github.event.pull_request.labels.*.name, 'updated-profile-release-dev') || contains(github.event.pull_request.labels.*.name, 'updated-profile-release-prod')
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.AUTH_TOKEN }}
          release_name: ${{ env.profileVersion }}
          tag: ${{ env.profileVersion }}
          file: Microsoft.PowerShell_profile.ps1
          asset_name: Microsoft.PowerShell_profile.ps1
          body: ${{ github.event.pull_request.body }}
          prerelease: ${{ contains(github.event.pull_request.labels.*.name, 'updated-profile-release-dev') }}
          overwrite: ${{ contains(github.event.pull_request.labels.*.name, 'updated-profile-release-dev') }}

Post Summary

So to summerise in this post, We’ve covered creating an automated release pipeline based on two tags for prod and release. - I hope you found this useful and might be able to use it in your own side projects 🎉 So for now peeps, #KeepBlogging and I’ll catch you in the next one! - Simon