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
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ No outputs are provided by this action.

## `build-poetry`

Build and publish a Python project using Poetry.
Build, analyze, and publish a Python project using Poetry with SonarQube integration and Artifactory deployment.

### Requirements

Expand Down Expand Up @@ -196,6 +196,15 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- 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
Comment thread
hedinasr marked this conversation as resolved.
sonar-platform: next # SonarQube platform (next, sqc-eu, or sqc-us)
```

### Inputs
Expand All @@ -213,15 +222,7 @@ jobs:

### Outputs

No outputs are provided by this action.

### Features

- Automated dependency management with Poetry
- Conditional deployment based on branch patterns
- Python virtual environment caching for faster builds
- SonarQube analysis integration (configurable)
- Comprehensive build logging and error handling
- `project-version`: The project version from pyproject.toml with build number. The same is also exposed as `PROJECT_VERSION` environment variable.

## `build-gradle`

Expand Down
16 changes: 11 additions & 5 deletions build-poetry/action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: Build Poetry
description: GitHub Action to build, analyze, and deploy a Python project using Poetry
description: GitHub Action to build, analyze, and deploy a Python project using Poetry with SonarQube integration
inputs:
public:
description: Whether to build and deploy with/to public repositories. Set to `true` for public repositories (OSS), `false` for private.
Expand All @@ -27,9 +27,14 @@ inputs:
description: URL for Repox
default: https://repox.jfrog.io
sonar-platform:
description: SonarQube primary platform (next, sqc-eu, or sqc-us) - currently not enabled in build script
description: SonarQube primary platform (next, sqc-eu, or sqc-us)
default: next

outputs:
project-version:
description: The project version from pyproject.toml with BUILD_NUMBER
value: ${{ env.PROJECT_VERSION }}

runs:
using: composite
steps:
Expand Down Expand Up @@ -77,14 +82,15 @@ runs:
# Action inputs
ARTIFACTORY_URL: ${{ inputs.repox-url }}/artifactory
DEPLOY_PULL_REQUEST: ${{ inputs.deploy-pull-request }}
# SonarQube integration (currently disabled in build script)
SONAR_HOST_URL: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_HOST_URL }}
SONAR_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_TOKEN }}
ARTIFACTORY_PYPI_REPO: ${{ inputs.public == 'true' && 'sonarsource-pypi' || 'sonarsource-pypi' }} # FIXME: sonarsource-pypi-public
ARTIFACTORY_DEPLOY_REPO: ${{ inputs.public == 'true' && 'sonarsource-pypi-public-qa' || 'sonarsource-pypi-private-qa' }}
ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }}
ARTIFACTORY_DEPLOY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN }}
POETRY_VIRTUALENVS_PATH: ${{ github.workspace }}/${{ inputs.poetry-virtualenvs-path }}
POETRY_CACHE_DIR: ${{ github.workspace }}/${{ inputs.poetry-cache-dir }}

# Vault secrets
SONAR_HOST_URL: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_HOST_URL }}
SONAR_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_TOKEN }}
run: |
${GITHUB_ACTION_PATH}/build.sh
161 changes: 130 additions & 31 deletions build-poetry/build.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
#!/bin/bash
# Build script for SonarSource Poetry projects.
# Supports building, testing, and JFrog Artifactory deployment.
# Supports building, testing, SonarQube analysis, and JFrog Artifactory deployment.
#
# Required inputs (must be explicitly provided):
# - BUILD_NUMBER: Build number for versioning
# - SONAR_HOST_URL: URL of SonarQube server
# - SONAR_TOKEN: Access token to send analysis reports to SonarQube
# - ARTIFACTORY_URL: URL to Artifactory repository
# - ARTIFACTORY_PYPI_REPO: Repository to install dependencies from
# - ARTIFACTORY_ACCESS_TOKEN: Access token to access the repository
# - ARTIFACTORY_DEPLOY_REPO: Deployment repository name
# - 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
# - PULL_REQUEST_SHA: Pull request base SHA or empty string
#
# GitHub Actions auto-provided:
# - GITHUB_REF_NAME: Git branch name
# - GITHUB_SHA: Git commit SHA
# - GITHUB_REPOSITORY: Repository name (e.g. sonarsource/sonar-dummy-poetry)
# - GITHUB_RUN_ID: GitHub Actions run ID
# - GITHUB_EVENT_NAME: Event name (e.g. push, pull_request)
# - GITHUB_EVENT_PATH: Path to the event webhook payload file
# - GITHUB_ENV: Path to GitHub Actions environment file
# - GITHUB_OUTPUT: Path to GitHub Actions output file
# - GITHUB_BASE_REF: Base branch for pull requests (only during pull_request events)
#
# Optional user customization:
# - DEPLOY_PULL_REQUEST: Whether to deploy pull request artifacts (default: false)
Expand All @@ -34,7 +38,8 @@ set -euo pipefail
: "${ARTIFACTORY_PYPI_REPO:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" "${ARTIFACTORY_DEPLOY_REPO:?}" "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}"
: "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" "${GITHUB_EVENT_PATH:?}"
: "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}"
: "${GITHUB_ENV:?}" "${GITHUB_OUTPUT:?}"
: "${GITHUB_ENV:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_SHA:?}" "${GITHUB_RUN_ID:?}"
: "${SONAR_HOST_URL:?}" "${SONAR_TOKEN:?}"
: "${DEPLOY_PULL_REQUEST:=false}"
export ARTIFACTORY_URL DEPLOY_PULL_REQUEST

Expand All @@ -47,13 +52,39 @@ check_tool() {
"$@"
}

git_fetch_unshallow() {
# The --filter=blob:none flag significantly speeds up the download
if git rev-parse --is-shallow-repository --quiet >/dev/null 2>&1; then
echo "Fetch Git references for SonarQube analysis..."
git fetch --unshallow --filter=blob:none
elif [ -n "${GITHUB_BASE_REF:-}" ]; then
echo "Fetch ${GITHUB_BASE_REF} for SonarQube analysis..."
git fetch --filter=blob:none origin "${GITHUB_BASE_REF}"
fi
}

run_sonar_scanner() {
local additional_params=("$@")

# Install pysonar into Poetry's virtual environment without modifying project files
poetry run pip install pysonar

poetry run pysonar \
-Dsonar.host.url="${SONAR_HOST_URL}" \
-Dsonar.token="${SONAR_TOKEN}" \
-Dsonar.analysis.buildNumber="${BUILD_NUMBER}" \
-Dsonar.analysis.pipeline="${GITHUB_RUN_ID}" \
-Dsonar.analysis.sha1="${GITHUB_SHA}" \
-Dsonar.analysis.repository="${GITHUB_REPOSITORY}" \
"${additional_params[@]}"
echo "SonarQube scanner finished"
}

# FIXME BUILD-8337? this is similar to source github-env <BUILD|BUILD-PRIVATE>
set_build_env() {
DEFAULT_BRANCH=${DEFAULT_BRANCH:=$(gh repo view --json defaultBranchRef --jq ".defaultBranchRef.name")}
export PROJECT=${GITHUB_REPOSITORY#*/}
echo "PROJECT: $PROJECT"
echo "PULL_REQUEST: $PULL_REQUEST"
export DEFAULT_BRANCH PULL_REQUEST PULL_REQUEST_SHA
git_fetch_unshallow
}

is_default_branch() {
Expand All @@ -72,16 +103,23 @@ is_dogfood_branch() {
[[ "${GITHUB_REF_NAME}" == "dogfood-on-"* ]]
}

# is_long_lived_feature_branch() {
# [[ "${GITHUB_REF_NAME}" == "feature/long/"* ]]
# }
is_long_lived_feature_branch() {
[[ "${GITHUB_REF_NAME}" == "feature/long/"* ]]
}

is_merge_queue_branch() {
[[ "${GITHUB_REF_NAME}" == "gh-readonly-queue/"* ]]
}

set_project_version() {
local current_version release_version digit_count

if ! current_version=$(poetry version -s); then
echo "Could not get version from Poetry project ('poetry version -s')" >&2
echo "$current_version" >&2
return 1
fi

release_version=${current_version%".dev"*}
# In case of 2 digits, we need to add a '0' as the 3rd digit.
digit_count=$(echo "${release_version//./ }" | wc -w)
Expand All @@ -100,6 +138,61 @@ set_project_version() {
echo "PROJECT_VERSION=$PROJECT_VERSION" >> "$GITHUB_ENV"
}

# Determine build configuration based on branch type
get_build_config() {
local enable_sonar enable_deploy
local sonar_args=()

if is_default_branch && ! is_pull_request; then
echo "======= Building main branch ======="

enable_sonar=true
enable_deploy=true

elif is_maintenance_branch && ! is_pull_request; then
echo "======= Building maintenance branch ======="

enable_sonar=true
enable_deploy=true
sonar_args=("-Dsonar.branch.name=${GITHUB_REF_NAME}")

elif is_pull_request; then
echo "======= Building pull request ======="

enable_sonar=true
sonar_args=("-Dsonar.analysis.prNumber=${PULL_REQUEST}")

if [ "${DEPLOY_PULL_REQUEST:-false}" == "true" ]; then
echo "======= with deploy ======="
enable_deploy=true
else
echo "======= no deploy ======="
enable_deploy=false
fi

elif is_dogfood_branch && ! is_pull_request; then
echo "======= Build dogfood branch ======="
enable_sonar=false
enable_deploy=true

elif is_long_lived_feature_branch && ! is_pull_request; then
echo "======= Build long-lived feature branch ======="
enable_sonar=true
enable_deploy=false
sonar_args=("-Dsonar.branch.name=${GITHUB_REF_NAME}")

else
echo "======= Build other branch ======="
enable_sonar=false
enable_deploy=false
fi

# Export the configuration for use by build_poetry
export BUILD_ENABLE_SONAR="$enable_sonar"
export BUILD_ENABLE_DEPLOY="$enable_deploy"
export BUILD_SONAR_ARGS="${sonar_args[*]:-}"
}

jfrog_poetry_install() {
jf config add repox --artifactory-url "$ARTIFACTORY_URL" --access-token "$ARTIFACTORY_ACCESS_TOKEN"
jf poetry-config --server-id-resolve repox --repo-resolve "$ARTIFACTORY_PYPI_REPO"
Expand All @@ -122,36 +215,42 @@ jfrog_poetry_publish() {
}

build_poetry() {
check_tool jq --version
check_tool python --version
check_tool poetry --version
check_tool jf --version
set_build_env
echo "=== Poetry Build, Deploy, and Analyze ==="
echo "Branch: ${GITHUB_REF_NAME}"
echo "Pull Request: ${PULL_REQUEST}"
echo "Deploy Pull Request: ${DEPLOY_PULL_REQUEST}"

set_project_version
get_build_config

echo "Installing dependencies..."
jfrog_poetry_install

echo "Building project..."
poetry build
if (is_pull_request && [[ "${DEPLOY_PULL_REQUEST:-}" == "true" ]]) || \
(! is_pull_request && (is_default_branch || is_maintenance_branch || is_dogfood_branch)); then

if [ "${BUILD_ENABLE_SONAR}" = "true" ]; then
read -ra sonar_args <<< "$BUILD_SONAR_ARGS"
run_sonar_scanner "${sonar_args[@]}"
fi

if [ "${BUILD_ENABLE_DEPLOY}" = "true" ]; then
jfrog_poetry_publish
fi

# run scanner?
#if is_main_branch && ! is_pull_request; then
# run_sonar_scanner \
# -Dsonar.projectVersion="$CURRENT_VERSION"
#elif is_maintenance_branch && ! is_pull_request; then
# run_sonar_scanner \
# -Dsonar.branch.name="$GITHUB_REF_NAME"
#elif is_pull_request; then
# run_sonar_scanner \
# -Dsonar.analysis.prNumber="$PULL_REQUEST"
#elif is_long_lived_feature_branch && ! is_pull_request; then
# run_sonar_scanner \
# -Dsonar.branch.name="$GITHUB_REF_NAME"
echo "=== Build completed successfully ==="
}

# run_sonar_scanner
main() {
check_tool jq --version
check_tool python --version
check_tool poetry --version
check_tool jf --version

set_build_env
build_poetry
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
build_poetry
main "$@"
fi
Loading
Loading