diff --git a/.github/workflows/chart-ci.yml b/.github/workflows/chart-ci.yml index 4795b30..9da2003 100644 --- a/.github/workflows/chart-ci.yml +++ b/.github/workflows/chart-ci.yml @@ -36,10 +36,15 @@ jobs: - name: Extract and add Helm repositories run: | - yq eval '.dependencies[] | "\(.name) \(.repository)"' ${{ inputs.chart-dir }}/Chart.yaml | \ - while read -r name repo; do - helm repo add "$name" "$repo" + yq eval -r ' + .dependencies[] + | select(.repository != "oci://*") + | "\(.name) \(.repository)" + ' "${{ inputs.chart-dir }}/Chart.yaml" \ + | while read -r name repo; do + helm repo add "$name" "$repo" done + helm repo update - name: Install chart dependencies @@ -180,7 +185,7 @@ jobs: path: ${{ inputs.chart-dir }} - name: Build Trivy Vulnerability report - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.35.0 env: TRIVY_HELM_KUBE_VERSION: ${{ inputs.kubernetes-version }} TRIVY_HELM_SET_FILE: ${{ inputs.chart-values }} @@ -198,7 +203,7 @@ jobs: sarif_file: 'trivy-vuln-results.sarif' - name: Run Trivy Vulnerability scan - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.35.0 env: TRIVY_HELM_KUBE_VERSION: ${{ inputs.kubernetes-version }} TRIVY_HELM_SET_FILE: ${{ inputs.chart-values }} @@ -271,57 +276,88 @@ jobs: with: fetch-depth: 0 - - name: Configure Git + # ### Release steps specific to `feature` branch ### + - name: Add release suffix - SNAPSHOT + if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - # Fix for "chart-cr" action bug https://github.com/helm/chart-releaser-action/issues/171#issuecomment-2372464055 - git fetch --tags - latest_tag=$(git tag --sort=-creatordate | head -n 1 || true) - echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT" - - - name: Download packaged Chart - uses: actions/download-artifact@v4 - with: - name: packaged-chart - path: ${{ inputs.chart-dir }} - - - name: Install Helm - uses: azure/setup-helm@v4.3.1 - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - - name: Extract and add Helm repositories - run: | - yq eval '.dependencies[] | "\(.name) \(.repository)"' ${{ inputs.chart-dir }}/Chart.yaml | \ - while read -r name repo; do - helm repo add "$name" "$repo" - done - helm repo update + VERSION_SUFFIX="-snapshot-$(git rev-parse --short ${{ github.sha }})" \ + yq -i '.version |= . + env(VERSION_SUFFIX)' ${{ inputs.chart-dir }}/Chart.yaml ### Release steps specific to `dev` branch ### - name: Add release suffix - DEV if: github.ref == 'refs/heads/dev' run: | - VERSION_SUFFIX="-dev.$(git rev-parse --short ${{ github.sha }})" \ + VERSION_SUFFIX="-dev-$(git rev-parse --short ${{ github.sha }})" \ yq -i '.version |= . + env(VERSION_SUFFIX)' ${{ inputs.chart-dir }}/Chart.yaml - - name: Run chart-releaser - DEV - if: github.ref == 'refs/heads/dev' - uses: helm/chart-releaser-action@v1.7.0 - env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - with: - skip_existing: true - mark_as_latest: false + - name: Package chart + run: | + helm package ${{ inputs.chart-dir }} -d .cr-release-packages/ + + # ### Prepare release variables ### + - name: Prepare release variables + id: prepare-release + run: | + git fetch origin gh-pages + name=$(git show origin/gh-pages:index.yaml | yq e '.entries | keys | .[0]' 2>/dev/null || echo " ??? ") - ### Release steps specific to `main` branch ### - - name: Run chart-releaser - MAIN + VERSION=$(yq '.version' ${{ inputs.chart-dir }}/Chart.yaml) + + TAG_NAME="${name}-${VERSION}" + + echo "TAG_NAME=$TAG_NAME" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + # ### Release steps specific to `feature` or `dev` branch ### + - name: Create GitHub pre-release + tag + if: github.ref != 'refs/heads/main' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ steps.prepare-release.outputs.TAG_NAME }}" \ + .cr-release-packages/*.tgz \ + --prerelease \ + --title "${{ steps.prepare-release.outputs.TAG_NAME }}" \ + --target ${{ github.sha}} \ + --notes "Version from ${{ github.ref_name }}" \ + -F CHANGELOG.md + + # ### Release steps specific to `main` branch ### + - name: Create GitHub release + tag if: github.ref == 'refs/heads/main' - uses: helm/chart-releaser-action@v1.7.0 env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - with: - skip_existing: true - mark_as_latest: true + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ steps.prepare-release.outputs.TAG_NAME }}" \ + .cr-release-packages/*.tgz \ + --latest \ + --target ${{ github.sha}} \ + --title "${{ steps.prepare-release.outputs.TAG_NAME }}" \ + --notes "Version from ${{ github.ref_name }}" \ + -F CHANGELOG.md + + - name: Update index.yaml on gh-pages + env: + CR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + # Get gh-pages + git fetch origin gh-pages + mkdir -p .cr-index + git show origin/gh-pages:index.yaml > .cr-index/index.yaml 2>/dev/null || echo "apiVersion: v1\nentries: {}" > .cr-index/index.yaml + + echo "Merge index.yaml with new chart version..." + helm repo index .cr-index \ + --url https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }} \ + --merge .cr-index/index.yaml + + args=(-o "${{ github.repository_owner }}" -r "${{ github.event.repository.name }}" --push) + + echo "Installing chart-releaser on $install_dir..." + curl -sSLo cr.tar.gz "https://github.com/helm/chart-releaser/releases/download/v1.8.1/chart-releaser_1.8.1_linux_amd64.tar.gz" + tar -xzf cr.tar.gz -C "/usr/local/bin/" + rm -f cr.tar.gz + + cr index "${args[@]}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d41bc8..75a32aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,4 @@ +--- name: CI # Controls when the workflow will run @@ -7,7 +8,7 @@ on: branches-ignore: - "main" pull_request: - branches: [ "main", "dev" ] + branches: ["main", "dev"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -25,4 +26,11 @@ jobs: - uses: actions/checkout@v4 - name: yaml-lint uses: ibiqlik/action-yamllint@v3.1.1 - \ No newline at end of file + with: + config_data: | + extends: default + rules: + trailing-spaces: + level: warning + line-length: disable # don't bother me with this rule + comments-indentation: disable # don't bother me with this rule diff --git a/.github/workflows/container-ci.yml b/.github/workflows/container-ci.yml index d7b6fd2..7f890cf 100644 --- a/.github/workflows/container-ci.yml +++ b/.github/workflows/container-ci.yml @@ -1,3 +1,4 @@ +--- name: build on: @@ -8,21 +9,65 @@ on: required: false type: string default: "Dockerfile" + hadolint-ignore: + description: "Comma separated list of Hadolint rules to ignore (for scan only, will still be present in the generated report)" + required: false + default: '' + type: string image-name: description: "Image name" required: true - type: string + type: string + image-custom-tag: + description: "Custom image tag, to be added to the ones generated by default" + required: false + default: '' + type: string + extra-build-args: + description: "Extra build args as KEY=VALUE, one per line, in a YAML scalar bloc" + required: false + default: '' + type: string + dockle-ignore: + description: "Comma separated list of Dockle rule IDs to ignore" + required: false + default: '' + type: string + dockle-accept-file: + description: "Comma separated list of filenames to accept (Dockle accept-file)" + required: false + default: '' + type: string + dockle-accept-key: + description: "Comma separated list of keys to accept (Dockle accept-key)" + required: false + default: '' + type: string + trivy-ignore-vuln-ids: + description: | + List of vulnerability IDs (CVE-..., GHSA-..., AVD-...) to ignore in Trivy. + One per line (recommended) or comma-separated. + required: false + default: '' + type: string + trivy-ignore-license-ids: + description: | + List of license IDs to ignore in Trivy (ex: GPL-3.0-only, MIT, Apache-2.0 WITH LLVM-exception). + One per line (recommended) or comma-separated. + required: false + default: '' + type: string jobs: - hadolint: - name: Hadolint - Dockerfile Lint + dockerfile-lint: + name: Dockerfile Lint runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Build Hadolint report - uses: hadolint/hadolint-action@v3.1.0 + uses: hadolint/hadolint-action@v3.3.0 with: dockerfile: ${{ inputs.dockerfile-path }} no-fail: true @@ -38,19 +83,44 @@ jobs: category: linting - name: Run Hadolint scan - uses: hadolint/hadolint-action@v3.1.0 + uses: hadolint/hadolint-action@v3.3.0 with: dockerfile: ${{ inputs.dockerfile-path }} - override-info: DL3008,DL3018,DL3041,SC2046 + ignore: ${{ inputs.hadolint-ignore }} failure-threshold: warning format: tty - build-image: - name: Buildx - Image Build + image-build: + name: Image Build runs-on: ubuntu-latest - needs: hadolint + needs: dockerfile-lint steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Parse build-args + id: args + shell: bash + run: | + { + echo "build-args<> "$GITHUB_OUTPUT" + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -60,7 +130,7 @@ jobs: # list of Docker images to use as base name for tags images: | ${{ inputs.image-name }} - # generate Docker tags based on the following events/attributes + # Generate Docker tags based on the following events/attributes tags: | type=schedule type=ref,event=branch @@ -69,6 +139,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha + type=raw,value=${{ inputs.image-custom-tag }},enable=${{ inputs.image-custom-tag != '' }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -82,32 +153,40 @@ jobs: file: ${{ inputs.dockerfile-path }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + ${{ steps.args.outputs.build-args }} push: false - outputs: type=docker,dest=/tmp/container-image.${{github.run_id}}.tar + outputs: type=docker,dest=/tmp/container.image.${{ hashFiles(inputs.dockerfile-path) }}.tar - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: container-image.${{github.run_id}} - path: /tmp/container-image.${{github.run_id}}.tar + name: container.image.${{ hashFiles(inputs.dockerfile-path) }} + path: /tmp/container.image.${{ hashFiles(inputs.dockerfile-path) }}.tar - dockle: - name: Dockle - Image Scan - needs: build-image + image-audit: + name: Image Audit + needs: image-build runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Download image tarball uses: actions/download-artifact@v4 with: - name: container-image.${{github.run_id}} + name: container.image.${{ hashFiles(inputs.dockerfile-path) }} path: /tmp - name: Load image run: | - echo "[INFO]: Importing container image from following tarball : $(ls -al /tmp/container-image.${{github.run_id}}.tar)" - docker load --input /tmp/container-image.${{github.run_id}}.tar - echo "[INFO]: The following images are now present in the local registry : $(docker image ls -a)" + TARBALL="/tmp/container.image.${{ hashFiles(inputs.dockerfile-path) }}.tar" + echo "[INFO]: Importing container image from following tarball :" + ls -al "$TARBALL" + docker load --input "$TARBALL" + echo "[INFO]: The following images are now present in the local registry :" + docker image ls -a - name: Generate SHORT_SHA id: short-sha @@ -116,11 +195,11 @@ jobs: length: 7 - name: Build Dockle report - uses: erzz/dockle-action@v1.4.0 + uses: goodwithtech/dockle-action@v0.4.15 with: image: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" - report-format: sarif - failure-threshold: fatal + format: sarif + output: 'dockle-report.sarif' exit-code: 0 - name: Upload Dockle report to GitHub Security tab @@ -129,30 +208,50 @@ jobs: sarif_file: 'dockle-report.sarif' category: code-quality - - name: Run Dockle check - uses: erzz/dockle-action@v1.4.0 - with: - image: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" - failure-threshold: fatal - exit-code: 1 + - name: Download Dockle + run: | + curl -sSL "https://github.com/goodwithtech/dockle/releases/download/v0.4.15/dockle_0.4.15_Linux-386.tar.gz" -o dockle.tar.gz + tar -xzf dockle.tar.gz + sudo mv dockle /usr/local/bin/dockle - - trivy-vulns: - name: Trivy - Vulnerability Scan - needs: build-image + - name: Run dockle + run: | + dockle_keys_formater(){ IFS=', ' read -r -a keys_list <<< "$VALUES" && \ + formated_keys="" && \ + for key in ${keys_list[@]}; do formated_keys="${formated_keys} $ARG $key" ; done && \ + echo "$formated_keys " ;} + + dockle \ + $(VALUES="${{ inputs.dockle-accept-key }}" ARG="--accept-key" dockle_keys_formater) \ + $(VALUES="${{ inputs.dockle-accept-file }}" ARG="--accept-file" dockle_keys_formater) \ + $(VALUES="${{ inputs.dockle-ignore }}" ARG="--ignore" dockle_keys_formater) \ + --exit-code 1 \ + --format json \ + "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" + + + image-scan: + name: Image Scan + needs: image-build runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Download image tarball uses: actions/download-artifact@v4 with: - name: container-image.${{github.run_id}} + name: container.image.${{ hashFiles(inputs.dockerfile-path) }} path: /tmp - name: Load image run: | - echo "[INFO]: Importing container image from following tarball : $(ls -al /tmp/container-image.${{github.run_id}}.tar)" - docker load --input /tmp/container-image.${{github.run_id}}.tar - echo "[INFO]: The following images are now present in the local registry : $(docker image ls -a)" + TARBALL="/tmp/container.image.${{ hashFiles(inputs.dockerfile-path) }}.tar" + echo "[INFO]: Importing container image from following tarball :" + ls -al "$TARBALL" + docker load --input "$TARBALL" + echo "[INFO]: The following images are now present in the local registry :" + docker image ls -a - name: Generate SHORT_SHA id: short-sha @@ -160,10 +259,48 @@ jobs: with: length: 7 + - name: Generate Trivy ignore file from inputs + env: + TRIVY_IGNORE_VULN_IDS: ${{ inputs.trivy-ignore-vuln-ids }} + TRIVY_IGNORE_LICENSE_IDS: ${{ inputs.trivy-ignore-license-ids }} + run: | + IGNORE_FILE="ci-trivy-ignore.txt" + echo "# Generated by reusable build workflow" > "$IGNORE_FILE" + + if [ -n "$TRIVY_IGNORE_VULN_IDS" ]; then + echo "" >> "$IGNORE_FILE" + echo "# Vulnerabilities" >> "$IGNORE_FILE" + printf '%s\n' "$TRIVY_IGNORE_VULN_IDS" | tr ',' '\n' >> "$IGNORE_FILE" + fi + + if [ -n "$TRIVY_IGNORE_LICENSE_IDS" ]; then + echo "" >> "$IGNORE_FILE" + echo "# Licenses" >> "$IGNORE_FILE" + printf '%s\n' "$TRIVY_IGNORE_LICENSE_IDS" | tr ',' '\n' >> "$IGNORE_FILE" + fi + + echo "[INFO] Generated $IGNORE_FILE:" + cat "$IGNORE_FILE" + + - name: Generate SBOM (SPDX JSON) + uses: aquasecurity/trivy-action@0.33.1 + with: + scan-type: 'image' + image-ref: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" + format: 'spdx-json' + output: 'image-sbom.spdx.json' + exit-code: '0' + + - name: Submit SBOM to GitHub Dependency Graph + uses: advanced-security/spdx-dependency-submission-action@v0.1.1 + with: + filePath: 'image-sbom.spdx.json' + - name: Build Trivy Vulnerability report - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.33.1 with: image-ref: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" + skip-setup-trivy: true exit-code: '0' ignore-unfixed: false scanners: 'vuln,secret' @@ -179,49 +316,11 @@ jobs: sarif_file: 'trivy-vuln-results.sarif' category: vulnerability - - name: Run Trivy Vulnerability scan - uses: aquasecurity/trivy-action@0.29.0 - # Overriding env vars from previous steps for them not to interfere with the scan - env: - TRIVY_FORMAT: 'table' - TRIVY_OUTPUT: '' - with: - image-ref: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" - exit-code: '1' - ignore-unfixed: true - scanners: 'vuln,secret' - vuln-type: 'os,library' - severity: 'CRITICAL' - format: 'table' - - - trivy-license: - name: Trivy - License scan - needs: build-image - runs-on: ubuntu-latest - steps: - - name: Download image tarball - uses: actions/download-artifact@v4 - with: - name: container-image.${{github.run_id}} - path: /tmp - - - name: Load image - run: | - echo "[INFO]: Importing container image from following tarball : $(ls -al /tmp/container-image.${{github.run_id}}.tar)" - docker load --input /tmp/container-image.${{github.run_id}}.tar - echo "[INFO]: The following images are now present in the local registry : $(docker image ls -a)" - - - name: Generate SHORT_SHA - id: short-sha - uses: benjlevesque/short-sha@v3.0 - with: - length: 7 - - name: Build Trivy License report - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.33.1 with: image-ref: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" + skip-setup-trivy: true exit-code: '0' ignore-unfixed: false scanners: 'license' @@ -237,39 +336,64 @@ jobs: sarif_file: 'trivy-license-results.sarif' category: license + - name: Run Trivy Vulnerability scan + uses: aquasecurity/trivy-action@0.33.1 + # Overriding env vars from previous steps for them not to interfere with the scan + env: + TRIVY_FORMAT: 'table' + TRIVY_OUTPUT: '' + with: + image-ref: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" + skip-setup-trivy: true + exit-code: '1' + ignore-unfixed: true + scanners: 'vuln,secret' + vuln-type: 'os,library' + severity: 'CRITICAL' + format: 'table' + trivyignores: 'ci-trivy-ignore.txt' + - name: Run Trivy License scan - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.33.1 # Overriding env vars from previous steps for them not to interfere with the scan env: TRIVY_FORMAT: 'table' TRIVY_OUTPUT: '' with: image-ref: "${{ inputs.image-name }}:sha-${{ steps.short-sha.outputs.sha }}" + skip-setup-trivy: true exit-code: '1' ignore-unfixed: true scanners: 'license' vuln-type: 'os,library' severity: 'CRITICAL' format: 'table' - + trivyignores: 'ci-trivy-ignore.txt' push-docker-image: - name: Docker - Image Push to GHCR - needs: [dockle, trivy-vulns, trivy-license] + name: Image Push to GHCR + needs: + - image-audit + - image-scan runs-on: ubuntu-24.04 - steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Download image tarball uses: actions/download-artifact@v4 with: - name: container-image.${{github.run_id}} + name: container.image.${{ hashFiles(inputs.dockerfile-path) }} path: /tmp - name: Load image run: | - echo "[INFO]: Importing container image from following tarball : $(ls -al /tmp/container-image.${{github.run_id}}.tar)" - docker load --input /tmp/container-image.${{github.run_id}}.tar - echo "[INFO]: The following images are now present in the local registry : $(docker image ls -a)" + TARBALL="/tmp/container.image.${{ hashFiles(inputs.dockerfile-path) }}.tar" + echo "[INFO]: Importing container image from following tarball :" + ls -al "$TARBALL" + docker load --input "$TARBALL" + echo "[INFO]: The following images are now present in the local registry :" + docker image ls -a - name: Login to GitHub Container Registry uses: docker/login-action@v3.3.0 diff --git a/README.md b/README.md index 4fe5c78..1630024 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,341 @@ # APHP CI GitHub Reusable Workflows -This directory holds the GitHub reusable workflows that you can import in your own project's CIs. +This repository hosts a collection of **reusable GitHub Actions workflows** for AP‑HP projects. -## Container Images +The goal is to **centralize CI/CD best practices** (linting, security scans, packaging and publishing) so that each project can: -### Description +- reuse the same, opinionated workflows, +- get consistent quality and security checks, +- keep CI configuration as small as possible. -This component aims to provide with security and quality checks for container images, before pushing them on your project's GHCR (GitHub Container Registry). +Current workflows mainly target: -### Tools +- **Container images** (build, scan and push to GHCR), +- **Helm charts** (lint, test, secure, document and publish). -- Hadolint - - Dockerfile linting +--- -- Buildx - - Docker Image build +## Table of contents -- Dockle - - Docker Image scan for misconfigurations and ba patterns +- [Overview](#overview) +- [Available reusable workflows](#available-reusable-workflows) + - [Container Images workflow](#container-images-workflow) + - [Helm Charts workflow](#helm-charts-workflow) +- [How to call a reusable workflow](#how-to-call-a-reusable-workflow) +- [Branching, versions and environments](#branching-versions-and-environments) +- [Contributing](#contributing) +- [License](#license) -- Trivy - - Docker Image scan for vulnerabilities - - Docker Image scan for potential licensing issues +--- -- Docker - - Pushing Docker Image to the project's GHCR +## Overview -### Reports +All workflows are stored under: -All the tools used are generating SARIF reports that are uploaded to your Github project's dashboard. You can consult the reports entries of your project under the `Security` tab -> `Code scanning` category. +```text +.github/workflows/ +``` +You **do not copy** these YAML files into your own repositories. +Instead, your project **calls them as reusable workflows** using the `uses:` syntax described in the GitHub Actions documentation. -### Prerequisites +This repository is meant to be used across all AP‑HP GitHub projects that need a **standard CI pipeline** for: -This workflow should work out-of-the-box for public projects. Execution on private projects is not supported for now, and may require some additional steps to set the correct permissions for the workflow being able to push the image in your private GHCR. +- building and scanning Docker images, +- validating, scanning and releasing Helm charts. -### How to use +--- -#### Calling this workflow +## Available reusable workflows -To define a job that calls a reusable workflow, just read the [the corresponding documentation](https://docs.github.com/en/actions/sharing-automations/reusing-workflows#calling-a-reusable-workflow). +Below is an overview of the main families of workflows currently documented. -You can use the [redcap-containers project CIs](https://github.com/aphp/redcap-containers/tree/main/.github/workflows) as an example. +### Container Images workflow -#### Inputs definition +#### Description -This workflow's inputs are as follows : -- `dockerfile-path`: - - description: "Path to Dockerfile of your project" - - required: false - - type: string - - default: "Dockerfile" -- `image-name`: - - description: "Image name, including tag" - - required: true - - type: string +Provides **security and quality checks** for container images before pushing them to your project’s **GitHub Container Registry (GHCR)**. -#### Releases management +Typical use cases: -This workflow uses a blend of several actions and steps to handle the release process. +- container images for applications (e.g. APIs, frontends, backends), +- base or runtime images (e.g. Jupyter EDS notebooks images), +- internal utilities or tools. -**This behavior could impact your lifecycle management practices, so be sure to read the lines below!** +#### Tools -The releases will be handled as follow, whenever you decide to call this workflow (when tagging, pushing, etc.): -- At build, you image will be tagged and version according to the [Docker Metadata Action](https://github.com/marketplace/actions/docker-metadata-action) rules (`build-image::Buildx - Image Build` step). -- If the scanning steps ends successfully, your previously tagged image will be pushed in the GHCR repository of your project (`push-docker-image::Push Image to GitHub Container Registry` step). +The workflow chains several tools: +- **Hadolint** + - Linting of `Dockerfile` (style, best practices, common pitfalls). +- **Buildx** + - Docker image build (supports advanced features like build kits, multi‑arch, extra build args…). +- **Dockle** + - Image scan for misconfigurations and bad patterns. +- **Trivy** + - Vulnerability scanning of images. + - License scanning of included dependencies. +- **Docker / GHCR** + - Push of validated images to your project’s GHCR repository. -## Helm Charts +#### Reports -### Description +All tools that support it produce **SARIF reports**, automatically uploaded to your repository: -This component aims to provide with security and quality checks for Helm charts, before pushing them a repository hosted in your project's Github Page that can be used as a Helm Repository. +- GitHub UI: `Security` tab → `Code scanning` section. +- You can browse findings by tool, severity, and impacted files. -### Tools +#### Prerequisites -- Linting (`lint-test` job) - - Helm ct-lint - - Kubeconform +- Works **out of the box for public repositories**. +- For **private repositories**, you may need to: + - adjust **Actions permissions** so the workflow can push to your private GHCR, + - ensure the workflow can **write packages** (GHCR). -- Security (`lint-test` job) - - Polaris - - Trivy +> The exact permission model may depend on your organization policies; coordinate with your AP‑HP GitHub admins if needed. -- Documentation (`generate-doc` job) - - Helm Docs - - Helm Values Schema JSON +#### Inputs -- Publishing (`release` job) - - Helm chart-releaser +Inputs currently supported by the container images workflow: -### Reports +| Input | Type | Required | Default | Description | +|---------------------------:|:------:|:--------:|:------------------:|-------------| +| `dockerfile-path` | string | No | `Dockerfile` | Path to the Dockerfile of your project. | +| `hadolint-ignore` | string | No | `""` | Comma‑separated list of **Hadolint rule IDs** to ignore in **blocking** checks. Findings still appear in reports. | +| `image-name` | string | **Yes** | – | Full image name including registry and repository, e.g. `ghcr.io/aphp/my-service`. | +| `image-custom-tag` | string | No | `""` | Custom tag added **in addition** to automatically generated tags. Typical values: `x86_64-ubuntu-24.04`, `x86_64-ubuntu-24.04-dev`, `nightly`. | +| `extra-build-args` | string | No | `""` | Extra Docker build arguments, provided as `KEY=VALUE`. Usually passed as a multiline YAML scalar (one `KEY=VALUE` per line). | +| `dockle-ignore` | string | No | `""` | Comma‑separated list of **Dockle rule IDs** to ignore in **blocking** checks. | +| `dockle-accept-file` | string | No | `""` | Comma‑separated list of file names to accept in Dockle (`--accept-file`). | +| `dockle-accept-key` | string | No | `""` | Comma‑separated list of keys to accept in Dockle (`--accept-key`). | +| `trivy-ignore-vuln-ids` | string | No | `""` | List of vulnerability IDs (`CVE-…`, `GHSA-…`, `AVD-…`) to ignore for **blocking** Trivy checks. Can be comma‑separated or one per line. | +| `trivy-ignore-license-ids`| string | No | `""` | List of license identifiers (e.g. `GPL-3.0-only`, `MIT`) to ignore in **blocking** Trivy license checks. Can be comma‑separated or one per line. | -A few tools (only Trivy at this time) are generating SARIF reports that are uploaded to your Github project's dashboard. You can consult the reports entries of your project under the `Security` tab -> `Code scanning` category. +Always refer to the workflow file in `.github/workflows/` for the most up‑to‑date list of inputs and defaults. -### Prerequisites +#### Release management -If you want to publish your chart as an artefact in your Github project, and for it to be retrieved as a Chart by Helm, you must follow these steps : -- Create a branch names `gh-pages` in your repository -- Set this branch as the Github Page branch in the github project's page, under the `Settings` tab -> `Code and automation/Pages` category, `Branch` section -- Set the correct permissions for Github Actions, under the `Settings` tab -> `Actions/General` category : - - Set `Actions permissions` to `Allow all actions and reusable workflows` - - Set `Workflow permissions` to `Read and write permissions` -- Place your Helm Chart under a `charts` directory in the root of your repository, and push your changes +The workflow relies on a combination of actions and steps to handle **image tagging and publishing**: +- During the build step, tags are computed according to **Docker Metadata Action** rules. +- If all scans pass successfully: + - the previously tagged image is pushed to your project’s GHCR repository. -### How to use +This behavior directly impacts your **release and tagging strategy**; make sure to align it with your project’s lifecycle (branching model, tags, environments). -#### Calling this workflow +--- -To define a job that calls a reusable workflow, just read the [the corresponding documentation](https://docs.github.com/en/actions/sharing-automations/reusing-workflows#calling-a-reusable-workflow). +### Helm Charts workflow -#### Inputs definition +#### Description -This workflow's inputs are as follows : -- `chart-dir`: - - description: "Directory holding your Chart" - - required: true - - type: string - - default: "chart" -- `chart-values`: - - description: "Chart values file that will be used for the testing and scanning steps" - - required: false - - type: string - - default: "chart/values.yaml" -- `kubernetes-version`: - - description: "Version of the target Kubernetes cluster the Chart will run on" - - required: true - - type: string - - default: "1.24.2" +Provides **security and quality checks for Helm charts**, and automates **publishing** to a Helm repository hosted via your project’s GitHub Pages (`gh-pages` branch). -#### Releases management +Typical use cases: -This workflow uses [the Helm Cr Action](https://github.com/marketplace/actions/helm-chart-releaser) to release charts. +- Helm charts for applications like **HELIX**, **REDCap**, etc., +- any Kubernetes deployment managed via Helm within AP‑HP projects. -**This behavior could impact your lifecycle management practices, so be sure to read the lines below!** +#### Tools -The releases will be handled as follow : +The Helm charts workflow is organized by job: -- `dev` branch : - - Update the Chart version Chart.yaml with the `-dev` suffix (`helm-chart-releaser::Add release suffix - DEV` step) - - Create the tag with the Chart version (`helm-chart-releaser::Run chart-releaser - DEV` step) - - Create the Release with the dev Chart archive as package (`helm-chart-releaser::Run chart-releaser - DEV` step) - - Update the `index.yaml` file in the `gh-page` branch of your repo to include the reference to the new dev Chart (`helm-chart-releaser::Run chart-releaser - DEV` step) -- `main` branch : - - Create the tag with the Chart version (`helm-chart-releaser::Run chart-releaser - MAIN` step) - - Create the Release with the Chart archive as package, and mark this release as `latest` (`helm-chart-releaser::Run chart-releaser - MAIN` step) - - Update the `index.yaml` file in the `gh-page` branch of your repo to include the reference to the new Chart (`helm-chart-releaser::Run chart-releaser - MAIN` step) \ No newline at end of file +- **Linting (`lint-test` job)** + - `ct lint` (Helm chart-testing), + - `kubeconform` (Kubernetes manifest validation against schemas). + +- **Security (`lint-test` job)** + - **Polaris** (configuration and security best practices), + - **Trivy** (vulnerability scanning on rendered manifests). + +- **Documentation (`generate-doc` job)** + - **helm-docs** (README / values documentation from chart), + - **Values schema JSON** generation (for validation and tooling). + +- **Publishing (`release` job)** + - **helm/chart-releaser** to package charts and update the Helm index. + +#### Reports + +Some tools (currently **Trivy**) generate **SARIF reports**, uploaded to: + +- `Security` tab → `Code scanning`. + +This allows you to track vulnerabilities and security issues directly in GitHub. + +#### Prerequisites + +To be able to **publish charts** and use the repository as a **Helm repository**, ensure: + +1. A branch named `gh-pages` exists in your repository. +2. In your repository **Settings**: + - `Code and automation / Pages` → `Branch`: select `gh-pages`. +3. In **Settings → Actions → General**: + - `Actions permissions`: set to **Allow all actions and reusable workflows**. + - `Workflow permissions`: set to **Read and write permissions**. +4. Your chart lives under a `charts` directory at the repository root: + + ```text + charts/ + mychart/ + Chart.yaml + values.yaml + templates/... + ``` + +#### Inputs + +Inputs currently supported by the Helm charts workflow: + +| Input | Type | Required | Default | Description | +|--------------------:|:------:|:--------:|:---------------------:|-------------| +| `chart-dir` | string | **Yes** | `chart` | Directory containing your Helm chart (must contain a `Chart.yaml`). | +| `chart-values` | string | No | `chart/values.yaml` | Values file used for `kubeconform`, Polaris, Trivy and `ct install` tests. | +| `kubernetes-version`| string| No | `1.24.2` | Target Kubernetes version used by kubeconform and Trivy for validations and scans. | + +Again, always check the workflow file in `.github/workflows/` for the authoritative list of inputs. + +#### Release management + +Chart releases are handled via the **Helm CR action**, with behavior depending on the branch: + +- On **`feature` branch**: + - Chart version in `Chart.yaml` is suffixed with `-snapshot`, + - A Git tag is created with this snapshot version, + - A **Release** is created containing the snapshot chart archive, + - `index.yaml` in the `gh-pages` branch is updated to reference the new **snapshot** chart. + +- On **`dev` branch**: + - Chart version in `Chart.yaml` is suffixed with `-dev`, + - A Git tag is created with this dev version, + - A **Release** is created containing the dev chart archive, + - `index.yaml` in the `gh-pages` branch is updated to reference the new **dev** chart. + +- On **`main` branch**: + - A Git tag is created with the chart version from `Chart.yaml`, + - A **Release** is created with the chart archive, marked as **latest**, + - `index.yaml` in `gh-pages` is updated to reference the new **stable** chart. + +This gives you a standard separation between **snapshot**,**dev** and **stable** releases for Helm charts. + +--- + +## How to call a reusable workflow + +To use one of these workflows from another repository: + +1. Create a workflow file in your project, e.g.: + + ```text + .github/workflows/container-ci.yml + ``` + +2. In that file, define a job that **uses** one of the workflows from this repo: + + ```yaml + name: Container CI + + on: + push: + branches: [ main, dev ] + pull_request: + + permissions: + contents: write + security-events: write + + jobs: + container-ci: + uses: aphp/ci-workflows/.github/workflows/.yml@main + with: + image-name: ghcr.io/aphp/my-service + dockerfile-path: Dockerfile + image-custom-tag: x86_64-ubuntu-24.04 + ``` + +3. For Helm charts, a similar pattern applies: + + ```yaml + name: Helm Chart CI + + on: + push: + branches: [ main, dev ] + pull_request: + + permissions: + contents: write + security-events: write + + jobs: + helm-ci: + uses: aphp/ci-workflows/.github/workflows/.yml@main + with: + chart-dir: charts/mychart + chart-values: charts/mychart/values.yaml + kubernetes-version: "1.24.2" + ``` + +> Replace `.yml` and `.yml` with the actual filenames from this repository’s `.github/workflows` directory. + +For a concrete example of usage, you can refer to a project CI configuration that calls these workflows (e.g. container image or Helm chart repositories within the AP‑HP GitHub organization). + +--- + +## Branching, versions and environments + +This repository is versioned like any other Git repository: + +- **Branches** such as `feature`, `dev` or `main` represent the maturity of workflows. +- In your consuming projects, you should: + - Prefer **tags** (once defined) for stable usage, e.g. `@v1`, + - Use the `dev` branch (`@dev`) when experimenting or adopting new features early. + +*note: `feature` name is the name of your branch created from Issue.* + +Examples: + +- Stable usage (recommended when available): + + ```yaml + uses: aphp/ci-workflows/.github/workflows/.yml@v1 + ``` + +- Development usage (bleeding edge): + + ```yaml + uses: aphp/ci-workflows/.github/workflows/.yml@dev + ``` + +Coordinate with the AP‑HP CI maintainers to know which refs are recommended for production usage. + +--- + +## Contributing + +Contributions, bug reports and improvement ideas are welcome. + +- See [`CONTRIBUTING.md`](CONTRIBUTING.md) for: + - coding standards, + - how to run tests/linters locally, + - the release workflow for this repository. +- Use **GitHub Issues** to: + - report problems with existing workflows, + - request new reusable workflows, + - ask for documentation improvements. + +Before opening a pull request: + +1. Check there is an existing issue (or open a new one) describing the change. +2. Update or add documentation for new inputs/behavior. +3. Run relevant tests or dry‑runs for the workflows you modify. + +--- + +## License + +This project is licensed under the **Apache License 2.0**. + +- See [`LICENSE`](LICENSE) for details. + +By contributing to this repository, you agree that your contributions will be licensed under the same terms.