diff --git a/.shellspec b/.shellspec index 149a2dfd..81e4f39c 100644 --- a/.shellspec +++ b/.shellspec @@ -1,5 +1,5 @@ # kcov (coverage) options ---kcov-options "--include-pattern=build-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" +--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" # --kcov-options "--exclude-pattern=.github,.idea,.git" # define minimum coverage (fail otherwise) diff --git a/README.md b/README.md index 9c7d0873..eed49552 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ These badges show the status of workflows in dummy repositories that use (or sho - [`get-build-number`](#get-build-number) - [`config-maven`](#config-maven) - [`build-maven`](#build-maven) +- [`config-poetry`](#config-poetry) - [`build-poetry`](#build-poetry) - [`config-gradle`](#config-gradle) - [`build-gradle`](#build-gradle) @@ -364,10 +365,77 @@ See also [`config-maven`](#config-maven) output environment variables. --- +## `config-poetry` + +Configure Poetry build environment with build number, JFrog authentication, and caching. + +This action configures Poetry to pull packages from the internal JFrog Artifactory registry instead of the public PyPI. + +> **Note:** This action automatically calls [`get-build-number`](#get-build-number) to manage the build number. + +### Requirements + +#### Required GitHub Permissions + +- `id-token: write` +- `contents: write` + +#### Required Vault Permissions + +- `public-reader` or `private-reader`: Artifactory role for reading dependencies + +#### Other Dependencies + +The Python and Poetry tools must be pre-installed. Use of `mise` is recommended. + +### Usage + +```yaml +permissions: + id-token: write + contents: write +steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: SonarSource/ci-github-actions/config-poetry@v1 + - run: poetry install +``` + +### 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 | +| `artifactory-pypi-repo` | PyPI virtual repository to resolve dependencies from | `sonarsource-pypi` | +| `repox-url` | URL for Repox | `https://repox.jfrog.io` | +| `poetry-virtualenvs-path` | Path to the Poetry virtual environments, relative to GitHub workspace | `.cache/pypoetry/virtualenvs` | +| `poetry-cache-dir` | Path to the Poetry cache directory, relative to GitHub workspace | `.cache/pypoetry` | +| `disable-caching` | Whether to disable Poetry 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 | +| `POETRY_HTTP_BASIC_REPOX_USERNAME` | Repox username for Poetry | +| `POETRY_HTTP_BASIC_REPOX_PASSWORD` | Repox access token for Poetry | + +See also [`get-build-number`](#get-build-number) output environment variables. + +--- + ## `build-poetry` Build, analyze, and publish a Python project using Poetry with SonarQube integration and Artifactory deployment. +> **Note:** This action automatically calls [`config-poetry`](#config-poetry) to set up the Poetry environment. + ### Requirements #### Required GitHub Permissions diff --git a/build-poetry/action.yml b/build-poetry/action.yml index 11295471..8d85c842 100644 --- a/build-poetry/action.yml +++ b/build-poetry/action.yml @@ -64,7 +64,7 @@ outputs: value: ${{ steps.build.outputs.project-version }} BUILD_NUMBER: description: The build number, incremented or reused if already cached - value: ${{ steps.get_build_number.outputs.BUILD_NUMBER }} + value: ${{ steps.config.outputs.BUILD_NUMBER }} deployed: description: Whether artifacts were deployed value: ${{ steps.build.outputs.deployed }} @@ -87,6 +87,7 @@ runs: mkdir -p ".actions" ln -sf "$host_actions_root/get-build-number" .actions/get-build-number + ln -sf "$host_actions_root/config-poetry" .actions/config-poetry ln -sf "$host_actions_root/shared" .actions/shared ls -la .actions/* echo "::endgroup::" @@ -103,29 +104,29 @@ runs: echo "ARTIFACTORY_DEPLOYER_ROLE=${ARTIFACTORY_DEPLOYER_ROLE}" >> "$GITHUB_ENV" cp "$ACTION_PATH_BUILD_POETRY/mise.local.toml" mise.local.toml - - uses: ./.actions/get-build-number - id: get_build_number - with: - host-actions-root: ${{ steps.set-path.outputs.host_actions_root }} - - name: Cache local Poetry cache - uses: SonarSource/gh-action_cache@a7d13cdd1c9f097a5f8382ccec463be2831e3dbc # v1.6.0 - if: inputs.disable-caching == 'false' - with: - path: ${{ github.workspace }}/${{ inputs.poetry-cache-dir }} - key: poetry-${{ runner.os }}-${{ hashFiles('poetry.lock') }} - restore-keys: poetry-${{ runner.os }}- - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: version: 2026.5.9 + + - uses: ./.actions/config-poetry + id: config + with: + host-actions-root: ${{ steps.set-path.outputs.host_actions_root }} + artifactory-reader-role: ${{ env.ARTIFACTORY_READER_ROLE }} + repox-url: ${{ inputs.repox-url }} + working-directory: ${{ inputs.working-directory }} + poetry-cache-dir: ${{ inputs.poetry-cache-dir }} + poetry-virtualenvs-path: ${{ inputs.poetry-virtualenvs-path }} + disable-caching: ${{ inputs.disable-caching }} + - uses: SonarSource/vault-action-wrapper@0a3114fe1230b784c35b53b099f9ab1f1e538cc7 # 3.5.0 + 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: | - development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} access_token | ARTIFACTORY_ACCESS_TOKEN; - development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} username | ARTIFACTORY_USERNAME; - ${{ inputs.deploy != 'false' && inputs.run-shadow-scans != 'true' && format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;', env.ARTIFACTORY_DEPLOYER_ROLE) || '' }} + ${{ format('development/artifactory/token/{{REPO_OWNER_NAME_DASH}}-{0} access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;', env.ARTIFACTORY_DEPLOYER_ROLE) }} # yamllint enable rule:line-length - uses: SonarSource/vault-action-wrapper@0a3114fe1230b784c35b53b099f9ab1f1e538cc7 # 3.5.0 id: secrets @@ -152,12 +153,12 @@ runs: ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-url) }} DEPLOY: ${{ inputs.deploy }} DEPLOY_PULL_REQUEST: ${{ inputs.deploy-pull-request }} - ARTIFACTORY_PYPI_REPO: ${{ inputs.public == 'true' && 'sonarsource-pypi' || 'sonarsource-pypi' }} # FIXME: sonarsource-pypi-public ARTIFACTORY_DEPLOY_REPO: ${{ inputs.artifactory-deploy-repo != '' && inputs.artifactory-deploy-repo || github.event.repository.visibility == 'public' && 'sonarsource-pypi-public-qa' || 'sonarsource-pypi-private-qa' }} - ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.artifactory.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }} - ARTIFACTORY_USERNAME: ${{ fromJSON(steps.artifactory.outputs.vault).ARTIFACTORY_USERNAME }} - ARTIFACTORY_DEPLOY_ACCESS_TOKEN: ${{ fromJSON(steps.artifactory.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN }} + # yamllint disable rule:line-length + ARTIFACTORY_DEPLOY_ACCESS_TOKEN: ${{ steps.artifactory.outputs.vault && + fromJSON(steps.artifactory.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN || '' }} + # yamllint enable rule:line-length POETRY_VIRTUALENVS_PATH: ${{ github.workspace }}/${{ inputs.poetry-virtualenvs-path }} POETRY_CACHE_DIR: ${{ github.workspace }}/${{ inputs.poetry-cache-dir }} @@ -215,3 +216,10 @@ runs: echo "🐸 [Browse build \`${build_name}:${BUILD_NUMBER}\` in Artifactory](${ARTIFACTORY_BROWSE_URL})" >> $GITHUB_STEP_SUMMARY "$ACTION_PATH_BUILD_POETRY/../shared/generate-jfrog-summary.sh" repox fi + + - name: Clean up local action symlinks + if: always() + shell: bash + run: | + rm -f .actions/get-build-number .actions/config-poetry .actions/shared + rmdir .actions 2>/dev/null || true diff --git a/build-poetry/build.sh b/build-poetry/build.sh index c65e3e97..d39918ce 100755 --- a/build-poetry/build.sh +++ b/build-poetry/build.sh @@ -5,8 +5,6 @@ # Required inputs (must be explicitly provided): # - BUILD_NUMBER: Build number for versioning # - ARTIFACTORY_URL: URL to Artifactory repository -# - ARTIFACTORY_PYPI_REPO: Repository to install dependencies from -# - ARTIFACTORY_ACCESS_TOKEN: Access token to read Repox repositories # - 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) @@ -45,7 +43,7 @@ set -euo pipefail # shellcheck source=../shared/common-functions.sh source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" -: "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_PYPI_REPO:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" "${ARTIFACTORY_USERNAME:?}" "${RUN_SHADOW_SCANS:?}" +: "${ARTIFACTORY_URL:?}" "${RUN_SHADOW_SCANS:?}" : "${ARTIFACTORY_DEPLOY_REPO:?}" "${DEPLOY_PULL_REQUEST:=false}" : "${GITHUB_REF_NAME:?}" "${BUILD_NUMBER:?}" "${GITHUB_REPOSITORY:?}" "${GITHUB_EVENT_NAME:?}" "${GITHUB_EVENT_PATH:?}" : "${PULL_REQUEST?}" "${DEFAULT_BRANCH:?}" "${GITHUB_ENV:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_SHA:?}" "${GITHUB_RUN_ID:?}" @@ -262,13 +260,7 @@ get_build_config() { export BUILD_SONAR_ARGS="${sonar_args[*]:-}" } -jfrog_poetry_install() { - 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 - jf poetry-config --server-id-resolve repox --repo-resolve "$ARTIFACTORY_PYPI_REPO" - export POETRY_HTTP_BASIC_REPOX_USERNAME="$ARTIFACTORY_USERNAME" - export POETRY_HTTP_BASIC_REPOX_PASSWORD="$ARTIFACTORY_ACCESS_TOKEN" +poetry_install_dependencies() { poetry install } @@ -302,7 +294,7 @@ build_poetry() { echo "::group::Install dependencies" echo "Installing dependencies..." - jfrog_poetry_install + poetry_install_dependencies echo "::endgroup::" echo "::group::Build project" diff --git a/config-poetry/action.yml b/config-poetry/action.yml new file mode 100644 index 00000000..9ef73581 --- /dev/null +++ b/config-poetry/action.yml @@ -0,0 +1,145 @@ +--- +name: Config Poetry +description: GitHub Action to configure Poetry build environment with build number, JFrog 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: '' + artifactory-pypi-repo: + description: PyPI virtual repository to resolve dependencies from + default: sonarsource-pypi + repox-url: + description: URL for Repox + default: https://repox.jfrog.io + poetry-virtualenvs-path: + description: Path to the Poetry virtual environments, relative to GitHub workspace. The folder is cached only if it is a subdirectory of + `poetry-cache-dir`. + default: .cache/pypoetry/virtualenvs + poetry-cache-dir: + description: Path to the Poetry cache directory, relative to GitHub workspace + default: .cache/pypoetry + disable-caching: + description: Whether to disable Poetry 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-poetry-completed + if: env.CONFIG_POETRY_COMPLETED != '' + shell: bash + run: | + echo "Action already called by $CONFIG_POETRY_COMPLETED, execution skipped." + echo "skip=true" >> "$GITHUB_OUTPUT" + + - name: Set local action paths + id: set-path + if: steps.config-poetry-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_POETRY="${{ github.action_path }}" + host_actions_root="${{ inputs.host-actions-root }}" + if [[ -z "$host_actions_root" ]]; then + host_actions_root="$(dirname "$ACTION_PATH_CONFIG_POETRY")" + else + ACTION_PATH_CONFIG_POETRY="$host_actions_root/config-poetry" + fi + echo "ACTION_PATH_CONFIG_POETRY=$ACTION_PATH_CONFIG_POETRY" + echo "ACTION_PATH_CONFIG_POETRY=$ACTION_PATH_CONFIG_POETRY" >> "$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 Poetry 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_POETRY/mise.local.toml" mise.local.toml + echo "::endgroup::" + + - name: Set Artifactory reader role + if: steps.config-poetry-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" + + - uses: ./.actions/get-build-number + id: get-build-number + if: steps.config-poetry-completed.outputs.skip != 'true' + with: + host-actions-root: ${{ steps.set-path.outputs.host_actions_root }} + + - name: Cache local Poetry cache + uses: SonarSource/gh-action_cache@a7d13cdd1c9f097a5f8382ccec463be2831e3dbc # v1.6.0 + if: steps.config-poetry-completed.outputs.skip != 'true' && inputs.disable-caching == 'false' + with: + path: ${{ github.workspace }}/${{ inputs.poetry-cache-dir }} + key: poetry-${{ runner.os }}-${{ hashFiles('poetry.lock') }} + restore-keys: poetry-${{ runner.os }}- + + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 + if: steps.config-poetry-completed.outputs.skip != 'true' + with: + version: 2026.5.9 + + - uses: SonarSource/vault-action-wrapper@0a3114fe1230b784c35b53b099f9ab1f1e538cc7 # 3.5.0 + if: steps.config-poetry-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 Poetry authentication + if: steps.config-poetry-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 }} + ARTIFACTORY_PYPI_REPO: ${{ inputs.artifactory-pypi-repo }} + run: | + $ACTION_PATH_CONFIG_POETRY/poetry_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::" + + - name: Set Config Poetry completed + if: steps.config-poetry-completed.outputs.skip != 'true' + shell: bash + run: echo "CONFIG_POETRY_COMPLETED=$GITHUB_ACTION" >> "$GITHUB_ENV" + + - name: Clean up local action symlinks + if: always() && steps.config-poetry-completed.outputs.skip != 'true' + shell: bash + run: | + rm -f .actions/get-build-number .actions/shared + rmdir .actions 2>/dev/null || true diff --git a/config-poetry/mise.local.toml b/config-poetry/mise.local.toml new file mode 100644 index 00000000..7c5a49cc --- /dev/null +++ b/config-poetry/mise.local.toml @@ -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*" diff --git a/config-poetry/poetry_config.sh b/config-poetry/poetry_config.sh new file mode 100755 index 00000000..61ec51f8 --- /dev/null +++ b/config-poetry/poetry_config.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Config script for Poetry to use SonarSource Artifactory via JFrog CLI. +# +# 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 +# - ARTIFACTORY_PYPI_REPO: PyPI virtual repository to resolve dependencies from +# +# GitHub Actions auto-provided: +# - GITHUB_ENV: Path to GitHub Actions environment file + +set -euo pipefail + +: "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_USERNAME:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" "${ARTIFACTORY_PYPI_REPO:?}" "${GITHUB_ENV:?}" + +configure_poetry_repox() { + echo "Configuring Poetry to use Artifactory..." + + 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 + jf poetry-config --server-id-resolve repox --repo-resolve "$ARTIFACTORY_PYPI_REPO" + + { + echo "POETRY_HTTP_BASIC_REPOX_USERNAME=$ARTIFACTORY_USERNAME" + echo "POETRY_HTTP_BASIC_REPOX_PASSWORD=$ARTIFACTORY_ACCESS_TOKEN" + } >> "$GITHUB_ENV" + + return 0 +} + +main() { + echo "::group::Configure Poetry" + configure_poetry_repox + echo "::endgroup::" + return 0 +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main +fi diff --git a/spec/build-poetry_spec.sh b/spec/build-poetry_spec.sh index 936d065f..4b81a644 100755 --- a/spec/build-poetry_spec.sh +++ b/spec/build-poetry_spec.sh @@ -14,9 +14,6 @@ End # Minimal environment variables export GITHUB_ENV=/dev/null export ARTIFACTORY_URL="https://dummy.repox" -export ARTIFACTORY_PYPI_REPO="" -export ARTIFACTORY_ACCESS_TOKEN="dummy access token" -export ARTIFACTORY_USERNAME="dummy-user" export ARTIFACTORY_DEPLOY_REPO="" export ARTIFACTORY_DEPLOY_ACCESS_TOKEN="" export GITHUB_REPOSITORY="my-org/my-repo" @@ -70,7 +67,7 @@ Describe 'build-poetry/build.sh' export GITHUB_OUTPUT When run script build-poetry/build.sh The status should be success - The lines of stdout should equal 37 + The lines of stdout should equal 34 The line 1 should equal "::group::Check tools" The line 2 should include "jq" The line 3 should include "jq" @@ -98,15 +95,13 @@ Describe 'build-poetry/build.sh' The line 25 should equal "======= Build other branch =======" The line 26 should equal "::group::Install dependencies" The line 27 should equal "Installing dependencies..." - The line 28 should equal "jf config add repox --url https://dummy.repox --artifactory-url https://dummy.repox --access-token dummy access token" - The line 29 should equal "jf config use repox" - The line 30 should equal "jf poetry-config --server-id-resolve repox --repo-resolve " - The line 31 should equal "poetry install" - The line 32 should equal "::endgroup::" - The line 33 should equal "::group::Build project" - The line 35 should equal "poetry build" - The line 36 should equal "::endgroup::" - The line 37 should equal "=== Build completed successfully ===" + The line 28 should equal "poetry install" + The line 29 should equal "::endgroup::" + The line 30 should equal "::group::Build project" + The line 31 should equal "Building project..." + The line 32 should equal "poetry build" + The line 33 should equal "::endgroup::" + The line 34 should equal "=== Build completed successfully ===" End End @@ -301,14 +296,11 @@ Describe 'set_project_version()' End End -Describe 'jfrog_poetry_install()' +Describe 'poetry_install_dependencies()' export PROJECT="my-repo" - It 'configures JFrog and installs with plain poetry install' - When call jfrog_poetry_install - The line 1 should include "jf config add repox" - The line 2 should include "jf config use repox" - The line 3 should include "jf poetry-config" - The line 4 should equal "poetry install" + It 'installs dependencies with plain poetry install' + When call poetry_install_dependencies + The line 1 should equal "poetry install" End End @@ -333,7 +325,7 @@ Describe 'build_poetry()' End export PROJECT_VERSION="1.0.0.$BUILD_NUMBER" export PROJECT="my-repo" - Mock jfrog_poetry_install + Mock poetry_install_dependencies End It 'builds and publishes when on the default branch (main) and not a PR' diff --git a/spec/config-poetry_spec.sh b/spec/config-poetry_spec.sh new file mode 100644 index 00000000..a4a62976 --- /dev/null +++ b/spec/config-poetry_spec.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +eval "$(shellspec - -c) exit 1" + +Mock jf + echo "jf $*" +End + +export GITHUB_REPOSITORY="my-org/test-project" +export GITHUB_ENV=/dev/null +export ARTIFACTORY_URL="https://repox.jfrog.io/artifactory" +export ARTIFACTORY_USERNAME="test-user" +export ARTIFACTORY_ACCESS_TOKEN="test-token" +export ARTIFACTORY_PYPI_REPO="sonarsource-pypi" + +MESSAGE_CONFIGURING_POETRY="Configuring Poetry to use Artifactory..." + +Describe 'config-poetry/poetry_config.sh' + It 'does not run main when sourced' + When run source config-poetry/poetry_config.sh + The status should be success + The lines of output should equal 0 + The lines of error should equal 0 + End +End + +Include config-poetry/poetry_config.sh + +Describe 'configure_poetry_repox()' + It 'configures JFrog CLI and Poetry authentication' + When call configure_poetry_repox + The status should be success + The line 1 should equal "$MESSAGE_CONFIGURING_POETRY" + The line 2 should include "jf config add repox" + The line 3 should include "jf config use repox" + The line 4 should include "jf poetry-config --server-id-resolve repox --repo-resolve sonarsource-pypi" + End +End + +Describe 'main()' + It 'runs configure_poetry_repox within a GitHub Actions group' + GITHUB_ENV=$(mktemp) + export GITHUB_ENV + When run script config-poetry/poetry_config.sh + The status should be success + The line 1 should equal "::group::Configure Poetry" + The line 2 should equal "$MESSAGE_CONFIGURING_POETRY" + The line 3 should include "jf config add repox" + The line 4 should include "jf config use repox" + The line 5 should include "jf poetry-config" + The line 6 should equal "::endgroup::" + The contents of file "$GITHUB_ENV" should include "POETRY_HTTP_BASIC_REPOX_USERNAME=test-user" + The contents of file "$GITHUB_ENV" should include "POETRY_HTTP_BASIC_REPOX_PASSWORD=test-token" + End +End