BUILD-11568 Add config-uv action#293
Conversation
Extract uv index credentials and caching into a reusable composite action so workflows can authenticate against the repox PyPI index.
There is no jf uv-config command. Configure jf config for Repox, set native UV_INDEX_* credentials, and document jf uv usage.
| 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/_*$//') |
There was a problem hiding this comment.
💡 Edge Case: Degenerate uv-index-name yields malformed env var names
index_name_upper is built by uppercasing the index name and replacing non-alphanumerics with _, then stripping only trailing underscores via sed 's/_*$//'. For a normal name like repox or my-index this works (and the trailing-underscore strip correctly removes the _ produced from echo's newline). However, edge inputs are not guarded: an empty or all-symbol uv-index-name produces UV_INDEX__USERNAME/UV_INDEX__PASSWORD, and a name beginning with a digit (e.g. 2repox) produces UV_INDEX_2REPOX_.... uv would then look up a differently-named variable and authentication would silently fall back to anonymous/public PyPI access, causing confusing 401/resolution failures rather than a clear error. Consider validating that the sanitized index name is non-empty (and optionally that it matches uv's own naming rules) and failing fast with a descriptive message if not.
Strip leading underscores too and fail fast when the sanitized name is empty.:
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/_*$//;s/^_*//')
if [[ -z "$index_name_upper" ]]; then
echo "::error::Invalid uv-index-name '${UV_INDEX_NAME}': no usable characters for env var name" >&2
return 1
fi
- Apply fix
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎
| { | ||
| echo "UV_INDEX_${index_name_upper}_USERNAME=$ARTIFACTORY_USERNAME" | ||
| echo "UV_INDEX_${index_name_upper}_PASSWORD=$ARTIFACTORY_ACCESS_TOKEN" | ||
| } >> "$GITHUB_ENV" |
There was a problem hiding this comment.
💡 Security: Artifactory token persisted to GITHUB_ENV for whole job
Unlike config-pip, which writes the token into a local pip.conf file, config-uv writes UV_INDEX_<NAME>_PASSWORD=<access_token> into GITHUB_ENV. This is required so that later uv sync steps can authenticate, but be aware it persists the credential as an environment variable visible to every subsequent step in the job, including any third-party actions invoked afterward. The value is masked in logs only because the vault-action-wrapper registers the secret with ::add-mask::; the persistence itself broadens the exposure surface compared to a file written under $HOME. This is largely inherent to how uv consumes index credentials, so no change may be possible, but it is worth documenting the trade-off and ensuring no untrusted steps run later in the same job.
Was this helpful? React with 👍 / 👎
| 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::" |
There was a problem hiding this comment.
⚠️ Bug: mise backup/restore use mismatched working directories
The mise backup logic and the restore logic run in different directories, which breaks when inputs.working-directory is not the default ..
- The "Set local action paths" step (lines 68-73) has no
working-directory, so it runs at the workspace root: it moves existing mise files into$mise_backupandcpsmise.local.tomlinto the workspace root. - The "Configure uv authentication" step (lines 124-131) sets
working-directory: ${{ inputs.working-directory }}, so the restore block (rm mise.local.toml,mv "$MISE_BACKUP"/* ./ ...) executes inside that subdirectory.
When working-directory is 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.- Even if it didn't fail, the backed-up mise files would be restored into the wrong directory, leaving the workspace root permanently polluted with
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 remove working-directory from the configure step and have uv_config.sh/the build handle the path. Make sure the cp target and the rm/mv operate 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.:
# In the "Configure uv authentication" step, drop the working-directory so
# backup (workspace root) and restore happen in the same place, OR run the
# backup at inputs.working-directory. Simplest: keep both at workspace root.
#
# backup step (no working-directory) already at workspace root -> keep restore there too:
- name: Configure uv authentication
if: steps.config-uv-completed.outputs.skip != 'true'
shell: bash
env:
...
run: |
(cd "${{ inputs.working-directory }}" && $ACTION_PATH_CONFIG_UV/uv_config.sh)
echo "::group::Restore mise files"
rm mise.local.toml
mv "${{ steps.set-path.outputs.MISE_BACKUP }}"/* ./ 2>/dev/null || true
- Apply fix
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎
| 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::" |
There was a problem hiding this comment.
💡 Bug: mise restore not run when uv_config.sh fails or action errors
The mise restore block is appended after $ACTION_PATH_CONFIG_UV/uv_config.sh in the same run: step and is not guarded by always(). Because the step uses -eo pipefail, if uv_config.sh exits non-zero the restore lines never execute. This leaves the workspace with mise.local.toml copied in and the repo's original mise files (mise., .mise., .tool-versions) stranded in the temp backup directory. In self-hosted or reused checkouts this corrupts subsequent steps/runs. Consider moving the restore into a separate step with if: always() (and tolerating a missing backup), so cleanup happens even on failure.
Move restore into a dedicated always() step and use .[!.] to avoid matching . and .. when restoring dotfiles.:*
- name: Restore mise files
if: always() && steps.config-uv-completed.outputs.skip != 'true'
shell: bash
run: |
echo "::group::Restore mise files"
rm -f mise.local.toml
backup="${{ steps.set-path.outputs.MISE_BACKUP }}"
if [[ -n "$backup" && -d "$backup" ]]; then
mv "$backup"/* "$backup"/.[!.]* ./ 2>/dev/null || true
rmdir "$backup" 2>/dev/null || true
fi
echo "::endgroup::"
- Apply fix
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎
Route npm through Repox before mise installs markdownlint-cli on sonar-xs runners that cannot reach registry.npmjs.org directly.
|
Code Review
|
| Auto-apply | Compact | Unblock |
|
|
|
Was this helpful? React with 👍 / 👎 | Gitar



Summary
config-uvcomposite action to fetch Artifactory reader credentials from Vault and exposeUV_INDEX_*authentication for the Repox PyPI index.cache/uvkeyed onuv.lockandpyproject.tomlTest plan
shellspec spec/config-uv_spec.shpasses locally (5 examples)