From ba1b3fe56f17e828c229db976ae887cc703be782 Mon Sep 17 00:00:00 2001 From: Carl Reid Date: Tue, 18 Feb 2025 18:28:49 +0100 Subject: [PATCH] feat: Split workflow/shared workflow To support building PRs with same pipeline --- .github/workflows/Manual_Publish_Docker.yml | 221 -------------------- .github/workflows/docker-build-shared.yml | 139 ++++++++++++ .github/workflows/pr-build.yml | 19 ++ .github/workflows/pr-cleanup.yml | 33 +++ .github/workflows/release-build.yml | 108 ++++++++++ 5 files changed, 299 insertions(+), 221 deletions(-) delete mode 100644 .github/workflows/Manual_Publish_Docker.yml create mode 100644 .github/workflows/docker-build-shared.yml create mode 100644 .github/workflows/pr-build.yml create mode 100644 .github/workflows/pr-cleanup.yml create mode 100644 .github/workflows/release-build.yml diff --git a/.github/workflows/Manual_Publish_Docker.yml b/.github/workflows/Manual_Publish_Docker.yml deleted file mode 100644 index 881867221..000000000 --- a/.github/workflows/Manual_Publish_Docker.yml +++ /dev/null @@ -1,221 +0,0 @@ -name: Build and Publish Docker Images - -on: - workflow_dispatch: - inputs: - build_all: - description: 'Build all steps (includes main build unless skipped)' - type: boolean - default: false - build_base: - description: 'Build base image' - type: boolean - default: false - build_build: - description: 'Build build image' - type: boolean - default: false - build_sm: - description: 'Build SM image' - type: boolean - default: false - skip_release_main_build: - description: 'Skip release of a main build' - type: boolean - default: false - release_as_latest: - description: 'Release as latest' - type: boolean - default: false - -permissions: - contents: read - packages: write - -env: - REGISTRY: ghcr.io - BASE_IMAGE_NAME: ${{ github.repository_owner }}/streammaster-builds - FINAL_IMAGE_NAME: ${{ github.repository_owner }}/streammaster - -jobs: - - semantic-release: - runs-on: ubuntu-latest - permissions: - contents: write - outputs: - new_release_published: ${{ steps.semantic.outputs.new_release_published }} - new_release_version: ${{ steps.semantic.outputs.new_release_version }} - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - run: | - set -e - npm ci - - - name: Copy release config - run: | - set -e - if [[ "${{ inputs.release_as_latest }}" == "true" ]]; then - cp release.config.release.cjs release.config.cjs - else - cp release.config.norelease.cjs release.config.cjs - fi - - - name: Semantic Release - id: semantic - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -e - - # Configure git user - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - # Fetch only the latest tag instead of all tags - git fetch --depth=1 origin refs/tags/*:refs/tags/* - - # Run semantic-release with force flag - if npx semantic-release; then - # Force push to handle any tag conflicts on re-runs - git push --follow-tags --force origin HEAD:${{ github.ref }} - else - # If no new release would be created, set the outputs manually - echo "new_release_published=false" >> $GITHUB_OUTPUT - # Get the latest tag without requiring all tags - LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - if [ ! -z "$LATEST_TAG" ]; then - echo "new_release_version=${LATEST_TAG#v}" >> $GITHUB_OUTPUT - fi - fi - - setup: - needs: semantic-release - runs-on: ubuntu-latest - outputs: - version: ${{ steps.gitversion.outputs.semVer }} - branchName: ${{ steps.gitversion.outputs.branchName }} - buildMeta: ${{ steps.gitversion.outputs.buildMetadata }} - build_base: ${{ inputs.build_base || inputs.build_all }} - build_build: ${{ inputs.build_build || inputs.build_all }} - build_sm: ${{ inputs.build_sm || inputs.build_all }} - skip_release_main_build: ${{ inputs.skip_release_main_build }} - release_as_latest: ${{ inputs.release_as_latest }} - semantic_version: ${{ needs.semantic-release.outputs.new_release_version }} - is_release: ${{ needs.semantic-release.outputs.new_release_published }} - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0 - with: - versionSpec: "5.x" - - - name: Determine Version - id: gitversion - uses: gittools/actions/gitversion/execute@v0 - - test: - needs: [setup] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Generate code hash - id: hash - run: | - # Create hash of all source and test files - find src -type f \( -name "*.cs" -o -name "*.csproj" -o -name "*.json" -o -name "*.xml" \) -print0 | sort -z | xargs -0 sha256sum | sha256sum | cut -d' ' -f1 > code_hash.txt - echo "code_hash=$(cat code_hash.txt)" >> $GITHUB_OUTPUT - - - name: Check code hash cache - id: cache-hash - uses: actions/cache@v4 - with: - path: code_hash.txt - key: ${{ runner.os }}-code-${{ steps.hash.outputs.code_hash }} - - - name: Set up Docker Buildx - if: steps.cache-hash.outputs.cache-hit != 'true' - uses: docker/setup-buildx-action@v2 - - - name: Run Tests - run: | - docker buildx build \ - --platform linux/amd64 \ - -f Dockerfile.tests \ - --progress=plain \ - --no-cache \ - . - - build: - needs: [setup, semantic-release, test] - runs-on: ubuntu-latest - env: - VERSION: ${{ needs.setup.outputs.semantic_version || needs.setup.outputs.version }} - BRANCH_NAME: ${{ needs.setup.outputs.branchName }} - BUILD_BASE: ${{ needs.setup.outputs.build_base }} - BUILD_BUILD: ${{ needs.setup.outputs.build_build }} - BUILD_SM: ${{ needs.setup.outputs.build_sm }} - SKIP_RELEASE_MAIN_BUILD: ${{ needs.setup.outputs.skip_release_main_build }} - RELEASE_AS_LATEST: ${{ needs.setup.outputs.release_as_latest || needs.setup.outputs.is_release }} - - steps: - - uses: actions/checkout@v4 - - - name: Download AssemblyInfo.cs - if: needs.semantic-release.outputs.new_release_published == 'true' - uses: actions/download-artifact@v4 - with: - name: assembly-info - path: StreamMaster.API/ - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to GHCR - uses: docker/login-action@v2 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Base - Build and Push - if: ${{ env.BUILD_BASE == 'true' }} - run: | - docker buildx build --platform linux/amd64,linux/arm64 -t ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ env.VERSION }}-base -f Dockerfile.base --push . - - - name: Build - Build and Push - if: ${{ env.BUILD_BUILD == 'true' }} - run: | - docker buildx build --platform linux/amd64,linux/arm64 -t ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ env.VERSION }}-build -f Dockerfile.build --push . - - - name: SM - Build and Push - if: ${{ env.BUILD_SM == 'true' }} - run: | - set -e - echo "FROM --platform=\$BUILDPLATFORM ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ env.VERSION }}-build AS build" > Dockerfile.sm - cat Dockerfile.sm.template >> Dockerfile.sm - docker buildx build --platform linux/amd64,linux/arm64 -t ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ env.VERSION }}-sm -f Dockerfile.sm --push . - - - name: Final - Build and Push - if: ${{ env.SKIP_RELEASE_MAIN_BUILD != 'true' }} - run: | - set -e - echo "FROM ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ env.VERSION }}-sm AS sm" > Dockerfile - echo "FROM ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ env.VERSION }}-base AS base" >> Dockerfile - cat Dockerfile.template >> Dockerfile - docker buildx build --platform linux/amd64,linux/arm64 -t ${{ env.REGISTRY }}/${{ env.FINAL_IMAGE_NAME }}:${{ env.VERSION }} -t ${{ env.REGISTRY }}/${{ env.FINAL_IMAGE_NAME }}:${{ env.RELEASE_AS_LATEST == 'true' && 'latest' || env.BRANCH_NAME }} -f Dockerfile --push . \ No newline at end of file diff --git a/.github/workflows/docker-build-shared.yml b/.github/workflows/docker-build-shared.yml new file mode 100644 index 000000000..c5e2c88d6 --- /dev/null +++ b/.github/workflows/docker-build-shared.yml @@ -0,0 +1,139 @@ +name: Shared Docker Build + +on: + workflow_call: + inputs: + version: + required: true + type: string + build_all: + type: boolean + default: false + build_base: + type: boolean + default: false + build_build: + type: boolean + default: false + build_sm: + type: boolean + default: false + skip_release_main_build: + type: boolean + default: false + release_as_latest: + type: boolean + default: false + secrets: + token: + required: true + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io + BASE_IMAGE_NAME: ${{ github.repository_owner }}/streammaster-builds + FINAL_IMAGE_NAME: ${{ github.repository_owner }}/streammaster + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.gitversion.outputs.semVer }} + branchName: ${{ steps.gitversion.outputs.branchName }} + buildMeta: ${{ steps.gitversion.outputs.buildMetadata }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v0 + with: + versionSpec: "5.x" + + - name: Determine Version + id: gitversion + uses: gittools/actions/gitversion/execute@v0 + + test: + needs: [setup] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Generate code hash + id: hash + run: | + find src -type f \( -name "*.cs" -o -name "*.csproj" -o -name "*.json" -o -name "*.xml" \) -print0 | sort -z | xargs -0 sha256sum | sha256sum | cut -d' ' -f1 > code_hash.txt + echo "code_hash=$(cat code_hash.txt)" >> $GITHUB_OUTPUT + + - name: Check code hash cache + id: cache-hash + uses: actions/cache@v4 + with: + path: code_hash.txt + key: ${{ runner.os }}-code-${{ steps.hash.outputs.code_hash }} + + - name: Set up Docker Buildx + if: steps.cache-hash.outputs.cache-hit != 'true' + uses: docker/setup-buildx-action@v2 + + - name: Run Tests + run: | + docker buildx build \ + --platform linux/amd64 \ + -f Dockerfile.tests \ + --progress=plain \ + --no-cache \ + . + + build: + needs: [setup, test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GHCR + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.token }} + + - name: Base - Build and Push + if: ${{ inputs.build_base || inputs.build_all }} + run: | + docker buildx build --platform linux/amd64,linux/arm64 -t ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ inputs.version }}-base -f Dockerfile.base --push . + + - name: Build - Build and Push + if: ${{ inputs.build_build || inputs.build_all }} + run: | + docker buildx build --platform linux/amd64,linux/arm64 -t ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ inputs.version }}-build -f Dockerfile.build --push . + + - name: SM - Build and Push + if: ${{ inputs.build_sm || inputs.build_all }} + run: | + set -e + echo "FROM --platform=\$BUILDPLATFORM ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ inputs.version }}-build AS build" > Dockerfile.sm + cat Dockerfile.sm.template >> Dockerfile.sm + docker buildx build --platform linux/amd64,linux/arm64 -t ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ inputs.version }}-sm -f Dockerfile.sm --push . + + - name: Final - Build and Push + if: ${{ !inputs.skip_release_main_build }} + run: | + set -e + echo "FROM ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ inputs.version }}-sm AS sm" > Dockerfile + echo "FROM ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:${{ inputs.version }}-base AS base" >> Dockerfile + cat Dockerfile.template >> Dockerfile + docker buildx build --platform linux/amd64,linux/arm64 \ + -t ${{ env.REGISTRY }}/${{ env.FINAL_IMAGE_NAME }}:${{ inputs.version }} \ + -t ${{ env.REGISTRY }}/${{ env.FINAL_IMAGE_NAME }}:$(echo "${{ needs.setup.outputs.branchName }}" | tr '/' '-') \ + -f Dockerfile --push . + diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml new file mode 100644 index 000000000..f6c5ef295 --- /dev/null +++ b/.github/workflows/pr-build.yml @@ -0,0 +1,19 @@ +name: PR Docker Build + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + packages: write + +jobs: + build: + uses: ./.github/workflows/docker-build-shared.yml + with: + version: pr-${{ github.event.pull_request.number }} + build_all: true + release_as_latest: false + secrets: + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pr-cleanup.yml b/.github/workflows/pr-cleanup.yml new file mode 100644 index 000000000..40052e4c1 --- /dev/null +++ b/.github/workflows/pr-cleanup.yml @@ -0,0 +1,33 @@ +name: PR Cleanup + +on: + pull_request: + types: [closed] + +permissions: + packages: write + +jobs: + cleanup: + runs-on: ubuntu-latest + env: + PR_VERSION: pr-${{ github.event.pull_request.number }} + BASE_IMAGE_NAME: ${{ github.repository_owner }}/streammaster-builds + FINAL_IMAGE_NAME: ${{ github.repository_owner }}/streammaster + steps: + - name: Delete PR Images + run: | + # Delete all related images + for suffix in "" "-base" "-build" "-sm"; do + IMAGE_VERSION="${PR_VERSION}${suffix}" + + # Delete from streammaster-builds repository + gh api --method DELETE "/user/packages/container/${BASE_IMAGE_NAME}/versions/${IMAGE_VERSION}" || true + + # Delete from main streammaster repository + if [ -z "$suffix" ]; then + gh api --method DELETE "/user/packages/container/${FINAL_IMAGE_NAME}/versions/${IMAGE_VERSION}" || true + fi + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 000000000..69ec4c4c5 --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,108 @@ +name: Release Docker Build + +on: + workflow_dispatch: + inputs: + build_all: + description: 'Build all steps (includes main build unless skipped)' + type: boolean + default: false + build_base: + type: boolean + default: false + build_build: + type: boolean + default: false + build_sm: + type: boolean + default: false + skip_release_main_build: + type: boolean + default: false + release_as_latest: + type: boolean + default: false + pull_request: + types: [closed] + branches: + - main + +permissions: + contents: read + packages: write + +jobs: + semantic-release: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + new_release_published: ${{ steps.semantic.outputs.new_release_published }} + new_release_version: ${{ steps.semantic.outputs.new_release_version }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: | + set -e + npm ci + + - name: Copy release config + run: | + set -e + if [[ "${{ inputs.release_as_latest }}" == "true" ]]; then + cp release.config.release.cjs release.config.cjs + else + cp release.config.norelease.cjs release.config.cjs + fi + + - name: Semantic Release + id: semantic + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + + # Configure git user + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + # Fetch only the latest tag instead of all tags + git fetch --depth=1 origin refs/tags/*:refs/tags/* + + # Run semantic-release with force flag + if npx semantic-release; then + # Force push to handle any tag conflicts on re-runs + git push --follow-tags --force origin HEAD:${{ github.ref }} + else + # If no new release would be created, set the outputs manually + echo "new_release_published=false" >> $GITHUB_OUTPUT + # Get the latest tag without requiring all tags + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ ! -z "$LATEST_TAG" ]; then + echo "new_release_version=${LATEST_TAG#v}" >> $GITHUB_OUTPUT + fi + fi + + build: + needs: semantic-release + uses: ./.github/workflows/docker-build-shared.yml + with: + version: ${{ needs.semantic-release.outputs.new_release_version || github.sha }} + build_all: ${{ inputs.build_all || github.event_name == 'pull_request' }} + build_base: ${{ inputs.build_base }} + build_build: ${{ inputs.build_build }} + build_sm: ${{ inputs.build_sm }} + skip_release_main_build: ${{ inputs.skip_release_main_build }} + release_as_latest: ${{ inputs.release_as_latest || needs.semantic-release.outputs.new_release_published == 'true' }} + secrets: + token: ${{ secrets.GITHUB_TOKEN }}