diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e204047..2a36b0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,34 +2,12 @@ name: CI on: push: - branches: - - main + branches: [main] pull_request: - branches: - - main + branches: [main] workflow_dispatch: {} -env: - python_version: "3.13" - jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checks-out the repository - uses: actions/checkout@v4 - - name: Lints Markdown files - uses: DavidAnson/markdownlint-cli2-action@v20 - with: - globs: "**/*.md" - - name: Set up Python ${{ env.python_version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ env.python_version }} - - name: Installs Python packages - run: | - python -m pip install --upgrade pip - pip install yamllint - - name: Lint YAML files - run: | - yamllint . + markup-lint: + name: Markup + uses: ./.github/workflows/reusable-markup-lint.yml diff --git a/.github/workflows/reusable-container-publication.yml b/.github/workflows/reusable-container-publication.yml new file mode 100644 index 0000000..e2160c6 --- /dev/null +++ b/.github/workflows/reusable-container-publication.yml @@ -0,0 +1,138 @@ +name: Reusable - Container publication +# description: | +# Builds a new container image with Docker and pushes it to a registry +# Make sure to add (needed by cosign): +# ``` +# permissions: +# id-token: write +# contents: read +# ``` + +on: + workflow_call: + inputs: + container-registry: + description: Container registry + type: string + required: false + default: "docker.io" + create-latest: + description: "Create latest tag?" + type: boolean + required: false + default: false + extra-build-arguments: + description: Container build additional arguments + type: string + required: false + default: "" + image-definition: + description: Path to the container definition file (Dockerfile, Containerfile) + type: string + required: true + image-name: + description: Image name + type: string + required: true + image-path: + description: Image path + type: string + required: true + image-tag: + description: Image tag + type: string + required: true + job-name: + description: Job name + type: string + required: false + default: Publication + operating-system: + description: Operating system executing the runner + type: string + required: false + default: ubuntu-latest + workflow-parts-version: + description: GitHub workflow parts version (branch/tag/SHA) + type: string + required: false + default: main + working-directory: + description: Working directory + type: string + required: false + default: "." + secrets: + container-registry-username: + description: Container registry username + required: true + container-registry-password: + description: Container registry password + required: true + extra-vars: + description: "Additional environment variables" + required: false + +jobs: + container-publication: + name: ${{ inputs.job-name }} + runs-on: ${{ inputs.operating-system }} + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Set additional variables + run: | + if [[ -z "${{ secrets.extra-vars }}" ]]; then + echo "No extra-vars bundle provided - skipping." + else + echo "${{ secrets.extra-vars }}" | while IFS='=' read -r key val; do + if [[ -n "$val" ]]; then + echo "::add-mask::$val" + fi + done + echo "${{ secrets.extra-vars }}" >> "$GITHUB_ENV" + fi + - name: Clone repository + uses: actions/checkout@v6 + - name: Checkout workflow parts + uses: actions/checkout@v6 + with: + repository: devpro/github-workflow-parts + ref: ${{ inputs.workflow-parts-version }} + path: workflow-parts + - name: Login to container registry + uses: docker/login-action@v3 + with: + registry: ${{ inputs.container-registry }} + username: ${{ secrets.container-registry-username }} + password: ${{ secrets.container-registry-password }} + - name: Build container image + run: docker build . --file ${{ inputs.image-definition }} --tag ${{ env.IMAGE_REF }} ${{ secrets.extra-build-arguments }} + shell: bash + - name: Generate SBOM with Syft + uses: anchore/sbom-action@v0 + continue-on-error: true + with: + image: ${{ env.IMAGE_REF }} + # format: spdx-json # Or cyclonedx-json + # output-file: sbom.json + # upload-artifact: true # Auto-upload to workflow artifacts + - name: Push image to container registry + run: docker push ${{ env.IMAGE_REF }} + shell: bash + - name: Push latest tag to container registry + if: ${{ inputs.create_latest }} + run: | + docker tag ${{ env.IMAGE_REF }} ${{ env.IMAGE_REF_LATEST }} + docker push ${{ env.IMAGE_REF_LATEST }} + shell: bash + - name: Sign container image with Cosign + uses: ./workflow-parts/actions/cosign/sign + with: + image-name: ${{ inputs.image-name }} + image-path: ${{ inputs.image-path }} + image-tag: ${{ inputs.image-tag }} + env: + IMAGE_REF: ${{ inputs.image-path }}/${{ inputs.image-name }}:${{ inputs.image-tag }} + IMAGE_REF_LATEST: ${{ inputs.image-path }}/${{ inputs.image-name }}:latest diff --git a/.github/workflows/reusable-container-scan.yml b/.github/workflows/reusable-container-scan.yml new file mode 100644 index 0000000..541de61 --- /dev/null +++ b/.github/workflows/reusable-container-scan.yml @@ -0,0 +1,91 @@ +name: Reusable - Container scan + +on: + workflow_call: + inputs: + image-definition: + description: Path to the container definition file (Dockerfile, Containerfile) + type: string + required: true + image-name: + description: Image name + type: string + required: true + image-path: + description: Image path + type: string + required: true + image-tag: + description: Image tag + type: string + required: true + job-name: + description: Job name + type: string + required: false + default: Scan + max-high-cves: + description: Maximum number of high CVEs authorized + type: number + required: false + default: 0 + max-medium-cves: + description: Maximum number of medium CVEs authorized + type: number + required: false + default: 0 + neuvector-enabled: + description: "Use NeuVector to scan the image?" + type: string + required: false + default: false + operating-system: + description: Operating system executing the runner + type: string + required: false + default: ubuntu-latest + trivy-enabled: + description: "Use Trivy to scan the image?" + type: boolean + required: false + default: true + working-directory: + description: Working directory + type: string + required: false + default: "." + +jobs: + container-scan: + name: ${{ inputs.job-name }} + runs-on: ${{ inputs.operating-system }} + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Clone repository + uses: actions/checkout@v6 + - name: Build container image + run: docker build . --file ${{ inputs.image-definition }} --tag ${{ env.IMAGE_REF }} + shell: bash + - name: Scan container image with NeuVector + if: ${{ inputs.neuvector-enabled }} + uses: neuvector/scan-action@main + with: + image-repository: ${{ inputs.image-path }}/${{ inputs.image-name }} + image-tag: ${{ inputs.image-tag }} + min-high-cves-to-fail: '${{ inputs.max-high-cves }}' + min-medium-cves-to-fail: '${{ inputs.max-medium-cves }}' + - name: Scan container image with Trivy + if: ${{ inputs.trivy-enabled }} + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.IMAGE_REF }} + format: 'table' + exit-code: '1' + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' + env: + IMAGE_REF: ${{ inputs.image-path }}/${{ inputs.image-name }}:${{ inputs.image-tag }} + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/reusable-dotnet-quality.yml b/.github/workflows/reusable-dotnet-quality.yml new file mode 100644 index 0000000..ce6a604 --- /dev/null +++ b/.github/workflows/reusable-dotnet-quality.yml @@ -0,0 +1,146 @@ +name: Reusable - .NET quality + +on: + workflow_call: + inputs: + dotnet-version: + description: .NET version + type: string + default: "10.0" + extra-vars: + description: "Additional environment variables at the start of the pipeline" + type: string + required: false + default: "" + fossa-enabled: + description: Enable license compliance with FOSSA + type: boolean + required: false + default: false + job-name: + description: Job name + type: string + required: false + default: Quality + mongodb-enabled: + description: "Start MongoDB for integration tests?" + type: boolean + required: false + default: false + operating-system: + description: Operating system executing the runner + type: string + required: false + default: ubuntu-latest + sonar-enabled: + description: Enable check done by Sonar (SonarQube or SonarCloud) + type: boolean + required: false + default: false + sonar-host-url: + description: Sonar host URL + type: string + required: false + default: "https://sonarcloud.io" + sonar-organization: + description: Sonar organization + type: string + required: false + default: "" + sonar-project-key: + description: Sonar project key + type: string + required: false + default: "" + sonar-project-name: + description: Sonar project name + type: string + required: false + default: "" + workflow-parts-version: + description: GitHub workflow parts version (branch/tag/SHA) + type: string + required: false + default: main + working-directory: + description: Working directory + type: string + required: false + default: "." + secrets: + fossa-api-key: + description: FOSSA API KEY + required: false + sonar-token: + description: Sonar token for login + required: false + +jobs: + dotnet-quality: + name: ${{ inputs.job-name }} + runs-on: ${{ inputs.operating-system }} + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Set additional variables + run: | + if [[ -z "${{ secrets.extra-vars }}" ]]; then + echo "No extra-vars provided - skipping" + else + echo "${{ secrets.extra-vars }}" >> "$GITHUB_ENV" + fi + - name: Clone repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Checkout workflow parts + uses: actions/checkout@v6 + with: + repository: devpro/github-workflow-parts + ref: ${{ inputs.workflow-parts-version }} + path: workflow-parts + - name: Install .NET & lint + uses: ./workflow-parts/actions/dotnet/install-lint-restore + with: + dotnet-version: ${{ inputs.dotnet-version }} + - name: Start MongoDB + if: ${{ inputs.mongodb-enabled }} + uses: ./workflow-parts/actions/mongodb/start + - name: Build & test + if: ${{ ! inputs.sonar-enabled }} + uses: ./workflow-parts/actions/dotnet/build-test + - name: Build, test & analyze + if: ${{ inputs.sonar-enabled }} + uses: ./workflow-parts/actions/dotnet/build-test-sonar + with: + sonar-organization: ${{ inputs.sonar-organization }} + sonar-host-url: ${{ inputs.sonar-host-url }} + sonar-project-name: ${{ inputs.sonar-project-name }} + sonar-project-key: ${{ inputs.sonar-project-key }} + sonar-token: ${{ secrets.sonar-token }} + - name: Check license compliance with FOSSA + if: ${{ inputs.fossa-enabled }} + uses: fossas/fossa-action@v1 + with: + api-key: "${{ secrets.fossa-api-key }}" + run-tests: false + - name: Generate SBOM with Syft + uses: anchore/sbom-action@v0 + # with: + # path: . # Or Dockerfile path + # format: spdx-json # Or cyclonedx-json + # output-file: sbom.json + # upload-artifact: true # Auto-upload to workflow artifacts + - name: Archive test results + uses: actions/upload-artifact@v4 + with: + name: dotnet-test-results + path: | + ./**/*test-result.xml + ./test/*/TestResults/*/coverage.cobertura.xml + ./**/SonarQube.xml + ./**/Summary.txt + env: + # https://docs.github.com/en/actions/reference/workflows-and-actions/contexts + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/reusable-markup-lint.yml b/.github/workflows/reusable-markup-lint.yml new file mode 100644 index 0000000..8d131ff --- /dev/null +++ b/.github/workflows/reusable-markup-lint.yml @@ -0,0 +1,46 @@ +name: Reusable - Markup Lint + +on: + workflow_call: + inputs: + job-name: + description: "Job name" + type: string + required: false + default: "Lint" + working-directory: + description: "Working directory" + type: string + required: false + default: "." + operating-system: + description: "Operating system executing the runner" + type: string + required: false + default: "ubuntu-latest" + python-version: + description: "Python version" + type: string + required: false + default: "3.14" + +jobs: + markup-lint: + name: ${{ inputs.job-name }} + runs-on: ${{ inputs.operating-system }} + steps: + - name: Check out repository + uses: actions/checkout@v6 + - name: Lint Markdown files + uses: DavidAnson/markdownlint-cli2-action@v22 + with: + globs: "**/*.md" + - name: Install Python ${{ inputs.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ inputs.python-version }} + - name: Lint YAML files + run: | + python -m pip install --upgrade pip + pip install yamllint + yamllint . diff --git a/.github/workflows/reusable-terraform-apply.yml b/.github/workflows/reusable-terraform-deployment.yml similarity index 91% rename from .github/workflows/reusable-terraform-apply.yml rename to .github/workflows/reusable-terraform-deployment.yml index 0e1ea69..7b6fe40 100644 --- a/.github/workflows/reusable-terraform-apply.yml +++ b/.github/workflows/reusable-terraform-deployment.yml @@ -3,13 +3,24 @@ on: workflow_call: inputs: - environment: - description: "GitHub environment" + job-name: + description: "Job name" type: string - required: true + required: false + default: "Deploy" working-directory: description: "Working directory" type: string + required: false + default: "." + operating-system: + description: "Operating system executing the runner" + type: string + required: false + default: "ubuntu-latest" + environment: + description: "GitHub environment" + type: string required: true tfbackend-project: description: "Terraform backend project" @@ -60,9 +71,9 @@ on: required: false jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest + terraform-apply: + name: ${{ inputs.job-name }} + runs-on: ${{ inputs.operating-system }} environment: ${{ inputs.environment }} defaults: run: diff --git a/.github/workflows/reusable-terraform-validate.yml b/.github/workflows/reusable-terraform-quality.yml similarity index 93% rename from .github/workflows/reusable-terraform-validate.yml rename to .github/workflows/reusable-terraform-quality.yml index 7ada9b2..89918f6 100644 --- a/.github/workflows/reusable-terraform-validate.yml +++ b/.github/workflows/reusable-terraform-quality.yml @@ -5,19 +5,24 @@ on: inputs: job-name: description: "Job name" - required: false type: string + required: false default: "Validate" working-directory: description: "Working directory" - required: false type: string + required: false default: "." + operating-system: + description: "Operating system executing the runner" + type: string + required: false + default: "ubuntu-latest" jobs: terraform-validate: name: ${{ inputs.job-name }} - runs-on: ubuntu-latest + runs-on: ${{ inputs.operating-system }} defaults: run: working-directory: ${{ inputs.working-directory }} diff --git a/README.md b/README.md index 009280d..d4efd66 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,47 @@ # GitHub workflow parts -Repository of workflow parts to be used in GitHub Actions. +[![CI](https://github.com/devpro/github-workflow-parts/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/devpro/github-workflow-parts/actions/workflows/ci.yml) -## Actions +GitHub workflow components you can trust for your repositories. +Keep your pipelines DRY! (Don't Repeat Yourself) -Technology | Role | Action | Detail ------------|----------|---------------------------------------------------------|---------------------------------------------------------------------------- -Docker | CD | [Build & Push](docker/build-push/action.yml) | Build a new container image with Docker, and push it to a container registry -Docker | CI | [Build & Scan](docker/build-scan/action.yml) | Build a new container image with Docker, and scan it -.NET | CI | [Build, lint & test](dotnet/build-lint-test/action.yml) | Build .NET code, check the code with linter and Sonar, and run tests -MongoDB | Services | [Start](mongodb/start/action.yml) | Start a local MongoDB database +## Reusable workflows + +> Rather than copying and pasting from one workflow to another, you can make workflows reusable ([Reusing workflow configurations](https://docs.github.com/en/actions/concepts/workflows-and-actions/reusing-workflow-configurations)) + +Containers: + +- [Publish your image](.github/workflows/reusable-container-publication.yml) +- [Scan your image](.github/workflows/reusable-container-scan.yml) + +.NET: + +- [Ensure the quality of your code](.github/workflows/reusable-dotnet-quality.yml) + +Markup (Markdown, YAML): + +- [Ensure the quality of your content](.github/workflows/reusable-markup-lint.yml) + +Terraform: + +- [Deploy your environment](.github/workflows/reusable-terraform-deployment.yml) +- [Ensure the quality of your infrastructure code](.github/workflows/reusable-terraform-quality.yml) + +## Composite actions + +> Composite actions allow you to collect a series of workflow job steps into a single action which you can then run as a single job step in multiple workflows ([Creating a composite action](https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action)) + +Containers: + +- [Sign your image with Cosign](actions/cosign/sign/action.yml) + +.NET: + +- [Build, test your code and analyze it with Sonar](actions/dotnet/build-test-sonar/action.yml) +- [Build and test your code](actions/dotnet/build-test/action.yml) +- [Install the SDK, lint the code and restore NuGet packages](actions/dotnet/install-lint-restore/) + +MongoDB: + +- [Add your runner IP address to Atlas access list](actions/mongodb-atlas/add-runner-ip/action.yml) +- [Start a server in your pipeline](actions/mongodb/start/action.yml) diff --git a/actions/cosign/sign/action.yml b/actions/cosign/sign/action.yml new file mode 100644 index 0000000..85611c7 --- /dev/null +++ b/actions/cosign/sign/action.yml @@ -0,0 +1,39 @@ +name: Sign a container image +description: | + Make sure to add (needed by cosign): + ``` + permissions: + id-token: write + contents: read + ``` + +inputs: + image-name: + description: Image name + required: true + image-path: + description: Image path + required: true + image-tag: + description: Image tag + required: true + +runs: + using: "composite" + steps: + - name: Install Cosign + uses: sigstore/cosign-installer@v4.0.0 + with: + cosign-release: 'v3.0.3' + - name: Get image digest + id: digest + run: | + DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ${{ inputs.image-path }}/${{ inputs.image-name }}:${{ inputs.image-tag }} | cut -d'@' -f2) + echo "DIGEST=$DIGEST" >> $GITHUB_OUTPUT + shell: bash + - name: Sign image with Cosign + env: + COSIGN_EXPERIMENTAL: 1 + run: | + cosign sign --yes ${{ inputs.image-path }}/${{ inputs.image-name }}@${{ steps.digest.outputs.DIGEST }} + shell: bash diff --git a/actions/dotnet/build-test-sonar/action.yml b/actions/dotnet/build-test-sonar/action.yml new file mode 100644 index 0000000..89f873e --- /dev/null +++ b/actions/dotnet/build-test-sonar/action.yml @@ -0,0 +1,87 @@ +name: Build, Test & Analyze +description: Builds, runs tests on a .NET codebase and run analysis with Sonar + +inputs: + java-version: + description: Java version that will be installed (for Sonar CLI) + required: false + default: "21" + report-folder: + description: Folder where report files will be generated + required: false + default: report + sonar-host-url: + description: Sonar host URL + required: false + default: "" + sonar-organization: + description: Sonar organization + required: false + default: "" + sonar-project-key: + description: Sonar project key + required: false + default: "" + sonar-project-name: + description: Sonar project name + required: false + default: "" + sonar-token: + description: Sonar token for login + required: false + default: "" + +runs: + using: "composite" + steps: + - name: Set up JDK for Sonar + uses: actions/setup-java@v4 + with: + java-version: ${{ inputs.java-version }} + distribution: "zulu" + - name: Cache Sonar packages + uses: actions/cache@v4 + with: + path: ~/sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Sonar scanner + id: cache-sonar-scanner + uses: actions/cache@v4 + with: + path: ./.sonar/scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + - name: Install Sonar scanner + if: ${{ steps.cache-sonar-scanner.outputs.cache-hit != 'true' }} + run: | + mkdir -p ./.sonar/scanner + dotnet tool update dotnet-sonarscanner --tool-path ./.sonar/scanner + shell: bash + - name: Start code analysis + run: | + ./.sonar/scanner/dotnet-sonarscanner begin /k:"${{ inputs.sonar-project-key }}" /o:"${{ inputs.sonar-organization }}" \ + /n:"${{ inputs.sonar-project-name }}" /d:sonar.token="${{ inputs.sonar-token}}" /d:sonar.host.url="${{ inputs.sonar-host-url }}" \ + /d:sonar.cpd.exclusions="**/*Generated*.cs,${{ inputs.report-folder }}/**" /d:sonar.exclusions="${{ inputs.report-folder }}/**/*" \ + /d:sonar.coverageReportPaths="${{ inputs.report-folder }}/SonarQube.xml" + shell: bash + - name: Build .NET solution + run: dotnet build --no-restore + shell: bash + - name: Run tests + run: | + dotnet test --no-build --verbosity normal --configuration Debug \ + --logger:"junit;LogFilePath=..\..\artifacts\{assembly}-test-result.xml;MethodFormat=Class;FailureBodyFormat=Verbose" \ + --collect:"XPlat Code Coverage" + shell: bash + env: + ASPNETCORE_ENVIRONMENT: Development + - name: Generate test report + run: | + reportgenerator "-reports:./test/*/TestResults/*/coverage.cobertura.xml" \ + "-targetdir:${{ inputs.report-folder }}" \ + "-reporttypes:Cobertura;Html;TextSummary;SonarQube" + shell: bash + - name: Complete code analysis + run: ./.sonar/scanner/dotnet-sonarscanner end /d:sonar.token="${{ inputs.sonar-token }}" + shell: bash diff --git a/actions/dotnet/build-test/action.yml b/actions/dotnet/build-test/action.yml new file mode 100644 index 0000000..c1afcdc --- /dev/null +++ b/actions/dotnet/build-test/action.yml @@ -0,0 +1,29 @@ +name: Build & Test +description: Builds and runs tests on a .NET codebase + +inputs: + report-folder: + description: Folder where report files will be generated + required: false + default: report + +runs: + using: "composite" + steps: + - name: Build .NET code + run: dotnet build --no-restore + shell: bash + - name: Run .NET tests + run: | + dotnet test --no-build --verbosity normal --configuration Debug \ + --logger:"junit;LogFilePath=..\..\artifacts\{assembly}-test-result.xml;MethodFormat=Class;FailureBodyFormat=Verbose" \ + --collect:"XPlat Code Coverage" + shell: bash + env: + ASPNETCORE_ENVIRONMENT: Development + - name: Generate test report + run: | + reportgenerator "-reports:./test/*/TestResults/*/coverage.cobertura.xml" \ + "-targetdir:${{ inputs.report-folder }}" \ + "-reporttypes:Cobertura;Html;TextSummary" + shell: bash diff --git a/actions/dotnet/install-lint-restore/action.yml b/actions/dotnet/install-lint-restore/action.yml new file mode 100644 index 0000000..87679a8 --- /dev/null +++ b/actions/dotnet/install-lint-restore/action.yml @@ -0,0 +1,31 @@ +name: Install & Lint +description: Installs .NET SDK, lints and restores .NET code + +inputs: + dotnet-version: + description: .NET SDK version to be used + required: false + default: "10.0" + +runs: + using: "composite" + steps: + - name: Install .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ inputs.dotnet-version }} + - name: Install .NET linters + if: ${{ startsWith(inputs.dotnet-version, '7') }} + run: dotnet tool install -g dotnet-format --version "7.*" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json + shell: bash + - name: Install .NET tools + run: | + dotnet tool install --global dotnet-reportgenerator-globaltool + export PATH="$PATH:/root/.dotnet/tools" + shell: bash + - name: Lint .NET code + run: dotnet format --verify-no-changes --severity warn --verbosity:diagnostic + shell: bash + - name: Restore .NET packages + run: dotnet restore + shell: bash diff --git a/mongodb/start/action.yml b/actions/mongodb/start/action.yml similarity index 100% rename from mongodb/start/action.yml rename to actions/mongodb/start/action.yml diff --git a/docker/build-push/action.yml b/docker/build-push/action.yml deleted file mode 100644 index 48a5005..0000000 --- a/docker/build-push/action.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Build and push a container image -description: | - Builds a new container image with Docker and pushes it to a registry - Make sure to add (needed by cosign): - ``` - permissions: - id-token: write - contents: read - ``` - -inputs: - container_registry: - description: Container registry - required: true - container_registry_username: - description: Container registry username - required: true - container_registry_password: - description: Container registry password - required: true - docker_file: - description: Path to the Dockerfile - required: true - image_path: - description: Image tag - required: true - image_name: - description: Image name - required: true - image_tag: - description: Image tag - required: true - create_latest: - description: Create latest tag? - required: false - default: 'false' - build_arguments: - description: Container build arguments - required: false - default: '' - -runs: - using: "composite" - steps: - - name: Login to container registry - uses: docker/login-action@v3 - with: - registry: ${{ inputs.container_registry }} - username: ${{ inputs.container_registry_username }} - password: ${{ inputs.container_registry_password }} - - - name: Build container image - run: docker build . --file ${{ inputs.docker_file }} --tag ${{ inputs.image_path }}/${{ inputs.image_name }}:${{ inputs.image_tag }}${{ inputs.build_arguments }} - shell: bash - - - name: Generate SBOM with Syft - uses: anchore/sbom-action@v0 - continue-on-error: true - with: - image: ${{ inputs.image_path }}/${{ inputs.image_name }}:${{ inputs.image_tag }} - # format: spdx-json # Or cyclonedx-json - # output-file: sbom.json - # upload-artifact: true # Auto-upload to workflow artifacts - - - name: Push image to container registry - run: docker push ${{ inputs.image_path }}/${{ inputs.image_name }}:${{ inputs.image_tag }} - shell: bash - - - name: Push latest tag to container registry - if: ${{ inputs.create_latest == 'true' }} - run: | - docker tag ${{ inputs.image_path }}/${{ inputs.image_name }}:${{ inputs.image_tag }} ${{ inputs.image_path }}/${{ inputs.image_name }}:latest - docker push ${{ inputs.image_path }}/${{ inputs.image_name }}:latest - shell: bash - - - name: Install Cosign - uses: sigstore/cosign-installer@v4.0.0 - with: - cosign-release: 'v3.0.3' - - - name: Get image digest - id: digest - run: | - DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ${{ inputs.image_path }}/${{ inputs.image_name }}:${{ inputs.image_tag }} | cut -d'@' -f2) - echo "DIGEST=$DIGEST" >> $GITHUB_OUTPUT - shell: bash - - - name: Sign image with Cosign - env: - COSIGN_EXPERIMENTAL: 1 - run: | - cosign sign --yes ${{ inputs.image_path }}/${{ inputs.image_name }}@${{ steps.digest.outputs.DIGEST }} - shell: bash diff --git a/docker/build-scan/action.yml b/docker/build-scan/action.yml deleted file mode 100644 index fbc1a5d..0000000 --- a/docker/build-scan/action.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Scan container image -description: Builds a new container image with Docker and scans it - -inputs: - docker_file: - description: Path to the Dockerfile - required: true - image_tag: - description: Image tag - required: true - image_path: - description: Image tag - required: true - image_name: - description: Image name - required: true - neuvector_enabled: - description: Use NeuVector to scan the image? - required: false - default: 'true' - trivy_enabled: - description: Use Trivy to scan the image? - required: false - default: 'true' - max_high_cves: - description: Maximum number of high CVE authorized - required: false - default: '1' - max_medium_cves: - description: Maximum number of medium CVE authorized - required: false - default: '1' - -runs: - using: "composite" - steps: - - name: Build container image - run: docker build . --file ${{inputs.docker_file}} --tag ${{inputs.image_path}}/${{inputs.image_name}}:${{inputs.image_tag}} - shell: bash - - name: Scan container image with NeuVector - if: ${{ inputs.neuvector_enabled == 'true' }} - uses: neuvector/scan-action@main - with: - image-repository: ${{inputs.image_path}}/${{inputs.image_name}} - image-tag: ${{inputs.image_tag}} - min-high-cves-to-fail: '${{inputs.max_high_cves}}' - min-medium-cves-to-fail: '${{inputs.max_medium_cves}}' - - name: Scan container image with Trivy - if: ${{ inputs.trivy_enabled == 'true' }} - uses: aquasecurity/trivy-action@master - with: - image-ref: '${{inputs.image_path}}/${{inputs.image_name}}:${{inputs.image_tag}}' - format: 'table' - exit-code: '1' - ignore-unfixed: true - vuln-type: 'os,library' - severity: 'CRITICAL,HIGH' diff --git a/dotnet/build-lint-test/action.yml b/dotnet/build-lint-test/action.yml deleted file mode 100644 index 2fe7b85..0000000 --- a/dotnet/build-lint-test/action.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: Build, lint and test .NET -description: Builds, checks and runs tests on a .NET codebase - -inputs: - dotnet_version: - description: .NET SDK version to be used - required: false - default: "10.0.x" - sonar_enabled: - description: Enable code scan by Sonar - required: false - default: "false" - sonar_organization: - description: Sonar organization - required: false - default: "" - sonar_host_url: - description: Sonar host URL - required: false - default: "" - sonar_project_name: - description: Sonar project name - required: false - default: "" - sonar_project_key: - description: Sonar project key - required: false - default: "" - sonar_token: - description: Sonar token for login - required: false - default: "" - report_folder: - description: Folder where report files will be generated - required: false - default: report - fossa_enabled: - description: Enable license compliance with FOSSA - required: false - default: "false" - fossa_api_key: - description: FOSSA API KEY - required: false - default: "" - -runs: - using: "composite" - steps: - - name: Install .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ inputs.dotnet_version }} - - name: Set up JDK for Sonar - if: ${{ inputs.sonar_enabled == 'true' }} - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: "zulu" - - name: Install .NET linters - if: ${{ inputs.dotnet_version == '7.0.x' }} - run: dotnet tool install -g dotnet-format --version "7.*" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json - shell: bash - - name: Install .NET tools - run: | - dotnet tool install --global dotnet-reportgenerator-globaltool - export PATH="$PATH:/root/.dotnet/tools" - shell: bash - - name: Restore .NET packages - run: dotnet restore - shell: bash - - name: Lint .NET code - run: dotnet format --verify-no-changes --severity warn --verbosity:diagnostic - shell: bash - - name: Cache Sonar packages - if: ${{ inputs.sonar_enabled == 'true' }} - uses: actions/cache@v4 - with: - path: ~/sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache Sonar scanner - if: ${{ inputs.sonar_enabled == 'true' }} - id: cache-sonar-scanner - uses: actions/cache@v4 - with: - path: ./.sonar/scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - name: Install Sonar scanner - if: ${{ inputs.sonar_enabled == 'true' && steps.cache-sonar-scanner.outputs.cache-hit != 'true' }} - run: | - mkdir -p ./.sonar/scanner - dotnet tool update dotnet-sonarscanner --tool-path ./.sonar/scanner - shell: bash - - name: Start code analysis - if: ${{ inputs.sonar_enabled == 'true' }} - run: | - ./.sonar/scanner/dotnet-sonarscanner begin /k:"${{ inputs.sonar_project_key }}" /o:"${{ inputs.sonar_organization }}" \ - /n:"${{ inputs.sonar_project_name }}" /d:sonar.token="${{ inputs.sonar_token}}" /d:sonar.host.url="${{inputs.sonar_host_url}}" \ - /d:sonar.cpd.exclusions="**/*Generated*.cs,${{ inputs.report_folder }}/**" /d:sonar.exclusions="${{ inputs.report_folder }}/**/*" \ - /d:sonar.coverageReportPaths="${{ inputs.report_folder }}/SonarQube.xml" - shell: bash - - name: Build .NET solution - run: dotnet build --no-restore - shell: bash - - name: Run tests - run: | - dotnet test --no-build --verbosity normal --configuration Debug \ - --logger:"junit;LogFilePath=..\..\artifacts\{assembly}-test-result.xml;MethodFormat=Class;FailureBodyFormat=Verbose" \ - --collect:"XPlat Code Coverage" - shell: bash - env: - ASPNETCORE_ENVIRONMENT: Development - Application__IsHttpsRedirectionEnabled: "false" - - name: Generate test report - run: | - reportgenerator "-reports:./test/*/TestResults/*/coverage.cobertura.xml" \ - "-targetdir:${{inputs.report_folder}}" \ - "-reporttypes:Cobertura;Html;TextSummary;SonarQube" - shell: bash - - name: Complete code analysis - if: ${{ inputs.sonar_enabled == 'true' }} - run: ./.sonar/scanner/dotnet-sonarscanner end /d:sonar.token="${{inputs.sonar_token}}" - shell: bash - - - name: License Compliance with FOSSA - if: ${{ inputs.fossa_enabled == 'true' }} - uses: fossas/fossa-action@v1 - with: - api-key: "${{ inputs.fossa_api_key }}" - run-tests: false - - - name: Generate SBOM with Syft - uses: anchore/sbom-action@v0 - # with: - # path: . # Or Dockerfile path - # format: spdx-json # Or cyclonedx-json - # output-file: sbom.json - # upload-artifact: true # Auto-upload to workflow artifacts - - - name: Archive test results - uses: actions/upload-artifact@v4 - with: - name: dotnet-test-results - path: | - ./**/*test-result.xml - ./test/*/TestResults/*/coverage.cobertura.xml - ./**/SonarQube.xml - ./**/Summary.txt