diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f519b68..3903c81 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,53 +1,14 @@ -FROM mcr.microsoft.com/devcontainers/base:ubuntu - -# provide DOCKER_GID via build args if you need to force group id to match host -ARG DOCKER_GID +ARG IMAGE_NAME=node_24_python_3_14 +ARG IMAGE_VERSION=latest +FROM ghcr.io/nhsdigital/eps-devcontainers/${IMAGE_NAME}:${IMAGE_VERSION} +USER root # specify DOCKER_GID to force container docker group id to match host RUN if [ -n "${DOCKER_GID}" ]; then \ - if ! getent group docker; then \ - groupadd -g ${DOCKER_GID} docker; \ - else \ - groupmod -g ${DOCKER_GID} docker; \ - fi && \ - usermod -aG docker vscode; \ + if ! getent group docker; then \ + groupadd -g ${DOCKER_GID} docker; \ + else \ + groupmod -g ${DOCKER_GID} docker; \ + fi && \ + usermod -aG docker vscode; \ fi - -# Anticipate and resolve potential permission issues with apt -RUN mkdir -p /tmp && chmod 1777 /tmp - -RUN apt-get update \ - && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y dist-upgrade \ - && apt-get -y install --no-install-recommends htop vim curl git build-essential \ - libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev libbz2-dev \ - zlib1g-dev unixodbc unixodbc-dev libsecret-1-0 libsecret-1-dev libsqlite3-dev \ - jq apt-transport-https ca-certificates gnupg-agent \ - software-properties-common bash-completion python3-pip make libbz2-dev \ - libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \ - xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev - -USER vscode - -# Install ASDF -RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3 && \ - echo '. $HOME/.asdf/asdf.sh' >> ~/.bashrc && \ - echo '. $HOME/.asdf/completions/asdf.bash' >> ~/.bashrc - -ENV PATH="$PATH:/home/vscode/.asdf/bin/:/workspaces/eps-prescription-tracker-ui/node_modules/.bin:/workspaces/eps-common-workflows/.venv/bin" - -# Install ASDF plugins# -RUN asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git && \ - asdf plugin add actionlint && \ - asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git && \ - asdf plugin add poetry https://github.com/asdf-community/asdf-poetry.git && \ - asdf plugin add python - -WORKDIR /workspaces/eps-common-workflows - -ADD .tool-versions /workspaces/eps-common-workflows/.tool-versions -ADD .tool-versions /home/vscode/.tool-versions - -RUN asdf install python && \ - asdf install && \ - asdf reshim nodejs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9510367..29bb180 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,15 +1,18 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu { - "name": "Ubuntu", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "name": "eps-common-workflows", "build": { "dockerfile": "Dockerfile", "context": "..", "args": { - "DOCKER_GID": "${env:DOCKER_GID:}" - } + "DOCKER_GID": "${env:DOCKER_GID:}", + "IMAGE_NAME": "node_24_python_3_14", + "IMAGE_VERSION": "v1.0.6", + "USER_UID": "${localEnv:USER_ID:}", + "USER_GID": "${localEnv:GROUP_ID:}" + }, + "updateRemoteUserUID": false }, + "postAttachCommand": "git-secrets --register-aws; git-secrets --add-provider -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt", "mounts": [ "source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind", "source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind", @@ -20,15 +23,7 @@ "remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }, - "postAttachCommand": "make install && docker build -f /workspaces/eps-common-workflows/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . && pre-commit install --install-hooks -f", - "features": { - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { - "version": "latest", - "moby": "true", - "installDockerBuildx": "true" - } - }, + "features": {}, "customizations": { "vscode": { "extensions": [ diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f606e1f..cdb4ec7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,36 +16,45 @@ jobs: AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} pr_title_format_check: uses: ./.github/workflows/pr_title_check.yml - get_asdf_version: + get_config_values: runs-on: ubuntu-22.04 outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} + devcontainer_version: ${{ steps.load-config.outputs.DEVCONTAINER_VERSION }} + devcontainer_image: ${{ steps.load-config.outputs.DEVCONTAINER_IMAGE }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - name: Load config value id: load-config run: | TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" + DEVCONTAINER_IMAGE=$(jq -r '.build.args.IMAGE_NAME' .devcontainer/devcontainer.json) + DEVCONTAINER_VERSION=$(jq -r '.build.args.IMAGE_VERSION' .devcontainer/devcontainer.json) + { + echo "TAG_FORMAT=$TAG_FORMAT" + echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE" + echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION" + } >> "$GITHUB_OUTPUT" quality_checks: - uses: ./.github/workflows/quality-checks.yml - needs: [get_asdf_version] + uses: ./.github/workflows/quality-checks-devcontainer.yml + needs: [get_config_values] with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} tag_release: - needs: [quality_checks, get_asdf_version] - uses: ./.github/workflows/tag-release.yml + needs: [quality_checks, get_config_values] + uses: ./.github/workflows/tag-release-devcontainer.yml + permissions: + contents: read + packages: read + attestations: read with: dry_run: true - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: ${{ github.event.pull_request.head.ref }} - tag_format: ${{ needs.get_asdf_version.outputs.tag_format }} + tag_format: ${{ needs.get_config_values.outputs.tag_format }} + verify_published_from_main_image: false secrets: inherit diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml new file mode 100644 index 0000000..6f8afad --- /dev/null +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -0,0 +1,448 @@ +name: Quality Checks + +on: + workflow_call: + secrets: + SONAR_TOKEN: + required: false + inputs: + run_sonar: + type: boolean + description: Toggle to run sonar code analyis on this repository. + default: true + required: false + run_docker_scan: + type: boolean + description: Toggle to run docker vulnerability scan on this repository. + default: false + required: false + docker_images: + type: string + description: comma separated list of docker image references to scan when docker scanning is enabled. + default: "" + required: false + runtime_docker_image: + type: string + required: true + +jobs: + verify_attestation: + uses: ./.github/workflows/verify-attestation.yml + with: + runtime_docker_image: "${{ inputs.runtime_docker_image }}" + verify_published_from_main_image: false + quality_checks: + runs-on: ubuntu-22.04 + needs: verify_attestation + container: + image: ${{ needs.verify_attestation.outputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + steps: + - &init_tool_versions + name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + + - &checkout + name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ env.BRANCH_NAME }} + fetch-depth: 0 + + - &setup_npmrc + name: Setting up .npmrc + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc + echo "@nhsdigital:registry=https://npm.pkg.github.com" >> ~/.npmrc + + - &cache_npm + name: Cache npm dependencies + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb + with: + path: ./node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + + - name: make install + run: | + make install + - name: Run secrets scan + run: | + make secret-scan + - name: Run actionlint + run: | + make actionlint + + - name: Check language tools used and setup trivy config + id: check_languages + run: | + if [ -f "pyproject.toml" ] && grep -q '\[tool.poetry\]' "pyproject.toml"; then + echo "****************" + echo "Detected a poetry project" + echo "****************" + echo "uses_poetry=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use poetry" + echo "****************" + echo "uses_poetry=false" >> "$GITHUB_OUTPUT" + fi + if [ -f pom.xml ]; then + echo "****************" + echo "Detected a Java project" + echo "****************" + echo "uses_java=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use Java" + echo "****************" + echo "uses_java=false" >> "$GITHUB_OUTPUT" + fi + if [ -f package-lock.json ]; then + echo "****************" + echo "Detected a Node.js project" + echo "****************" + echo "uses_node=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use Node.js" + echo "****************" + echo "uses_node=false" >> "$GITHUB_OUTPUT" + fi + if [ -f src/go.sum ]; then + echo "****************" + echo "Detected a Go project" + echo "****************" + echo "uses_go=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use Go" + echo "****************" + echo "uses_go=false" >> "$GITHUB_OUTPUT" + fi + - name: Check licenses + run: | + make trivy-license-check + + - name: Show license scan output + if: always() + run: | + if [ -f license_scan.txt ]; then + cat .trivy_out/license_scan.txt + fi + - name: Run code lint + run: | + make lint + + - name: Run ShellCheck + run: | + make shellcheck + + - name: Run unit tests + run: | + make test + - name: make generate sbom + run: | + make trivy-generate-sbom + - name: Upload sbom + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: sbom.cdx.json + path: .trivy_out/sbom.cdx.json + + - name: Check python vulnerabilities + if: ${{ steps.check_languages.outputs.uses_poetry == 'true' }} + run: | + make trivy-scan-python + + - name: Check node vulnerabilities + if: ${{ steps.check_languages.outputs.uses_node == 'true' }} + run: | + make trivy-scan-node + - name: Check go vulnerabilities + if: ${{ steps.check_languages.outputs.uses_go == 'true' }} + run: | + make trivy-scan-go + - name: Check java vulnerabilities + if: ${{ steps.check_languages.outputs.uses_java == 'true' }} + run: | + make trivy-scan-java + - name: Show vulnerability output + if: always() + run: | + if [ -f .trivy_out/dependency_results_python.txt ]; then + cat .trivy_out/dependency_results_python.txt + fi + if [ -f .trivy_out/dependency_results_node.txt ]; then + cat .trivy_out/dependency_results_node.txt + fi + if [ -f .trivy_out/dependency_results_java.txt ]; then + cat .trivy_out/dependency_results_java.txt + fi + if [ -f .trivy_out/dependency_results_go.txt ]; then + cat .trivy_out/dependency_results_go.txt + fi + - name: "check is SONAR_TOKEN exists" + env: + super_secret: ${{ secrets.SONAR_TOKEN }} + if: ${{ env.super_secret != '' && inputs.run_sonar == true }} + run: echo "SONAR_TOKEN_EXISTS=true" >> "$GITHUB_ENV" + + - name: Run SonarQube analysis + if: ${{ steps.check_languages.outputs.uses_java == 'true' && env.SONAR_TOKEN_EXISTS == 'true' }} + run: | + # issues with sonar scanner and sslcontext-kickstart 9.1.0, forcing re-download + rm -rf ~/.m2/repository/io/github/hakky54/sslcontext-kickstart/9.1.0 + mvn dependency:get -U -Dartifact=io.github.hakky54:sslcontext-kickstart:9.1.0 + # run sonar scan + mvn sonar:sonar -Dsonar.token="$SONAR_TOKEN" + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 + if: ${{ steps.check_languages.outputs.uses_java == 'false' && env.SONAR_TOKEN_EXISTS == 'true' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + get_docker_images_to_scan: + runs-on: ubuntu-22.04 + needs: verify_attestation + container: + image: ${{ needs.verify_attestation.outputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + outputs: + docker_images: ${{ steps.normalized_docker_images.outputs.images }} + steps: + - *init_tool_versions + - *checkout + - name: Determine docker images to scan + id: normalized_docker_images + env: + DOCKER_IMAGES: ${{ inputs.docker_images }} + run: | + if [ "${{ inputs.run_docker_scan }}" != "true" ]; then + echo "Docker scanning disabled; emitting empty image list." + echo 'images=[]' >> "$GITHUB_OUTPUT" + exit 0 + fi + + INPUT="${DOCKER_IMAGES}" + + if [ -z "$INPUT" ]; then + INPUT="[]" + fi + + normalize_to_json_array() { + local raw="$1" + + # If the input already looks like JSON, return as-is + if echo "$raw" | grep -q '^[[:space:]]*\['; then + echo "$raw" + return + fi + + local json="[" + local first=true + IFS=',' read -ra ITEMS <<< "$raw" + for item in "${ITEMS[@]}"; do + # Trim whitespace around each image reference + item=$(echo "$item" | xargs) + if [ -z "$item" ]; then + continue + fi + if [ "$first" = true ]; then + first=false + else + json+=", " + fi + json+="\"$item\"" + done + json+="]" + echo "$json" + } + + NORMALIZED=$(normalize_to_json_array "$INPUT") + + if [ "$NORMALIZED" = "[]" ]; then + echo "No docker images provided" + exit 1 + fi + + echo "Using provided docker images: $NORMALIZED" + echo "images=$NORMALIZED" >> "$GITHUB_OUTPUT" + + docker_vulnerability_scan: + runs-on: ubuntu-22.04 + needs: [get_docker_images_to_scan, verify_attestation] + container: + image: ${{ needs.verify_attestation.outputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + if: ${{ inputs.run_docker_scan == true }} + strategy: + matrix: + docker_image: ${{ fromJson(needs.get_docker_images_to_scan.outputs.docker_images) }} + steps: + - *init_tool_versions + - *checkout + - *setup_npmrc + - *cache_npm + + - name: make install + run: | + make install + + - name: Build docker images + run: | + make docker-build + env: + DOCKER_IMAGE: ${{ matrix.docker_image }} + + - name: Check docker vulnerabilities + run: | + make trivy-scan-docker + env: + DOCKER_IMAGE: ${{ matrix.docker_image }} + + - name: Show docker vulnerability output + if: always() + run: | + echo "Scan output for ${{ matrix.docker_image }}" + if [ -f .trivy_out/dependency_results_docker.txt ]; then + cat .trivy_out/dependency_results_docker.txt + fi + + IaC-validation: + runs-on: ubuntu-22.04 + needs: verify_attestation + container: + image: ${{ needs.verify_attestation.outputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + steps: + - *init_tool_versions + - *checkout + - *setup_npmrc + - *cache_npm + + - name: Check for SAM templates + id: check_sam_templates + run: | + if [ -d "SAMtemplates" ]; then + echo "****************" + echo "Project has SAM templates" + echo "****************" + echo "sam_exists=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not have SAM templates" + echo "****************" + echo "sam_exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Check for cloudformation templates + id: check_cf_templates + run: | + if [ -d "cloudformation" ]; then + echo "****************" + echo "Project has cloudformation templates" + echo "****************" + echo "cf_exists=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not have cloudformation templates" + echo "****************" + echo "cf_exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Check for cdk + id: check_cdk + run: | + if [ -d "packages/cdk" ]; then + echo "****************" + echo "Project has cdk" + echo "****************" + echo "cdk_exists=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not have cdk" + echo "****************" + echo "cdk_exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Run cfn-lint + if: steps.check_sam_templates.outputs.sam_exists == 'true' || steps.check_cf_templates.outputs.cf_exists == 'true' + run: | + make cfn-lint + + - name: make install NodeJS + if: steps.check_cdk.outputs.cdk_exists == 'true' + run: | + make install-node compile + + - name: Run cdk-synth + if: steps.check_cdk.outputs.cdk_exists == 'true' + run: | + make cdk-synth + + - name: Run cfn-guard script for sam templates + run: | + make cfn-guard-sam-templates + + - name: Run cfn-guard script for cloudformation templates + if: steps.check_cf_templates.outputs.cf_exists == 'true' + run: | + make cfn-guard-cloudformation + - name: Run cfn-guard script for cdk templates + if: steps.check_cdk.outputs.cdk_exists == 'true' + run: | + make cfn-guard-cdk + + - name: Download terraform plans + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + pattern: "*_terraform_plan" + path: terraform_plans/ + merge-multiple: true + + - name: Check terraform plans exist + id: check_terraform_plans + run: | + if [ ! -d terraform_plans ]; then + echo "Terraform plans not present." + echo "terraform_plans_exist=false" >> "$GITHUB_OUTPUT" + else + echo "Terraform plans present:" + ls -l terraform_plans/ + echo "terraform_plans_exist=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run cfn-guard script for terraform plans + if: steps.check_terraform_plans.outputs.terraform_plans_exist == 'true' + run: | + make cfn-guard-terraform + + - name: Show cfn-guard output + if: failure() + run: find .cfn_guard_out -type f -print0 | xargs -0 cat + + - name: Upload cfn_guard_output + if: failure() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: cfn_guard_output + path: .cfn_guard_out diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4cc00a..5aeaab9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,36 +8,41 @@ env: BRANCH_NAME: ${{ github.event.ref.BRANCH_NAME }} jobs: - get_asdf_version: + get_config_values: runs-on: ubuntu-22.04 outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} + devcontainer_version: ${{ steps.load-config.outputs.DEVCONTAINER_VERSION }} + devcontainer_image: ${{ steps.load-config.outputs.DEVCONTAINER_IMAGE }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - name: Load config value id: load-config run: | TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" + DEVCONTAINER_IMAGE=$(jq -r '.build.args.IMAGE_NAME' .devcontainer/devcontainer.json) + DEVCONTAINER_VERSION=$(jq -r '.build.args.IMAGE_VERSION' .devcontainer/devcontainer.json) + { + echo "TAG_FORMAT=$TAG_FORMAT" + echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE" + echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION" + } >> "$GITHUB_OUTPUT" quality_checks: - needs: [get_asdf_version] - uses: ./.github/workflows/quality-checks.yml + needs: [get_config_values] + uses: ./.github/workflows/quality-checks-devcontainer.yml with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} tag_release: - needs: [quality_checks, get_asdf_version] - uses: ./.github/workflows/tag-release.yml + needs: [quality_checks, get_config_values] + uses: ./.github/workflows/tag-release-devcontainer.yml with: dry_run: false - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: main - tag_format: ${{ needs.get_asdf_version.outputs.tag_format }} + tag_format: ${{ needs.get_config_values.outputs.tag_format }} + verify_published_from_main_image: true secrets: inherit diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml new file mode 100644 index 0000000..ba93091 --- /dev/null +++ b/.github/workflows/tag-release-devcontainer.yml @@ -0,0 +1,287 @@ +name: Tag Release + +on: + workflow_call: + inputs: + dry_run: + description: "Whether to run in dry-run mode (true) or create actual tags (false)" + required: true + type: boolean + branch_name: + description: "The branch name to base the release on" + required: true + type: string + runtime_docker_image: + type: string + required: true + publish_packages: + description: "comma separated list of package folders to publish to an npm registry" + required: false + type: string + default: "" + tag_format: + description: "The tag format to use for the release tags" + required: false + type: string + default: "v${version}" + main_branch: + description: "The main branch name for releases" + required: false + type: string + default: "main" + extra_artifact_name: + description: "An extra artifact to include in the release" + required: false + type: string + extra_artifact_id: + description: "An id for the extra artifact" + required: false + type: string + extra_artifact_run_id: + description: "An run id for the extra artifact" + required: false + type: string + extra_artifact_repository: + description: "An repository for the extra artifact" + required: false + type: string + verify_published_from_main_image: + required: true + type: boolean + outputs: + version_tag: + value: ${{ jobs.tag_release.outputs.version_tag }} + change_set_version: + description: "The change set version for deployments" + value: ${{ jobs.tag_release.outputs.change_set_version }} + next_version_tag: + description: "The next version tag that will be created" + value: ${{ jobs.tag_release.outputs.next_version_tag }} + secrets: + NPM_TOKEN: + required: false + description: "NPM token to publish packages" +jobs: + verify_attestation: + uses: ./.github/workflows/verify-attestation.yml + with: + runtime_docker_image: "${{ inputs.runtime_docker_image }}" + verify_published_from_main_image: ${{ inputs.verify_published_from_main_image }} + tag_release: + runs-on: ubuntu-22.04 + needs: verify_attestation + container: + image: ${{ needs.verify_attestation.outputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + outputs: + version_tag: ${{steps.output_version_tag.outputs.VERSION_TAG}} + change_set_version: ${{ steps.output_change_set_version.outputs.CHANGE_SET_VERSION }} + next_version_tag: ${{ steps.output_version_tag.outputs.NEXT_VERSION_TAG }} + steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + - name: Clone calling repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} + + - name: Checkout semantic-release workflow + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + repository: NHSDigital/eps-common-workflows + sparse-checkout-cone-mode: false + path: common_workflow_config + sparse-checkout: | + package.json + package-lock.json + release.config.cjs + releaseNotesTemplates/commit.hbs + - name: Install semantic release dependencies globally + run: | + cd common_workflow_config + dependencies="$(jq -r '.devDependencies | to_entries | map("\(.key)@\(.value)") | join(" ")' package.json)" + echo "Installing: $dependencies" + + # shellcheck disable=SC2086 + npm install --global $dependencies + echo "Copying semantic-release config and templates for use in the workflow" + cp release.config.cjs ../ + mkdir -p ../releaseNotesTemplates + cp releaseNotesTemplates/commit.hbs ../releaseNotesTemplates/ + echo "Current dir is ${PWD}" + - name: Setup Git branch for semantic-release + run: | + # When running from a PR, GitHub checks out a merge commit + # We need to ensure we're on the actual branch for semantic-release + git checkout -B "${BRANCH_NAME}" + git branch --show-current + env: + BRANCH_NAME: ${{ inputs.branch_name }} + + - name: Install Dependencies and Build Package + if: inputs.publish_packages != '' + run: | + make install + make build + + - name: Download extra artifact + if: ${{ inputs.extra_artifact_name != '' }} + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + artifact-ids: ${{ inputs.extra_artifact_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ inputs.extra_artifact_repository }} + run-id: ${{ inputs.extra_artifact_run_id }} + + - name: Set VERSION_TAG based on dry_run flag + id: output_version_tag + run: | + if [ "${{ inputs.dry_run }}" = "true" ]; then + # Determine semantic-release command based on branch + if [ "${BRANCH_NAME}" = "${MAIN_BRANCH}" ]; then + echo "on ${MAIN_BRANCH} branch" + npx semantic-release --dry-run --tag-format "${TAG_FORMAT}" > semantic-release-output.log + exit_code=1 + else + # For non-main branches, override the branches configuration + echo "overriding branches for semantic-release to ${BRANCH_NAME}" + + # we need to set GITHUB_REF and GITHUB_EVENT_NAME for semantic-release to work correctly + # but need to ensure that actionlint does not complain about unused variables + + # shellcheck disable=SC2034 + GITHUB_REF=refs/heads/${BRANCH_NAME} + # shellcheck disable=SC2034 + GITHUB_EVENT_NAME=push + echo "running semantic-release" + npx semantic-release --dry-run --branches "${BRANCH_NAME}" --branch "${BRANCH_NAME}" --tag-format "${TAG_FORMAT}" > semantic-release-output.log + echo "finish semantic-release with exit code $?" + exit_code=0 + fi + # Dry run mode: use short git SHA and get next version for summary + VERSION_TAG=$(git rev-parse --short HEAD) + echo "Getting next_version" + NEXT_VERSION=$(grep -i 'The next release version is' semantic-release-output.log | sed -E 's/.* ([[:digit:].]+)$/\1/' || true) + NEXT_VERSION=${NEXT_VERSION:-UNKNOWN} + echo "got next version" + # disabling shellcheck as replace does not work + # shellcheck disable=SC2001 + NEW_VERSION_TAG=$(echo "$TAG_FORMAT" | sed "s/\${version}/$NEXT_VERSION/") + echo "## VERSION TAG : ${VERSION_TAG}" >> "$GITHUB_STEP_SUMMARY" + echo "## NEXT TAG WILL BE : ${NEW_VERSION_TAG}" >> "$GITHUB_STEP_SUMMARY" + if [ "${NEXT_VERSION}" == "UNKNOWN" ] + then + echo "Could not get next tag. Here is the log from semantic-release" + cat semantic-release-output.log + exit ${exit_code} + fi + else + # Production mode: get next version and create actual tag + npx semantic-release --dry-run --tag-format "${TAG_FORMAT}" > semantic-release-output.log + NEXT_VERSION=$(grep -i 'The next release version is' semantic-release-output.log | sed -E 's/.* ([[:digit:].]+)$/\1/') + # disabling shellcheck as replace does not work + # shellcheck disable=SC2001 + VERSION_TAG=$(echo "$TAG_FORMAT" | sed "s/\${version}/$NEXT_VERSION/") + echo "## VERSION TAG : ${VERSION_TAG}" >> "$GITHUB_STEP_SUMMARY" + fi + echo "VERSION_TAG=${VERSION_TAG}" >> "$GITHUB_OUTPUT" + echo "VERSION_TAG=${VERSION_TAG}" >> "$GITHUB_ENV" + echo "NEXT_VERSION_TAG=${NEW_VERSION_TAG}" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ github.token }} + BRANCH_NAME: ${{ inputs.branch_name }} + PUBLISH_PACKAGES: ${{ inputs.publish_packages }} + TAG_FORMAT: ${{ inputs.tag_format }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + MAIN_BRANCH: ${{ inputs.main_branch }} + EXTRA_ASSET: ${{ inputs.extra_artifact_name }} + + - name: Create semantic release tag + if: ${{ !inputs.dry_run }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + PUBLISH_PACKAGES: ${{ inputs.publish_packages }} + TAG_FORMAT: ${{ inputs.tag_format }} + MAIN_BRANCH: ${{ inputs.main_branch }} + EXTRA_ASSET: ${{ inputs.extra_artifact_name }} + run: | + npx semantic-release --tag-format "${TAG_FORMAT}" + + - name: Get release for editing + if: ${{ !inputs.dry_run }} + id: get_release + # version 1.2.4 + uses: cardinalby/git-get-release-action@5172c3a026600b1d459b117738c605fabc9e4e44 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + tag: ${{ steps.output_version_tag.outputs.VERSION_TAG }} + + - name: Edit Release + if: ${{ !inputs.dry_run }} + # version 1.2.0 + uses: irongut/EditRelease@ccf529ad26dddf9996e7dd0f24ca5da4ea507cc2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: ${{ steps.get_release.outputs.id }} + body: | + ## Info + [Release workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - Workflow ID: ${{ github.run_id }} + + It was initialized by [${{ github.event.sender.login }}](${{ github.event.sender.html_url }}) + + - name: Checkout gh-pages branch + if: ${{ !inputs.dry_run }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + repository: ${{ github.repository }} + ref: gh-pages + path: gh-pages + + - name: Publish release notes to gh-pages + if: ${{ !inputs.dry_run }} + working-directory: gh-pages + env: + RELEASE_ID: ${{ steps.get_release.outputs.id }} + VERSION_TAG: ${{ steps.output_version_tag.outputs.VERSION_TAG }} + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + notes_dir="release_notes" + mkdir -p "$notes_dir" + note_file="$notes_dir/${VERSION_TAG}.md" + + gh api "/repos/${GH_REPO}/releases/${RELEASE_ID}" | jq -r '.body // ""' > "$note_file" + + if [ ! -s "$note_file" ]; then + echo "Release notes are empty; skipping gh-pages update." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add -f "$note_file" + if git diff --cached --quiet; then + echo "No changes detected in release notes; skipping commit." + exit 0 + fi + + git commit -m "docs: add release notes for ${VERSION_TAG}" + parallel --retries 10 --delay 3 ::: "git pull --rebase && git push" + + - name: Output Change Set Version + id: output_change_set_version + shell: bash + run: | + TIMESTAMP=$(date +%s) + VERSION=$(echo ${{ steps.output_version_tag.outputs.VERSION_TAG }} | tr . -) + echo CHANGE_SET_VERSION="$VERSION-$TIMESTAMP" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/verify-attestation.yml b/.github/workflows/verify-attestation.yml new file mode 100644 index 0000000..a996e34 --- /dev/null +++ b/.github/workflows/verify-attestation.yml @@ -0,0 +1,111 @@ +name: Verify image digest and attestation +"on": + workflow_call: + inputs: + runtime_docker_image: + required: true + type: string + description: Image reference as name:tag (for example node_24_python_3_12:v1.2.3) or fully qualified image ref + registry: + required: false + type: string + default: ghcr.io + namespace: + required: false + type: string + default: nhsdigital/eps-devcontainers + owner: + required: false + type: string + default: NHSDigital + verify_published_from_main_image: + required: false + type: boolean + default: true + predicate_type: + required: false + type: string + default: https://slsa.dev/provenance/v1 + outputs: + pinned_image: + description: Fully-qualified digest-pinned image reference + value: ${{ jobs.verify_attestation.outputs.pinned_image }} + resolved_digest: + description: Resolved digest for the supplied image reference + value: ${{ jobs.verify_attestation.outputs.resolved_digest }} + +jobs: + verify_attestation: + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: read + attestations: read + outputs: + pinned_image: ${{ steps.resolve.outputs.pinned_image }} + resolved_digest: ${{ steps.resolve.outputs.resolved_digest }} + steps: + - name: Login to github container registry + if: startsWith(inputs.registry, 'ghcr.io') + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve digest + id: resolve + shell: bash + env: + RUNTIME_DOCKER_IMAGE: ${{ inputs.runtime_docker_image }} + REGISTRY: ${{ inputs.registry }} + NAMESPACE: ${{ inputs.namespace }} + run: | + set -euo pipefail + + if [[ "$RUNTIME_DOCKER_IMAGE" == *"/"* ]]; then + IMAGE_REF="$RUNTIME_DOCKER_IMAGE" + else + IMAGE_REF="${REGISTRY}/${NAMESPACE}/${RUNTIME_DOCKER_IMAGE}" + fi + + if [[ "$IMAGE_REF" == *@sha256:* ]]; then + IMAGE_BASE="${IMAGE_REF%@*}" + RESOLVED_DIGEST="${IMAGE_REF#*@}" + else + RESOLVED_DIGEST="$(docker buildx imagetools inspect "$IMAGE_REF" | awk '/^Digest:/ {print $2; exit}')" + IMAGE_BASE="${IMAGE_REF%:*}" + fi + + if [[ -z "$RESOLVED_DIGEST" ]]; then + echo "Could not resolve digest for image: $IMAGE_REF" >&2 + exit 1 + fi + + PINNED_IMAGE="${IMAGE_BASE}@${RESOLVED_DIGEST}" + echo "resolved_digest=${RESOLVED_DIGEST}" >> "$GITHUB_OUTPUT" + echo "pinned_image=${PINNED_IMAGE}" >> "$GITHUB_OUTPUT" + echo "Resolved image reference: ${IMAGE_REF}" + echo "Resolved digest: ${RESOLVED_DIGEST}" + echo "Resolved image reference: ${PINNED_IMAGE}" + + - name: Verify attestation + shell: bash + env: + GH_TOKEN: ${{ github.token }} + OWNER: ${{ inputs.owner }} + VERIFY_PUBLISHED_FROM_MAIN_IMAGE: ${{ inputs.verify_published_from_main_image }} + PREDICATE_TYPE: ${{ inputs.predicate_type }} + PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }} + run: | + set -euo pipefail + + args=("oci://${PINNED_IMAGE}" "--owner" "$OWNER" "--predicate-type" "$PREDICATE_TYPE") + + if [[ "$VERIFY_PUBLISHED_FROM_MAIN_IMAGE" == "true" ]]; then + args+=("--source-ref" "refs/heads/main") + fi + + + GH_FORCE_TTY=120 gh attestation verify "${args[@]}" 2>&1 + echo "Verified attestation for ${PINNED_IMAGE}" diff --git a/.gitignore b/.gitignore index 5642dae..0ed3cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ .DS_Store release_notes .venv -.asdf \ No newline at end of file +.asdf +.trivy_out diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index db94a2e..f319077 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: entry: bash args: - -c - - 'docker run -v "$LOCAL_WORKSPACE_FOLDER:/src" git-secrets --pre_commit_hook' + - "git-secrets --pre_commit_hook" language: system - id: lint-githubactions name: Lint github actions diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 8605e6e..0000000 --- a/.tool-versions +++ /dev/null @@ -1,5 +0,0 @@ -nodejs 24.12.0 -actionlint 1.7.10 -shellcheck 0.11.0 -python 3.14.2 -poetry 2.2.1 diff --git a/.tool-versions.asdf b/.tool-versions.asdf deleted file mode 100644 index aa85979..0000000 --- a/.tool-versions.asdf +++ /dev/null @@ -1,2 +0,0 @@ -# define the .asdf-version to use here -0.18.0 \ No newline at end of file diff --git a/Makefile b/Makefile index aae27f8..e466290 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,8 @@ install-hooks: install-python deep-clean: find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + -check-licenses: check-licenses-python - -check-licenses-python: - scripts/check_python_licenses.sh - lint: lint-githubactions lint-githubaction-scripts + echo "Linting complete" lint-githubactions: actionlint @@ -32,3 +28,6 @@ test: build: echo "Not implemented" + +%: + @$(MAKE) -f /usr/local/share/eps/Mk/common.mk $@ diff --git a/README.md b/README.md index 590766a..3413b65 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,25 @@ A collection of common workflows used by other EPS repositories The workflows that are available to use are -## Adding exclusions to trivy scanning -The quality checks job uses trivy to scan for vulnerabilities. +## Workflow Index + +- [Combine Dependabot PRs](#combine-dependabot-prs) +- [Dependabot Auto Approve and Merge](#dependabot-auto-approve-and-merge) +- [PR Title Check](#pr-title-check) +- [Quality Checks](#quality-checks) +- [Quality Checks - Dev Container Version](#quality-checks---dev-container-version) +- [Verify Image Digest and Attestation](#verify-image-digest-and-attestation) +- [Tag Release](#tag-release) +- [Tag Release - Devcontainer Version](#tag-release---devcontainer-version) + +## Other Docs + +- [Adding Exclusions to Trivy Scanning](#adding-exclusions-to-trivy-scanning) +- [Secret Scanning Docker](#secret-scanning-docker) +- [Run All Releases](#run-all-releases) + +## Adding Exclusions to Trivy Scanning +The quality checks job uses Trivy to scan for vulnerabilities. There may be times you want to add an exclusion for a known vulnerability that we are happy to accept To do this, in the calling repo, add trivy.yaml with this content ``` @@ -22,7 +39,7 @@ vulnerabilities: ``` See https://trivy.dev/docs/latest/configuration/filtering/#trivyignoreyaml for more details -## combine dependabot prs +## Combine Dependabot PRs This workflow can be called to combine multiple open Dependabot PRs into a single PR. @@ -68,7 +85,7 @@ jobs: ignoreLabel: ${{ github.event.inputs.ignoreLabel }} ``` -## dependabot auto approve and merge +## Dependabot Auto Approve and Merge This workflow can be called to automatically approve and merge Dependabot PRs as part of the pull request workflow. #### Requirements @@ -91,7 +108,7 @@ jobs: AUTOMERGE_APP_ID: ${{ secrets.AUTOMERGE_APP_ID }} AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} ``` -## pr title check +## PR Title Check This workflow checks that all pull requests have a title that matches the required format, and comments on the PR with a link to the relevant ticket if a ticket reference is found. #### Example @@ -110,7 +127,7 @@ jobs: uses: NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@f5c8313a10855d0cc911db6a9cd666494c00045a ``` -## quality checks +## Quality Checks This workflow runs common quality checks. To use this, you must have the following Makefile targets defined - install @@ -123,15 +140,15 @@ To use this, you must have the following Makefile targets defined #### Inputs -- `install_java`: Whether to install java or not -- `run_sonar`: Whether to run sonar checks or not. +- `install_java`: Whether to install Java or not +- `run_sonar`: Whether to run Sonar checks or not. - `asdfVersion`: Override the version of asdf to install. -- `reinstall_poetry`: If you are using this from a primarily python based project, you should set this to true to force a poetry reinstallation after python is installed -- `run_docker_scan`: whether to run a scan of docker images -- `docker_images`: csv list of docker images to scan. These must match images produced by make docker-build +- `reinstall_poetry`: If you are using this from a primarily Python based project, you should set this to true to force a poetry reinstallation after Python is installed +- `run_docker_scan`: whether to run a scan of Docker images +- `docker_images`: csv list of Docker images to scan. These must match images produced by make docker-build #### Secret Inputs -- `SONAR_TOKEN`: Token used to authenticate to sonar +- `SONAR_TOKEN`: Token used to authenticate to Sonar #### Outputs @@ -157,10 +174,86 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ``` +## Quality Checks - Dev Container Version +This workflow runs common quality checks using a prebuilt devcontainer (https://github.com/NHSDigital/eps-devcontainers). +To use this, you must have overridden any common makefile targets described in https://github.com/NHSDigital/eps-devcontainers?tab=readme-ov-file#common-makefile-targets +#### Inputs + +- `run_sonar`: Whether to run Sonar checks or not. +- `run_docker_scan`: whether to run a scan of Docker images +- `docker_images`: csv list of Docker images to scan. These must match images produced by make docker-build +- `runtime_docker_image`: the Docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers +#### Secret Inputs +- `SONAR_TOKEN`: Token used to authenticate to Sonar + +#### Outputs + +None + +#### Example + +To use this workflow in your repository, call it from another workflow file: + +```yaml +name: Release + +on: + workflow_dispatch: +jobs: + quality_checks: + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@f5c8313a10855d0cc911db6a9cd666494c00045a + needs: [get_config_values] + with: + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" + run_docker_scan: true + docker_images: fhir-facade,validator + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +``` -## tag release -This workflow uses the semantic-release npm package to generate a new version tag, changelog, and github release for a repo. +## Verify Image Digest and Attestation +This workflow resolves an image reference to a pinned digest and verifies GitHub artifact attestation for that image. + +#### Inputs + +- `runtime_docker_image`: Image reference as `name:tag` (for example `node_24_python_3_12:v1.2.3`) or a fully qualified image reference. +- `registry`: Container registry host. Default: `ghcr.io` +- `namespace`: Image namespace/repository prefix. Default: `nhsdigital/eps-devcontainers` +- `owner`: GitHub owner used by `gh attestation verify --owner`. Default: `NHSDigital` +- `verify_published_from_main_image`: If true, verifies attestations published from `refs/heads/main`. Default: `true` +- `predicate_type`: Attestation predicate type. Default: `https://slsa.dev/provenance/v1` + +#### Outputs + +- `pinned_image`: Fully-qualified digest-pinned image reference. +- `resolved_digest`: Resolved digest for the supplied image reference. + +#### Example + +To use this workflow in your repository, call it from another workflow file: + +```yaml +name: Verify Devcontainer Image + +on: + workflow_dispatch: + +jobs: + verify_attestation: + uses: NHSDigital/eps-common-workflows/.github/workflows/verify-attestation.yml@f5c8313a10855d0cc911db6a9cd666494c00045a + with: + runtime_docker_image: node_24_python_3_12:githubactions-v1.2.3 + registry: ghcr.io + namespace: nhsdigital/eps-devcontainers + owner: NHSDigital + verify_published_from_main_image: true + predicate_type: https://slsa.dev/provenance/v1 +``` + + +## Tag Release +This workflow uses the semantic-release npm package to generate a new version tag, changelog, and GitHub release for a repo. #### Inputs @@ -174,7 +267,7 @@ This workflow uses the semantic-release npm package to generate a new version ta #### Outputs - `version_tag`: The version tag created by semantic-release. -- `change_set_version`: A timestamped string that con be used for creating changesets. +- `change_set_version`: A timestamped string that can be used for creating changesets. #### Example @@ -197,10 +290,54 @@ jobs: publish_package: false ``` +## Tag Release - Devcontainer Version +This workflow uses the semantic-release npm package to generate a new version tag, changelog, and GitHub release for a repo. +*The devcontainer MUST have Node installed* +#### Inputs + +- `dry_run`: Whether to run in dry_run mode (do not create tags) or not +- `branch_name`: The branch name to base the release on +- `runtime_docker_image`: the Docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers +- `publish_packages`: comma separated list of package folders to publish to an npm registry +- `tagFormat`: Default `v\\${version}`. A template for the version tag. +- `main_branch`: The branch to use for publishing. Defaults to main +- `extra_artifact_name`: optional param to include an extra artifact in the release +- `extra_artifact_id`: optional param of the extra artifact id to include in the release +- `extra_artifact_run_id`: optional param of the run id to download the extra artifact id to include in the release +- `extra_artifact_repository` optional param to indicate which repo the run to download the artifact was from +- `verify_published_from_main_image` indicates if we should verify the image was published from main branch in eps-devcontainers + +#### Outputs + +- `version_tag`: The version tag created by semantic-release. +- `change_set_version`: A timestamped string that can be used for creating changesets. + +#### Example + +To use this workflow in your repository, call it from another workflow file: + +```yaml +name: Release + +on: + workflow_dispatch: + +jobs: + tag_release: + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release-devcontainer.yml@f5c8313a10855d0cc911db6a9cd666494c00045a + needs: [get_config_values] + with: + tagFormat: "v\\${version}-beta" + dry_run: true + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" + branch_name: main + publish_package: false +``` + -## Secret scanning docker +## Secret Scanning Docker -The secret scanning also has a dockerfile, which can be run against a repo in order to scan it manually (or as part of pre-commit hooks). This can be done like so: +The secret scanning also has a Dockerfile, which can be run against a repo in order to scan it manually (or as part of pre-commit hooks). This can be done like so: ```bash docker build -f https://raw.githubusercontent.com/NHSDigital/eps-workflow-quality-checks/refs/tags/v3.0.0/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . docker run -v /path/to/repo:/src git-secrets --scan-history . @@ -222,7 +359,7 @@ In order to enable the pre-commit hook for secret scanning (to prevent developer } ``` -And the this pre-commit hook to the `.pre-commit-config.yaml` file: +And add this pre-commit hook to the `.pre-commit-config.yaml` file: ```yaml repos: - repo: local @@ -237,28 +374,28 @@ repos: language: system ``` -## Run all releases +## Run All Releases There are some scripts that can be used to trigger releases for all our repos. It is invoked by running `./scripts/run_all_release.sh`. -This first authenticates to github using github cli tools to get a valid github token. +This first authenticates to GitHub using GitHub CLI tools to get a valid GitHub token. It then has an array of repos which it loops through asking for confirmation if you want to run deployment for it. -For any that you have answered yes to, it then calls the python script `scripts/trigger_release.py`. +For any that you have answered yes to, it then calls the Python script `scripts/trigger_release.py`. -The python script will trigger the release.yml workflow for that repo and monitor the the run for it. +The Python script will trigger the release.yml workflow for that repo and monitor the run for it. When it reaches one of the steps release_qa, release_ref, release_int it will approve release to that environment. -Once the run reaches release_prod step, the python script will exit. -The python script will also exit if the github run fails, or is cancelled at any step, or there is an unexpected response from github (eg user does not have permission to approve a deployment). -When the python script finishes, it logs the run url, the tag and summary of what happened. +Once the run reaches release_prod step, the Python script will exit. +The Python script will also exit if the GitHub run fails, or is cancelled at any step, or there is an unexpected response from GitHub (eg user does not have permission to approve a deployment). +When the Python script finishes, it logs the run URL, the tag and summary of what happened. Python logs go to the console, and to a timestamped file in the logs folder. When all runs of the python script have finished then the shell script exits showing a summary of failed and successful runs. -If a run fails on a step BEFORE the tag_release step, and the failure is transient (eg quality checks fails installing dependencies due to npm being down) then the whole release workflow can be rerun - either via this script or using the github website. +If a run fails on a step BEFORE the tag_release step, and the failure is transient (eg quality checks fails installing dependencies due to npm being down) then the whole release workflow can be rerun - either via this script or using the GitHub website. -If a run fails on a step AFTER the tag_release step, and the failure is transient (eg regression tests failure) then that failing step can just be re-run manually via the github website. +If a run fails on a step AFTER the tag_release step, and the failure is transient (eg regression tests failure) then that failing step can just be re-run manually via the GitHub website. If a run fails due to a code or cloudformation/cdk issue, then a new pull request should be created to fix this, merged to main, and a new release triggered. diff --git a/package-lock.json b/package-lock.json index eacb7ed..e2f6b80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "@semantic-release/github": "^12.0.6", "@semantic-release/release-notes-generator": "^14.1.0", "conventional-changelog-eslint": "^6.0.0", - "license-checker": "^25.0.1", "semantic-release": "^25.0.3" } }, @@ -132,6 +131,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -634,13 +634,6 @@ "dev": true, "license": "MIT" }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "license": "ISC" - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -728,16 +721,6 @@ "dev": true, "license": "MIT" }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", @@ -745,20 +728,6 @@ "dev": true, "license": "MIT" }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/before-after-hook": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", @@ -773,17 +742,6 @@ "dev": true, "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1112,13 +1070,6 @@ "dot-prop": "^5.1.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -1326,17 +1277,6 @@ } } }, - "node_modules/debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -1347,17 +1287,6 @@ "node": ">=4.0.0" } }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1736,23 +1665,6 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/function-timeout": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", @@ -1817,28 +1729,6 @@ "traverse": "0.6.8" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1878,19 +1768,6 @@ "node": ">=4" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -1914,13 +1791,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -2034,18 +1904,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2084,22 +1942,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2257,48 +2099,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/license-checker": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", - "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "read-installed": "~4.0.3", - "semver": "^5.5.0", - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-satisfies": "^4.0.0", - "treeify": "^1.1.0" - }, - "bin": { - "license-checker": "bin/license-checker" - } - }, - "node_modules/license-checker/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/license-checker/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -2430,6 +2230,7 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -2535,19 +2336,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -2558,19 +2346,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2620,43 +2395,6 @@ "node": ">=18" } }, - "node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-url": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", @@ -2825,13 +2563,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true, - "license": "ISC" - }, "node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -4580,6 +4311,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4705,16 +4437,6 @@ "node": ">=0.10.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", @@ -4731,38 +4453,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -4971,16 +4661,6 @@ "node": ">=4" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4991,13 +4671,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5098,49 +4771,6 @@ "rc": "cli.js" } }, - "node_modules/read-installed": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", - "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/read-installed/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-package-json": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", - "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", - "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.1", - "json-parse-even-better-errors": "^2.3.0", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, "node_modules/read-package-up": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", @@ -5223,20 +4853,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, "node_modules/registry-auth-token": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", @@ -5260,27 +4876,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -5304,6 +4899,7 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -5631,16 +5227,6 @@ "node": ">=8" } }, - "node_modules/slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5658,18 +5244,6 @@ "dev": true, "license": "MIT" }, - "node_modules/spdx-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", - "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-find-index": "^1.0.2", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -5706,25 +5280,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/spdx-ranges": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", - "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", - "dev": true, - "license": "(MIT AND CC-BY-3.0)" - }, - "node_modules/spdx-satisfies": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz", - "integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-compare": "^1.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, "node_modules/split2": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", @@ -5898,19 +5453,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tagged-tag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", @@ -6070,6 +5612,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6103,16 +5646,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/treeify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", - "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -6233,13 +5766,6 @@ "dev": true, "license": "MIT" }, - "node_modules/util-extend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", - "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", - "dev": true, - "license": "MIT" - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -6335,13 +5861,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 601493b..1f6ac68 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "@semantic-release/github": "^12.0.6", "@semantic-release/release-notes-generator": "^14.1.0", "conventional-changelog-eslint": "^6.0.0", - "license-checker": "^25.0.1", "semantic-release": "^25.0.3" } -} \ No newline at end of file +} diff --git a/scripts/check_python_licenses.sh b/scripts/check_python_licenses.sh deleted file mode 100755 index ea3de41..0000000 --- a/scripts/check_python_licenses.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# known packages with dual licensing -IGNORE_PACKAGES=("PyGithub" "chardet" "text-unidecode") -LICENSES=$(poetry run pip-licenses --ignore-packages "${IGNORE_PACKAGES[@]}") -INCOMPATIBLE_LIBS=$(echo "$LICENSES" | grep 'GPL' || true) - -if [[ -z $INCOMPATIBLE_LIBS ]]; then - exit 0 -else - echo "The following libraries were found which are not compatible with this project's license:" - echo "$INCOMPATIBLE_LIBS" - exit 1 -fi