From d3dc244411e6c2eca73ee4e921b0b0f627c5613b Mon Sep 17 00:00:00 2001 From: Julien Carsique Date: Thu, 12 Mar 2026 14:04:23 +0100 Subject: [PATCH] BUILD-10586 Fix inconsistencies between actions inputs, outputs and behaviors - Add `deploy` input to build-npm, build-yarn, build-poetry (consistent with build-maven/build-gradle) - Enable deployment on long-lived feature branches (feature/long/*) for npm/yarn/poetry - Add `disable-caching` input to build-npm, build-yarn, config-npm; deprecate `cache-npm`/`cache-yarn` - Add `should_scan()` to build-gradle to filter SonarQube analysis by branch type - Add CONFIG_NPM_COMPLETED idempotency guard to config-npm (consistent with config-gradle/config-maven) - Condition Artifactory deploy token vault secret on deploy being enabled - Align shadow scan warning to GitHub Actions ::warning stderr format across all actions - Align run-shadow-scans description and sh header documentation across all build actions - Fix artifactory-deploy-repo description in build-npm (private-qa was incorrectly named public-qa) - Update README: deployment strategy table, input tables, remove resolved known bugs and TODOs - Code style: consolidate parameter validation, fix readonly declarations, align sh header labels Co-Authored-By: Claude Sonnet 4.6 --- README.md | 54 +++++----------- build-gradle/action.yml | 18 +++--- build-gradle/build.sh | 44 +++++++------ build-maven/action.yml | 18 +++--- build-maven/build.sh | 29 +++------ build-npm/action.yml | 38 ++++++++---- build-npm/build.sh | 48 ++++++++------- build-poetry/action.yml | 24 +++++--- build-poetry/build.sh | 51 +++++++++------- build-yarn/action.yml | 36 +++++++---- build-yarn/build.sh | 54 ++++++++-------- config-gradle/action.yml | 2 +- config-gradle/set_gradle_project_version.sh | 4 +- config-maven/action.yml | 6 +- config-maven/set_maven_project_version.sh | 4 +- config-npm/action.yml | 36 +++++++++-- config-npm/npm_set_project_version.sh | 3 +- config-pip/action.yml | 2 +- get-build-number/action.yml | 2 +- pr_cleanup/cleanup.sh | 7 +-- promote/promote.sh | 15 ++--- shared/common-functions.sh | 4 +- spec/build-gradle_spec.sh | 68 +++++++++++++++++++++ spec/build-npm_spec.sh | 20 +++++- spec/build-poetry_spec.sh | 15 ++++- spec/build-yarn_spec.sh | 18 +++++- 26 files changed, 378 insertions(+), 242 deletions(-) diff --git a/README.md b/README.md index f5964a88..3a52dc36 100644 --- a/README.md +++ b/README.md @@ -411,16 +411,6 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: SonarSource/ci-github-actions/build-poetry@v1 - with: - public: false # Defaults to `true` if the repository is public - artifactory-reader-role: private-reader # or public-reader if `public` is `true` - artifactory-deployer-role: qa-deployer # or public-deployer if `public` is `true` - deploy-pull-request: false # Deploy pull request artifacts - poetry-virtualenvs-path: .cache/pypoetry/virtualenvs # Poetry virtual environment path - poetry-cache-dir: .cache/pypoetry # Poetry cache directory - repox-url: https://repox.jfrog.io # Repox URL - sonar-platform: next # SonarQube platform (next, sqc-eu, or sqc-us) - run-shadow-scans: false # Run SonarQube scans on all 3 platforms (next, sqc-eu, sqc-us) ``` **Disable caching entirely:** @@ -439,7 +429,8 @@ jobs: | `artifactory-reader-role` | Suffix for the Artifactory reader role in Vault | `private-reader` for private repos, `public-reader` for public repos | | `artifactory-deployer-role` | Suffix for the Artifactory deployer role in Vault | `qa-deployer` for private repos, `public-deployer` for public repos | | `artifactory-deploy-repo` | Deployment repository | `sonarsource-pypi-private-qa` for private repositories, `sonarsource-pypi-public-qa` for public repos | -| `deploy-pull-request` | Whether to deploy pull request artifacts | `false` | +| `deploy` | Whether to deploy on master, maintenance, dogfood and long-lived branches | `true` | +| `deploy-pull-request` | Whether to also deploy pull request artifacts. If `deploy` is `false`, this has no effect | `false` | | `poetry-virtualenvs-path` | Path to the Poetry virtual environments, relative to GitHub workspace | `.cache/pypoetry/virtualenvs` | | `poetry-cache-dir` | Path to the Poetry cache directory, relative to GitHub workspace | `.cache/pypoetry` | | `repox-url` | URL for Repox | `https://repox.jfrog.io` | @@ -656,11 +647,6 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: SonarSource/ci-github-actions/build-gradle@v1 - with: - # Enable shadow scans for unified platform dogfooding (optional) - run-shadow-scans: 'true' - # Primary platform when shadow scans disabled (optional) - sonar-platform: 'next' ``` ### Input Environment Variables @@ -867,7 +853,8 @@ See also [`get-build-number`](#get-build-number) input environment variables. |---------------------------|-----------------------------------------------------------------------------|----------------------------------------------------------------------| | `working-directory` | Relative path under github.workspace to execute the build in | `.` | | `artifactory-reader-role` | Suffix for the Artifactory reader role in Vault | `private-reader` for private repos, `public-reader` for public repos | -| `cache-npm` | Whether to cache NPM dependencies | `true` | +| `disable-caching` | Whether to disable NPM caching entirely | `false` | +| `cache-npm` | Deprecated. Use `disable-caching: 'true'` instead | `true` | | `repox-url` | URL for Repox | `https://repox.jfrog.io` | | `repox-artifactory-url` | URL for Repox Artifactory API (overrides repox-url/artifactory if provided) | (optional) | @@ -939,11 +926,6 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: SonarSource/ci-github-actions/build-npm@v1 - with: - # Enable shadow scans for unified platform dogfooding (optional) - run-shadow-scans: 'true' - # Primary platform when shadow scans disabled (optional) - sonar-platform: 'next' ``` ### Input Environment Variables @@ -962,9 +944,11 @@ See also [`config-npm`](#config-npm) input environment variables. | `artifactory-reader-role` | Suffix for the Artifactory reader role in Vault | `private-reader` for private repos, `public-reader` for public repos | | `artifactory-deployer-role` | Suffix for the Artifactory deployer role in Vault | `qa-deployer` for private repos, `public-deployer` for public repos | | `artifactory-deploy-repo` | Deployment repository | `sonarsource-npm-private-qa` for private repos, `sonarsource-npm-public-qa` for public repos | -| `deploy-pull-request` | Whether to deploy pull request artifacts | `false` | +| `deploy` | Whether to deploy on master, maintenance, dogfood and long-lived branches | `true` | +| `deploy-pull-request` | Whether to also deploy pull request artifacts. If `deploy` is `false`, this has no effect | `false` | | `skip-tests` | Whether to skip running tests | `false` | -| `cache-npm` | Whether to cache NPM dependencies | `true` | +| `disable-caching` | Whether to disable NPM caching entirely | `false` | +| `cache-npm` | Deprecated. Use `disable-caching: 'true'` instead | `true` | | `repox-url` | URL for Repox | `https://repox.jfrog.io` | | `repox-artifactory-url` | URL for Repox Artifactory API (overrides repox-url/artifactory if provided) | (optional) | | `sonar-platform` | SonarQube primary platform - 'next', 'sqc-eu', or 'sqc-us' | `next` | @@ -1040,11 +1024,6 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: SonarSource/ci-github-actions/build-yarn@v1 - with: - # Enable shadow scans for unified platform dogfooding (optional) - run-shadow-scans: 'true' - # Primary platform when shadow scans disabled (optional) - sonar-platform: 'next' ``` ### Input Environment Variables @@ -1062,9 +1041,11 @@ jobs: | `artifactory-reader-role` | Suffix for the Artifactory reader role in Vault | `private-reader` for private repos, `public-reader` for public repos | | `artifactory-deployer-role` | Suffix for the Artifactory deployer role in Vault | `qa-deployer` for private repos, `public-deployer` for public repos | | `artifactory-deploy-repo` | Deployment repository | `sonarsource-private-qa` for private repositories, `sonarsource-public-qa` for public repos | -| `deploy-pull-request` | Whether to deploy pull request artifacts | `false` | +| `deploy` | Whether to deploy on master, maintenance, dogfood and long-lived branches | `true` | +| `deploy-pull-request` | Whether to also deploy pull request artifacts. If `deploy` is `false`, this has no effect | `false` | | `skip-tests` | Whether to skip running tests | `false` | -| `cache-yarn` | Whether to cache Yarn dependencies | `true` | +| `disable-caching` | Whether to disable Yarn caching entirely | `false` | +| `cache-yarn` | Deprecated. Use `disable-caching: 'true'` instead | `true` | | `repox-url` | URL for Repox | `https://repox.jfrog.io` | | `repox-artifactory-url` | URL for Repox Artifactory API (overrides repox-url/artifactory if provided) | (optional) | | `sonar-platform` | SonarQube primary platform - 'next', 'sqc-eu', 'sqc-us', or 'none'. Use 'none' to skip sonar scans | `next` | @@ -1372,16 +1353,13 @@ concrete deploy and scan behavior is implemented in each build script: | Maintenance (`branch-*`) | yes | yes | | Pull request | optional | yes | | Dogfood (`dogfood-on-*`) | yes | no | -| Long-lived feature (`feature/long/*`) | yes ¹ | yes | +| Long-lived feature (`feature/long/*`) | yes | yes | | Other branches | no | no | - Pull request deployment requires `deploy-pull-request: 'true'`. -- SonarQube analysis also requires `sonar-platform` to be set (not `none`). -- ¹ `build-maven` and `build-gradle` only; `build-npm`, `build-yarn`, and `build-poetry` do not deploy on long-lived feature branches. -- `build-maven` and `build-gradle` support a `deploy: 'false'` input to override deployment regardless of branch. `build-npm`, - `build-yarn`, and `build-poetry` do not have this input (TODO: add for consistency). -- **`build-gradle` known bug**: SonarQube analysis is not filtered by branch type. When `sonar-platform ≠ none`, analysis runs on all - branches, including dogfood and other branches (unlike all other build actions). +- SonarQube analysis also requires either `sonar-platform` to be set (not `none`) or `run-shadow-scans: 'true'` (in which case + `sonar-platform` is ignored and the deployment is disabled). +- All build actions support a `deploy: 'false'` input to override deployment regardless of branch. --- diff --git a/build-gradle/action.yml b/build-gradle/action.yml index 1f29e639..b1ea809a 100644 --- a/build-gradle/action.yml +++ b/build-gradle/action.yml @@ -16,7 +16,7 @@ inputs: description: Whether to deploy on master, maintenance, dogfood and long-lived branches. default: 'true' deploy-pull-request: - description: Whether to also deploy pull request artifacts. If deploy is 'false', this has no effect. + description: Whether to also deploy pull request artifacts. If deploy is `false`, this has no effect. default: 'false' skip-tests: description: Whether to skip running tests @@ -25,8 +25,8 @@ inputs: description: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. default: next run-shadow-scans: - description: If true, run SonarQube analysis on all three platforms (next, sqc-eu, sqc-us). - If false, run analysis on the platform specified with sonar-platform. + description: If `true`, run sonar scanner on all 3 platforms. If `false`, run on the platform provided by `sonar-platform`. + When enabled, the `sonar-platform` setting is ignored. default: 'false' provenance: description: Whether to generate provenance attestation for built artifacts @@ -126,12 +126,12 @@ runs: with: # yamllint disable rule:line-length secrets: | - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} username | ARTIFACTORY_DEPLOY_USERNAME;', env.ARTIFACTORY_DEPLOYER_ROLE) || '' }} ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;', env.ARTIFACTORY_DEPLOYER_ROLE) || '' }} development/kv/data/sign key | SIGN_KEY; diff --git a/build-gradle/build.sh b/build-gradle/build.sh index 7a4e17fc..2c94b984 100755 --- a/build-gradle/build.sh +++ b/build-gradle/build.sh @@ -12,6 +12,7 @@ # - SQC_EU_URL: URL of SonarQube server for sqc-eu platform # - SQC_EU_TOKEN: Access token to send analysis reports to SonarQube for sqc-eu platform # - RUN_SHADOW_SCANS: If true, run sonar scanner on all 3 platforms. If false, run on the platform provided by SONAR_PLATFORM. +# When enabled, SONAR_PLATFORM is ignored. # - CURRENT_VERSION: Current project version as in gradle.properties # - ARTIFACTORY_ACCESS_TOKEN: Access token to read Repox repositories # - ARTIFACTORY_DEPLOY_REPO: Name of deployment repository @@ -48,28 +49,21 @@ set -euo pipefail # shellcheck source=../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${ARTIFACTORY_ACCESS_TOKEN:?}" -: "${ARTIFACTORY_DEPLOY_REPO:?}" -: "${DEPLOY:=true}" +: "${ARTIFACTORY_ACCESS_TOKEN:?}" "${ARTIFACTORY_DEPLOY_REPO:?}" "${DEPLOY_PULL_REQUEST:=false}" "${RUN_SHADOW_SCANS:?}" : "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_RUN_ID:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" "${GITHUB_SHA:?}" -: "${GITHUB_OUTPUT:?}" -: "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" -: "${RUN_SHADOW_SCANS:?}" -if [[ "$DEPLOY" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then +: "${GITHUB_OUTPUT:?}" "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" "${CURRENT_VERSION:?}" +if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then : "${ARTIFACTORY_DEPLOY_USERNAME:?}" "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" fi -: "${CURRENT_VERSION:?}" -if [[ "${SONAR_PLATFORM:?}" != "none" ]]; then +if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" fi : "${ORG_GRADLE_PROJECT_signingKey:?}" "${ORG_GRADLE_PROJECT_signingPassword:?}" "${ORG_GRADLE_PROJECT_signingKeyId:?}" -: "${DEPLOY_PULL_REQUEST:=false}" -export DEPLOY_PULL_REQUEST -: "${SKIP_TESTS:=false}" -: "${GRADLE_ARGS:=}" +: "${SKIP_TESTS:=false}" "${GRADLE_ARGS:=}" +export DEPLOY DEPLOY_PULL_REQUEST SKIP_TESTS GRADLE_ARGS git_fetch_unshallow() { - if [ "$SONAR_PLATFORM" = "none" ]; then + if [[ "$SONAR_PLATFORM" = "none" && "$RUN_SHADOW_SCANS" != "true" ]]; then echo "Skipping git fetch (Sonar analysis disabled)" return 0 fi @@ -112,6 +106,14 @@ should_deploy() { fi } +should_scan() { + if [[ "$SONAR_PLATFORM" = "none" && "$RUN_SHADOW_SCANS" != "true" ]]; then + return 1 + fi + is_default_branch || is_maintenance_branch || is_pull_request || is_long_lived_feature_branch + return $? +} + build_gradle_args() { local args=() @@ -216,19 +218,15 @@ gradle_build() { echo "Sonar Platform: ${SONAR_PLATFORM}" echo "Run Shadow Scans: ${RUN_SHADOW_SCANS}" - if [[ "$SONAR_PLATFORM" == "none" ]]; then + if should_scan; then + # Build with sonar analysis via orchestrator + # shellcheck disable=SC2119 + orchestrate_sonar_platforms + else # Build without sonar - call gradle_build_and_analyze directly echo "::group::Gradle build" gradle_build_and_analyze echo "::endgroup::" - else - # Build with sonar analysis via orchestrator - # TODO BUILD-10586: sonar analysis is not filtered by branch type here — it runs on all branches - # (including dogfood and other branches) when sonar-platform != none. This differs from - # build-maven/build-npm/build-yarn/build-poetry which skip sonar on dogfood/other branches. - # Should add a should_scan() guard consistent with the other build scripts. - # shellcheck disable=SC2119 - orchestrate_sonar_platforms fi } diff --git a/build-maven/action.yml b/build-maven/action.yml index ad139116..c4a4d32c 100644 --- a/build-maven/action.yml +++ b/build-maven/action.yml @@ -14,7 +14,7 @@ inputs: description: Whether to deploy on master, maintenance, dogfood and long-lived branches. default: 'true' deploy-pull-request: - description: Whether to also deploy pull request artifacts. If deploy is 'false', this has no effect. + description: Whether to also deploy pull request artifacts. If deploy is `false`, this has no effect. default: 'false' maven-args: description: Additional Maven arguments to pass to the build script. @@ -26,8 +26,8 @@ inputs: description: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. default: next run-shadow-scans: - description: If true, run SonarQube analysis on all three platforms (next, sqc-eu, sqc-us). - If false, run analysis on the platform specified with sonar-platform. + description: If `true`, run sonar scanner on all 3 platforms. If `false`, run on the platform provided by `sonar-platform`. + When enabled, the `sonar-platform` setting is ignored. default: 'false' provenance: description: Whether to generate provenance attestation for built artifacts @@ -155,12 +155,12 @@ runs: with: # yamllint disable rule:line-length secrets: | - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && steps.params.outputs.ARTIFACTORY_DEPLOY_USERNAME_VAULT || '' }} ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && steps.params.outputs.ARTIFACTORY_DEPLOY_ACCESS_TOKEN_VAULT || '' }} ${{ inputs.deploy != 'false' && inputs.mixed-privacy == 'true' && steps.params.outputs.ARTIFACTORY_PRIVATE_DEPLOY_ACCESS_TOKEN_VAULT || '' }} diff --git a/build-maven/build.sh b/build-maven/build.sh index c02ce37c..614bb89b 100755 --- a/build-maven/build.sh +++ b/build-maven/build.sh @@ -14,7 +14,7 @@ # - RUN_SHADOW_SCANS: If true, run sonar scanner on all 3 platforms. If false, run on the platform provided by SONAR_PLATFORM. # - ARTIFACTORY_URL: Artifactory repository URL # - ARTIFACTORY_ACCESS_TOKEN: Access token to read Repox repositories -# - ARTIFACTORY_DEPLOY_REPO: Deployment repository name +# - ARTIFACTORY_DEPLOY_REPO: Deployment repository name. Required by maven-enforcer-plugin in SonarSource parent POM. # - ARTIFACTORY_DEPLOY_USERNAME: Username used by artifactory-maven-plugin # - ARTIFACTORY_DEPLOY_PASSWORD: Access token to deploy to the repository # - CURRENT_VERSION: Current project version as in pom.xml @@ -45,26 +45,17 @@ set -euo pipefail # shellcheck source=../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${ARTIFACTORY_URL:?}" -# Required by maven-enforcer-plugin in SonarSource parent POM -: "${ARTIFACTORY_DEPLOY_REPO:?}" -: "${DEPLOY:=true}" -: "${CURRENT_VERSION:?}" +: "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_DEPLOY_REPO:?}" "${DEPLOY_PULL_REQUEST:=false}" "${RUN_SHADOW_SCANS:?}" : "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_RUN_ID:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" -: "${GITHUB_SHA:?}" -: "${GITHUB_OUTPUT:?}" -: "${RUNNER_OS:?}" -: "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" -if [[ "${SONAR_PLATFORM:?}" != "none" ]]; then - : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" -fi -: "${RUN_SHADOW_SCANS:?}" -if [[ "$DEPLOY" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then +: "${GITHUB_SHA:?}" "${GITHUB_OUTPUT:?}" "${RUNNER_OS:?}" "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" "${CURRENT_VERSION:?}" +if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then : "${ARTIFACTORY_DEPLOY_USERNAME:?}" "${ARTIFACTORY_DEPLOY_PASSWORD:?}" fi -: "${DEPLOY_PULL_REQUEST:=false}" +if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then + : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" +fi : "${USER_MAVEN_ARGS:=}" -export ARTIFACTORY_URL DEPLOY_PULL_REQUEST +export DEPLOY DEPLOY_PULL_REQUEST USER_MAVEN_ARGS readonly DEPLOYED_OUTPUT_KEY="deployed" # FIXME Workaround for SonarSource parent POM; it can be removed after releases of parent 73+ and parent-oss 84+ @@ -106,7 +97,7 @@ git_fetch_unshallow() { } check_settings_xml() { - if [ ! -f "$HOME/.m2/settings.xml" ]; then + if [[ ! -f "$HOME/.m2/settings.xml" ]]; then echo "::error title=Missing Maven settings.xml::Maven settings.xml file not found at $HOME/.m2/settings.xml" >&2 exit 1 fi @@ -136,7 +127,7 @@ should_deploy() { } should_scan() { - if [ "$SONAR_PLATFORM" = "none" ]; then + if [[ "$SONAR_PLATFORM" = "none" && "$RUN_SHADOW_SCANS" != "true" ]]; then return 1 fi is_default_branch || is_maintenance_branch || is_pull_request || is_long_lived_feature_branch diff --git a/build-npm/action.yml b/build-npm/action.yml index 811415c3..9ba4fd41 100644 --- a/build-npm/action.yml +++ b/build-npm/action.yml @@ -14,17 +14,23 @@ inputs: `public-deployer` for public repositories. default: '' artifactory-deploy-repo: - description: Deployment repository. Defaults to `sonarsource-npm-public-qa` for private repositories, and `sonarsource-npm-public-qa` + description: Deployment repository. Defaults to `sonarsource-npm-private-qa` for private repositories, and `sonarsource-npm-public-qa` for public repositories. default: '' + deploy: + description: Whether to deploy on master, maintenance, dogfood and long-lived branches. + default: 'true' deploy-pull-request: - description: Whether to deploy pull request artifacts + description: Whether to also deploy pull request artifacts. If deploy is `false`, this has no effect. default: 'false' skip-tests: description: Whether to skip running tests default: 'false' + disable-caching: + description: Whether to disable NPM caching entirely + default: 'false' cache-npm: - description: Whether to cache NPM dependencies + description: Deprecated. Use `disable-caching` instead. Whether to cache NPM dependencies. default: 'true' repox-url: description: URL for Repox @@ -36,8 +42,8 @@ inputs: description: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. default: next run-shadow-scans: - description: If true, run sonar scanner on all 3 platforms using the provided URL and token. - If false, run on the platform provided by SONAR_PLATFORM. + description: If `true`, run sonar scanner on all 3 platforms. If `false`, run on the platform provided by `sonar-platform`. + When enabled, the `sonar-platform` setting is ignored. default: 'false' build-name: description: Name of the build to publish. Defaults to the repository name. @@ -98,10 +104,15 @@ runs: (github.event.repository.visibility == 'public' && 'public-reader' || 'private-reader') }} ARTIFACTORY_DEPLOYER_ROLE: ${{ inputs.artifactory-deployer-role != '' && inputs.artifactory-deployer-role || (github.event.repository.visibility == 'public' && 'public-deployer' || 'qa-deployer') }} + CACHE_NPM: ${{ inputs.cache-npm }} run: | echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV" echo "ARTIFACTORY_DEPLOYER_ROLE=${ARTIFACTORY_DEPLOYER_ROLE}" >> "$GITHUB_ENV" cp "$ACTION_PATH_BUILD_NPM/mise.local.toml" mise.local.toml + if [[ "$CACHE_NPM" != "true" ]]; then + echo "::warning::The \`cache-npm\` input is deprecated and will be removed in future releases. " \ + "Use \`disable-caching\` instead." >&2 + fi - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 with: @@ -115,20 +126,20 @@ runs: repox-url: ${{ inputs.repox-url }} repox-artifactory-url: ${{ inputs.repox-artifactory-url }} working-directory: ${{ inputs.working-directory }} - cache-npm: ${{ inputs.cache-npm }} + disable-caching: ${{ inputs.cache-npm != 'true' && 'true' || inputs.disable-caching }} - uses: SonarSource/vault-action-wrapper@3d5c87cb535e4a2c7a09adcbcfdefa751854dee3 # 3.3.0 id: secrets # yamllint disable rule:line-length with: secrets: | - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} - development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_DEPLOYER_ROLE }} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN; + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;', env.ARTIFACTORY_DEPLOYER_ROLE) || '' }} # yamllint enable rule:line-length - name: Build, test, analyze and deploy id: build @@ -145,6 +156,7 @@ runs: ARTIFACTORY_DEPLOY_REPO: ${{ inputs.artifactory-deploy-repo != '' && inputs.artifactory-deploy-repo || (github.event.repository.visibility == 'public' && 'sonarsource-npm-public-qa' || 'sonarsource-npm-private-qa') }} ARTIFACTORY_DEPLOY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN }} + DEPLOY: ${{ inputs.deploy }} DEPLOY_PULL_REQUEST: ${{ inputs.deploy-pull-request }} SKIP_TESTS: ${{ inputs.skip-tests }} diff --git a/build-npm/build.sh b/build-npm/build.sh index f392e325..a8e1d091 100755 --- a/build-npm/build.sh +++ b/build-npm/build.sh @@ -2,7 +2,7 @@ # Build script for SonarSource NPM projects. # Supports building, testing, SonarQube analysis, and JFrog Artifactory deployment. # -# Required environment variables (must be explicitly provided): +# Required inputs (must be explicitly provided): # - BUILD_NUMBER: Build number for versioning # - BUILD_NAME: Name of the JFrog Artifactory build (e.g. sonar-dummy) # - SONAR_PLATFORM: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. @@ -13,6 +13,7 @@ # - SQC_EU_URL: URL of SonarQube server for sqc-eu platform # - SQC_EU_TOKEN: Access token to send analysis reports to SonarQube for sqc-eu platform # - RUN_SHADOW_SCANS: If true, run sonar scanner on all 3 platforms. If false, run on the platform provided by SONAR_PLATFORM. +# When enabled, SONAR_PLATFORM is ignored. # - ARTIFACTORY_URL: URL to Artifactory repository # - ARTIFACTORY_DEPLOY_ACCESS_TOKEN: Access token to deploy to Artifactory # - ARTIFACTORY_DEPLOY_REPO: Name of deployment repository @@ -30,6 +31,7 @@ # - GITHUB_BASE_REF: Base branch for pull requests (only during pull_request events) # # Optional user customization: +# - DEPLOY: Whether to deploy (default: true) # - DEPLOY_PULL_REQUEST: Whether to deploy pull request artifacts (default: false) # - SKIP_TESTS: Whether to skip running tests (default: false) # - SQ_SCANNER_VERSION: Version of sonarqube-scanner to use (default: 4.3.0) @@ -41,22 +43,20 @@ set -euo pipefail # shellcheck source=../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${ARTIFACTORY_URL:?}" -: "${ARTIFACTORY_DEPLOY_REPO:?}" "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" +: "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_DEPLOY_REPO:?}" "${DEPLOY_PULL_REQUEST:=false}" "${RUN_SHADOW_SCANS:?}" : "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_RUN_ID:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" "${GITHUB_SHA:?}" -: "${GITHUB_OUTPUT:?}" -: "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" -: "${RUN_SHADOW_SCANS:?}" -if [[ "${SONAR_PLATFORM:?}" != "none" ]]; then +: "${GITHUB_OUTPUT:?}" "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" +if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then + : "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" +fi +if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" fi -: "${DEPLOY_PULL_REQUEST:=false}" "${SKIP_TESTS:=false}" -export DEPLOY_PULL_REQUEST SKIP_TESTS -: "${BUILD_NAME:?}" "${PROJECT_VERSION:?}" "${CURRENT_VERSION:?}" -: "${SQ_SCANNER_VERSION:=4.3.0}" +: "${SKIP_TESTS:=false}" "${BUILD_NAME:?}" "${PROJECT_VERSION:?}" "${CURRENT_VERSION:?}" "${SQ_SCANNER_VERSION:=4.3.0}" +export DEPLOY DEPLOY_PULL_REQUEST SKIP_TESTS SQ_SCANNER_VERSION git_fetch_unshallow() { - if [ "$SONAR_PLATFORM" = "none" ]; then + if [[ "$SONAR_PLATFORM" = "none" && "$RUN_SHADOW_SCANS" != "true" ]]; then echo "Skipping git fetch (Sonar analysis disabled)" return 0 fi @@ -64,7 +64,7 @@ git_fetch_unshallow() { if git rev-parse --is-shallow-repository --quiet >/dev/null 2>&1; then echo "Fetch Git references for SonarQube analysis..." git fetch --unshallow || true # Ignore errors like "fatal: --unshallow on a complete repository does not make sense" - elif [ -n "${GITHUB_BASE_REF:-}" ]; then + elif [[ -n "${GITHUB_BASE_REF:-}" ]]; then echo "Fetch ${GITHUB_BASE_REF} for SonarQube analysis..." git fetch origin "${GITHUB_BASE_REF}" fi @@ -89,7 +89,7 @@ sonar_scanner_implementation() { scanner_args+=("-Dsonar.projectVersion=${CURRENT_VERSION}") # Add region parameter only for sqc-us platform - if [ -n "${SONAR_REGION:-}" ]; then + if [[ -n "${SONAR_REGION:-}" ]]; then scanner_args+=("-Dsonar.region=${SONAR_REGION}") fi scanner_args+=("${additional_params[@]+${additional_params[@]}}") @@ -123,9 +123,6 @@ jfrog_npm_publish() { } # Determine build configuration based on branch type -# TODO BUILD-10586: this function does not support a DEPLOY env var to override deployment (unlike build-maven and build-gradle). -# Should add a DEPLOY=${DEPLOY:=true} check consistent with those build scripts. -# Note: unlike build-maven and build-gradle, long-lived feature branches (feature/long/*) do not deploy here. get_build_config() { local enable_sonar enable_deploy local sonar_args=() @@ -146,7 +143,7 @@ get_build_config() { enable_sonar=true sonar_args=("-Dsonar.analysis.prNumber=${PULL_REQUEST}") - if [ "${DEPLOY_PULL_REQUEST:-false}" == "true" ]; then + if [[ "${DEPLOY_PULL_REQUEST:-false}" == "true" ]]; then echo "======= with deploy =======" enable_deploy=true else @@ -162,7 +159,7 @@ get_build_config() { elif is_long_lived_feature_branch && ! is_pull_request; then echo "======= Build long-lived feature branch =======" enable_sonar=true - enable_deploy=false + enable_deploy=true sonar_args=("-Dsonar.branch.name=${GITHUB_REF_NAME}") else @@ -171,9 +168,14 @@ get_build_config() { enable_deploy=false fi + # Disable deployment when explicitly requested + if [[ "${DEPLOY}" != "true" ]]; then + enable_deploy=false + fi + # Disable deployment when shadow scans are enabled to prevent duplicate artifacts if [[ "${RUN_SHADOW_SCANS}" = "true" ]]; then - echo "======= Shadow scans enabled - disabling deployment to prevent duplicate artifacts =======" + echo "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" >&2 enable_deploy=false fi @@ -193,7 +195,7 @@ run_standard_pipeline() { npm ci --ignore-scripts echo "::endgroup::" - if [ "$SKIP_TESTS" != "true" ]; then + if [[ "$SKIP_TESTS" != "true" ]]; then echo "::group::Run tests" echo "Running tests..." npm test @@ -202,7 +204,7 @@ run_standard_pipeline() { echo "Skipping tests (SKIP_TESTS=true)" fi - if [ "${BUILD_ENABLE_SONAR}" = "true" ]; then + if [[ "${BUILD_ENABLE_SONAR}" = "true" ]]; then read -ra sonar_args <<< "$BUILD_SONAR_ARGS" # This will call back to shared sonar_scanner_implementation() function # orchestrate_sonar_platforms emits its own groups @@ -214,7 +216,7 @@ run_standard_pipeline() { npm run build echo "::endgroup::" - if [ "${BUILD_ENABLE_DEPLOY}" = "true" ]; then + if [[ "${BUILD_ENABLE_DEPLOY}" = "true" ]]; then echo "::group::Publish to Artifactory" jfrog_npm_publish echo "::endgroup::" diff --git a/build-poetry/action.yml b/build-poetry/action.yml index 8b3f6130..8ebc7f8b 100644 --- a/build-poetry/action.yml +++ b/build-poetry/action.yml @@ -17,8 +17,11 @@ inputs: description: Deployment repository. Defaults to `sonarsource-pypi-private-qa` for private repositories, and `sonarsource-pypi-public-qa` for public repositories. default: '' + deploy: + description: Whether to deploy on master, maintenance, dogfood and long-lived branches. + default: 'true' deploy-pull-request: - description: Whether to deploy pull request artifacts. Set to `false` if not using the promote action. + description: Whether to also deploy pull request artifacts. If deploy is `false`, this has no effect. default: 'false' poetry-virtualenvs-path: description: Path to the Poetry virtual environments, relative to GitHub workspace. The folder is cached only if it is a subdirectory of @@ -37,8 +40,8 @@ inputs: description: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. default: next run-shadow-scans: - description: If true, run sonar scanner on all 3 platforms using the provided URL and token. - If false, run on the platform provided by sonar-platform. When enabled, the sonar-platform setting is ignored. + description: If `true`, run sonar scanner on all 3 platforms. If `false`, run on the platform provided by `sonar-platform`. + When enabled, the `sonar-platform` setting is ignored. default: 'false' working-directory: description: Relative path under github.workspace to execute the build in @@ -121,14 +124,14 @@ runs: # yamllint disable rule:line-length with: secrets: | - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} access_token | ARTIFACTORY_ACCESS_TOKEN; - development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_DEPLOYER_ROLE }} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN; + ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;', env.ARTIFACTORY_DEPLOYER_ROLE) || '' }} # yamllint enable rule:line-length - name: Build, Analyze and deploy id: build @@ -142,6 +145,7 @@ runs: # Action inputs ARTIFACTORY_URL: ${{ inputs.repox-artifactory-url != '' && inputs.repox-artifactory-url || format('{0}/artifactory', inputs.repox-url) }} + DEPLOY: ${{ inputs.deploy }} DEPLOY_PULL_REQUEST: ${{ inputs.deploy-pull-request }} ARTIFACTORY_PYPI_REPO: ${{ inputs.public == 'true' && 'sonarsource-pypi' || 'sonarsource-pypi' }} # FIXME: sonarsource-pypi-public ARTIFACTORY_DEPLOY_REPO: ${{ inputs.artifactory-deploy-repo != '' && inputs.artifactory-deploy-repo || diff --git a/build-poetry/build.sh b/build-poetry/build.sh index fd7c9181..a922d618 100755 --- a/build-poetry/build.sh +++ b/build-poetry/build.sh @@ -11,7 +11,7 @@ # - ARTIFACTORY_DEPLOY_ACCESS_TOKEN: Access token to deploy to the repository # - DEFAULT_BRANCH: Default branch name (e.g. main) # - PULL_REQUEST: Pull request number (e.g. 1234) or empty string -# - SONAR_PLATFORM: SonarQube primary platform (next, sqc-eu, or sqc-us) +# - SONAR_PLATFORM: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. # - NEXT_URL: URL of SonarQube server for next platform # - NEXT_TOKEN: Access token to send analysis reports to SonarQube for next platform # - SQC_US_URL: URL of SonarQube server for sqc-us platform @@ -19,6 +19,7 @@ # - SQC_EU_URL: URL of SonarQube server for sqc-eu platform # - SQC_EU_TOKEN: Access token to send analysis reports to SonarQube for sqc-eu platform # - RUN_SHADOW_SCANS: If true, run sonar scanner on all 3 platforms. If false, run on the platform provided by SONAR_PLATFORM. +# When enabled, SONAR_PLATFORM is ignored. # # GitHub Actions auto-provided: # - GITHUB_REF_NAME: Git branch name @@ -32,6 +33,7 @@ # - GITHUB_BASE_REF: Base branch for pull requests (only during pull_request events) # # Optional user customization: +# - DEPLOY: Whether to deploy (default: true) # - DEPLOY_PULL_REQUEST: Whether to deploy pull request artifacts (default: false) # # Auto-derived by script: @@ -43,22 +45,21 @@ set -euo pipefail # shellcheck source=../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${ARTIFACTORY_URL:?}" -: "${ARTIFACTORY_PYPI_REPO:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" "${ARTIFACTORY_DEPLOY_REPO:?}" "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" +: "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_PYPI_REPO:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" "${RUN_SHADOW_SCANS:?}" +: "${ARTIFACTORY_DEPLOY_REPO:?}" "${DEPLOY_PULL_REQUEST:=false}" : "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" "${GITHUB_EVENT_PATH:?}" -: "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" -: "${GITHUB_ENV:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_SHA:?}" "${GITHUB_RUN_ID:?}" -# Only validate sonar credentials if platform is not 'none' -if [[ "${SONAR_PLATFORM:?}" != "none" ]]; then +: "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" "${GITHUB_ENV:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_SHA:?}" "${GITHUB_RUN_ID:?}" +if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then + : "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" +fi +if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" fi -: "${RUN_SHADOW_SCANS:?}" -: "${DEPLOY_PULL_REQUEST:=false}" -export ARTIFACTORY_URL DEPLOY_PULL_REQUEST +export DEPLOY DEPLOY_PULL_REQUEST # Unshallow and fetch all commit history for SonarQube analysis and issue assignment git_fetch_unshallow() { - if [ "$SONAR_PLATFORM" = "none" ]; then + if [[ "$SONAR_PLATFORM" = "none" && "$RUN_SHADOW_SCANS" != "true" ]]; then echo "Skipping git fetch (sonar analysis disabled)" return 0 fi @@ -66,7 +67,7 @@ git_fetch_unshallow() { if git rev-parse --is-shallow-repository --quiet >/dev/null 2>&1; then echo "Fetch Git references for SonarQube analysis..." git fetch --unshallow || true # Ignore errors like "fatal: --unshallow on a complete repository does not make sense" - elif [ -n "${GITHUB_BASE_REF:-}" ]; then + elif [[ -n "${GITHUB_BASE_REF:-}" ]]; then echo "Fetch ${GITHUB_BASE_REF} for SonarQube analysis..." git fetch origin "${GITHUB_BASE_REF}" fi @@ -129,7 +130,7 @@ run_sonar_scanner() { run_sonar_analysis() { local sonar_args=("$@") echo "run_sonar_analysis()" - if [ "${RUN_SHADOW_SCANS}" = "true" ]; then + if [[ "${RUN_SHADOW_SCANS}" = "true" ]]; then echo "=== Running Sonar analysis on all platforms (shadow scan enabled) ===" local platforms=("next" "sqc-us" "sqc-eu") @@ -143,7 +144,7 @@ run_sonar_analysis() { echo "=== Completed Sonar analysis on all platforms ===" else - if [ "$SONAR_PLATFORM" = "none" ]; then + if [[ "$SONAR_PLATFORM" = "none" ]]; then echo "=== Sonar platform set to 'none'. Skipping Sonar analysis." return 0 fi @@ -193,9 +194,6 @@ set_project_version() { } # Determine build configuration based on branch type -# TODO BUILD-10586: this function does not support a DEPLOY env var to override deployment (unlike build-maven and build-gradle). -# Should add a DEPLOY=${DEPLOY:=true} check consistent with those build scripts. -# Note: unlike build-maven and build-gradle, long-lived feature branches (feature/long/*) do not deploy here. get_build_config() { local enable_sonar enable_deploy local sonar_args=() @@ -219,7 +217,7 @@ get_build_config() { enable_sonar=true sonar_args=("-Dsonar.analysis.prNumber=${PULL_REQUEST}") - if [ "${DEPLOY_PULL_REQUEST:-false}" == "true" ]; then + if [[ "${DEPLOY_PULL_REQUEST:-false}" == "true" ]]; then echo "======= with deploy =======" enable_deploy=true else @@ -235,7 +233,7 @@ get_build_config() { elif is_long_lived_feature_branch && ! is_pull_request; then echo "======= Build long-lived feature branch =======" enable_sonar=true - enable_deploy=false + enable_deploy=true sonar_args=("-Dsonar.branch.name=${GITHUB_REF_NAME}") else @@ -244,6 +242,17 @@ get_build_config() { enable_deploy=false fi + # Disable deployment when explicitly requested + if [[ "${DEPLOY}" != "true" ]]; then + enable_deploy=false + fi + + # Disable deployment when shadow scans are enabled to prevent duplicate artifacts + if [[ "${RUN_SHADOW_SCANS}" = "true" ]]; then + echo "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" >&2 + enable_deploy=false + fi + # Export the configuration for use by build_poetry export BUILD_ENABLE_SONAR="$enable_sonar" export BUILD_ENABLE_DEPLOY="$enable_deploy" @@ -296,13 +305,13 @@ build_poetry() { poetry build echo "::endgroup::" - if [ "${BUILD_ENABLE_SONAR}" = "true" ]; then + if [[ "${BUILD_ENABLE_SONAR}" = "true" ]]; then read -ra sonar_args <<< "$BUILD_SONAR_ARGS" # run_sonar_analysis emits its own groups run_sonar_analysis "${sonar_args[@]+${sonar_args[@]}}" fi - if [ "${BUILD_ENABLE_DEPLOY}" = "true" ]; then + if [[ "${BUILD_ENABLE_DEPLOY}" = "true" ]]; then echo "::group::Publish to Artifactory" jfrog_poetry_publish echo "::endgroup::" diff --git a/build-yarn/action.yml b/build-yarn/action.yml index 667d8e3b..5367b14b 100644 --- a/build-yarn/action.yml +++ b/build-yarn/action.yml @@ -20,14 +20,20 @@ inputs: description: Deployment repository. Defaults to `sonarsource-private-qa` for private repositories, and `sonarsource-public-qa` for public repositories. default: '' + deploy: + description: Whether to deploy on master, maintenance, dogfood and long-lived branches. + default: 'true' deploy-pull-request: - description: Whether to deploy pull request artifacts + description: Whether to also deploy pull request artifacts. If deploy is `false`, this has no effect. default: 'false' skip-tests: description: Whether to skip running tests default: 'false' + disable-caching: + description: Whether to disable Yarn caching entirely + default: 'false' cache-yarn: - description: Whether to cache Yarn dependencies + description: Deprecated. Use `disable-caching` instead. Whether to cache Yarn dependencies. default: 'true' repox-url: description: URL for Repox @@ -39,8 +45,8 @@ inputs: description: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. default: next run-shadow-scans: - description: If true, run sonar scanner on all 3 platforms using the provided URL and token. - If false, run on the platform provided by SONAR_PLATFORM. + description: If `true`, run sonar scanner on all 3 platforms. If `false`, run on the platform provided by `sonar-platform`. + When enabled, the `sonar-platform` setting is ignored. default: 'false' provenance: description: Whether to generate provenance attestation for built artifacts @@ -99,11 +105,16 @@ runs: (github.event.repository.visibility == 'public' && 'public-reader' || 'private-reader') }} ARTIFACTORY_DEPLOYER_ROLE: ${{ inputs.artifactory-deployer-role != '' && inputs.artifactory-deployer-role || (github.event.repository.visibility == 'public' && 'public-deployer' || 'qa-deployer') }} + CACHE_YARN: ${{ inputs.cache-yarn }} working-directory: ${{ inputs.working-directory }} run: | echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV" echo "ARTIFACTORY_DEPLOYER_ROLE=${ARTIFACTORY_DEPLOYER_ROLE}" >> "$GITHUB_ENV" cp "$ACTION_PATH_BUILD_YARN/mise.local.toml" mise.local.toml + if [[ "$CACHE_YARN" != "true" ]]; then + echo "::warning::The \`cache-yarn\` input is deprecated and will be removed in future releases. " \ + "Use \`disable-caching\` instead." >&2 + fi - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 with: @@ -112,7 +123,7 @@ runs: - name: Cache Yarn dependencies uses: SonarSource/gh-action_cache@957cb1f6f70956976b834546bf09839080b5bb00 # v1.2.3 - if: ${{ inputs.cache-yarn == 'true' }} + if: ${{ inputs.cache-yarn == 'true' && inputs.disable-caching != 'true' }} with: path: | ~/.yarn @@ -124,15 +135,15 @@ runs: # yamllint disable rule:line-length with: secrets: | - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ inputs.sonar-platform != 'none' && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} username | ARTIFACTORY_USERNAME; development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} access_token | ARTIFACTORY_ACCESS_TOKEN; - development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_DEPLOYER_ROLE }} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN; + ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;', env.ARTIFACTORY_DEPLOYER_ROLE) || '' }} # yamllint enable rule:line-length - name: Build, test, analyze and deploy @@ -151,6 +162,7 @@ runs: ARTIFACTORY_USERNAME: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_USERNAME }} ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }} ARTIFACTORY_DEPLOY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN }} + DEPLOY: ${{ inputs.deploy }} DEPLOY_PULL_REQUEST: ${{ inputs.deploy-pull-request }} SKIP_TESTS: ${{ inputs.skip-tests }} diff --git a/build-yarn/build.sh b/build-yarn/build.sh index d66f4f9a..9ae46bcf 100755 --- a/build-yarn/build.sh +++ b/build-yarn/build.sh @@ -4,7 +4,7 @@ # # Required inputs (must be explicitly provided): # - BUILD_NUMBER: Build number for versioning -# - SONAR_PLATFORM: SonarQube primary platform (next, sqc-eu, or sqc-us) +# - SONAR_PLATFORM: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. # - NEXT_URL: URL of SonarQube server for next platform # - NEXT_TOKEN: Access token to send analysis reports to SonarQube for next platform # - SQC_US_URL: URL of SonarQube server for sqc-us platform @@ -12,6 +12,7 @@ # - SQC_EU_URL: URL of SonarQube server for sqc-eu platform # - SQC_EU_TOKEN: Access token to send analysis reports to SonarQube for sqc-eu platform # - RUN_SHADOW_SCANS: If true, run sonar scanner on all 3 platforms. If false, run on the platform provided by SONAR_PLATFORM. +# When enabled, SONAR_PLATFORM is ignored. # - ARTIFACTORY_URL: URL to Artifactory repository # - ARTIFACTORY_USERNAME: Username that matches the access token to read Repox repositories # - ARTIFACTORY_ACCESS_TOKEN: Access token to read Repox repositories @@ -31,6 +32,7 @@ # - GITHUB_BASE_REF: Base branch for pull requests (only during pull_request events) # # Optional user customization: +# - DEPLOY: Whether to deploy (default: true) # - DEPLOY_PULL_REQUEST: Whether to deploy pull request artifacts (default: false) # - SKIP_TESTS: Whether to skip running tests (default: false) # @@ -45,22 +47,20 @@ PACKAGE_JSON="package.json" # shellcheck source=../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${ARTIFACTORY_URL:?}" -: "${ARTIFACTORY_USERNAME:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" -: "${ARTIFACTORY_DEPLOY_REPO:?}" "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" +: "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_USERNAME:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" +: "${ARTIFACTORY_DEPLOY_REPO:?}" "${DEPLOY_PULL_REQUEST:=false}" "${SKIP_TESTS:=false}" "${RUN_SHADOW_SCANS:?}" : "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_RUN_ID:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" "${GITHUB_SHA:?}" -: "${GITHUB_OUTPUT:?}" -: "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" -: "${RUN_SHADOW_SCANS:?}" -if [[ "${SONAR_PLATFORM:?}" != "none" ]]; then +: "${GITHUB_OUTPUT:?}" "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" "${SQ_SCANNER_VERSION:=4.3.0}" +if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then + : "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" +fi +if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" fi -: "${DEPLOY_PULL_REQUEST:=false}" "${SKIP_TESTS:=false}" -export ARTIFACTORY_URL DEPLOY_PULL_REQUEST SKIP_TESTS -: "${SQ_SCANNER_VERSION:=4.3.0}" +export DEPLOY DEPLOY_PULL_REQUEST SKIP_TESTS SQ_SCANNER_VERSION git_fetch_unshallow() { - if [ "$SONAR_PLATFORM" = "none" ]; then + if [[ "$SONAR_PLATFORM" = "none" && "$RUN_SHADOW_SCANS" != "true" ]]; then echo "Skipping git fetch (Sonar analysis disabled)" return 0 fi @@ -68,7 +68,7 @@ git_fetch_unshallow() { if git rev-parse --is-shallow-repository --quiet >/dev/null 2>&1; then echo "Fetch Git references for SonarQube analysis..." git fetch --unshallow || true # Ignore errors like "fatal: --unshallow on a complete repository does not make sense" - elif [ -n "${GITHUB_BASE_REF:-}" ]; then + elif [[ -n "${GITHUB_BASE_REF:-}" ]]; then echo "Fetch ${GITHUB_BASE_REF} for SonarQube analysis..." git fetch origin "${GITHUB_BASE_REF}" fi @@ -80,11 +80,11 @@ set_build_env() { git_fetch_unshallow # Validate required files exist - if [ ! -f "package.json" ]; then + if [[ ! -f "package.json" ]]; then echo "::error title=Missing package.json::package.json file not found in current directory." >&2 exit 1 fi - if [ ! -f "yarn.lock" ]; then + if [[ ! -f "yarn.lock" ]]; then echo "::error title=Missing yarn.lock::yarn.lock file not found. This is required for yarn --immutable installs." >&2 exit 1 fi @@ -113,7 +113,7 @@ set_project_version() { local current_version release_version digit_count current_version=$(jq -r .version "$PACKAGE_JSON") - if [ -z "${current_version}" ] || [ "${current_version}" == "null" ]; then + if [[ -z "${current_version}" || "${current_version}" == "null" ]]; then echo "::error file=${PACKAGE_JSON},title=Invalid project version::Could not get version from ${PACKAGE_JSON}" >&2 exit 1 fi @@ -160,7 +160,7 @@ sonar_scanner_implementation() { scanner_args+=("-Dsonar.projectVersion=${CURRENT_VERSION}") # Add region parameter only for sqc-us platform - if [ -n "${SONAR_REGION:-}" ]; then + if [[ -n "${SONAR_REGION:-}" ]]; then scanner_args+=("-Dsonar.region=${SONAR_REGION}") fi @@ -193,9 +193,6 @@ jfrog_yarn_publish() { } # Determine build configuration based on branch type -# TODO BUILD-10586: this function does not support a DEPLOY env var to override deployment (unlike build-maven and build-gradle). -# Should add a DEPLOY=${DEPLOY:=true} check consistent with those build scripts. -# Note: unlike build-maven and build-gradle, long-lived feature branches (feature/long/*) do not deploy here. get_build_config() { local enable_sonar enable_deploy local sonar_args=() @@ -216,7 +213,7 @@ get_build_config() { enable_sonar=true sonar_args=("-Dsonar.analysis.prNumber=${PULL_REQUEST}") - if [ "${DEPLOY_PULL_REQUEST:-false}" == "true" ]; then + if [[ "${DEPLOY_PULL_REQUEST:-false}" == "true" ]]; then echo "======= with deploy =======" enable_deploy=true else @@ -232,7 +229,7 @@ get_build_config() { elif is_long_lived_feature_branch && ! is_pull_request; then echo "======= Build long-lived feature branch =======" enable_sonar=true - enable_deploy=false + enable_deploy=true sonar_args=("-Dsonar.branch.name=${GITHUB_REF_NAME}") else @@ -241,9 +238,14 @@ get_build_config() { enable_deploy=false fi + # Disable deployment when explicitly requested + if [[ "${DEPLOY}" != "true" ]]; then + enable_deploy=false + fi + # Disable deployment when shadow scans are enabled to prevent duplicate artifacts if [[ "${RUN_SHADOW_SCANS}" = "true" ]]; then - echo "======= Shadow scans enabled - disabling deployment to prevent duplicate artifacts =======" + echo "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" >&2 enable_deploy=false fi @@ -263,7 +265,7 @@ run_standard_pipeline() { yarn install --immutable echo "::endgroup::" - if [ "$SKIP_TESTS" != "true" ]; then + if [[ "$SKIP_TESTS" != "true" ]]; then echo "::group::Run tests" echo "Running tests..." yarn test @@ -272,7 +274,7 @@ run_standard_pipeline() { echo "Skipping tests (SKIP_TESTS=true)" fi - if [ "${BUILD_ENABLE_SONAR}" = "true" ]; then + if [[ "${BUILD_ENABLE_SONAR}" = "true" ]]; then read -ra sonar_args <<< "$BUILD_SONAR_ARGS" # This will call back to shared sonar_scanner_implementation() function # orchestrate_sonar_platforms emits its own groups @@ -284,7 +286,7 @@ run_standard_pipeline() { yarn build echo "::endgroup::" - if [ "${BUILD_ENABLE_DEPLOY}" = "true" ]; then + if [[ "${BUILD_ENABLE_DEPLOY}" = "true" ]]; then echo "::group::Publish to Artifactory" jfrog_yarn_publish echo "::endgroup::" diff --git a/config-gradle/action.yml b/config-gradle/action.yml index 105253ca..9b0ade8c 100644 --- a/config-gradle/action.yml +++ b/config-gradle/action.yml @@ -56,7 +56,7 @@ runs: echo "github.action_path=${{ github.action_path }}" ACTION_PATH_CONFIG_GRADLE="${{ github.action_path }}" host_actions_root="${{ inputs.host-actions-root }}" - if [ -z "$host_actions_root" ]; then + if [[ -z "$host_actions_root" ]]; then host_actions_root="$(dirname "$ACTION_PATH_CONFIG_GRADLE")" else ACTION_PATH_CONFIG_GRADLE="$host_actions_root/config-gradle" diff --git a/config-gradle/set_gradle_project_version.sh b/config-gradle/set_gradle_project_version.sh index ee8625ef..495f6892 100755 --- a/config-gradle/set_gradle_project_version.sh +++ b/config-gradle/set_gradle_project_version.sh @@ -10,11 +10,11 @@ # # Optional user customization: # - CURRENT_VERSION and PROJECT_VERSION: If both are set, they will be used as-is and no version update will be performed. +# - SKIP: If true, the script will skip setting the project version and outputting the current version. Defaults to false. set -euo pipefail -: "${BUILD_NUMBER:?}" -: "${GITHUB_OUTPUT:?}" "${GITHUB_ENV:?}" "${SKIP:=false}" +: "${BUILD_NUMBER:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_ENV:?}" "${SKIP:=false}" # shellcheck source=SCRIPTDIR/../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" diff --git a/config-maven/action.yml b/config-maven/action.yml index 23eac665..71e69263 100644 --- a/config-maven/action.yml +++ b/config-maven/action.yml @@ -57,7 +57,7 @@ runs: echo "github.action_path=${{ github.action_path }}" ACTION_PATH_CONFIG_MAVEN="${{ github.action_path }}" host_actions_root="${{ inputs.host-actions-root }}" - if [ -z "$host_actions_root" ]; then + if [[ -z "$host_actions_root" ]]; then host_actions_root="$(dirname "$ACTION_PATH_CONFIG_MAVEN")" else ACTION_PATH_CONFIG_MAVEN="$host_actions_root/config-maven" @@ -148,7 +148,7 @@ runs: # Maven retrieves the user home directory from the passwd database, that may differ from $HOME (e.g., in a container) echo "HOME: $HOME" - if [ "$RUNNER_OS" != "Windows" ]; then + if [[ "$RUNNER_OS" != "Windows" ]]; then # Get user home directory - use getent on Linux, dscl on macOS if command -v getent &> /dev/null; then USER_HOME="$(getent passwd $(whoami) | cut -d: -f6)" @@ -157,7 +157,7 @@ runs: else USER_HOME="$HOME" fi - if [ "$USER_HOME" != "$HOME" ]; then + if [[ "$USER_HOME" != "$HOME" ]]; then echo "::group::USER_HOME symlinks workaround for Maven when it differs from HOME" echo "USER_HOME (from passwd): $USER_HOME" mkdir -p "$USER_HOME/.m2" diff --git a/config-maven/set_maven_project_version.sh b/config-maven/set_maven_project_version.sh index a0680fe9..998419dd 100755 --- a/config-maven/set_maven_project_version.sh +++ b/config-maven/set_maven_project_version.sh @@ -10,11 +10,11 @@ # # Optional user customization: # - CURRENT_VERSION and PROJECT_VERSION: If both are set, they will be used as-is and no version update will be performed. +# - SKIP: If true, the script will skip setting the project version and outputting the current version. Defaults to false. set -euo pipefail -: "${BUILD_NUMBER:?}" -: "${GITHUB_OUTPUT:?}" "${GITHUB_ENV:?}" "${SKIP:=false}" +: "${BUILD_NUMBER:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_ENV:?}" "${SKIP:=false}" # shellcheck source=SCRIPTDIR/../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" diff --git a/config-npm/action.yml b/config-npm/action.yml index 31f1f84d..f6307c7d 100644 --- a/config-npm/action.yml +++ b/config-npm/action.yml @@ -9,8 +9,11 @@ inputs: description: Suffix for the Artifactory reader role in Vault. Defaults to `private-reader` for private repositories, and `public-reader` for public repositories. default: '' + disable-caching: + description: Whether to disable NPM caching entirely + default: 'false' cache-npm: - description: Whether to cache NPM dependencies + description: Deprecated. Use `disable-caching` instead. Whether to cache NPM dependencies. default: 'true' repox-url: description: URL for Repox @@ -36,18 +39,27 @@ outputs: runs: using: composite steps: + - id: config-npm-completed + if: env.CONFIG_NPM_COMPLETED != '' + shell: bash + run: | + echo "Action already called by $CONFIG_NPM_COMPLETED, execution skipped." + echo "skip=true" >> $GITHUB_OUTPUT + - id: setup + if: steps.config-npm-completed.outputs.skip != 'true' shell: bash env: ARTIFACTORY_READER_ROLE: ${{ inputs.artifactory-reader-role != '' && inputs.artifactory-reader-role || (github.event.repository.visibility == 'public' && 'public-reader' || 'private-reader') }} + CACHE_NPM: ${{ inputs.cache-npm }} run: | echo "::group::Fix for using local actions" echo "GITHUB_ACTION_PATH=$GITHUB_ACTION_PATH" echo "github.action_path=${{ github.action_path }}" ACTION_PATH_CONFIG_NPM="${{ github.action_path }}" host_actions_root="${{ inputs.host-actions-root }}" - if [ -z "$host_actions_root" ]; then + if [[ -z "$host_actions_root" ]]; then host_actions_root="$(dirname "$ACTION_PATH_CONFIG_NPM")" else ACTION_PATH_CONFIG_NPM="$host_actions_root/config-npm" @@ -70,12 +82,18 @@ runs: echo "::endgroup::" echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV" + if [[ "$CACHE_NPM" != "true" ]]; then + echo "::warning::The \`cache-npm\` input is deprecated and will be removed in future releases. " \ + "Use \`disable-caching\` instead." >&2 + fi - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + if: steps.config-npm-completed.outputs.skip != 'true' with: version: 2026.3.7 - uses: SonarSource/vault-action-wrapper@3d5c87cb535e4a2c7a09adcbcfdefa751854dee3 # 3.3.0 + if: steps.config-npm-completed.outputs.skip != 'true' id: secrets with: secrets: | @@ -83,6 +101,7 @@ runs: development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} access_token | ARTIFACTORY_ACCESS_TOKEN; - name: Configure NPM authentication + if: steps.config-npm-completed.outputs.skip != 'true' shell: bash env: ARTIFACTORY_URL: ${{ inputs.repox-artifactory-url != '' && inputs.repox-artifactory-url || @@ -99,7 +118,7 @@ runs: - name: Sanitize workflow name for cache key id: sanitize_workflow - if: ${{ inputs.cache-npm == 'true' }} + if: steps.config-npm-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' && inputs.cache-npm == 'true' shell: bash env: WORKFLOW_NAME: ${{ github.workflow }} @@ -107,7 +126,7 @@ runs: - name: Cache NPM dependencies uses: SonarSource/gh-action_cache@957cb1f6f70956976b834546bf09839080b5bb00 # v1.2.3 - if: ${{ inputs.cache-npm == 'true' }} + if: steps.config-npm-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' && inputs.cache-npm == 'true' with: path: ~/.npm key: npm-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}-${{ hashFiles('**/package-lock.json') }} @@ -115,6 +134,7 @@ runs: - name: Check for package.json id: check_package_json + if: steps.config-npm-completed.outputs.skip != 'true' shell: bash working-directory: ${{ inputs.working-directory }} run: | @@ -127,14 +147,20 @@ runs: - uses: ./.actions/get-build-number id: get_build_number + if: steps.config-npm-completed.outputs.skip != 'true' with: host-actions-root: ${{ steps.setup.outputs.host_actions_root }} - name: Update project version and set current-version and project-version variables id: set_version - if: ${{ steps.check_package_json.outputs.exists == 'true' }} + if: steps.config-npm-completed.outputs.skip != 'true' && steps.check_package_json.outputs.exists == 'true' shell: bash env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} working-directory: ${{ inputs.working-directory }} run: $ACTION_PATH_CONFIG_NPM/npm_set_project_version.sh + + - name: Set Config NPM completed + if: steps.config-npm-completed.outputs.skip != 'true' + shell: bash + run: echo "CONFIG_NPM_COMPLETED=$GITHUB_ACTION" >> "$GITHUB_ENV" diff --git a/config-npm/npm_set_project_version.sh b/config-npm/npm_set_project_version.sh index 9b35ab44..684411f0 100755 --- a/config-npm/npm_set_project_version.sh +++ b/config-npm/npm_set_project_version.sh @@ -18,8 +18,7 @@ PACKAGE_JSON="package.json" # shellcheck source=SCRIPTDIR/../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${BUILD_NUMBER:?}" -: "${GITHUB_OUTPUT:?}" "${GITHUB_ENV:?}" +: "${BUILD_NUMBER:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_ENV:?}" check_version_format() { local version="$1" diff --git a/config-pip/action.yml b/config-pip/action.yml index a474ca04..23c21414 100644 --- a/config-pip/action.yml +++ b/config-pip/action.yml @@ -43,7 +43,7 @@ runs: echo "github.action_path=${{ github.action_path }}" ACTION_PATH_CONFIG_PIP="${{ github.action_path }}" host_actions_root="${{ inputs.host-actions-root }}" - if [ -z "$host_actions_root" ]; then + if [[ -z "$host_actions_root" ]]; then host_actions_root="$(dirname "$ACTION_PATH_CONFIG_PIP")" else ACTION_PATH_CONFIG_PIP="$host_actions_root/config-pip" diff --git a/get-build-number/action.yml b/get-build-number/action.yml index e3ed27ce..02df2b14 100644 --- a/get-build-number/action.yml +++ b/get-build-number/action.yml @@ -22,7 +22,7 @@ runs: echo "github.action_path=${{ github.action_path }}" ACTION_PATH_GET_BUILD_NUMBER="${{ github.action_path }}" host_actions_root="${{ inputs.host-actions-root }}" - if [ -z "$host_actions_root" ]; then + if [[ -z "$host_actions_root" ]]; then host_actions_root="$(dirname "$ACTION_PATH_GET_BUILD_NUMBER")" else ACTION_PATH_GET_BUILD_NUMBER="$host_actions_root/get-build-number" diff --git a/pr_cleanup/cleanup.sh b/pr_cleanup/cleanup.sh index a6a6ac73..e845287c 100755 --- a/pr_cleanup/cleanup.sh +++ b/pr_cleanup/cleanup.sh @@ -1,17 +1,14 @@ #!/bin/bash # Cleanup caches and artifacts for a pull request in a GitHub repository. # Required environment variables: -# GH_TOKEN - GitHub token with actions:write permission for cache and artifact deletion +# GH_TOKEN - GitHub token with actions:write permission for cache and artifact deletion. Used by gh CLI # CACHE_REF - Cache reference in the format "refs/pull//merge" # GITHUB_REPOSITORY - Repository name with owner (e.g. "owner/repo") # GITHUB_HEAD_REF - Head branch reference of the pull request set -euo pipefail -: "${GH_TOKEN:?Required environment variable not set}" # used by gh CLI -: "${CACHE_REF:?Required environment variable not set}" -: "${GITHUB_REPOSITORY:?Required environment variable not set}" -: "${GITHUB_HEAD_REF:?Required environment variable not set}" +: "${GH_TOKEN:?}" "${CACHE_REF:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_HEAD_REF:?}" CURDIR=$(dirname "$0") readonly CACHE_LIST_LIMIT=100000 diff --git a/promote/promote.sh b/promote/promote.sh index 0b6f1242..d54fa093 100755 --- a/promote/promote.sh +++ b/promote/promote.sh @@ -34,18 +34,15 @@ set -euo pipefail # shellcheck source=../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${ARTIFACTORY_URL:="https://repox.jfrog.io/artifactory"}" -: "${ARTIFACTORY_PROMOTE_ACCESS_TOKEN:?}" +: "${ARTIFACTORY_URL:="https://repox.jfrog.io/artifactory"}" "${ARTIFACTORY_PROMOTE_ACCESS_TOKEN:?}" "${BUILD_NAME:?}" : "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" "${GITHUB_EVENT_PATH:?}" "${GITHUB_TOKEN:?}" : "${GITHUB_SHA:?}" "${GITHUB_JOB:?}" -GH_API_VERSION_HEADER="X-GitHub-Api-Version: 2022-11-28" +: "${MULTI_REPO_PROMOTE:=false}" "${ARTIFACTORY_DEPLOY_REPO:=}" "${ARTIFACTORY_TARGET_REPO:=}" "${PROMOTE_PULL_REQUEST:=false}" +readonly MULTI_REPO_SRC_PRIVATE=sonarsource-private-qa +readonly MULTI_REPO_SRC_PUBLIC=sonarsource-public-qa +readonly GH_API_VERSION_HEADER="X-GitHub-Api-Version: 2022-11-28" BUILD_INFO_FILE=$(mktemp) rm -f "$BUILD_INFO_FILE" -: "${BUILD_NAME:?}" - -: "${MULTI_REPO_PROMOTE:=false}" "${ARTIFACTORY_DEPLOY_REPO:=}" "${ARTIFACTORY_TARGET_REPO:=}" "${PROMOTE_PULL_REQUEST:=false}" -MULTI_REPO_SRC_PRIVATE=sonarsource-private-qa -MULTI_REPO_SRC_PUBLIC=sonarsource-public-qa set_build_env() { : "${DEFAULT_BRANCH:=$(gh repo view --json defaultBranchRef --jq ".defaultBranchRef.name")}" @@ -171,8 +168,8 @@ jfrog_promote() { status='it-passed-pr' fi - export PROJECT_VERSION : "${PROJECT_VERSION:=$(get_build_info_property PROJECT_VERSION)}" + export PROJECT_VERSION if [[ "${MULTI_REPO_PROMOTE}" == "true" ]]; then local targetRepo1 targetRepo2 diff --git a/shared/common-functions.sh b/shared/common-functions.sh index 2e26ea4a..e5694055 100755 --- a/shared/common-functions.sh +++ b/shared/common-functions.sh @@ -55,12 +55,12 @@ set_sonar_platform_vars() { # CALLBACK DEPENDENCY: # Requires build script to implement sonar_scanner_implementation() function orchestrate_sonar_platforms() { - if [ "$SONAR_PLATFORM" = "none" ] && [ "${RUN_SHADOW_SCANS}" != "true" ]; then + if [[ "$SONAR_PLATFORM" = "none" && "$RUN_SHADOW_SCANS" != "true" ]]; then echo "=== ORCHESTRATOR: Skipping Sonar analysis (platform: none) ===" return 0 fi - if [ "${RUN_SHADOW_SCANS}" = "true" ]; then + if [[ "$RUN_SHADOW_SCANS" = "true" ]]; then echo "=== ORCHESTRATOR: Running Sonar analysis on all platforms (shadow scan enabled) ===" local platforms=("next" "sqc-us" "sqc-eu") diff --git a/spec/build-gradle_spec.sh b/spec/build-gradle_spec.sh index dddfb9fc..b11b9004 100755 --- a/spec/build-gradle_spec.sh +++ b/spec/build-gradle_spec.sh @@ -305,6 +305,50 @@ Describe 'get_build_type' End +Describe 'should_scan' + It 'returns true for default branch' + When call should_scan + The status should be success + End + + It 'returns true for maintenance branch' + export GITHUB_REF_NAME="branch-1.0" + When call should_scan + The status should be success + End + + It 'returns true for pull request' + export GITHUB_EVENT_NAME="$GITHUB_EVENT_NAME_PR" + export GITHUB_REF_NAME="$GITHUB_REF_NAME_PR" + When call should_scan + The status should be success + End + + It 'returns true for long-lived feature branch' + export GITHUB_REF_NAME="feature/long/my-feature" + When call should_scan + The status should be success + End + + It 'returns false for dogfood branch' + export GITHUB_REF_NAME="dogfood-on-main" + When call should_scan + The status should be failure + End + + It 'returns false for regular feature branch' + export GITHUB_REF_NAME="feature/test" + When call should_scan + The status should be failure + End + + It 'returns false when sonar platform is none' + export SONAR_PLATFORM="none" + When call should_scan + The status should be failure + End +End + Describe 'gradle_build' It 'executes gradle build successfully' export GRADLE_ARGS="" @@ -347,6 +391,30 @@ Describe 'gradle_build' rm -f gradle-mock gradle.properties End + + It 'calls gradle_build_and_analyze directly for dogfood branch (no sonar)' + export GITHUB_REF_NAME="dogfood-on-main" + export SONAR_PLATFORM="next" + export GRADLE_ARGS="" + + echo "version=1.0-SNAPSHOT" > gradle.properties + echo '#!/bin/bash' > gradle-mock + echo 'echo "Gradle executed with: $*"' >> gradle-mock + chmod +x gradle-mock + export GRADLE_CMD="./gradle-mock" + + Mock get_build_type + echo "dogfood branch" + End + + When call gradle_build + The output should include "Starting dogfood branch build" + The output should include "Gradle executed with:" + The output should not include "=== ORCHESTRATOR:" + The stderr should include "::warning title=No artifacts found::" + + rm -f gradle-mock gradle.properties + End End Describe 'sonar_scanner_implementation()' diff --git a/spec/build-npm_spec.sh b/spec/build-npm_spec.sh index 3bd75347..071f4786 100755 --- a/spec/build-npm_spec.sh +++ b/spec/build-npm_spec.sh @@ -76,6 +76,7 @@ export GITHUB_RUN_ID="12345" export GITHUB_SHA="abc123" export NEXT_TOKEN="next-token" export NEXT_URL="https://next.sonarqube.com" +export DEPLOY="true" export PULL_REQUEST="false" export RUN_SHADOW_SCANS="false" export SKIP_TESTS="false" @@ -262,7 +263,7 @@ Describe 'build_npm()' The output should not include "SonarQube scanner" End - It 'builds long-lived feature branch without deploy' + It 'builds long-lived feature branch with deploy' export GITHUB_REF_NAME="feature/long/test-feature" export GITHUB_EVENT_NAME="push" export BUILD_NUMBER="42" @@ -271,7 +272,18 @@ Describe 'build_npm()' The output should include "======= Build long-lived feature branch =======" The output should include "Installing npm dependencies..." The output should include "npx -- @sonar/scan" - The output should not include "DEBUG: JFrog operations" + The variable BUILD_ENABLE_DEPLOY should equal "true" + End + + It 'skips deploy when DEPLOY is false' + export GITHUB_REF_NAME="main" + export GITHUB_EVENT_NAME="push" + export BUILD_NUMBER="42" + export DEPLOY="false" + When call build_npm + The status should be success + The output should include "======= Building main branch =======" + The variable BUILD_ENABLE_DEPLOY should equal "false" End It 'builds other branches without sonar or deploy' @@ -356,7 +368,8 @@ Describe 'get_build_config()' export BUILD_NUMBER="42" When call get_build_config The status should be success - The output should include "======= Shadow scans enabled - disabling deployment to prevent duplicate artifacts =======" + The output should include "======= Building main branch =======" + The stderr should include "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" The variable BUILD_ENABLE_DEPLOY should equal "false" The variable BUILD_ENABLE_SONAR should equal "true" End @@ -390,5 +403,6 @@ Describe 'build_npm()' The output should include "Sonar Platform: next" The output should include "shadow scan enabled" The output should not include "DEBUG: JFrog operations" + The stderr should include "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" End End diff --git a/spec/build-poetry_spec.sh b/spec/build-poetry_spec.sh index 582ece73..4146439c 100755 --- a/spec/build-poetry_spec.sh +++ b/spec/build-poetry_spec.sh @@ -38,6 +38,7 @@ export SQC_US_URL="https://sonarqube-us.com" export SQC_US_TOKEN="sqc-us-token" export SQC_EU_URL="https://sonarcloud.io" export SQC_EU_TOKEN="sqc-eu-token" +export DEPLOY="true" export RUN_SHADOW_SCANS="false" # Constant outputs (exported so mocks run in subshells can access it) @@ -459,7 +460,7 @@ Describe 'build_poetry()' The variable BUILD_ENABLE_SONAR should equal "false" End - It 'disables deploy on long-lived branch' + It 'enables deploy on long-lived branch' export GITHUB_REF_NAME="feature/long/test" When call build_poetry @@ -472,9 +473,20 @@ Describe 'build_poetry()' The line 7 should equal '======= Build long-lived feature branch =======' The status should be success The variable BUILD_ENABLE_SONAR should equal "true" + The variable BUILD_ENABLE_DEPLOY should equal "true" The variable BUILD_SONAR_ARGS should equal "-Dsonar.branch.name=feature/long/test" End + It 'disables deploy when DEPLOY is false' + export GITHUB_REF_NAME="main" + export DEPLOY="false" + + When call build_poetry + The status should be success + The output should include "======= Building main branch =======" + The variable BUILD_ENABLE_DEPLOY should equal "false" + End + It 'enables deploy and scan on maintenance branch' export GITHUB_REF_NAME="branch-1.2" @@ -542,6 +554,7 @@ Describe 'build_poetry()' The line 36 should equal 'poetry run pysonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=sqc-eu-token -Dsonar.analysis.buildNumber=42 -Dsonar.analysis.pipeline=dummy-run-id -Dsonar.analysis.repository=my-org/my-repo' The line 37 should equal '::endgroup::' The line 38 should equal '=== Completed Sonar analysis on all platforms ===' + The stderr should include "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" The status should be success End diff --git a/spec/build-yarn_spec.sh b/spec/build-yarn_spec.sh index f96de2d5..7e48d7ed 100755 --- a/spec/build-yarn_spec.sh +++ b/spec/build-yarn_spec.sh @@ -64,6 +64,7 @@ export SQC_US_URL="https://sonarqube-us.example.com" export SQC_US_TOKEN="sqc-us-token" export SQC_EU_URL="https://sonarcloud.io" export SQC_EU_TOKEN="sqc-eu-token" +export DEPLOY="true" export DEPLOY_PULL_REQUEST="false" SKIP_TESTS="false" DEFAULT_BRANCH="main" PULL_REQUEST="" common_setup() { @@ -351,7 +352,8 @@ Describe 'build-yarn/build.sh' export RUN_SHADOW_SCANS="true" When call get_build_config The status should be success - The output should include "======= Shadow scans enabled - disabling deployment to prevent duplicate artifacts =======" + The output should include "======= Building main branch =======" + The stderr should include "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" The variable BUILD_ENABLE_DEPLOY should equal "false" The variable BUILD_ENABLE_SONAR should equal "true" End @@ -381,6 +383,7 @@ Describe 'build-yarn/build.sh' The output should include "Sonar Platform: next" The output should include "shadow scan enabled" The output should not include "JFrog operations" + The stderr should include "::warning title=Deployment disabled::Shadow scans enabled - disabling deployment" End End @@ -420,11 +423,22 @@ Describe 'build-yarn/build.sh' The stderr should include "::warning title=No artifacts found::" End - It 'builds long-lived feature branch' + It 'builds long-lived feature branch with deploy' export GITHUB_REF_NAME="feature/long/test" GITHUB_EVENT_NAME="push" PROJECT="test" When call build_yarn The status should be success The output should include "======= Build long-lived feature branch =======" + The variable BUILD_ENABLE_DEPLOY should equal "true" + The stderr should include "::warning title=No artifacts found::" + End + + It 'skips deploy when DEPLOY is false' + export GITHUB_REF_NAME="main" GITHUB_EVENT_NAME="push" PROJECT="test" + export DEPLOY="false" + When call build_yarn + The status should be success + The output should include "======= Building main branch =======" + The variable BUILD_ENABLE_DEPLOY should equal "false" End End