-
Notifications
You must be signed in to change notification settings - Fork 1
BUILD-11568 Add config-uv action #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| --- | ||
| name: Config uv | ||
| description: GitHub Action to configure uv build environment with build number, Artifactory authentication, and caching | ||
| inputs: | ||
| working-directory: | ||
| description: Relative path under github.workspace to execute the build in | ||
| default: . | ||
| artifactory-reader-role: | ||
| description: Suffix for the Artifactory reader role in Vault. Defaults to `private-reader` for private repositories, and `public-reader` | ||
| for public repositories. | ||
| default: '' | ||
| uv-index-name: | ||
| 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 | ||
| default: https://repox.jfrog.io | ||
| uv-cache-dir: | ||
| description: Path to the uv cache directory, relative to GitHub workspace | ||
| default: .cache/uv | ||
| disable-caching: | ||
| description: Whether to disable uv caching entirely | ||
| default: 'false' | ||
| host-actions-root: | ||
| description: Path to the actions folder on the host (used when called from another local action) | ||
| default: '' | ||
|
|
||
| outputs: | ||
| BUILD_NUMBER: | ||
| description: The current build number. Also set as environment variable BUILD_NUMBER | ||
| value: ${{ steps.get-build-number.outputs.BUILD_NUMBER }} | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - id: config-uv-completed | ||
| if: env.CONFIG_UV_COMPLETED != '' | ||
| shell: bash | ||
| run: | | ||
| echo "Action already called by $CONFIG_UV_COMPLETED, execution skipped." | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Set local action paths | ||
| id: set-path | ||
| if: steps.config-uv-completed.outputs.skip != 'true' | ||
| shell: bash | ||
| 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_UV="${{ github.action_path }}" | ||
| host_actions_root="${{ inputs.host-actions-root }}" | ||
| if [[ -z "$host_actions_root" ]]; then | ||
| host_actions_root="$(dirname "$ACTION_PATH_CONFIG_UV")" | ||
| else | ||
| ACTION_PATH_CONFIG_UV="$host_actions_root/config-uv" | ||
| fi | ||
| echo "ACTION_PATH_CONFIG_UV=$ACTION_PATH_CONFIG_UV" | ||
| echo "ACTION_PATH_CONFIG_UV=$ACTION_PATH_CONFIG_UV" >> "$GITHUB_ENV" | ||
| echo "host_actions_root=$host_actions_root" >> "$GITHUB_OUTPUT" | ||
|
|
||
| mkdir -p ".actions" | ||
| ln -sf "$host_actions_root/get-build-number" .actions/get-build-number | ||
| ln -sf "$host_actions_root/shared" .actions/shared | ||
| ls -la .actions/* | ||
| echo "::endgroup::" | ||
|
|
||
| echo "::group::Backup mise files to configure uv without interference" | ||
| mise_backup=$(mktemp -d) | ||
| echo "MISE_BACKUP=$mise_backup" >> "$GITHUB_OUTPUT" | ||
| mv mise.* .mise.* mise/ .mise/ .tool-versions "$mise_backup/" 2>/dev/null || true | ||
| cp "$ACTION_PATH_CONFIG_UV/mise.local.toml" mise.local.toml | ||
| echo "::endgroup::" | ||
|
|
||
| - uses: ./.actions/get-build-number | ||
| id: get-build-number | ||
| if: steps.config-uv-completed.outputs.skip != 'true' | ||
| with: | ||
| host-actions-root: ${{ steps.set-path.outputs.host_actions_root }} | ||
|
|
||
| - name: Set Artifactory reader role | ||
| if: steps.config-uv-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') }} | ||
| run: | | ||
| echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Cache uv dependencies | ||
| uses: SonarSource/gh-action_cache@a7d13cdd1c9f097a5f8382ccec463be2831e3dbc # v1.6.0 | ||
| if: steps.config-uv-completed.outputs.skip != 'true' && inputs.disable-caching == 'false' | ||
| with: | ||
| path: ${{ github.workspace }}/${{ inputs.uv-cache-dir }} | ||
| key: uv-${{ runner.os }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory), | ||
| format('{0}/pyproject.toml', inputs.working-directory)) }} | ||
| restore-keys: uv-${{ runner.os }}- | ||
|
|
||
| - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 | ||
| if: steps.config-uv-completed.outputs.skip != 'true' | ||
| with: | ||
| version: 2026.5.9 | ||
|
|
||
| - uses: SonarSource/vault-action-wrapper@0a3114fe1230b784c35b53b099f9ab1f1e538cc7 # 3.5.0 | ||
| 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; | ||
|
|
||
| - name: Configure uv authentication | ||
| if: steps.config-uv-completed.outputs.skip != 'true' | ||
| shell: bash | ||
| working-directory: ${{ inputs.working-directory }} | ||
| env: | ||
| 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 }} | ||
| UV_INDEX_NAME: ${{ inputs.uv-index-name }} | ||
| UV_CACHE_DIR: ${{ github.workspace }}/${{ inputs.uv-cache-dir }} | ||
| run: | | ||
| $ACTION_PATH_CONFIG_UV/uv_config.sh | ||
|
|
||
| echo "::group::Restore mise files" | ||
| rm mise.local.toml | ||
| mv "${{ steps.set-path.outputs.MISE_BACKUP }}"/* "${{ steps.set-path.outputs.MISE_BACKUP }}"/.* ./ 2>/dev/null || true | ||
| rmdir "${{ steps.set-path.outputs.MISE_BACKUP }}" | ||
| echo "::endgroup::" | ||
|
Comment on lines
+124
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Bug: mise restore not run when uv_config.sh fails or action errorsThe mise restore block is appended after Move restore into a dedicated always() step and use .[!.] to avoid matching . and .. when restoring dotfiles.:*
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎 |
||
|
|
||
| - name: Set Config uv completed | ||
| if: steps.config-uv-completed.outputs.skip != 'true' | ||
| shell: bash | ||
| run: echo "CONFIG_UV_COMPLETED=$GITHUB_ACTION" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Clean up local action symlinks | ||
| if: always() && steps.config-uv-completed.outputs.skip != 'true' | ||
| shell: bash | ||
| run: | | ||
| rm -f .actions/get-build-number .actions/shared | ||
| rmdir .actions 2>/dev/null || true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [tools] | ||
| jfrog-cli = "2.96.0" | ||
|
|
||
| [env] | ||
| JFROG_CLI_AVOID_NEW_VERSION_WARNING = "true" | ||
| JFROG_CLI_ENV_EXCLUDE = "*password*;*secret*;*key*;*token*;*auth*;*credential*" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| #!/bin/bash | ||
| # Config script for uv to use SonarSource Artifactory via JFrog CLI. | ||
| # | ||
| # There is no `jf uv-config` command. Configure `jf config` and declare | ||
| # `[[tool.uv.index]]` entries in pyproject.toml, then run `jf uv` subcommands. | ||
| # See https://docs.jfrog.com/artifactory/docs/jf-uv | ||
| # | ||
| # Required environment variables (must be explicitly provided): | ||
| # - ARTIFACTORY_URL: URL to Artifactory repository | ||
| # - ARTIFACTORY_USERNAME: Username for Artifactory authentication | ||
| # - ARTIFACTORY_ACCESS_TOKEN: Access token to read Repox repositories | ||
| # - UV_INDEX_NAME: Name of the uv index in pyproject.toml (e.g. repox) | ||
| # | ||
| # Optional environment variables: | ||
| # - UV_CACHE_DIR: Path to the uv cache directory | ||
| # | ||
| # GitHub Actions auto-provided: | ||
| # - GITHUB_ENV: Path to GitHub Actions environment file | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| : "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_USERNAME:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" "${UV_INDEX_NAME:?}" "${GITHUB_ENV:?}" | ||
|
|
||
| uv_index_env_suffix() { | ||
| local index_name_upper | ||
|
|
||
| index_name_upper=$(echo "$UV_INDEX_NAME" | tr '[:lower:]' '[:upper:]') | ||
| index_name_upper=$(echo "$index_name_upper" | tr -c 'A-Za-z0-9' '_' | sed 's/_*$//') | ||
|
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Edge Case: Degenerate uv-index-name yields malformed env var names
Strip leading underscores too and fail fast when the sanitized name is empty.:
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎 |
||
| echo "$index_name_upper" | ||
| } | ||
|
|
||
| configure_uv_repox() { | ||
| local index_name_upper | ||
|
|
||
| echo "Configuring uv to use Artifactory via JFrog CLI..." | ||
|
|
||
| jf config remove repox > /dev/null 2>&1 || true # Ignore inexistent configuration | ||
| jf config add repox --url "${ARTIFACTORY_URL%/artifactory*}" --artifactory-url "$ARTIFACTORY_URL" --access-token "$ARTIFACTORY_ACCESS_TOKEN" | ||
| jf config use repox | ||
|
|
||
| index_name_upper=$(uv_index_env_suffix) | ||
|
|
||
| # Native uv variables for named indexes. Also set explicitly so plain `uv` works; | ||
| # `jf uv` injects the same variables when they are unset. | ||
| { | ||
| echo "UV_INDEX_${index_name_upper}_USERNAME=$ARTIFACTORY_USERNAME" | ||
| echo "UV_INDEX_${index_name_upper}_PASSWORD=$ARTIFACTORY_ACCESS_TOKEN" | ||
| echo "UV_KEYRING_PROVIDER=disabled" | ||
| } >> "$GITHUB_ENV" | ||
|
|
||
| if [[ -n "${UV_CACHE_DIR:-}" ]]; then | ||
| echo "UV_CACHE_DIR=$UV_CACHE_DIR" >> "$GITHUB_ENV" | ||
| mkdir -p "$UV_CACHE_DIR" | ||
| fi | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| main() { | ||
| echo "::group::Configure uv" | ||
| configure_uv_repox | ||
| echo "::endgroup::" | ||
| return 0 | ||
| } | ||
|
|
||
| if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then | ||
| main | ||
| fi | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mise backup logic and the restore logic run in different directories, which breaks when
inputs.working-directoryis not the default..working-directory, so it runs at the workspace root: it moves existing mise files into$mise_backupandcpsmise.local.tomlinto the workspace root.working-directory: ${{ inputs.working-directory }}, so the restore block (rm mise.local.toml,mv "$MISE_BACKUP"/* ./ ...) executes inside that subdirectory.When
working-directoryis a subdirectory:rm mise.local.tomlfails because the file was created at the workspace root, not the subdirectory. GitHub runsshell: bashwith-eo pipefail, so this aborts the step and fails the whole action.mise.local.tomland missing its original mise files.Perform the backup and restore in the same directory. Either add
working-directory: ${{ inputs.working-directory }}to the backup step, or removeworking-directoryfrom the configure step and haveuv_config.sh/the build handle the path. Make sure thecptarget and therm/mvoperate on the same location.Run uv_config.sh in the working-directory but keep the mise restore at the workspace root where the backup/cp occurred.:
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎