Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .shellspec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# kcov (coverage) options
--kcov-options "--include-pattern=build-poetry,config-poetry,get-build-number,pr_cleanup,promote,build-gradle,config-maven,build-maven,config-npm,build-npm,build-yarn,shared,config-gradle,config-pip,update-release-channel,report-ci-metrics"
--kcov-options "--include-pattern=build-poetry,config-uv,config-poetry,get-build-number,pr_cleanup,promote,build-gradle,config-maven,build-maven,config-npm,build-npm,build-yarn,shared,config-gradle,config-pip,update-release-channel,report-ci-metrics"
# --kcov-options "--exclude-pattern=.github,.idea,.git"

# define minimum coverage (fail otherwise)
Expand Down
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ These badges show the status of workflows in dummy repositories that use (or sho
- [`build-npm`](#build-npm)
- [`build-yarn`](#build-yarn)
- [`config-pip`](#config-pip)
- [`config-uv`](#config-uv)
- [`promote`](#promote)
- [`pr_cleanup`](#pr_cleanup)
- [`code-signing`](#code-signing)
Expand Down Expand Up @@ -1257,6 +1258,89 @@ Both actions produce the same configuration and are functionally equivalent.

---

## `config-uv`

Configure uv build environment with build number, JFrog CLI authentication, and caching.

This action configures `jf config` for the Repox server.
It also sets native `UV_INDEX_*` credentials so uv resolves dependencies from Artifactory instead of PyPI.

There is no `jf uv-config` command. Configure indexes in `pyproject.toml` and run `jf uv` subcommands.
Comment thread
hedinasr marked this conversation as resolved.

See the [JFrog uv documentation](https://docs.jfrog.com/artifactory/docs/jf-uv).

Repositories using uv should declare the Repox index in `pyproject.toml`:

```toml
[[tool.uv.index]]
name = "repox"
url = "https://repox.jfrog.io/artifactory/api/pypi/sonarsource-pypi/simple"
default = true
```

> **Note:** This action automatically calls [`get-build-number`](#get-build-number) to manage the build number.

### Requirements

#### Required GitHub Permissions

- `id-token: write`
- `contents: read`

#### Required Vault Permissions

- `public-reader` or `private-reader`: Artifactory role for reading dependencies

#### Other Dependencies

The `uv` tool must be pre-installed. Use of `mise` is recommended.

### Usage

```yaml
permissions:
id-token: write
contents: read
steps:
Comment thread
hedinasr marked this conversation as resolved.
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
- uses: SonarSource/ci-github-actions/config-uv@v1
- run: jf uv sync
```

For build-info collection, pass `--build-name` and `--build-number` to `jf uv` and publish with `jf rt build-publish`.

### Inputs

| Input | Description | Default |
|---------------------------|-----------------------------------------------------------------------------|----------------------------------------------------------------------|
| `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 |
| `uv-index-name` | Name of the uv index in `pyproject.toml` to authenticate | `repox` |
| `repox-url` | URL for Repox | `https://repox.jfrog.io` |
| `uv-cache-dir` | Path to the uv cache directory, relative to GitHub workspace | `.cache/uv` |
| `disable-caching` | Whether to disable uv caching entirely | `false` |

### Outputs

| Output | Description |
|----------------|---------------------------------------------------------------------------|
| `BUILD_NUMBER` | The current build number. Also set as environment variable `BUILD_NUMBER` |

### Output Environment Variables

| Environment Variable | Description |
|---------------------------------|--------------------------|
| `BUILD_NUMBER` | The current build number |
| `UV_INDEX_REPOX_USERNAME` | Repox username for uv (`repox` index name) |
| `UV_INDEX_REPOX_PASSWORD` | Repox access token for uv |
| `UV_KEYRING_PROVIDER` | Set to `disabled` when index credentials are injected |
| `UV_CACHE_DIR` | Path to the uv cache directory |

See also [`get-build-number`](#get-build-number) output environment variables.

---

## `promote`

This action promotes a build in JFrog Artifactory and updates the GitHub status check accordingly.
Expand Down
143 changes: 143 additions & 0 deletions config-uv/action.yml
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
Comment thread
hedinasr marked this conversation as resolved.
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'
Comment thread
gitar-bot[bot] marked this conversation as resolved.
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::"
Comment thread
gitar-bot[bot] marked this conversation as resolved.

- 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 != 'true'
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 }}-

Comment thread
hedinasr marked this conversation as resolved.
- 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 thread
hedinasr marked this conversation as resolved.
Comment thread
hedinasr marked this conversation as resolved.

- 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
6 changes: 6 additions & 0 deletions config-uv/mise.local.toml
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*"
68 changes: 68 additions & 0 deletions config-uv/uv_config.sh
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 thread
hedinasr marked this conversation as resolved.
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 nonexistent configuration
jf config add repox --url "${ARTIFACTORY_URL%/artifactory*}" --artifactory-url "$ARTIFACTORY_URL" --access-token "$ARTIFACTORY_ACCESS_TOKEN"
jf config use repox
Comment thread
hedinasr marked this conversation as resolved.

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
Loading
Loading