diff --git a/build-maven/action.yml b/build-maven/action.yml index 9c8db9f7..46156bdb 100644 --- a/build-maven/action.yml +++ b/build-maven/action.yml @@ -49,7 +49,10 @@ inputs: description: Maven flags for all subsequent mvn calls default: --batch-mode --no-transfer-progress --errors --fail-at-end --show-version -Dmaven.test.redirectTestOutputToFile=false repox-url: - description: URL for Repox + description: URL for Repox used for dependency resolution (Maven mirror) + default: https://repox.jfrog.io + repox-deploy-url: + description: URL for Repox used for artifact deployment. Defaults to SaaS Repox so deploy does not depend on edge Access Federation. default: https://repox.jfrog.io use-develocity: description: Whether to use Develocity for build tracking. @@ -125,6 +128,8 @@ runs: env: MIXED_PRIVACY: ${{ inputs.mixed-privacy }} USER_MAVEN_ARGS: ${{ inputs.maven-args }} + ARTIFACTORY_RESOLVE_URL: ${{ format('{0}/artifactory', inputs.repox-url) }} + ARTIFACTORY_DEPLOY_URL: ${{ format('{0}/artifactory', inputs.repox-deploy-url) }} ARTIFACTORY_DEPLOY_REPO: ${{ inputs.artifactory-deploy-repo != '' && inputs.artifactory-deploy-repo || (github.event.repository.visibility == 'public' || inputs.mixed-privacy == 'true') && 'sonarsource-public-qa' || 'sonarsource-private-qa' }} @@ -141,17 +146,21 @@ runs: fi echo "USER_MAVEN_ARGS=${USER_MAVEN_ARGS}" echo "ARTIFACTORY_DEPLOY_REPO=${ARTIFACTORY_DEPLOY_REPO}" + echo "ARTIFACTORY_DEPLOY_URL=${ARTIFACTORY_DEPLOY_URL}" echo "ARTIFACTORY_DEPLOY_USERNAME_VAULT=development/artifactory/token/{REPO_OWNER_NAME_DASH}-${ARTIFACTORY_DEPLOYER_ROLE} username | ARTIFACTORY_DEPLOY_USERNAME;" echo "ARTIFACTORY_DEPLOY_ACCESS_TOKEN_VAULT=development/artifactory/token/{REPO_OWNER_NAME_DASH}-${ARTIFACTORY_DEPLOYER_ROLE} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;" } >> "$GITHUB_OUTPUT" - echo "SONARSOURCE_REPOSITORY_URL=${ARTIFACTORY_URL}/sonarsource" >> "$GITHUB_ENV" + # Downloads (Maven settings mirror) always use repox-url; uploads use ARTIFACTORY_DEPLOY_URL / repox-deploy-url. + echo "ARTIFACTORY_URL=${ARTIFACTORY_RESOLVE_URL}" >> "$GITHUB_ENV" + echo "SONARSOURCE_REPOSITORY_URL=${ARTIFACTORY_RESOLVE_URL}/sonarsource" >> "$GITHUB_ENV" + echo "Artifactory resolve (download): ${ARTIFACTORY_RESOLVE_URL}" + echo "Artifactory deploy (upload): ${ARTIFACTORY_DEPLOY_URL}" # yamllint enable rule:line-length - uses: SonarSource/vault-action-wrapper@881045d830534a70ec3c7c275fa3714412c8ff6e # 3.6.1 if: inputs.deploy != 'false' id: artifactory with: - url: ${{ contains(inputs.repox-url, 'dev.sonar.build') && 'https://vault.dev.sonar.build' || 'https://vault.sonar.build' }} # yamllint disable rule:line-length secrets: | ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && steps.params.outputs.ARTIFACTORY_DEPLOY_USERNAME_VAULT || '' }} @@ -190,6 +199,8 @@ runs: SONAR_PLATFORM: ${{ inputs.sonar-platform }} RUN_SHADOW_SCANS: ${{ inputs.run-shadow-scans }} ARTIFACTORY_DEPLOY_REPO: ${{ steps.params.outputs.ARTIFACTORY_DEPLOY_REPO }} + # Upload target for the Artifactory Maven plugin / parent POM (downloads still use SONARSOURCE_REPOSITORY_URL). + ARTIFACTORY_URL: ${{ steps.params.outputs.ARTIFACTORY_DEPLOY_URL }} # Vault secrets # yamllint disable rule:line-length @@ -214,6 +225,7 @@ runs: inputs.mixed-privacy == 'true' && steps.build.outputs.deployed == 'true' && (github.event_name != 'pull_request' || inputs.deploy-pull-request == 'true') env: + ARTIFACTORY_URL: ${{ steps.params.outputs.ARTIFACTORY_DEPLOY_URL }} ARTIFACTORY_DEPLOY_REPO: ${{ steps.params.outputs.ARTIFACTORY_DEPLOY_REPO }} # yamllint disable rule:line-length ARTIFACTORY_DEPLOY_ACCESS_TOKEN: ${{ steps.artifactory.outputs.vault && fromJSON(steps.artifactory.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN || '' }} @@ -246,6 +258,7 @@ runs: if: always() && inputs.generate-summary != 'false' shell: bash env: + ARTIFACTORY_URL: ${{ steps.params.outputs.ARTIFACTORY_DEPLOY_URL }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary run: | build_name="${GITHUB_REPOSITORY#*/}" diff --git a/build-npm/action.yml b/build-npm/action.yml index 6bd09439..f6677443 100644 --- a/build-npm/action.yml +++ b/build-npm/action.yml @@ -33,7 +33,10 @@ inputs: description: Deprecated. Use `disable-caching` instead. Whether to cache NPM dependencies. default: 'true' repox-url: - description: URL for Repox + description: URL for Repox used for dependency resolution (npm registry) + default: https://repox.jfrog.io + repox-deploy-url: + description: URL for Repox used for artifact deployment. Defaults to SaaS Repox so deploy does not depend on edge Access Federation. default: https://repox.jfrog.io sonar-platform: description: SonarQube primary platform (next, sqc-eu, sqc-us, or none). Use 'none' to skip sonar scans. @@ -135,7 +138,6 @@ runs: if: inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' id: artifactory with: - url: ${{ contains(inputs.repox-url, 'dev.sonar.build') && 'https://vault.dev.sonar.build' || 'https://vault.sonar.build' }} # yamllint disable rule:line-length secrets: | ${{ format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;', env.ARTIFACTORY_DEPLOYER_ROLE) }} @@ -162,7 +164,8 @@ runs: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} BUILD_NAME: ${{ inputs.build-name || github.event.repository.name }} - ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-url) }} + # Upload target for jf npm publish (downloads still use ~/.npmrc from config-npm / repox-url). + ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-deploy-url) }} ARTIFACTORY_DEPLOY_REPO: ${{ inputs.artifactory-deploy-repo != '' && inputs.artifactory-deploy-repo || (github.event.repository.visibility == 'public' && 'sonarsource-npm-public-qa' || 'sonarsource-npm-private-qa') }} # yamllint disable rule:line-length @@ -215,7 +218,7 @@ runs: if: always() && inputs.generate-summary != 'false' shell: bash env: - ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-url) }} + ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-deploy-url) }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary run: | build_name="${GITHUB_REPOSITORY#*/}" diff --git a/config-maven/action.yml b/config-maven/action.yml index 2738ea18..9ee8ee04 100644 --- a/config-maven/action.yml +++ b/config-maven/action.yml @@ -93,7 +93,6 @@ runs: if: steps.config-maven-completed.outputs.skip != 'true' id: artifactory with: - url: ${{ contains(inputs.repox-url, 'dev.sonar.build') && 'https://vault.dev.sonar.build' || 'https://vault.sonar.build' }} secrets: | 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; @@ -140,6 +139,14 @@ runs: echo "DEVELOCITY_ACCESS_KEY=${{ steps.develocity-hostname.outputs.hostname }}=$DEVELOCITY_TOKEN" >> "$GITHUB_ENV" fi + # Temporary: Access Federation can lag when SaaS-minted tokens are used on an edge node. + - name: Wait for Artifactory token to sync + if: steps.config-maven-completed.outputs.skip != 'true' + shell: bash + env: + REPOX_URL: ${{ inputs.repox-url }} + run: "$(dirname "$ACTION_PATH_CONFIG_MAVEN")/shared/wait_artifactory_token_sync.sh" + - name: Configure Maven settings and set repository URL if: steps.config-maven-completed.outputs.skip != 'true' shell: bash diff --git a/config-npm/action.yml b/config-npm/action.yml index 451034af..dd76c518 100644 --- a/config-npm/action.yml +++ b/config-npm/action.yml @@ -16,7 +16,7 @@ inputs: description: Deprecated. Use `disable-caching` instead. Whether to cache NPM dependencies. default: 'true' repox-url: - description: URL for Repox + description: URL for Repox used for dependency resolution default: https://repox.jfrog.io host-actions-root: description: Path to the actions folder on the host (used when called from another local action) @@ -93,11 +93,21 @@ runs: if: steps.config-npm-completed.outputs.skip != 'true' id: secrets with: - url: ${{ contains(inputs.repox-url, 'dev.sonar.build') && 'https://vault.dev.sonar.build' || 'https://vault.sonar.build' }} secrets: | 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; + # Temporary: Access Federation can lag when SaaS-minted tokens are used on an edge node. + - name: Wait for Artifactory token to sync + if: steps.config-npm-completed.outputs.skip != 'true' + shell: bash + env: + REPOX_URL: ${{ inputs.repox-url }} + ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-url) }} + ARTIFACTORY_USERNAME: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_USERNAME || '' }} + ARTIFACTORY_ACCESS_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN || '' }} + run: "$(dirname "$ACTION_PATH_CONFIG_NPM")/shared/wait_artifactory_token_sync.sh" + - name: Configure NPM authentication if: steps.config-npm-completed.outputs.skip != 'true' shell: bash diff --git a/config-uv/action.yml b/config-uv/action.yml index 6d4fbfbf..db3de8b5 100644 --- a/config-uv/action.yml +++ b/config-uv/action.yml @@ -13,7 +13,7 @@ inputs: description: Name of the uv index in pyproject.toml to authenticate (e.g. repox for `[[tool.uv.index]]` with `name = "repox"`) default: repox repox-url: - description: URL for Repox + description: URL for Repox used for dependency resolution default: https://repox.jfrog.io uv-cache-dir: description: Path to the uv cache directory, relative to GitHub workspace @@ -106,11 +106,21 @@ runs: if: steps.config-uv-completed.outputs.skip != 'true' id: secrets with: - url: ${{ contains(inputs.repox-url, 'dev.sonar.build') && 'https://vault.dev.sonar.build' || 'https://vault.sonar.build' }} secrets: | 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; + # Temporary: Access Federation can lag when SaaS-minted tokens are used on an edge node. + - name: Wait for Artifactory token to sync + if: steps.config-uv-completed.outputs.skip != 'true' + shell: bash + env: + REPOX_URL: ${{ inputs.repox-url }} + ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-url) }} + ARTIFACTORY_USERNAME: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_USERNAME }} + ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }} + run: "$(dirname "$ACTION_PATH_CONFIG_UV")/shared/wait_artifactory_token_sync.sh" + - name: Configure uv authentication if: steps.config-uv-completed.outputs.skip != 'true' shell: bash diff --git a/shared/wait_artifactory_token_sync.sh b/shared/wait_artifactory_token_sync.sh new file mode 100755 index 00000000..44ede870 --- /dev/null +++ b/shared/wait_artifactory_token_sync.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Wait until a SaaS-minted Artifactory token is accepted by an edge node. +# +# /api/system/ping is anonymous; use a protected storage API that validates the token. +# +# Required environment variables: +# - ARTIFACTORY_URL: Artifactory base URL (…/artifactory) +# - ARTIFACTORY_USERNAME: Artifactory username +# - ARTIFACTORY_ACCESS_TOKEN: Artifactory access token +# +# Optional: +# - REPOX_URL: Instance URL used to skip the wait for SaaS (jfrog.io) + +set -euo pipefail + +REPOX_URL="${REPOX_URL:-${ARTIFACTORY_URL:-}}" +if [[ "$REPOX_URL" == *jfrog.io* ]]; then + echo "Skipping token sync wait for SaaS Repox ($REPOX_URL)" + exit 0 +fi + +if [[ -z "${ARTIFACTORY_URL:-}" || -z "${ARTIFACTORY_USERNAME:-}" || -z "${ARTIFACTORY_ACCESS_TOKEN:-}" ]]; then + echo "::error title=Missing Artifactory credentials::Cannot wait for token sync without credentials" + exit 1 +fi + +check_url="${ARTIFACTORY_URL%/}/api/storage/sonarsource-qa" +max_attempts=60 +sleep_seconds=5 +attempt=0 + +echo "Waiting for Artifactory token federation sync at $check_url (up to $((max_attempts * sleep_seconds))s)" + +while true; do + attempt=$((attempt + 1)) + http_code="$(curl -sS -o /dev/null -w '%{http_code}' \ + -u "${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}" \ + "$check_url" || echo "000")" + + if [[ "$http_code" == "200" ]]; then + echo "Artifactory accepted credentials after ${attempt} attempt(s) (HTTP 200)" + exit 0 + fi + + if (( attempt >= max_attempts )); then + echo "::error title=Artifactory token sync timeout::Credentials were not accepted by $check_url" \ + "within $((max_attempts * sleep_seconds))s (last HTTP ${http_code})" + exit 1 + fi + + echo "Attempt ${attempt}/${max_attempts}: HTTP ${http_code}, retrying in ${sleep_seconds}s..." + sleep "$sleep_seconds" +done