From a7b673c7b86d2643d9c37f0eabebc4e7f4a910b5 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Thu, 14 Nov 2024 11:22:16 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20(CI/CD):=20add=20GitHub=20Action?= =?UTF-8?q?s=20workflows=20for=20build,=20release,=20and=20PR=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new GitHub Actions workflows to automate the build, release, and cleanup processes. The `.spellcheck.yml` configuration is updated to include HTML files for spell checking, ensuring consistency across documentation. The `GitVersion.yml` file is introduced to manage versioning, supporting continuous delivery with semantic versioning. The `close-pr.yaml` workflow automates the cleanup of environments after pull requests are closed, improving resource management. The `main.yaml` workflow handles the build and release process, including versioning, artifact management, and deployment to Azure Static Web Apps. These changes enhance the CI/CD pipeline, ensuring efficient and consistent deployment processes. --- .github/.spellcheck.yml | 2 + .github/GitVersion.yml | 27 +++ .github/workflows/close-pr.yaml | 25 +++ .github/workflows/main.yaml | 346 ++++++++++++++++++++++++++++++++ 4 files changed, 400 insertions(+) create mode 100644 .github/GitVersion.yml create mode 100644 .github/workflows/close-pr.yaml create mode 100644 .github/workflows/main.yaml diff --git a/.github/.spellcheck.yml b/.github/.spellcheck.yml index 5c42886..1deb74b 100644 --- a/.github/.spellcheck.yml +++ b/.github/.spellcheck.yml @@ -3,6 +3,8 @@ matrix: sources: - "**/*.md" - "*.md" + - "**/*.html" + - "*.html" default_encoding: utf-8 aspell: lang: en diff --git a/.github/GitVersion.yml b/.github/GitVersion.yml new file mode 100644 index 0000000..e407845 --- /dev/null +++ b/.github/GitVersion.yml @@ -0,0 +1,27 @@ +assembly-versioning-scheme: MajorMinorPatch +mode: ContinuousDelivery +continuous-delivery-fallback-tag: 'Canary' +next-version: 0.0.1 +branches: + main: + mode: ContinuousDelivery + tag: 'Preview' + increment: Patch + is-mainline: true + prevent-increment-of-merged-branch-version: false + tracks-release-branches: true + regex: ^master$|^main$ + release: + mode: ContinuousDelivery + tag: "" + increment: Patch + track-merge-target: false + regex: ^release(s)?[\/-] + source-branches: + - master + - main + is-release-branch: true + is-mainline: false +ignore: + sha: [] +merge-message-formats: {} \ No newline at end of file diff --git a/.github/workflows/close-pr.yaml b/.github/workflows/close-pr.yaml new file mode 100644 index 0000000..9df0ced --- /dev/null +++ b/.github/workflows/close-pr.yaml @@ -0,0 +1,25 @@ +name: Azure Static Web App PR Cleanup + +on: + pull_request: + types: [closed] + +jobs: + pr_cleanup: + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} + - name: Clean up pull request environment + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} + action: "close" + deployment_environment: canary-${{ github.event.pull_request.number }} + app_location: "" + skip_app_build: true + skip_api_build: true + repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..ae01e06 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,346 @@ +name: Build & Release (SAFeDelusion) + +permissions: + contents: read + pull-requests: write + +on: + push: + branches: ["main"] + tags-ignore: ["v*-*"] + pull_request: + branches: ["main"] + workflow_dispatch: + inputs: + ForceRelease: + description: "Force a release! Use when changes hapen out of sync and `src` and `docs` folder changes are not detected." + required: false + default: false + type: boolean + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: pwsh + +jobs: + # Setup & Configuration + Setup: + name: "Setup & Configuration " + runs-on: ubuntu-latest + outputs: + GitVersion_BranchName: ${{ steps.gitversion.outputs.GitVersion_BranchName }} + GitVersion_SemVer: ${{ steps.gitversion.outputs.GitVersion_SemVer }} + GitVersion_PreReleaseLabel: ${{ steps.gitversion.outputs.GitVersion_PreReleaseLabel }} + GitVersion_AssemblySemVer: ${{ steps.gitversion.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ steps.gitversion.outputs.GitVersion_InformationalVersion }} + GitVersion_NuGetVersion: ${{ steps.gitversion.outputs.GitVersion_NuGetVersion }} + GitVersion_PreReleaseNumber: ${{ steps.gitversion.outputs.GitVersion_PreReleaseNumber }} + GitVersion_MajorMinorPatch: ${{ steps.gitversion.outputs.GitVersion_MajorMinorPatch }} + SAFeDelusion_Ring: ${{ steps.SAFeDelusion.outputs.Ring }} + SAFeDelusion_AzureSitesEnvironment: ${{ steps.SAFeDelusion.outputs.AzureSitesEnvironment }} + SAFeDelusion_AzureSitesConfig: ${{ steps.SAFeDelusion.outputs.AzureSitesConfig }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v3.0.0 + with: + versionSpec: "5.x" + includePrerelease: true + - name: Execute GitVersion + id: gitversion + uses: gittools/actions/gitversion/execute@v3.0.0 + with: + useConfigFile: true + configFilePath: .github/GitVersion.yml + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.SAFeDelusion_BOT_APP_ID }} + private-key: ${{ secrets.SAFeDelusion_BOT_CLIENTSECRET }} + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + content: + - 'content/**' + - name: "Build SAFeDelusion Outputs" + shell: pwsh + id: SAFeDelusion + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + # Get Branch Name + Write-Output "::group::Get Branch Name" + Write-Output "-------------------------------------------" + $branchName = "${{ github.head_ref || github.ref_name }}" + Write-Output "We are running on: $branchName!" + $branchSafeName = $branchName.Replace("/", "-") + Write-Output "branchSafeName: $branchSafeName!" + Write-Output "-------------------------------------------" + Write-Output "::endgroup::" + # Ring Setup + Write-Output "::group::Ring Control Setup" + Write-Output "-------------------------------------------" + Write-Output "Ring Control Setup" + Write-Output "-------------------------------------------" + $Ring = "Canary" + $docs_deploy_folder = "./azure-devops-migration-tools/"; + $docs_baseURL = "/learn/azure-devops-migration-tools" + switch ($Env:GitVersion_PreReleaseLabel) { + "" { + $Ring = "Production"; + $AzureSitesEnvironment = "" + $AzureSitesConfig = "production" + $docs_baseURL = "/" + } + "Preview" { + $Ring = "Preview"; + $AzureSitesEnvironment = "preview"; + $AzureSitesConfig = "preview" + $docs_baseURL = "/preview" + } + default { + $Ring = "Canary"; + $AzureSitesEnvironment = "canary-${{ github.event.pull_request.number }}" + $AzureSitesConfig = "canary" + $docs_baseURL = "/canary/$branchSafeName" + } + } + Write-Output "We are running for the $Ring Ring!" + echo "Ring=$Ring" >> $env:GITHUB_OUTPUT + + Write-Output "We are running for the $AzureSitesEnvironment AzureSitesEnvironment!" + echo "AzureSitesEnvironment=$AzureSitesEnvironment" >> $env:GITHUB_OUTPUT + + Write-Output "We are running for the $AzureSitesConfig AzureSitesConfig!" + echo "AzureSitesConfig=$AzureSitesConfig" >> $env:GITHUB_OUTPUT + + Write-Output "docs_baseURL=$docs_baseURL" + echo "docs_baseURL=$docs_baseURL" >> $env:GITHUB_OUTPUT + Write-Output "-------------------------------------------" + Write-Output "::endgroup::" + - uses: actions/upload-artifact@v4 + with: + name: Scripts + path: ./.powershell/** + + # Setup Validator + SetupSummeryStage: + name: "Build Run Data" + runs-on: ubuntu-latest + needs: Setup + steps: + - name: "Show Summery" + if: always() + shell: pwsh + id: SAFeDelusion-summery + run: | + $markdown = @" + ## ${{needs.Setup.outputs.GitVersion_SemVer}} (${{needs.Setup.outputs.SAFeDelusion_Ring}}) + ### SAFeDelusion + - SAFeDelusion_Ring: ${{needs.Setup.outputs.SAFeDelusion_Ring}} + - SAFeDelusion_AzureSitesEnvironment: ${{needs.Setup.outputs.SAFeDelusion_AzureSitesEnvironment}} + - SAFeDelusion_AzureSitesConfig: ${{needs.Setup.outputs.SAFeDelusion_AzureSitesConfig}} + ### GitVersion + - GitVersion_BranchName: ${{needs.Setup.outputs.GitVersion_BranchName}} + - GitVersion_SemVer: ${{needs.Setup.outputs.GitVersion_SemVer}} + - GitVersion_PreReleaseLabel: ${{needs.Setup.outputs.GitVersion_PreReleaseLabel}} + - GitVersion_AssemblySemVer: ${{needs.Setup.outputs.GitVersion_AssemblySemVer}} + - GitVersion_InformationalVersion: ${{needs.Setup.outputs.GitVersion_InformationalVersion}} + - GitVersion_NuGetVersion: ${{needs.Setup.outputs.GitVersion_NuGetVersion}} + "@ + echo $markdown >> $Env:GITHUB_STEP_SUMMARY + + # Build Docs + BuildSite: + name: "Build Site" + runs-on: ubuntu-latest + if: ${{ success() }} + needs: [Setup] + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true # Fetch Hugo themes (true OR recursive) + - uses: cschleiden/replace-tokens@v1 + with: + files: '["**/*.html", "**/*.yaml"]' + tokenPrefix: "#{" + tokenSuffix: "}#" + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} + GitVersion.SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + SAFeDelusion_AzureSitesConfig: ${{ needs.Setup.outputs.SAFeDelusion_AzureSitesConfig }} + PR_Number: ${{ github.event.number }} + - uses: igsekor/pyspelling-any@v1.0.4 + id: spellcheck + name: Spellcheck + with: + args: --report report.json + - name: Setup Hugo + uses: peaceiris/actions-hugo@v3 + with: + hugo-version: "latest" + extended: true + + - name: Build + run: | + Write-Host "Building site for ${{ (needs.Setup.outputs.SAFeDelusion_AzureSitesConfig) }}"; + $env:HUGO_ENV = "${{ (needs.Setup.outputs.SAFeDelusion_AzureSitesConfig) }}"; + hugo --config hugo.yaml,hugo.${{ (needs.Setup.outputs.SAFeDelusion_AzureSitesConfig) }}.yaml --logLevel info; # --minify --source site + + - name: Copy Files + shell: pwsh + run: | + Get-ChildItem -Path "${{ github.workspace }}" -Directory | Select-Object -ExpandProperty FullName + Copy-Item -Path "./staticwebapp.config.json" -Destination ./public/ + Get-ChildItem -Path "./" -Filter "staticwebapp.config.*.json" | ForEach-Object { + Copy-Item -Path $_.FullName -Destination "./public/" + } + + - name: Check if folder size exceeds 500MB + shell: pwsh + run: | + $folder = "./public/" + $size = (Get-ChildItem -Recurse -File -Path $folder | Measure-Object -Property Length -Sum).Sum + + # Example: Perform an action if the folder is larger than 500MB (524288000 bytes) + if ($size -gt 524288000 ) { + Write-Host "Folder is larger than 500MB" + # Add additional actions or logic here + } else { + Write-Host "Folder is under 500MB" + } + + - uses: actions/upload-artifact@v4 + with: + name: Site + path: ./public/**/* + + # Release to Docs + Publsh: + name: "Publish Site" + runs-on: ubuntu-latest + needs: [Setup, BuildSite] + if: ${{ success() }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true # Fetch Hugo themes (true OR recursive) + - name: Download a single artifact + uses: actions/download-artifact@v4 + with: + name: Site + path: ./_site + - name: Download a single artifact + uses: actions/download-artifact@v4 + with: + name: API + path: ./_api + + - name: Merge Configuration Files + run: | + # Find all files matching the pattern in a safe way + $files = Get-ChildItem -Path "./" -Filter "staticwebapp.*.json" -ErrorAction Stop + + if ($files.Count -eq 0) { + Write-Host "No files matching the pattern 'staticwebapp.*.json' were found." + exit 1 + } + + # Output each file's full name (for verification/debugging purposes) + $files | ForEach-Object { + Write-Host "Found file: $($_.FullName)" + } + + # Paths to main and environment-specific config files + $rootConfig = "./staticwebapp.config.json" + $environmentConfig = "./staticwebapp.config.${{ (needs.Setup.outputs.SAFeDelusion_AzureSitesConfig) }}.json" + + # Check if both target files exist + if ((Test-Path -Path $rootConfig -ErrorAction Stop) -and (Test-Path -Path $environmentConfig -ErrorAction Stop)) { + try { + # Run jq to merge files and capture the output + $mergedContent = & jq -s 'reduce .[] as $item ({}; . * $item)' $rootConfig $environmentConfig + + if ($mergedContent -ne "") { + # Write the merged content to the output file + $mergedContent | Set-Content -Path "./staticwebapp.config.json" + Write-Host "Merged JSON files successfully." + } + else { + Write-Host "jq command produced empty output. Check JSON structures in input files." + exit 1 + } + } + catch { + Write-Host "Error merging JSON files with jq: $_" + exit 1 + } + } + else { + Write-Host "One or both of the specified config files were not found:" + if (!(Test-Path -Path $rootConfig)) { Write-Host " - $rootConfig not found" } + if (!(Test-Path -Path $environmentConfig)) { Write-Host " - $environmentConfig not found" } + exit 1 + } + + # Verify and read the merged file content + try { + $content = Get-Content -Path "./staticwebapp.config.json" -ErrorAction Stop + Write-Host "Content of merged config file:" + Write-Output $content + } + catch { + Write-Host "Error reading the merged config file: $_" + exit 1 + } + + - name: "Find files" + shell: pwsh + run: | + Get-ChildItem -Path ".\" -File + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.SAFeDelusion_BOT_APP_ID }} + private-key: ${{ secrets.SAFeDelusion_BOT_CLIENTSECRET }} + + - name: Build and Deploy + uses: Azure/static-web-apps-deploy@v1 + id: azureDeploy + env: + VERBOSE: true + with: + repo_token: ${{ steps.app-token.outputs.token }} + action: "upload" + app_location: ./_site + skip_app_build: true + skip_api_build: true + output_location: "" + deployment_environment: ${{ (needs.Setup.outputs.SAFeDelusion_AzureSitesEnvironment) }} + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} + + - name: "Show Summery" + if: always() + shell: pwsh + id: SAFeDelusion-summery + run: | + $markdown = @" + ## ${{needs.Setup.outputs.GitVersion_SemVer}} (${{needs.Setup.outputs.SAFeDelusion_Ring}}) + Deployed to [${{steps.azureDeploy.outputs.static_web_app_url}}](${{steps.azureDeploy.outputs.static_web_app_url}}) + "@ + echo $markdown >> $Env:GITHUB_STEP_SUMMARY