Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
281193c741 | ||
|
|
0228fce5b9 | ||
|
|
981d3e41cc |
+15
-2
@@ -1,6 +1,19 @@
|
|||||||
# Gitea API token (required for CI scripts that interact with Gitea)
|
# Role-based Gitea API tokens.
|
||||||
|
# Each token serves a specific role. For small teams the developer and CI
|
||||||
|
# tokens may belong to the same user, but the reviewer token MUST belong to a
|
||||||
|
# different Gitea user than the PR author so Gitea accepts approval reviews.
|
||||||
# Create at: https://git.oblachno.oblachno.fyi/user/settings/applications
|
# Create at: https://git.oblachno.oblachno.fyi/user/settings/applications
|
||||||
CI_GITEA_TOKEN=
|
|
||||||
|
# Developer token — used by local tooling: create-task, create-pr, setup, etc.
|
||||||
|
DEVELOPER_GITEA_API_TOKEN=
|
||||||
|
|
||||||
|
# CI token — used by CI workflows and scripts that do not post approvals.
|
||||||
|
# Legacy CI_GITEA_TOKEN is also accepted.
|
||||||
|
CI_GITEA_API_TOKEN=
|
||||||
|
|
||||||
|
# Reviewer token — used by the auto-merge workflow to post APPROVE reviews.
|
||||||
|
# This must be a different Gitea user from the developer/CI user.
|
||||||
|
REVIEWER_GITEA_API_TOKEN=
|
||||||
|
|
||||||
# Vikunja API token (required for post-merge task updates)
|
# Vikunja API token (required for post-merge task updates)
|
||||||
# Create at: https://work.oblachno.oblachno.fyi/settings/tokens
|
# Create at: https://work.oblachno.oblachno.fyi/settings/tokens
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-ci
|
run: make setup-ci
|
||||||
- name: Check if this is a release commit
|
- name: Check if this is a release commit
|
||||||
id: check
|
id: check
|
||||||
@@ -62,18 +64,22 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-release
|
run: make setup-release
|
||||||
- name: Docker registry login
|
- name: Docker registry login
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
echo "$CI_GITEA_TOKEN" | docker login git.oblachno.oblachno.fyi -u "$CI_GITEA_USERNAME" --password-stdin
|
_TOKEN="$CI_GITEA_API_TOKEN"
|
||||||
|
[ -z "$_TOKEN" ] && _TOKEN="$DEVELOPER_GITEA_API_TOKEN"
|
||||||
|
[ -z "$_TOKEN" ] && _TOKEN="$CI_GITEA_TOKEN"
|
||||||
|
if [ -z "$_TOKEN" ]; then echo "Gitea API token not set — skipping Docker login"; exit 1; fi
|
||||||
|
echo "$_TOKEN" | docker login git.oblachno.oblachno.fyi -u "$CI_GITEA_USERNAME" --password-stdin
|
||||||
- name: Build and push tier images
|
- name: Build and push tier images
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
@@ -103,7 +109,7 @@ jobs:
|
|||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -125,10 +131,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-ci
|
run: make setup-ci
|
||||||
- name: Clean up old image versions
|
- name: Clean up old image versions
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
|
|||||||
+14
-5
@@ -16,6 +16,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Lint all
|
- name: Lint all
|
||||||
run: |
|
run: |
|
||||||
@@ -79,6 +81,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Detect changed paths
|
- name: Detect changed paths
|
||||||
id: detect
|
id: detect
|
||||||
@@ -106,10 +110,11 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Release dry-run validation
|
- name: Release dry-run validation
|
||||||
env:
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -127,10 +132,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Run automated PR review
|
- name: Run automated PR review
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -160,12 +167,14 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.CI_GITEA_TOKEN }}
|
token: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Post approval review
|
- name: Post approval review
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.REVIEW_GITEA_TOKEN }}
|
REVIEWER_GITEA_API_TOKEN: ${{ secrets.REVIEWER_GITEA_API_TOKEN }}
|
||||||
PR_NUMBER: ${{ github.event.number }}
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
@@ -180,7 +189,7 @@ jobs:
|
|||||||
--body "Auto-approved: all CI checks passed (quality, pr-review, release-dry-run)."
|
--body "Auto-approved: all CI checks passed (quality, pr-review, release-dry-run)."
|
||||||
- name: Squash merge with task ID
|
- name: Squash merge with task ID
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
||||||
DEVX_VIKUNJA_PROJECT_ID: "8"
|
DEVX_VIKUNJA_PROJECT_ID: "8"
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Check if this is a release commit
|
- name: Check if this is a release commit
|
||||||
id: check
|
id: check
|
||||||
@@ -70,6 +72,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Validate latest commit message
|
- name: Validate latest commit message
|
||||||
env:
|
env:
|
||||||
@@ -95,10 +99,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.CI_GITEA_TOKEN }}
|
token: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Configure git
|
- name: Configure git
|
||||||
run: |
|
run: |
|
||||||
@@ -107,6 +111,7 @@ jobs:
|
|||||||
- name: Run release
|
- name: Run release
|
||||||
id: release-tag
|
id: release-tag
|
||||||
env:
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -115,7 +120,7 @@ jobs:
|
|||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -142,10 +147,12 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image EXTRAS=release
|
run: make setup-image EXTRAS=release
|
||||||
- name: Build and publish release
|
- name: Build and publish release
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -154,7 +161,7 @@ jobs:
|
|||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -183,10 +190,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Sync documentation to wiki
|
- name: Sync documentation to wiki
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -194,7 +203,7 @@ jobs:
|
|||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
@@ -219,15 +228,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: master
|
ref: master
|
||||||
token: ${{ secrets.CI_GITEA_TOKEN }}
|
token: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
- name: Fetch latest master
|
- name: Fetch latest master
|
||||||
run: |
|
run: |
|
||||||
git fetch origin master
|
git fetch origin master
|
||||||
git reset --hard origin/master
|
git reset --hard origin/master
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Generate and push badges
|
- name: Generate and push badges
|
||||||
env:
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PRE_COMMIT_ALLOW_NO_CONFIG: "1"
|
PRE_COMMIT_ALLOW_NO_CONFIG: "1"
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
@@ -235,7 +247,7 @@ jobs:
|
|||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
@@ -260,6 +272,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Update Vikunja task
|
- name: Update Vikunja task
|
||||||
env:
|
env:
|
||||||
@@ -272,7 +286,7 @@ jobs:
|
|||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
@@ -295,10 +309,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
|
env:
|
||||||
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
run: make setup-image
|
run: make setup-image
|
||||||
- name: Ensure branch protection and labels
|
- name: Ensure branch protection and labels
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
DEVX_REPO_NAME: devx
|
DEVX_REPO_NAME: devx
|
||||||
DEVX_REPO_OWNER: oblachno-oss
|
DEVX_REPO_OWNER: oblachno-oss
|
||||||
@@ -308,7 +324,7 @@ jobs:
|
|||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.38.0] - 2026-07-08
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Introduce role-based Gitea API token environment variables
|
||||||
|
|
||||||
## [0.37.0] - 2026-07-07
|
## [0.37.0] - 2026-07-07
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ quality badges.
|
|||||||
|
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||||
[](https://www.python.org/downloads/)
|
[](https://www.python.org/downloads/)
|
||||||
|
|
||||||
## Why devx?
|
## Why devx?
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ extra index and list devx in your dependencies:
|
|||||||
```toml
|
```toml
|
||||||
[project]
|
[project]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"devx>=0.37.0",
|
"devx>=0.38.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.pip]
|
[tool.pip]
|
||||||
@@ -101,8 +101,8 @@ pip install -e .
|
|||||||
```
|
```
|
||||||
|
|
||||||
> **Note:** If your project requires a specific devx version, pin it in
|
> **Note:** If your project requires a specific devx version, pin it in
|
||||||
> `dependencies` (for example, `"devx==0.37.0"`) or use a version constraint
|
> `dependencies` (for example, `"devx==0.38.0"`) or use a version constraint
|
||||||
> (for example, `"devx>=0.37.0,<0.38"`).
|
> (for example, `"devx>=0.38.0,<0.39"`).
|
||||||
|
|
||||||
### Optional extras
|
### Optional extras
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -12,12 +12,12 @@ project to be reusable across all oblachno-oss repositories.
|
|||||||
|
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||||
[](https://www.python.org/downloads/)
|
[](https://www.python.org/downloads/)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -74,14 +74,14 @@ Add devx to your `pyproject.toml` dependencies and configure the registry:
|
|||||||
```toml
|
```toml
|
||||||
[project]
|
[project]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"devx>=0.37.0",
|
"devx>=0.38.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.pip]
|
[tool.pip]
|
||||||
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
|
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
|
||||||
```
|
```
|
||||||
|
|
||||||
Pin a specific version if needed: `"devx==0.37.0"` or `"devx>=0.37.0,<0.38"`.
|
Pin a specific version if needed: `"devx==0.38.0"` or `"devx>=0.38.0,<0.39"`.
|
||||||
|
|
||||||
### Optional extras
|
### Optional extras
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ Add devx to your `pyproject.toml`:
|
|||||||
```toml
|
```toml
|
||||||
[project]
|
[project]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"devx>=0.37.0",
|
"devx>=0.38.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"devx>=0.37.0",
|
"devx>=0.38.0",
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
||||||
|
|
||||||
__version__ = "0.37.0"
|
__version__ = "0.38.0"
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ This allows the PR title to be a human-friendly Vikunja task title
|
|||||||
while the squashed commit follows conventional commits.
|
while the squashed commit follows conventional commits.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.ci.auto_merge <branch> <pr_title> <repo> <pr_number>
|
CI_GITEA_API_TOKEN=<token> VIKUNJA_TOKEN=<token> python3 -m devx.ci.auto_merge <branch> <pr_title> <repo> <pr_number>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -40,6 +39,7 @@ from devx.config import (
|
|||||||
)
|
)
|
||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token, get_vikunja_token
|
||||||
|
|
||||||
# Strip leading task ID prefix (e.g. "DEVX-12: " or "OBL-INFRA-364: ") from commit subjects.
|
# Strip leading task ID prefix (e.g. "DEVX-12: " or "OBL-INFRA-364: ") from commit subjects.
|
||||||
_TASK_ID_PREFIX_RE = re.compile(rf"^{TASK_PREFIX}-\d+:\s*")
|
_TASK_ID_PREFIX_RE = re.compile(rf"^{TASK_PREFIX}-\d+:\s*")
|
||||||
@@ -115,9 +115,12 @@ def get_vikunja_task_title(task_id: str) -> str:
|
|||||||
|
|
||||||
Raises ClickException if VIKUNJA_TOKEN is not set or the task is not found.
|
Raises ClickException if VIKUNJA_TOKEN is not set or the task is not found.
|
||||||
"""
|
"""
|
||||||
token = os.environ.get("VIKUNJA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_vikunja_token()
|
||||||
raise click.ClickException(_("VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(
|
||||||
|
_("VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.")
|
||||||
|
) from None
|
||||||
client = VikunjaClient(VIKUNJA_API_URL, token)
|
client = VikunjaClient(VIKUNJA_API_URL, token)
|
||||||
page = 1
|
page = 1
|
||||||
while True:
|
while True:
|
||||||
@@ -197,9 +200,10 @@ def extract_conventional_msg(commits: list[dict[str, Any]]) -> str:
|
|||||||
@click.argument("repo")
|
@click.argument("repo")
|
||||||
@click.argument("pr_number")
|
@click.argument("pr_number")
|
||||||
def main(branch: str, pr_title: str, repo: str, pr_number: str) -> None:
|
def main(branch: str, pr_title: str, repo: str, pr_number: str) -> None:
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_ci_token()
|
||||||
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
# Validate PR number is an integer
|
# Validate PR number is an integer
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Exit code 1 = NOT ready — fix issues before pushing.
|
|||||||
|
|
||||||
Usage::
|
Usage::
|
||||||
|
|
||||||
# CI (with VIKUNJA_TOKEN and CI_GITEA_TOKEN):
|
# CI (with VIKUNJA_TOKEN and CI_GITEA_API_TOKEN):
|
||||||
python3 -m devx.ci.check_auto_merge_ready \\
|
python3 -m devx.ci.check_auto_merge_ready \\
|
||||||
--branch "$HEAD_REF" \\
|
--branch "$HEAD_REF" \\
|
||||||
--pr-title "$PR_TITLE" \\
|
--pr-title "$PR_TITLE" \\
|
||||||
@@ -34,13 +34,12 @@ skipped (with a warning) — this allows local pre-push hooks to run
|
|||||||
without CI secrets. In CI, the token is always set and the check is
|
without CI secrets. In CI, the token is always set and the check is
|
||||||
mandatory.
|
mandatory.
|
||||||
|
|
||||||
If ``CI_GITEA_TOKEN`` is not set and ``--pr-number`` is not provided, only
|
If ``CI_GITEA_API_TOKEN`` is not set and ``--pr-number`` is not provided, only
|
||||||
branch-name and PR-title-format checks run (local mode).
|
branch-name and PR-title-format checks run (local mode).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@@ -55,6 +54,7 @@ from devx.config import (
|
|||||||
)
|
)
|
||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token, get_vikunja_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -99,10 +99,13 @@ def is_branch_behind_master(branch: str) -> bool:
|
|||||||
def get_pr_title_from_gitea(repo: str, pr_number: int) -> str | None:
|
def get_pr_title_from_gitea(repo: str, pr_number: int) -> str | None:
|
||||||
"""Fetch the PR title from the Gitea API.
|
"""Fetch the PR title from the Gitea API.
|
||||||
|
|
||||||
Returns ``None`` if ``CI_GITEA_TOKEN`` is not set or the PR cannot be fetched.
|
Returns ``None`` if no token is set or the PR cannot be fetched.
|
||||||
"""
|
"""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token or "/" not in repo:
|
token = get_ci_token()
|
||||||
|
except click.ClickException:
|
||||||
|
return None
|
||||||
|
if "/" not in repo:
|
||||||
return None
|
return None
|
||||||
owner, repo_name = repo.split("/", 1)
|
owner, repo_name = repo.split("/", 1)
|
||||||
client = GiteaClient(GITEA_API_URL, token, owner, repo_name)
|
client = GiteaClient(GITEA_API_URL, token, owner, repo_name)
|
||||||
@@ -120,8 +123,9 @@ def get_vikunja_title_optional(task_id: str) -> str | None:
|
|||||||
raise when ``VIKUNJA_TOKEN`` is missing — it returns ``None`` so the
|
raise when ``VIKUNJA_TOKEN`` is missing — it returns ``None`` so the
|
||||||
caller can skip the check in local mode.
|
caller can skip the check in local mode.
|
||||||
"""
|
"""
|
||||||
token = os.environ.get("VIKUNJA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_vikunja_token()
|
||||||
|
except click.ClickException:
|
||||||
return None
|
return None
|
||||||
client = VikunjaClient(VIKUNJA_API_URL, token)
|
client = VikunjaClient(VIKUNJA_API_URL, token)
|
||||||
from devx.config import DEFAULT_PER_PAGE
|
from devx.config import DEFAULT_PER_PAGE
|
||||||
@@ -221,7 +225,11 @@ def cli(
|
|||||||
if not skip_vikunja:
|
if not skip_vikunja:
|
||||||
vikunja_title = get_vikunja_title_optional(task_id)
|
vikunja_title = get_vikunja_title_optional(task_id)
|
||||||
if vikunja_title is None:
|
if vikunja_title is None:
|
||||||
token_set = bool(os.environ.get("VIKUNJA_TOKEN", ""))
|
try:
|
||||||
|
get_vikunja_token()
|
||||||
|
token_set = True
|
||||||
|
except click.ClickException:
|
||||||
|
token_set = False
|
||||||
if token_set:
|
if token_set:
|
||||||
errors.append(
|
errors.append(
|
||||||
_(
|
_(
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import requests
|
|||||||
|
|
||||||
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
DEFAULT_MAX_RUNNERS = 3
|
DEFAULT_MAX_RUNNERS = 3
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ def query_runners(api_url: str, token: str, owner: str, repo: str) -> int:
|
|||||||
return total
|
return total
|
||||||
|
|
||||||
|
|
||||||
def get_runner_count(api_url: str, token: str, owner: str, repo: str) -> int:
|
def get_runner_count(api_url: str, token: str | None, owner: str, repo: str) -> int:
|
||||||
"""Determine the number of available runners.
|
"""Determine the number of available runners.
|
||||||
|
|
||||||
Tries the Gitea API first, then falls back to env vars, then default.
|
Tries the Gitea API first, then falls back to env vars, then default.
|
||||||
@@ -152,7 +153,10 @@ def main(
|
|||||||
output_indices: bool,
|
output_indices: bool,
|
||||||
github_output: bool,
|
github_output: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
|
token = get_ci_token()
|
||||||
|
except click.ClickException:
|
||||||
|
token = None
|
||||||
|
|
||||||
if owner is None:
|
if owner is None:
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Usage::
|
|||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
GITEA_URL Base URL of the Gitea instance.
|
GITEA_URL Base URL of the Gitea instance.
|
||||||
CI_GITEA_TOKEN API token with repo access.
|
CI_GITEA_API_TOKEN API token with repo access (CI_GITEA_TOKEN accepted for legacy).
|
||||||
RUN_ID Workflow run ID (GITHUB_RUN_ID).
|
RUN_ID Workflow run ID (GITHUB_RUN_ID).
|
||||||
JOB_NAME Base job name (GITHUB_JOB), e.g. "integration-tests".
|
JOB_NAME Base job name (GITHUB_JOB), e.g. "integration-tests".
|
||||||
MATRIX_INDEX Current matrix index (runner-index).
|
MATRIX_INDEX Current matrix index (runner-index).
|
||||||
@@ -41,6 +41,7 @@ from devx.i18n import _
|
|||||||
from devx.molecule.molecule_ci_guard import (
|
from devx.molecule.molecule_ci_guard import (
|
||||||
poll_for_other_failures,
|
poll_for_other_failures,
|
||||||
)
|
)
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
POLL_INTERVAL = 10
|
POLL_INTERVAL = 10
|
||||||
|
|
||||||
@@ -50,7 +51,10 @@ POLL_INTERVAL = 10
|
|||||||
def cli(pytest_args: tuple[str, ...]) -> None:
|
def cli(pytest_args: tuple[str, ...]) -> None:
|
||||||
"""Run pytest with cross-runner failure detection."""
|
"""Run pytest with cross-runner failure detection."""
|
||||||
gitea_url = os.environ.get("GITEA_URL", "")
|
gitea_url = os.environ.get("GITEA_URL", "")
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
|
token = get_ci_token()
|
||||||
|
except click.ClickException:
|
||||||
|
token = None
|
||||||
run_id = int(os.environ.get("RUN_ID", "0"))
|
run_id = int(os.environ.get("RUN_ID", "0"))
|
||||||
job_name = os.environ.get("JOB_NAME", "integration-tests")
|
job_name = os.environ.get("JOB_NAME", "integration-tests")
|
||||||
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ otherwise go unnoticed in the Actions tab. Uses the ``tea`` Gitea CLI
|
|||||||
for issue creation — tea must be installed and configured.
|
for issue creation — tea must be installed and configured.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.ci.notify_failure \
|
CI_GITEA_API_TOKEN=<token> python3 -m devx.ci.notify_failure \
|
||||||
--repo <owner/repo> \
|
--repo <owner/repo> \
|
||||||
--run-id <run_id> \
|
--run-id <run_id> \
|
||||||
--workflow <workflow_name> \
|
--workflow <workflow_name> \
|
||||||
@@ -14,14 +14,13 @@ Usage:
|
|||||||
--auto-login
|
--auto-login
|
||||||
|
|
||||||
With ``--auto-login``, the script configures the tea CLI login profile
|
With ``--auto-login``, the script configures the tea CLI login profile
|
||||||
from ``CI_GITEA_TOKEN`` and ``DEVX_GITEA_API_URL`` before creating the issue,
|
from the CI API token and ``DEVX_GITEA_API_URL`` before creating the issue,
|
||||||
eliminating the need for a separate ``tea login add`` step in the workflow.
|
eliminating the need for a separate ``tea login add`` step in the workflow.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||||
@@ -29,6 +28,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
|||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL
|
||||||
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -74,9 +74,10 @@ def _create_issue_via_tea(repo: str, title: str, body: str) -> int:
|
|||||||
help="Configure tea CLI login from CI_GITEA_TOKEN before creating the issue.",
|
help="Configure tea CLI login from CI_GITEA_TOKEN before creating the issue.",
|
||||||
)
|
)
|
||||||
def main(repo: str, run_id: str, workflow: str, commit: str, auto_login: bool) -> None:
|
def main(repo: str, run_id: str, workflow: str, commit: str, auto_login: bool) -> None:
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
get_ci_token()
|
||||||
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
if auto_login:
|
if auto_login:
|
||||||
configure_tea_login()
|
configure_tea_login()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Usage:
|
|||||||
VIKUNJA_TOKEN=<token> python3 -m devx.ci.post_merge <commit_msg> [--commit-sha <sha>]
|
VIKUNJA_TOKEN=<token> python3 -m devx.ci.post_merge <commit_msg> [--commit-sha <sha>]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
|
|
||||||
@@ -17,6 +16,7 @@ from devx.ci._shared import extract_task_id as _extract_task_id
|
|||||||
from devx.config import DEFAULT_PER_PAGE, TASK_PREFIX, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID
|
from devx.config import DEFAULT_PER_PAGE, TASK_PREFIX, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID
|
||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_vikunja_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -127,9 +127,10 @@ def main(commit_msg: str | None, commit_sha: str, from_git: bool, git_sha: str)
|
|||||||
commit_sha = _get_git_commit_sha()
|
commit_sha = _get_git_commit_sha()
|
||||||
if not commit_msg:
|
if not commit_msg:
|
||||||
raise click.ClickException("commit_msg argument is required (or use --from-git or --git-sha)")
|
raise click.ClickException("commit_msg argument is required (or use --from-git or --git-sha)")
|
||||||
token = os.environ.get("VIKUNJA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_vikunja_token()
|
||||||
raise click.ClickException(_("ERROR: VIKUNJA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("ERROR: VIKUNJA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
task_id = extract_task_id(commit_msg)
|
task_id = extract_task_id(commit_msg)
|
||||||
if not task_id:
|
if not task_id:
|
||||||
|
|||||||
@@ -17,12 +17,11 @@ Checks performed:
|
|||||||
8. Commit conventions — conventional commit format on branch commits
|
8. Commit conventions — conventional commit format on branch commits
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.ci.pr_review <pr_number> <owner/repo>
|
CI_GITEA_API_TOKEN=<token> [REVIEWER_GITEA_API_TOKEN=<token>] python3 -m devx.ci.pr_review <pr_number> <owner/repo>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -34,6 +33,7 @@ from devx.api_clients import GiteaClient
|
|||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL
|
||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token, get_reviewer_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -636,9 +636,10 @@ def main(
|
|||||||
Without --event: runs automated checks and posts COMMENT/REQUEST_CHANGES.
|
Without --event: runs automated checks and posts COMMENT/REQUEST_CHANGES.
|
||||||
With --event: posts a manual review (skips automated checks).
|
With --event: posts a manual review (skips automated checks).
|
||||||
"""
|
"""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_reviewer_token() if (event and event.upper() == "APPROVE") else get_ci_token()
|
||||||
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
owner, repo_name = repo.split("/")
|
owner, repo_name = repo.split("/")
|
||||||
client = GiteaClient(GITEA_API_URL, token, owner, repo_name)
|
client = GiteaClient(GITEA_API_URL, token, owner, repo_name)
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ Publishing destinations (checked in order):
|
|||||||
``DEVX_PYPI_REGISTRY_URL`` env var is set, or ``GITEA_API_URL``
|
``DEVX_PYPI_REGISTRY_URL`` env var is set, or ``GITEA_API_URL``
|
||||||
is converted to a packages URL). Uses ``twine upload
|
is converted to a packages URL). Uses ``twine upload
|
||||||
--repository-url <url> -u <token> -p <token>`` with the
|
--repository-url <url> -u <token> -p <token>`` with the
|
||||||
``CI_GITEA_TOKEN`` as both username and password.
|
CI API token as both username and password.
|
||||||
2. **Standard PyPI** — if ``PYPI_TOKEN`` is set. Uses the standard
|
2. **Standard PyPI** — if ``PYPI_TOKEN`` is set. Uses the standard
|
||||||
``twine upload -u __token__ -p <token>`` flow.
|
``twine upload -u __token__ -p <token>`` flow.
|
||||||
3. **Skip** — if neither is configured, only the Gitea release is created.
|
3. **Skip** — if neither is configured, only the Gitea release is created.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
CI_GITEA_TOKEN=<token> [PYPI_TOKEN=<token>] python3 -m devx.ci.publish <tag> <repo>
|
CI_GITEA_API_TOKEN=<token> [PYPI_TOKEN=<token>] python3 -m devx.ci.publish <tag> <repo>
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.ci.publish <tag> <repo> --registry-url https://git.example.com/api/packages/owner/pypi
|
CI_GITEA_API_TOKEN=<token> python3 -m devx.ci.publish <tag> <repo> --registry-url https://git.example.com/api/packages/owner/pypi
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -31,6 +31,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
|||||||
from devx.config import GITEA_API_URL, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_OWNER
|
||||||
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -253,9 +254,10 @@ def main(
|
|||||||
|
|
||||||
if not tag:
|
if not tag:
|
||||||
raise click.ClickException(_("Tag is required (or use --from-tag)."))
|
raise click.ClickException(_("Tag is required (or use --from-tag)."))
|
||||||
gitea_token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not gitea_token:
|
gitea_token = get_ci_token()
|
||||||
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
pypi_token = os.environ.get("PYPI_TOKEN", "")
|
pypi_token = os.environ.get("PYPI_TOKEN", "")
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ version. This prevents duplicate release commits (a common issue when CI
|
|||||||
checkouts don't fetch tags) and ensures tag/version/commit alignment.
|
checkouts don't fetch tags) and ensures tag/version/commit alignment.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.ci.release [--dry-run] [--skip-tests]
|
CI_GITEA_API_TOKEN=<token> python3 -m devx.ci.release [--dry-run] [--skip-tests]
|
||||||
python3 -m devx.ci.release --verify # Check tag/version/release alignment
|
python3 -m devx.ci.release --verify # Check tag/version/release alignment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Link transformations:
|
|||||||
- Anchor-only links (``#section``) are preserved
|
- Anchor-only links (``#section``) are preserved
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.ci.sync_wiki [--dry-run] [--repo owner/repo]
|
CI_GITEA_API_TOKEN=<token> python3 -m devx.ci.sync_wiki [--dry-run] [--repo owner/repo]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -40,6 +40,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
|||||||
|
|
||||||
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -274,9 +275,10 @@ def commit_and_push(wiki_dir: Path, wiki_url: str, dry_run: bool) -> bool:
|
|||||||
)
|
)
|
||||||
def main(dry_run: bool, repo: str | None, verify: bool) -> None:
|
def main(dry_run: bool, repo: str | None, verify: bool) -> None:
|
||||||
"""Sync documentation to the Gitea wiki via Git."""
|
"""Sync documentation to the Gitea wiki via Git."""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_ci_token()
|
||||||
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
if repo is None:
|
if repo is None:
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ Usage::
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -49,6 +48,7 @@ import click
|
|||||||
|
|
||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
|
|
||||||
class TeaCLIError(Exception):
|
class TeaCLIError(Exception):
|
||||||
@@ -56,10 +56,10 @@ class TeaCLIError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
def configure_tea_login(login_name: str = "devx") -> None:
|
def configure_tea_login(login_name: str = "devx") -> None:
|
||||||
"""Configure tea CLI login from CI_GITEA_TOKEN and DEVX_GITEA_API_URL.
|
"""Configure tea CLI login from CI_GITEA_API_TOKEN and DEVX_GITEA_API_URL.
|
||||||
|
|
||||||
Idempotent: if a login with the same name already exists, it is not re-added.
|
Idempotent: if a login with the same name already exists, it is not re-added.
|
||||||
Skips silently if tea is not installed or CI_GITEA_TOKEN is not set.
|
Skips silently if tea is not installed or no token is set.
|
||||||
|
|
||||||
Used by CI scripts (publish, notify_failure) that need tea login but
|
Used by CI scripts (publish, notify_failure) that need tea login but
|
||||||
run in containerized environments where ``make setup`` was not called.
|
run in containerized environments where ``make setup`` was not called.
|
||||||
@@ -69,8 +69,9 @@ def configure_tea_login(login_name: str = "devx") -> None:
|
|||||||
click.echo(_("tea not installed — skipping login configuration."))
|
click.echo(_("tea not installed — skipping login configuration."))
|
||||||
return
|
return
|
||||||
|
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_ci_token()
|
||||||
|
except click.ClickException:
|
||||||
click.echo(_("CI_GITEA_TOKEN not set — skipping login configuration."))
|
click.echo(_("CI_GITEA_TOKEN not set — skipping login configuration."))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+13
-9
@@ -61,10 +61,12 @@ DEVX_VALE_LEVEL ?= warning
|
|||||||
# Usage: $(DEVX_PIP_INSTALL) install -e '.[ci,lint]'
|
# Usage: $(DEVX_PIP_INSTALL) install -e '.[ci,lint]'
|
||||||
# CI_GITEA_USERNAME can be set in .env, as an env var, or as a Make variable.
|
# CI_GITEA_USERNAME can be set in .env, as an env var, or as a Make variable.
|
||||||
# Projects can alias: PIP_INSTALL = $(DEVX_PIP_INSTALL)
|
# Projects can alias: PIP_INSTALL = $(DEVX_PIP_INSTALL)
|
||||||
DEVX_PIP_INSTALL := if [ -z "$$CI_GITEA_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
|
DEVX_PIP_INSTALL := if [ -z "$$CI_GITEA_API_TOKEN" ] && [ -z "$$DEVELOPER_GITEA_API_TOKEN" ] && [ -z "$$CI_GITEA_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
|
||||||
CI_GITEA_TOKEN="$$CI_GITEA_TOKEN"; \
|
_TOKEN="$$CI_GITEA_API_TOKEN"; \
|
||||||
|
[ -z "$$_TOKEN" ] && _TOKEN="$$DEVELOPER_GITEA_API_TOKEN"; \
|
||||||
|
[ -z "$$_TOKEN" ] && _TOKEN="$$CI_GITEA_TOKEN"; \
|
||||||
_PYPI_USER="$${CI_GITEA_USERNAME:-emil}"; \
|
_PYPI_USER="$${CI_GITEA_USERNAME:-emil}"; \
|
||||||
if [ -n "$$CI_GITEA_TOKEN" ] && [ -n "$$_PYPI_USER" ]; then export PIP_EXTRA_INDEX_URL="https://$$_PYPI_USER:$$CI_GITEA_TOKEN@$(DEVX_GITEA_PYPI_HOST)/api/packages/$(DEVX_GITEA_PYPI_ORG)/pypi/simple/"; fi; \
|
if [ -n "$$_TOKEN" ] && [ -n "$$_PYPI_USER" ]; then export PIP_EXTRA_INDEX_URL="https://$$_PYPI_USER:$$_TOKEN@$(DEVX_GITEA_PYPI_HOST)/api/packages/$(DEVX_GITEA_PYPI_ORG)/pypi/simple/"; fi; \
|
||||||
$(DEVX_BIN)/pip
|
$(DEVX_BIN)/pip
|
||||||
|
|
||||||
# ── Virtual environment management ────────────────────────────────────────────
|
# ── Virtual environment management ────────────────────────────────────────────
|
||||||
@@ -196,12 +198,14 @@ devx-pr-rebase:
|
|||||||
# ── Environment setup ─────────────────────────────────────────────────────────
|
# ── Environment setup ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Configure Gitea private PyPI registry so pip can find devx and other
|
# Configure Gitea private PyPI registry so pip can find devx and other
|
||||||
# private packages. In CI, CI_GITEA_TOKEN is set as a secret. Locally, it's in .env.
|
# private packages. In CI, CI_GITEA_API_TOKEN is set as a secret. Locally, DEVELOPER_GITEA_API_TOKEN or CI_GITEA_TOKEN can be used.
|
||||||
devx-configure-gitea-pypi:
|
devx-configure-gitea-pypi:
|
||||||
@if [ -z "$$CI_GITEA_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
|
@if [ -z "$$CI_GITEA_API_TOKEN" ] && [ -z "$$DEVELOPER_GITEA_API_TOKEN" ] && [ -z "$$CI_GITEA_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
|
||||||
CI_GITEA_TOKEN="$$CI_GITEA_TOKEN"; \
|
_TOKEN="$$CI_GITEA_API_TOKEN"; \
|
||||||
if [ -z "$$CI_GITEA_TOKEN" ]; then echo "[configure-gitea-pypi] CI_GITEA_TOKEN not set — skipping (devx must be on public PyPI)"; exit 0; fi; \
|
[ -z "$$_TOKEN" ] && _TOKEN="$$DEVELOPER_GITEA_API_TOKEN"; \
|
||||||
echo "[configure-gitea-pypi] Gitea PyPI registry configured (CI_GITEA_TOKEN present)."
|
[ -z "$$_TOKEN" ] && _TOKEN="$$CI_GITEA_TOKEN"; \
|
||||||
|
if [ -z "$$_TOKEN" ]; then echo "[configure-gitea-pypi] Gitea API token not set — skipping (devx must be on public PyPI)"; exit 0; fi; \
|
||||||
|
echo "[configure-gitea-pypi] Gitea PyPI registry configured (token present)."
|
||||||
|
|
||||||
# Create .env from .env.example if it doesn't exist
|
# Create .env from .env.example if it doesn't exist
|
||||||
devx-env:
|
devx-env:
|
||||||
@@ -268,7 +272,7 @@ devx-workflow-check: devx-workflow-lint devx-workflow-dryrun
|
|||||||
|
|
||||||
# Notify on CI failure — creates a Gitea issue via devx.ci.notify_failure.
|
# Notify on CI failure — creates a Gitea issue via devx.ci.notify_failure.
|
||||||
# Usage: make devx-notify-failure WORKFLOW=post-merge/release
|
# Usage: make devx-notify-failure WORKFLOW=post-merge/release
|
||||||
# Requires: CI_GITEA_TOKEN, GITHUB_REPOSITORY, GITHUB_RUN_ID, GITHUB_SHA
|
# Requires: CI_GITEA_API_TOKEN, GITHUB_REPOSITORY, GITHUB_RUN_ID, GITHUB_SHA
|
||||||
devx-notify-failure:
|
devx-notify-failure:
|
||||||
@. $(DEVX_VENV)/bin/activate 2>/dev/null || true; \
|
@. $(DEVX_VENV)/bin/activate 2>/dev/null || true; \
|
||||||
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import click
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
DEFAULT_MAX_RUNNERS = 3
|
DEFAULT_MAX_RUNNERS = 3
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ def query_runners(api_url: str, token: str, owner: str, repo: str) -> int:
|
|||||||
return total
|
return total
|
||||||
|
|
||||||
|
|
||||||
def get_runner_count(api_url: str, token: str, owner: str, repo: str) -> int:
|
def get_runner_count(api_url: str, token: str | None, owner: str, repo: str) -> int:
|
||||||
"""Determine the number of available runners.
|
"""Determine the number of available runners.
|
||||||
|
|
||||||
Tries the Gitea API first, then falls back to env vars, then default.
|
Tries the Gitea API first, then falls back to env vars, then default.
|
||||||
@@ -142,7 +143,10 @@ def main(
|
|||||||
output_indices: bool,
|
output_indices: bool,
|
||||||
github_output: bool,
|
github_output: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
|
token = get_ci_token()
|
||||||
|
except click.ClickException:
|
||||||
|
token = None
|
||||||
|
|
||||||
if owner is None:
|
if owner is None:
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Usage::
|
|||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
GITEA_URL Base URL of the Gitea instance.
|
GITEA_URL Base URL of the Gitea instance.
|
||||||
CI_GITEA_TOKEN API token with repo access.
|
CI_GITEA_API_TOKEN API token with repo access (CI_GITEA_TOKEN accepted for legacy).
|
||||||
RUN_ID Workflow run ID (GITHUB_RUN_ID).
|
RUN_ID Workflow run ID (GITHUB_RUN_ID).
|
||||||
JOB_NAME Base job name (GITHUB_JOB), e.g. "molecule-tests".
|
JOB_NAME Base job name (GITHUB_JOB), e.g. "molecule-tests".
|
||||||
MATRIX_INDEX Current matrix index (runner-index).
|
MATRIX_INDEX Current matrix index (runner-index).
|
||||||
@@ -45,6 +45,7 @@ import requests
|
|||||||
|
|
||||||
from devx.config import REPO_NAME, REPO_OWNER
|
from devx.config import REPO_NAME, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_ci_token
|
||||||
|
|
||||||
POLL_INTERVAL = 10
|
POLL_INTERVAL = 10
|
||||||
|
|
||||||
@@ -164,7 +165,10 @@ def resolve_role_dir(role: str, roles_root: Path | None, repo_root: Path) -> Pat
|
|||||||
def cli(pairs: tuple[str, ...], roles_root: Path | None) -> None:
|
def cli(pairs: tuple[str, ...], roles_root: Path | None) -> None:
|
||||||
"""Run molecule pairs sequentially, stop if another CI runner fails."""
|
"""Run molecule pairs sequentially, stop if another CI runner fails."""
|
||||||
gitea_url = os.environ.get("GITEA_URL", "")
|
gitea_url = os.environ.get("GITEA_URL", "")
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
|
token = get_ci_token()
|
||||||
|
except click.ClickException:
|
||||||
|
token = None
|
||||||
run_id = int(os.environ.get("RUN_ID", "0"))
|
run_id = int(os.environ.get("RUN_ID", "0"))
|
||||||
job_name = os.environ.get("JOB_NAME", "molecule-tests")
|
job_name = os.environ.get("JOB_NAME", "molecule-tests")
|
||||||
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
"""Token resolution helpers for devx tools.
|
||||||
|
|
||||||
|
Centralizes Gitea/Vikunja token discovery with role-based environment
|
||||||
|
variable names and backwards compatibility with the legacy
|
||||||
|
``CI_GITEA_TOKEN`` / ``REVIEW_GITEA_TOKEN`` naming convention.
|
||||||
|
|
||||||
|
Roles:
|
||||||
|
- ``CI_GITEA_API_TOKEN``: CI workflows (read actions, post status, merge, etc.)
|
||||||
|
- ``REVIEWER_GITEA_API_TOKEN``: PR approval reviews (must be a different user
|
||||||
|
from the PR author for Gitea to accept the review as an approval)
|
||||||
|
- ``DEVELOPER_GITEA_API_TOKEN``: local development tools (create-task,
|
||||||
|
create-pr, setup, etc.)
|
||||||
|
|
||||||
|
Fallbacks:
|
||||||
|
- New role names are checked first.
|
||||||
|
- Legacy names (``CI_GITEA_TOKEN``, ``REVIEW_GITEA_TOKEN``) are accepted for
|
||||||
|
backwards compatibility.
|
||||||
|
- If no role-specific token is set, the generic CI tokens are tried last.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from devx.i18n import _
|
||||||
|
|
||||||
|
# Token environment variable names, in lookup priority order.
|
||||||
|
CI_TOKEN_NAMES = ["CI_GITEA_API_TOKEN", "CI_GITEA_TOKEN"]
|
||||||
|
REVIEWER_TOKEN_NAMES = [
|
||||||
|
"REVIEWER_GITEA_API_TOKEN",
|
||||||
|
# Legacy name used before role-based tokens.
|
||||||
|
"REVIEW_GITEA_TOKEN",
|
||||||
|
*CI_TOKEN_NAMES,
|
||||||
|
]
|
||||||
|
DEVELOPER_TOKEN_NAMES = ["DEVELOPER_GITEA_API_TOKEN", *CI_TOKEN_NAMES]
|
||||||
|
|
||||||
|
VIKUNJA_TOKEN_NAMES = ["VIKUNJA_TOKEN"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_token(*names: str) -> str:
|
||||||
|
"""Return the first non-empty value from the listed environment variables.
|
||||||
|
|
||||||
|
Raises a ``click.ClickException`` if none of the listed variables are set.
|
||||||
|
"""
|
||||||
|
for name in names:
|
||||||
|
token = os.environ.get(name, "").strip()
|
||||||
|
if token:
|
||||||
|
return token
|
||||||
|
raise click.ClickException(
|
||||||
|
_(
|
||||||
|
"Gitea API token not set. Set one of: {names}",
|
||||||
|
names=", ".join(names),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ci_token() -> str:
|
||||||
|
"""Resolve the CI Gitea API token."""
|
||||||
|
return get_token(*CI_TOKEN_NAMES)
|
||||||
|
|
||||||
|
|
||||||
|
def get_reviewer_token() -> str:
|
||||||
|
"""Resolve the reviewer Gitea API token used for PR approvals."""
|
||||||
|
return get_token(*REVIEWER_TOKEN_NAMES)
|
||||||
|
|
||||||
|
|
||||||
|
def get_developer_token() -> str:
|
||||||
|
"""Resolve the developer Gitea API token used for local tooling."""
|
||||||
|
return get_token(*DEVELOPER_TOKEN_NAMES)
|
||||||
|
|
||||||
|
|
||||||
|
def get_vikunja_token() -> str:
|
||||||
|
"""Resolve the Vikunja API token."""
|
||||||
|
return get_token(*VIKUNJA_TOKEN_NAMES)
|
||||||
@@ -8,6 +8,8 @@ import subprocess # nosec B404
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
|
|
||||||
|
|
||||||
def arch_string() -> str:
|
def arch_string() -> str:
|
||||||
"""Return the architecture string used by release assets.
|
"""Return the architecture string used by release assets.
|
||||||
@@ -45,8 +47,9 @@ def detect_pr_number() -> int | None:
|
|||||||
if branch == "HEAD":
|
if branch == "HEAD":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
|
except click.ClickException:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "")
|
owner = os.environ.get("DEVX_REPO_OWNER", "")
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ The manifest file is a JSON list of dicts, each with:
|
|||||||
- ``context``: build context directory (optional, defaults to repo root)
|
- ``context``: build context directory (optional, defaults to repo root)
|
||||||
- ``tags``: list of tags (optional, defaults to ``["latest"]``)
|
- ``tags``: list of tags (optional, defaults to ``["latest"]``)
|
||||||
|
|
||||||
Registry authentication uses ``CI_GITEA_TOKEN`` and ``CI_GITEA_USERNAME``
|
Registry authentication uses ``CI_GITEA_API_TOKEN`` (or legacy ``CI_GITEA_TOKEN``)
|
||||||
environment variables, matching the existing CI workflow patterns.
|
and ``CI_GITEA_USERNAME`` environment variables, matching the existing CI workflow patterns.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -49,6 +49,7 @@ from pathlib import Path
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -221,9 +222,12 @@ def push_image(
|
|||||||
|
|
||||||
def _get_registry_creds() -> tuple[str, str]:
|
def _get_registry_creds() -> tuple[str, str]:
|
||||||
"""Get registry credentials from environment variables."""
|
"""Get registry credentials from environment variables."""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
|
token = get_developer_token()
|
||||||
|
except click.ClickException:
|
||||||
|
token = None
|
||||||
username = os.environ.get("CI_GITEA_USERNAME", "")
|
username = os.environ.get("CI_GITEA_USERNAME", "")
|
||||||
return username, token
|
return username, token or ""
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
|
|||||||
@@ -28,12 +28,11 @@ Usage::
|
|||||||
--keep 2 \\
|
--keep 2 \\
|
||||||
--dry-run
|
--dry-run
|
||||||
|
|
||||||
Authentication uses ``CI_GITEA_TOKEN`` environment variable.
|
Authentication uses ``CI_GITEA_API_TOKEN`` environment variable (or legacy ``CI_GITEA_TOKEN``).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -42,6 +41,7 @@ import requests
|
|||||||
|
|
||||||
from devx.config import GITEA_API_URL, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
|
|
||||||
|
|
||||||
def list_package_versions(
|
def list_package_versions(
|
||||||
@@ -187,9 +187,10 @@ def main(
|
|||||||
api_url: str | None,
|
api_url: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Clean up old Docker image versions from a Gitea registry."""
|
"""Clean up old Docker image versions from a Gitea registry."""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
raise click.ClickException(_("CI_GITEA_TOKEN environment variable required"))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("CI_GITEA_TOKEN environment variable required")) from None
|
||||||
if not owner:
|
if not owner:
|
||||||
owner = REPO_OWNER
|
owner = REPO_OWNER
|
||||||
if not owner:
|
if not owner:
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ ci-improvement, doc-improvement, workflow-improvement) are created
|
|||||||
idempotently via ``ensure_label``.
|
idempotently via ``ensure_label``.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.tools.configure_repo --repo my-repo
|
DEVELOPER_GITEA_API_TOKEN=<token> python3 -m devx.tools.configure_repo --repo my-repo
|
||||||
CI_GITEA_TOKEN=<token> python3 -m devx.tools.configure_repo --repo my-repo --owner my-org
|
DEVELOPER_GITEA_API_TOKEN=<token> python3 -m devx.tools.configure_repo --repo my-repo --owner my-org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -23,6 +23,7 @@ from devx.api_clients import GiteaClient
|
|||||||
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
|
|
||||||
|
|
||||||
def _default_status_checks() -> list[str]:
|
def _default_status_checks() -> list[str]:
|
||||||
@@ -175,7 +176,10 @@ def configure_repo(
|
|||||||
)
|
)
|
||||||
def main(repo: str | None, owner: str | None, branch: str, api_url: str | None) -> None:
|
def main(repo: str | None, owner: str | None, branch: str, api_url: str | None) -> None:
|
||||||
"""Configure branch protection and repository settings via the Gitea API."""
|
"""Configure branch protection and repository settings via the Gitea API."""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
|
token = get_developer_token()
|
||||||
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
if repo is None:
|
if repo is None:
|
||||||
repo = os.environ.get("DEVX_REPO_NAME", "") or REPO_NAME
|
repo = os.environ.get("DEVX_REPO_NAME", "") or REPO_NAME
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from devx.config import (
|
|||||||
VIKUNJA_PROJECT_ID,
|
VIKUNJA_PROJECT_ID,
|
||||||
)
|
)
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token, get_vikunja_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -72,9 +73,10 @@ def get_vikunja_task_title(task_id: str) -> str:
|
|||||||
|
|
||||||
Raises ClickException if VIKUNJA_TOKEN is not set or the task is not found.
|
Raises ClickException if VIKUNJA_TOKEN is not set or the task is not found.
|
||||||
"""
|
"""
|
||||||
token = os.environ.get("VIKUNJA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_vikunja_token()
|
||||||
raise click.ClickException(_("VIKUNJA_TOKEN is not set. Required to derive PR title."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("VIKUNJA_TOKEN is not set. Required to derive PR title.")) from None
|
||||||
client = VikunjaClient(VIKUNJA_API_URL, token)
|
client = VikunjaClient(VIKUNJA_API_URL, token)
|
||||||
task = client.find_task_by_identifier(VIKUNJA_PROJECT_ID, task_id, per_page=DEFAULT_PER_PAGE)
|
task = client.find_task_by_identifier(VIKUNJA_PROJECT_ID, task_id, per_page=DEFAULT_PER_PAGE)
|
||||||
if not task:
|
if not task:
|
||||||
@@ -118,9 +120,10 @@ def create_pr(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
raise click.ClickException(_("CI_GITEA_TOKEN is not set. Required to create a PR."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("CI_GITEA_TOKEN is not set. Required to create a PR.")) from None
|
||||||
|
|
||||||
vikunja_title = get_vikunja_task_title(task_id)
|
vikunja_title = get_vikunja_task_title(task_id)
|
||||||
pr_title = f"{task_id}: {vikunja_title}"
|
pr_title = f"{task_id}: {vikunja_title}"
|
||||||
|
|||||||
@@ -17,14 +17,13 @@ and ``DEVX_TASK_PREFIX`` environment variables (or ``.env``).
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from devx.api_clients import VikunjaClient
|
from devx.api_clients import VikunjaClient
|
||||||
from devx.config import TASK_PREFIX, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID
|
from devx.config import TASK_PREFIX, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_vikunja_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -39,9 +38,10 @@ load_dotenv()
|
|||||||
@click.option("--project-id", type=int, default=None, help="Vikunja project ID (default: DEVX_VIKUNJA_PROJECT_ID).")
|
@click.option("--project-id", type=int, default=None, help="Vikunja project ID (default: DEVX_VIKUNJA_PROJECT_ID).")
|
||||||
def cli(title: str, description: str, project_id: int | None) -> None:
|
def cli(title: str, description: str, project_id: int | None) -> None:
|
||||||
"""Create a Vikunja task and print its identifier."""
|
"""Create a Vikunja task and print its identifier."""
|
||||||
token = os.environ.get("VIKUNJA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_vikunja_token()
|
||||||
raise click.ClickException(_("VIKUNJA_TOKEN is not set. Set it in .env or environment."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("VIKUNJA_TOKEN is not set. Set it in .env or environment.")) from None
|
||||||
|
|
||||||
pid = project_id if project_id is not None else VIKUNJA_PROJECT_ID
|
pid = project_id if project_id is not None else VIKUNJA_PROJECT_ID
|
||||||
|
|
||||||
|
|||||||
@@ -22,14 +22,13 @@ The repository is auto-detected from ``DEVX_REPO_OWNER`` /
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from devx.api_clients import GiteaClient
|
from devx.api_clients import GiteaClient
|
||||||
from devx.config import GITEA_API_URL, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
from devx.tools.create_pr import get_repo_name
|
from devx.tools.create_pr import get_repo_name
|
||||||
from devx.tools.pr_status import _get_current_branch_pr
|
from devx.tools.pr_status import _get_current_branch_pr
|
||||||
|
|
||||||
@@ -48,9 +47,10 @@ def cli(
|
|||||||
repo: str | None,
|
repo: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add one or more labels to a pull request (idempotent)."""
|
"""Add one or more labels to a pull request (idempotent)."""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
raise click.ClickException(_("CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
repo_owner = owner or REPO_OWNER
|
repo_owner = owner or REPO_OWNER
|
||||||
if not repo_owner:
|
if not repo_owner:
|
||||||
|
|||||||
@@ -25,14 +25,13 @@ The repository is auto-detected from ``DEVX_REPO_OWNER`` /
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from devx.api_clients import APIError, GiteaClient
|
from devx.api_clients import APIError, GiteaClient
|
||||||
from devx.config import GITEA_API_URL, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
from devx.tools.create_pr import get_repo_name
|
from devx.tools.create_pr import get_repo_name
|
||||||
from devx.tools.pr_status import _get_current_branch_pr
|
from devx.tools.pr_status import _get_current_branch_pr
|
||||||
|
|
||||||
@@ -126,9 +125,10 @@ def cli(
|
|||||||
repo: str | None,
|
repo: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Fetch logs for failed CI jobs on a pull request."""
|
"""Fetch logs for failed CI jobs on a pull request."""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
raise click.ClickException(_("CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
repo_owner = owner or REPO_OWNER
|
repo_owner = owner or REPO_OWNER
|
||||||
if not repo_owner:
|
if not repo_owner:
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
|||||||
from devx.api_clients import APIError, GiteaClient
|
from devx.api_clients import APIError, GiteaClient
|
||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
from devx.tools._shared import detect_pr_number
|
from devx.tools._shared import detect_pr_number
|
||||||
|
|
||||||
|
|
||||||
@@ -41,9 +42,10 @@ def main(pr: int | None) -> None:
|
|||||||
"""Rebase a pull request's head branch onto master via Gitea API."""
|
"""Rebase a pull request's head branch onto master via Gitea API."""
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
raise click.ClickException(_("CI_GITEA_TOKEN is not set. Add it to .env or export it."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("CI_GITEA_TOKEN is not set. Add it to .env or export it.")) from None
|
||||||
|
|
||||||
pr_num = pr or detect_pr_number()
|
pr_num = pr or detect_pr_number()
|
||||||
if not pr_num:
|
if not pr_num:
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ The repository is auto-detected from ``DEVX_REPO_OWNER`` /
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -34,6 +33,7 @@ from dotenv import load_dotenv
|
|||||||
from devx.api_clients import GiteaClient
|
from devx.api_clients import GiteaClient
|
||||||
from devx.config import GITEA_API_URL, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
from devx.tools.create_pr import get_repo_name
|
from devx.tools.create_pr import get_repo_name
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -139,9 +139,10 @@ def cli(
|
|||||||
repo: str | None,
|
repo: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check CI status for a pull request or commit."""
|
"""Check CI status for a pull request or commit."""
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
raise click.ClickException(_("CI_GITEA_TOKEN is not set."))
|
except click.ClickException:
|
||||||
|
raise click.ClickException(_("CI_GITEA_TOKEN is not set.")) from None
|
||||||
|
|
||||||
repo_owner = owner or REPO_OWNER
|
repo_owner = owner or REPO_OWNER
|
||||||
if not repo_owner:
|
if not repo_owner:
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ Exit codes:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@@ -29,6 +28,7 @@ from dotenv import load_dotenv
|
|||||||
from devx.api_clients import VikunjaClient
|
from devx.api_clients import VikunjaClient
|
||||||
from devx.config import DEFAULT_PER_PAGE, TASK_ID_RE, TASK_PREFIX, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID
|
from devx.config import DEFAULT_PER_PAGE, TASK_ID_RE, TASK_PREFIX, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
from devx.tokens import get_vikunja_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -55,8 +55,9 @@ def task_exists(task_id: str) -> bool:
|
|||||||
|
|
||||||
Returns ``False`` if VIKUNJA_TOKEN is not set (soft-fail in local mode).
|
Returns ``False`` if VIKUNJA_TOKEN is not set (soft-fail in local mode).
|
||||||
"""
|
"""
|
||||||
token = os.environ.get("VIKUNJA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_vikunja_token()
|
||||||
|
except click.ClickException:
|
||||||
return False
|
return False
|
||||||
client = VikunjaClient(VIKUNJA_API_URL, token)
|
client = VikunjaClient(VIKUNJA_API_URL, token)
|
||||||
return client.find_task_by_identifier(VIKUNJA_PROJECT_ID, task_id, per_page=DEFAULT_PER_PAGE) is not None
|
return client.find_task_by_identifier(VIKUNJA_PROJECT_ID, task_id, per_page=DEFAULT_PER_PAGE) is not None
|
||||||
@@ -84,8 +85,9 @@ def validate(branch: str) -> None:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
token = os.environ.get("VIKUNJA_TOKEN", "")
|
try:
|
||||||
if not token:
|
get_vikunja_token()
|
||||||
|
except click.ClickException:
|
||||||
click.echo(
|
click.echo(
|
||||||
_(
|
_(
|
||||||
"WARNING: VIKUNJA_TOKEN not set — skipping task existence check. "
|
"WARNING: VIKUNJA_TOKEN not set — skipping task existence check. "
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ from pathlib import Path
|
|||||||
import click
|
import click
|
||||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||||
|
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
@@ -64,19 +66,20 @@ def _install_ansible_collections(bin_dir: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _configure_tea_login() -> None:
|
def _configure_tea_login() -> None:
|
||||||
"""Configure tea CLI login from .env if CI_GITEA_TOKEN is set.
|
"""Configure tea CLI login from .env if a Gitea token is set.
|
||||||
|
|
||||||
Idempotent: if a login with the same name already exists, it is not re-added.
|
Idempotent: if a login with the same name already exists, it is not re-added.
|
||||||
Skips if tea is not installed or CI_GITEA_TOKEN is not set.
|
Skips if tea is not installed or no Gitea token is set.
|
||||||
"""
|
"""
|
||||||
tea_bin = shutil.which("tea")
|
tea_bin = shutil.which("tea")
|
||||||
if tea_bin is None:
|
if tea_bin is None:
|
||||||
click.echo("tea: not installed — run 'make install-tools' to install it.")
|
click.echo("tea: not installed — run 'make install-tools' to install it.")
|
||||||
return
|
return
|
||||||
|
|
||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
if not token:
|
token = get_developer_token()
|
||||||
click.echo("tea: CI_GITEA_TOKEN not set — skipping login configuration.")
|
except click.ClickException:
|
||||||
|
click.echo("tea: Gitea API token not set — skipping login configuration.")
|
||||||
return
|
return
|
||||||
|
|
||||||
api_url = os.environ.get("DEVX_GITEA_API_URL", "https://git.oblachno.oblachno.fyi/api/v1")
|
api_url = os.environ.get("DEVX_GITEA_API_URL", "https://git.oblachno.oblachno.fyi/api/v1")
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
from devx.tokens import get_developer_token
|
||||||
|
|
||||||
DEFAULT_VENV = ".venv"
|
DEFAULT_VENV = ".venv"
|
||||||
OPT_VENV = "/opt/venv"
|
OPT_VENV = "/opt/venv"
|
||||||
FALLBACK_TARGET = "setup-ci"
|
FALLBACK_TARGET = "setup-ci"
|
||||||
@@ -67,7 +69,10 @@ def _install_in_image(
|
|||||||
cmd = [pip_bin, "install", "--no-cache-dir", "-e", spec]
|
cmd = [pip_bin, "install", "--no-cache-dir", "-e", spec]
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
token = env.get("CI_GITEA_TOKEN", "")
|
try:
|
||||||
|
token = get_developer_token()
|
||||||
|
except click.ClickException:
|
||||||
|
token = None
|
||||||
if token:
|
if token:
|
||||||
username = env.get("CI_GITEA_USERNAME", "emil")
|
username = env.get("CI_GITEA_USERNAME", "emil")
|
||||||
env["PIP_EXTRA_INDEX_URL"] = _build_pip_extra_index_url(
|
env["PIP_EXTRA_INDEX_URL"] = _build_pip_extra_index_url(
|
||||||
|
|||||||
@@ -959,6 +959,14 @@
|
|||||||
"ru": "CI checks failed.",
|
"ru": "CI checks failed.",
|
||||||
"zh": "CI checks failed."
|
"zh": "CI checks failed."
|
||||||
},
|
},
|
||||||
|
"Gitea API token not set. Set one of: {names}": {
|
||||||
|
"bg": "Gitea API token not set. Set one of: {names}",
|
||||||
|
"de": "Gitea API token not set. Set one of: {names}",
|
||||||
|
"en": "Gitea API token not set. Set one of: {names}",
|
||||||
|
"pl": "Gitea API token not set. Set one of: {names}",
|
||||||
|
"ru": "Gitea API token not set. Set one of: {names}",
|
||||||
|
"zh": "Gitea API token not set. Set one of: {names}"
|
||||||
|
},
|
||||||
"CI_GITEA_TOKEN environment variable required": {
|
"CI_GITEA_TOKEN environment variable required": {
|
||||||
"bg": "CI_GITEA_TOKEN environment variable required",
|
"bg": "CI_GITEA_TOKEN environment variable required",
|
||||||
"de": "CI_GITEA_TOKEN environment variable required",
|
"de": "CI_GITEA_TOKEN environment variable required",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import click
|
||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
@@ -249,3 +250,14 @@ class TestMain:
|
|||||||
args, kwargs = mock_count.call_args
|
args, kwargs = mock_count.call_args
|
||||||
assert "myorg" in args
|
assert "myorg" in args
|
||||||
assert "myrepo" in args
|
assert "myrepo" in args
|
||||||
|
|
||||||
|
@patch("devx.ci.discover_runners.get_ci_token", side_effect=click.ClickException("no token"))
|
||||||
|
@patch("devx.ci.discover_runners.get_runner_count", return_value=3)
|
||||||
|
def test_missing_token_runs_without_api(self, mock_count: MagicMock, mock_token: MagicMock) -> None:
|
||||||
|
"""When no token is available, runner discovery falls back to env/default."""
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(main, ["--count"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert result.output.strip() == "3"
|
||||||
|
args, _ = mock_count.call_args
|
||||||
|
assert args[1] is None # token passed as None when missing
|
||||||
|
|||||||
@@ -186,6 +186,28 @@ class TestCli:
|
|||||||
timeout=60,
|
timeout=60,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@patch("devx.molecule.molecule_ci_guard.get_ci_token", side_effect=click.ClickException("no token"))
|
||||||
|
def test_missing_token_runs_without_polling(self, mock_token: MagicMock) -> None:
|
||||||
|
"""When no token is available, cross-runner polling is skipped."""
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("devx.molecule.molecule_ci_guard.subprocess.Popen") as mock_popen,
|
||||||
|
patch("devx.molecule.molecule_ci_guard.subprocess.run") as mock_run,
|
||||||
|
patch("devx.molecule.molecule_ci_guard.poll_for_other_failures") as mock_poll,
|
||||||
|
patch("time.sleep"),
|
||||||
|
):
|
||||||
|
proc = MagicMock()
|
||||||
|
proc.poll.return_value = 0
|
||||||
|
proc.returncode = 0
|
||||||
|
mock_popen.return_value = proc
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_poll.assert_not_called()
|
||||||
|
|
||||||
def test_invalid_pair_format_raises(self) -> None:
|
def test_invalid_pair_format_raises(self) -> None:
|
||||||
"""Pair with fewer than 2 parts should raise."""
|
"""Pair with fewer than 2 parts should raise."""
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import click
|
||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
@@ -219,3 +220,14 @@ class TestMain:
|
|||||||
args, kwargs = mock_count.call_args
|
args, kwargs = mock_count.call_args
|
||||||
assert "myorg" in args
|
assert "myorg" in args
|
||||||
assert "myrepo" in args
|
assert "myrepo" in args
|
||||||
|
|
||||||
|
@patch("devx.molecule.discover_runners.get_ci_token", side_effect=click.ClickException("no token"))
|
||||||
|
@patch("devx.molecule.discover_runners.get_runner_count", return_value=3)
|
||||||
|
def test_missing_token_runs_without_api(self, mock_count: MagicMock, mock_token: MagicMock) -> None:
|
||||||
|
"""When no token is available, runner discovery falls back to env/default."""
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(main, ["--count"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert result.output.strip() == "3"
|
||||||
|
args, _ = mock_count.call_args
|
||||||
|
assert args[1] is None # token passed as None when missing
|
||||||
|
|||||||
@@ -12,9 +12,14 @@ from devx.tools.pr_label import cli
|
|||||||
|
|
||||||
class TestCli:
|
class TestCli:
|
||||||
def test_no_token_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_no_token_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.delenv("CI_GITEA_TOKEN", raising=False)
|
for name in ("DEVELOPER_GITEA_API_TOKEN", "CI_GITEA_API_TOKEN", "CI_GITEA_TOKEN"):
|
||||||
|
monkeypatch.delenv(name, raising=False)
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(cli, ["--pr", "42", "--label", "ready-to-merge"])
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
["--pr", "42", "--label", "ready-to-merge"],
|
||||||
|
env={"DEVELOPER_GITEA_API_TOKEN": "", "CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""},
|
||||||
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "CI_GITEA_TOKEN" in result.output
|
assert "CI_GITEA_TOKEN" in result.output
|
||||||
|
|
||||||
|
|||||||
@@ -153,9 +153,14 @@ class TestPrintLogs:
|
|||||||
|
|
||||||
class TestCli:
|
class TestCli:
|
||||||
def test_no_token_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_no_token_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.delenv("CI_GITEA_TOKEN", raising=False)
|
for name in ("DEVELOPER_GITEA_API_TOKEN", "CI_GITEA_API_TOKEN", "CI_GITEA_TOKEN"):
|
||||||
|
monkeypatch.delenv(name, raising=False)
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(cli, ["--pr", "42"])
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
["--pr", "42"],
|
||||||
|
env={"DEVELOPER_GITEA_API_TOKEN": "", "CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""},
|
||||||
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "CI_GITEA_TOKEN" in result.output
|
assert "CI_GITEA_TOKEN" in result.output
|
||||||
|
|
||||||
|
|||||||
@@ -756,9 +756,10 @@ class TestMain:
|
|||||||
result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_TOKEN": "fake"})
|
result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_TOKEN": "fake"})
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""})
|
||||||
def test_no_token_raises(self) -> None:
|
def test_no_token_raises(self) -> None:
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_TOKEN": ""})
|
result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""})
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "CI_GITEA_TOKEN" in result.output
|
assert "CI_GITEA_TOKEN" in result.output
|
||||||
|
|
||||||
|
|||||||
@@ -122,9 +122,14 @@ class TestWaitForCompletion:
|
|||||||
|
|
||||||
class TestCli:
|
class TestCli:
|
||||||
def test_no_token_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_no_token_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.delenv("CI_GITEA_TOKEN", raising=False)
|
for name in ("DEVELOPER_GITEA_API_TOKEN", "CI_GITEA_API_TOKEN", "CI_GITEA_TOKEN"):
|
||||||
|
monkeypatch.delenv(name, raising=False)
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(cli, ["--pr", "42"])
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
["--pr", "42"],
|
||||||
|
env={"DEVELOPER_GITEA_API_TOKEN": "", "CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""},
|
||||||
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "CI_GITEA_TOKEN" in result.output
|
assert "CI_GITEA_TOKEN" in result.output
|
||||||
|
|
||||||
|
|||||||
@@ -256,9 +256,10 @@ class TestCommitAndPush:
|
|||||||
|
|
||||||
class TestMain:
|
class TestMain:
|
||||||
def test_no_token_raises(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_no_token_raises(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.delenv("CI_GITEA_TOKEN", raising=False)
|
for name in ("CI_GITEA_API_TOKEN", "CI_GITEA_TOKEN"):
|
||||||
|
monkeypatch.delenv(name, raising=False)
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(main, [])
|
result = runner.invoke(main, [], env={"CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""})
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "CI_GITEA_TOKEN" in result.output
|
assert "CI_GITEA_TOKEN" in result.output
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user