From 745a67d1de5f2275be21ffd292daeb0fbc4129cb Mon Sep 17 00:00:00 2001
From: Mark Harrison <mark.harrison@octopus.com>
Date: Wed, 28 Aug 2024 16:53:08 +0100
Subject: [PATCH] Add GitHub CLI workertools

---
 .../workflows/gh-cli-docker-build-push.yml    | 156 +++++++++++++++++
 .github/workflows/gh-cli-ghcr-build-push.yml  | 162 ++++++++++++++++++
 github-cli/linux-amd64/Dockerfile             |  16 ++
 github-cli/linux-arm64/Dockerfile             |  15 ++
 4 files changed, 349 insertions(+)
 create mode 100644 .github/workflows/gh-cli-docker-build-push.yml
 create mode 100644 .github/workflows/gh-cli-ghcr-build-push.yml
 create mode 100644 github-cli/linux-amd64/Dockerfile
 create mode 100644 github-cli/linux-arm64/Dockerfile

diff --git a/.github/workflows/gh-cli-docker-build-push.yml b/.github/workflows/gh-cli-docker-build-push.yml
new file mode 100644
index 0000000..89f989b
--- /dev/null
+++ b/.github/workflows/gh-cli-docker-build-push.yml
@@ -0,0 +1,156 @@
+name: Build github-workertools image
+
+on:
+  push:
+    branches: 
+    - main
+    paths:
+    - github-cli/**
+  schedule:
+    - cron: '0 10 * * *'
+  workflow_dispatch:
+
+env:
+   REGISTRY_IMAGE_BASE: octopuslabs/workertools
+   REGISTRY_IMAGE: octopuslabs/github-workertools
+
+jobs:
+
+  get-version-number:
+    runs-on: windows-latest
+    outputs:
+      CONTINUE: ${{ steps.check-version.outputs.CONTINUE }}
+      VERSION: ${{ steps.check-version.outputs.VERSION }}
+    steps:    
+    - uses: actions/checkout@v4
+    - id: check-version
+      name: Compare latest version with container
+      run: |
+        Write-Output "Getting github cli version"
+        $chocoInformationRaw = choco info gh  --limitoutput
+        $versionOutput = ($chocoInformationRaw.Split("|"))[1]
+
+        [System.Version]$version = $null
+        $versionParsed = [System.Version]::TryParse($versionOutput, [ref]$version)
+        if(-not $versionParsed) {
+            Write-Host "Unable to parse '$versionOutput' as a valid version. Won't continue"
+            echo "CONTINUE=No" >> $env:GITHUB_OUTPUT
+        }
+        else {
+            $versionToCompare = "$($version.Major).$($version.Minor).$($version.Build)"
+            Write-Host "Parsed version as $versionToCompare"
+            
+            $workerToolsTags = Invoke-RestMethod "https://registry.hub.docker.com/v2/repositories/${{ env.REGISTRY_IMAGE }}/tags?page_size=50"
+            $matchingTag = $workerToolsTags.results | Where-Object { $_.name -eq $versionToCompare }
+
+            echo "VERSION=$versionToCompare" >> $env:GITHUB_OUTPUT
+
+            if ($null -ne $matchingTag)
+            {
+                Write-Host "Docker container already has latest version"
+                echo "CONTINUE=No" >> $env:GITHUB_OUTPUT
+            }
+            else
+            {
+                Write-Host "We need to upgrade the container to $versionToCompare"
+                echo "CONTINUE=Yes" >> $env:GITHUB_OUTPUT   
+            }
+        }
+      shell: pwsh
+
+  build-linux:
+    needs: [get-version-number]
+    if: ${{ needs.get-version-number.outputs.CONTINUE == 'Yes' }}
+    strategy:
+      matrix:
+        os: 
+          - ubuntu-latest
+        platform:
+          - linux/amd64
+          - linux/arm64
+    runs-on: ${{ matrix.os }}
+    steps:
+
+    - name: Prepare
+      run: |
+          platform=${{ matrix.platform }}
+          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV 
+          echo "PLATFORM_ARCH=${platform//[linux\/]/}" >> $GITHUB_ENV 
+
+    - name: Checkout  
+      uses: actions/checkout@v4
+
+    - name: Set up QEMU
+      uses: docker/setup-qemu-action@v3
+      with:
+        platforms: linux/amd64,linux/arm64
+
+    - name: Set up Docker Buildx
+      uses: docker/setup-buildx-action@v3
+    
+    - name: Login to Docker Hub
+      uses: docker/login-action@v3
+      with:
+        username: ${{ secrets.DOCKER_HUB_USER }}
+        password: ${{ secrets.DOCKER_HUB_PAT }}
+
+    - name: Build and push by digest
+      id: build  
+      uses: docker/build-push-action@v5
+      with:
+        context: github-cli/${{ env.PLATFORM_PAIR }}
+        platforms: ${{ matrix.platform }}
+        provenance: false
+        build-args: |
+          BASE_IMAGE=${{ env.REGISTRY_IMAGE_BASE }}
+        outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
+
+    - name: Export digest
+      run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.build.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"
+                    
+    - name: Upload digest
+      uses: actions/upload-artifact@v4
+      with:
+        name: digests-${{ env.PLATFORM_PAIR }}
+        path: /tmp/digests/*
+        if-no-files-found: error
+        retention-days: 1
+  merge:
+    needs: [get-version-number, build-linux]
+    if: ${{ needs.get-version-number.outputs.CONTINUE == 'Yes' }}
+    runs-on: ubuntu-latest
+    env:
+      VERSION_NUMBER: ${{ needs.get-version-number.outputs.VERSION }}
+    steps:
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          path: /tmp/digests
+          pattern: digests-*
+          merge-multiple: true
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.REGISTRY_IMAGE }}
+          tags: |
+            ${{ env.VERSION_NUMBER }}
+            latest
+      - name: Login to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKER_HUB_USER }}
+          password: ${{ secrets.DOCKER_HUB_PAT }}
+      - name: Create manifest list and push
+        working-directory: /tmp/digests
+        run: |
+          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)          
+      - name: Inspect image
+        run: |
+          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
\ No newline at end of file
diff --git a/.github/workflows/gh-cli-ghcr-build-push.yml b/.github/workflows/gh-cli-ghcr-build-push.yml
new file mode 100644
index 0000000..d6bff60
--- /dev/null
+++ b/.github/workflows/gh-cli-ghcr-build-push.yml
@@ -0,0 +1,162 @@
+name: Build GHCR github-workertools image
+
+permissions:
+  packages: write
+
+on:
+  push:
+    branches: 
+    - main
+    paths:
+    - github-cli/**
+  schedule:
+    - cron: '0 10 * * *'
+  workflow_dispatch:
+
+env:
+   REGISTRY_IMAGE_BASE: ghcr.io/octopusdeploylabs/workertools
+   REGISTRY_IMAGE: ghcr.io/octopusdeploylabs/github-workertools
+
+jobs:
+
+  get-version-number:
+    runs-on: windows-latest
+    outputs:
+      CONTINUE: ${{ steps.check-version.outputs.CONTINUE }}
+      VERSION: ${{ steps.check-version.outputs.VERSION }}
+    steps:    
+    - uses: actions/checkout@v4
+    - id: check-version
+      name: Compare latest version with container
+      run: |
+        Write-Output "Getting github cli version"
+        $chocoInformationRaw = choco info gh  --limitoutput
+        $versionOutput = ($chocoInformationRaw.Split("|"))[1]
+
+        [System.Version]$version = $null
+        $versionParsed = [System.Version]::TryParse($versionOutput, [ref]$version)
+        if(-not $versionParsed) {
+            Write-Host "Unable to parse '$versionOutput' as a valid version. Won't continue"
+            echo "CONTINUE=No" >> $env:GITHUB_OUTPUT
+        }
+        else {
+            $versionToCompare = "$($version.Major).$($version.Minor).$($version.Build)"
+            Write-Host "Parsed version as $versionToCompare"
+            
+            $token = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("${{ secrets.GITHUB_TOKEN }}"))
+            $workerToolsTags = Invoke-RestMethod -Uri "https://ghcr.io/v2/${{ env.REGISTRY_IMAGE }}/tags/list" -Headers @{Authorization="Bearer $token"} -SkipHttpErrorCheck
+            $matchingTag = $workerToolsTags.tags | Where-Object { $_ -eq $versionToCompare }
+
+            echo "VERSION=$versionToCompare" >> $env:GITHUB_OUTPUT
+
+            if ($null -ne $matchingTag -or ($null -ne $workerToolsTags.errors -and $workerToolsTags.errors[0].code -ne "NAME_UNKNOWN"))
+            {
+                Write-Host "Docker container already has latest version"
+                echo "CONTINUE=No" >> $env:GITHUB_OUTPUT
+            }
+            else
+            {
+                Write-Host "We need to upgrade the container to $versionToCompare"
+                echo "CONTINUE=Yes" >> $env:GITHUB_OUTPUT
+            }
+        }
+      shell: pwsh
+
+  build-linux:
+    needs: [get-version-number]
+    if: ${{ needs.get-version-number.outputs.CONTINUE == 'Yes' }}
+    strategy:
+      matrix:
+        os: 
+          - ubuntu-latest
+        platform:
+          - linux/amd64
+          - linux/arm64
+    runs-on: ${{ matrix.os }}
+    steps:
+
+    - name: Prepare
+      run: |
+          platform=${{ matrix.platform }}
+          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV 
+          echo "PLATFORM_ARCH=${platform//[linux\/]/}" >> $GITHUB_ENV 
+
+    - name: Checkout  
+      uses: actions/checkout@v4
+
+    - name: Set up QEMU
+      uses: docker/setup-qemu-action@v3
+      with:
+        platforms: linux/amd64,linux/arm64
+
+    - name: Set up Docker Buildx
+      uses: docker/setup-buildx-action@v3
+
+    - name: Login to GHCR
+      uses: docker/login-action@v3
+      with:
+        registry: https://ghcr.io
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    - name: Build and push by digest
+      id: build  
+      uses: docker/build-push-action@v5
+      with:
+        context: github-cli/${{ env.PLATFORM_PAIR }}
+        platforms: ${{ matrix.platform }}
+        provenance: false
+        build-args: |
+          BASE_IMAGE=${{ env.REGISTRY_IMAGE_BASE }}
+        outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
+
+    - name: Export digest
+      run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.build.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"
+                    
+    - name: Upload digest
+      uses: actions/upload-artifact@v4
+      with:
+        name: digests-${{ env.PLATFORM_PAIR }}
+        path: /tmp/digests/*
+        if-no-files-found: error
+        retention-days: 1
+  merge:
+    needs: [get-version-number, build-linux]
+    if: ${{ needs.get-version-number.outputs.CONTINUE == 'Yes' }}
+    runs-on: ubuntu-latest
+    env:
+      VERSION_NUMBER: ${{ needs.get-version-number.outputs.VERSION }}
+    steps:
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          path: /tmp/digests
+          pattern: digests-*
+          merge-multiple: true
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.REGISTRY_IMAGE }}
+          tags: |
+            ${{ env.VERSION_NUMBER }}
+            latest
+      - name: Login to GHCR
+        uses: docker/login-action@v3
+        with:
+          registry: https://ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Create manifest list and push
+        working-directory: /tmp/digests
+        run: |
+          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)          
+      - name: Inspect image
+        run: |
+          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
\ No newline at end of file
diff --git a/github-cli/linux-amd64/Dockerfile b/github-cli/linux-amd64/Dockerfile
new file mode 100644
index 0000000..7a63bd3
--- /dev/null
+++ b/github-cli/linux-amd64/Dockerfile
@@ -0,0 +1,16 @@
+ARG BASE_IMAGE
+FROM --platform=linux/amd64 ${BASE_IMAGE}:latest
+
+ARG DEBIAN_FRONTEND noninteractive
+
+# Install git
+RUN apt-get update && apt-get install -y git 
+
+# Install GitHub CLI
+RUN mkdir -p -m 755 /etc/apt/keyrings \
+    && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
+    && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
+    && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
+    && apt update \
+    && apt install gh -y
+
diff --git a/github-cli/linux-arm64/Dockerfile b/github-cli/linux-arm64/Dockerfile
new file mode 100644
index 0000000..da873f4
--- /dev/null
+++ b/github-cli/linux-arm64/Dockerfile
@@ -0,0 +1,15 @@
+ARG BASE_IMAGE
+FROM --platform=linux/arm64 ${BASE_IMAGE}:latest
+
+ARG DEBIAN_FRONTEND noninteractive
+
+# Install git
+RUN apt-get update && apt-get install -y git 
+
+# Install GitHub CLI
+RUN mkdir -p -m 755 /etc/apt/keyrings \
+    && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
+    && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
+    && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
+    && apt update \
+    && apt install gh -y