From 3da8599bb46cb9c8213d70be5a0be19368dc2cd7 Mon Sep 17 00:00:00 2001 From: Ash Davies <3853061+DrizzlyOwl@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:10:45 +0000 Subject: [PATCH] Add workflows --- .github/workflows/build-and-push-image.yml | 93 ++++++++++++++++++++ .github/workflows/build-and-push-package.yml | 88 ++++++++++++++++++ .github/workflows/docker-build.yml | 27 ++++++ .github/workflows/docker-test.yml | 49 +++++++++++ .github/workflows/dotnet-build.yml | 65 ++++++++++++++ .github/workflows/terraform.yml | 78 ++++++++++++++++ 6 files changed, 400 insertions(+) create mode 100644 .github/workflows/build-and-push-image.yml create mode 100644 .github/workflows/build-and-push-package.yml create mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/docker-test.yml create mode 100644 .github/workflows/dotnet-build.yml create mode 100644 .github/workflows/terraform.yml diff --git a/.github/workflows/build-and-push-image.yml b/.github/workflows/build-and-push-image.yml new file mode 100644 index 0000000..37152af --- /dev/null +++ b/.github/workflows/build-and-push-image.yml @@ -0,0 +1,93 @@ +name: Deploy + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + environment: + type: environment + description: "Choose an environment to deploy to" + required: true + +concurrency: + group: ${{ github.workflow }}-${{ github.event.inputs.environment }} + +env: + NODE_VERSION: 18.x + +jobs: + set-env: + name: Determine environment + runs-on: ubuntu-latest + outputs: + environment: ${{ steps.var.outputs.environment }} + release: ${{ steps.var.outputs.release }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - id: var + run: | + INPUT=${{ github.event.inputs.environment }} + ENVIRONMENT=${INPUT:-"development"} + RELEASE=${ENVIRONMENT,,}-`date +%Y-%m-%d`.${{ github.run_number }} + echo "environment=${ENVIRONMENT,,}" >> $GITHUB_OUTPUT + echo "release=${RELEASE}" >> $GITHUB_OUTPUT + + deploy-image: + permissions: + id-token: write + contents: read + packages: write + name: Deploy Container + needs: [ set-env ] + uses: DFE-Digital/deploy-azure-container-apps-action/.github/workflows/build-push-deploy.yml@v3.1.0 + with: + docker-image-name: 'persons-app' + docker-build-file-name: './Dockerfile' + docker-build-args: CI=true + environment: ${{ needs.set-env.outputs.environment }} + annotate-release: false + secrets: + azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} + azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + azure-acr-client-id: ${{ secrets.ACR_CLIENT_ID }} + azure-acr-name: ${{ secrets.ACR_NAME }} + azure-aca-client-id: ${{ secrets.ACA_CLIENT_ID }} + azure-aca-name: ${{ secrets.ACA_CONTAINERAPP_NAME }} + azure-aca-resource-group: ${{ secrets.ACA_RESOURCE_GROUP }} + + create-tag: + name: Tag and release + needs: [ set-env, deploy-image ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Create tag + run: | + git tag ${{ needs.set-env.outputs.release }} + git push origin ${{ needs.set-env.outputs.release }} + + - name: Create release + uses: "actions/github-script@v7" + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + script: | + try { + await github.rest.repos.createRelease({ + draft: false, + generate_release_notes: true, + name: "${{ needs.set-env.outputs.release }}", + owner: context.repo.owner, + prerelease: false, + repo: context.repo.repo, + tag_name: "${{ needs.set-env.outputs.release }}", + }); + } catch (error) { + core.setFailed(error.message); + } diff --git a/.github/workflows/build-and-push-package.yml b/.github/workflows/build-and-push-package.yml new file mode 100644 index 0000000..71a8115 --- /dev/null +++ b/.github/workflows/build-and-push-package.yml @@ -0,0 +1,88 @@ +name: Build and Push NuGet Package + +on: + push: + tags: + - 'production-*' + +env: + DOTNET_VERSION: '8.0.x' + +jobs: + build-and-test: + runs-on: ubuntu-latest + permissions: + packages: write + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + fetch-depth: 0 # Shallow clones disabled for a better relevancy of SC analysis + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Set up curl and jq + run: sudo apt-get install -y curl jq + + - name: Check for custom version in commit message or check the feed for the latest version and increment it + id: check_custom_version + run: | + # Search the last 10 commits for the version update indicator + COMMIT_HASH=$(git log -n 10 --pretty=format:"%H %s" | grep -P '\(#update package version to \d+\.\d+\.\d+\)' | grep -oP '^\w+' | head -n 1) + + if [[ -n "$COMMIT_HASH" ]]; then + echo "Found commit with version update indicator: $COMMIT_HASH" + + # Check if the commit is already tagged + if git rev-parse "processed-nuget-version-${COMMIT_HASH}" >/dev/null 2>&1; then + echo "This commit has already been processed for version update. Skipping." + else + # Extract the version from the commit message + CUSTOM_VERSION=$(git show -s --format=%s $COMMIT_HASH | grep -oP '\(#update package version to \K([0-9]+\.[0-9]+\.[0-9]+)') + + if [[ -n "$CUSTOM_VERSION" ]]; then + echo "Using custom version: $CUSTOM_VERSION" + echo "NEW_VERSION=$CUSTOM_VERSION" >> $GITHUB_ENV + + # Tag the commit to prevent reprocessing + git tag "processed-nuget-version-${COMMIT_HASH}" + git push origin "processed-nuget-version-${COMMIT_HASH}" + else + echo "Failed to extract version from commit message. Exiting." + exit 1 + fi + fi + fi + + if [[ -z "$CUSTOM_VERSION" ]]; then + echo "No unprocessed custom version found in the last 10 commits. Proceeding to fetch and increment the latest version from the feed." + + # Fetch the latest version and increment the version + PACKAGE_ID="Dfe.PersonsApi.Client" + FEED_URL="https://nuget.pkg.github.com/DFE-Digital/query?q=$PACKAGE_ID" + LATEST_VERSION=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "$FEED_URL" | jq -r '.data[0].version') + + if [[ -z "$LATEST_VERSION" || "$LATEST_VERSION" == "null" ]]; then + echo "No existing version found in the feed. Defaulting to version 1.0.0" + NEW_VERSION="1.0.0" + else + echo "Latest version is $LATEST_VERSION" + IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_VERSION" + NEW_VERSION="${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.$((VERSION_PARTS[2] + 1))" + echo "Incrementing to new version: $NEW_VERSION" + fi + + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV + fi + shell: /usr/bin/bash -e {0} + + - name: Build, pack and publish + working-directory: Dfe.PersonsApi.Client + run: | + dotnet build -c Release + dotnet pack -c Release -p:PackageVersion=${{ env.NEW_VERSION }} --output . + dotnet nuget push "*.nupkg" --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/DFE-Digital/index.json diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..2e29a4f --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,27 @@ +name: Docker + +on: + pull_request: + paths: + - Dockerfile + types: [opened, synchronize] + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build docker image + uses: docker/build-push-action@v6 + with: + secrets: github_token=${{ secrets.GITHUB_TOKEN }} + cache-from: type=gha + cache-to: type=gha + build-args: CI=true + push: false diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml new file mode 100644 index 0000000..bb01f3e --- /dev/null +++ b/.github/workflows/docker-test.yml @@ -0,0 +1,49 @@ +name: Docker + +on: + push: + branches: main + +jobs: + scan: + name: Scan for CVEs + runs-on: ubuntu-latest + outputs: + image: ${{ steps.build.outputs.imageid }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build docker image + uses: docker/build-push-action@v6 + id: build + with: + secrets: github_token=${{ secrets.GITHUB_TOKEN }} + load: true + cache-from: type=gha + cache-to: type=gha + build-args: CI=true + push: false + + - name: Export docker image as tar + run: docker save -o ${{ steps.build.outputs.imageid }}.tar ${{ steps.build.outputs.imageid }} + + - name: Scan Docker image for CVEs + uses: aquasecurity/trivy-action@0.24.0 + with: + input: ${{ steps.build.outputs.imageid }}.tar + format: 'sarif' + output: 'trivy-results.sarif' + limit-severities-for-sarif: true + ignore-unfixed: true + severity: 'CRITICAL,HIGH' + github-pat: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload scan results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml new file mode 100644 index 0000000..9e1c5d5 --- /dev/null +++ b/.github/workflows/dotnet-build.yml @@ -0,0 +1,65 @@ +name: .NET + +on: + push: + branches: [ main ] + paths: + - 'src/**' + pull_request: + branches: [ main ] + types: [opened, synchronize, reopened] + paths: + - 'src/**' + +env: + JAVA_VERSION: '21' + DOTNET_VERSION: '8.0.x' + +jobs: + build: + name: Build, Test and Analyse + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones disabled for a better relevancy of SC analysis + + - name: Setup .NET ${{ env.DOTNET_VERSION }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'microsoft' + + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Install SonarCloud scanners + run: dotnet tool install --global dotnet-sonarscanner + + - name: Install dotnet reportgenerator + run: dotnet tool install --global dotnet-reportgenerator-globaltool + + - name: Add nuget package source + run: dotnet nuget add source --username USERNAME --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/DFE-Digital/index.json" + + - name: Restore dependencies + run: dotnet restore Dfe.PersonsApi.sln + + - name: Build, Test and Analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + dotnet-sonarscanner begin /d:sonar.scanner.skipJreProvisioning=true /k:"DFE-Digital_persons-api" /o:"dfe-digital" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + dotnet build Dfe.PersonsApi.sln --no-restore + dotnet test Dfe.PersonsApi.sln --no-build --verbosity normal --collect:"XPlat Code Coverage" + dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml new file mode 100644 index 0000000..6e2dfc8 --- /dev/null +++ b/.github/workflows/terraform.yml @@ -0,0 +1,78 @@ +name: Terraform + +on: + push: + branches: main + paths: + - 'terraform/**.tf' + - 'terraform/**.hcl' + pull_request: + paths: + - 'terraform/**.tf' + - 'terraform/**.hcl' + +permissions: + contents: read + pull-requests: write + +jobs: + terraform-validate: + name: Validate + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Check for terraform version mismatch + run: | + DOTFILE_VERSION=$(cat terraform/.terraform-version) + TERRAFORM_IMAGE_REFERENCES=$(grep "uses: docker://hashicorp/terraform" .github/workflows/continuous-integration-terraform.yml | grep -v TERRAFORM_IMAGE_REFERENCES | wc -l | tr -d ' ') + if [ "$(grep "docker://hashicorp/terraform:${DOTFILE_VERSION}" .github/workflows/continuous-integration-terraform.yml | wc -l | tr -d ' ')" != "$TERRAFORM_IMAGE_REFERENCES" ] + then + echo -e "\033[1;31mError: terraform version in .terraform-version file does not match docker://hashicorp/terraform versions in .github/workflows/continuous-integration-terraform.yml" + exit 1 + fi + + - name: Validate Terraform docs + uses: terraform-docs/gh-actions@v1.3.0 + with: + working-dir: terraform + config-file: .terraform-docs.yml + output-file: README.md + output-method: inject + fail-on-diff: true + + - name: Remove azure backend + run: rm ./terraform/backend.tf + + - name: Run a Terraform init + uses: docker://hashicorp/terraform:1.9.8 + with: + entrypoint: terraform + args: -chdir=terraform init + + - name: Run a Terraform validate + uses: docker://hashicorp/terraform:1.9.8 + with: + entrypoint: terraform + args: -chdir=terraform validate + + - name: Run a Terraform format check + uses: docker://hashicorp/terraform:1.9.8 + with: + entrypoint: terraform + args: -chdir=terraform fmt -check=true -diff=true + + - name: Setup TFLint + uses: terraform-linters/setup-tflint@v4 + with: + tflint_version: v0.54.0 + + - name: Run TFLint + working-directory: terraform + run: tflint -f compact + + - name: Run TFSec + uses: aquasecurity/tfsec-pr-commenter-action@v1.3.1 + with: + github_token: ${{ github.token }}