Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4e4621fa1 | ||
|
|
a6f814c446 | ||
|
|
04aa5acb1f | ||
|
|
6973f9d851 | ||
|
|
2669a0ea73 | ||
|
|
03f057b55a |
@@ -32,7 +32,11 @@ concurrency:
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
container:
|
||||
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
credentials:
|
||||
username: ${{ vars.CI_GITEA_USERNAME }}
|
||||
password: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
is-release: ${{ steps.check.outputs.is-release }}
|
||||
@@ -116,7 +120,11 @@ jobs:
|
||||
needs: [build-and-push]
|
||||
if: always() && needs.build-and-push.result == 'success'
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
container:
|
||||
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
credentials:
|
||||
username: ${{ vars.CI_GITEA_USERNAME }}
|
||||
password: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
+10
-2
@@ -18,7 +18,11 @@ jobs:
|
||||
# Saves ~4x checkout+setup overhead vs 5 separate jobs.
|
||||
validate:
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
container:
|
||||
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
credentials:
|
||||
username: ${{ vars.CI_GITEA_USERNAME }}
|
||||
password: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
@@ -140,7 +144,11 @@ jobs:
|
||||
github.event_name == 'pull_request' &&
|
||||
needs.validate.result == 'success'
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
container:
|
||||
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
credentials:
|
||||
username: ${{ vars.CI_GITEA_USERNAME }}
|
||||
password: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
timeout-minutes: 10
|
||||
defaults:
|
||||
run:
|
||||
|
||||
@@ -35,7 +35,11 @@ env:
|
||||
jobs:
|
||||
detect-and-configure:
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
container:
|
||||
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
credentials:
|
||||
username: ${{ vars.CI_GITEA_USERNAME }}
|
||||
password: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
timeout-minutes: 10
|
||||
defaults:
|
||||
run:
|
||||
@@ -99,7 +103,11 @@ jobs:
|
||||
needs: [detect-and-configure]
|
||||
if: always() && needs.detect-and-configure.result == 'success'
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
container:
|
||||
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
credentials:
|
||||
username: ${{ vars.CI_GITEA_USERNAME }}
|
||||
password: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
tag: ${{ steps.release-tag.outputs.tag }}
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.49.4] - 2026-08-07
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add container.credentials for private registry auth
|
||||
|
||||
## [0.49.3] - 2026-08-07
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Retry ansible-galaxy collection install on transient timeouts
|
||||
|
||||
## [0.49.2] - 2026-08-07
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -16,12 +16,12 @@ quality badges.
|
||||
|
||||
[](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/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/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](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/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Why devx?
|
||||
|
||||
@@ -87,7 +87,7 @@ extra index and list devx in your dependencies:
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"devx>=0.49.2",
|
||||
"devx>=0.49.4",
|
||||
]
|
||||
|
||||
[tool.pip]
|
||||
@@ -101,8 +101,8 @@ pip install -e .
|
||||
```
|
||||
|
||||
> **Note:** If your project requires a specific devx version, pin it in
|
||||
> `dependencies` (for example, `"devx==0.49.2"`) or use a version constraint
|
||||
> (for example, `"devx>=0.49.2,<0.50"`).
|
||||
> `dependencies` (for example, `"devx==0.49.4"`) or use a version constraint
|
||||
> (for example, `"devx>=0.49.4,<0.50"`).
|
||||
|
||||
### 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/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/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](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/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -74,14 +74,14 @@ Add devx to your `pyproject.toml` dependencies and configure the registry:
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"devx>=0.49.2",
|
||||
"devx>=0.49.4",
|
||||
]
|
||||
|
||||
[tool.pip]
|
||||
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
|
||||
```
|
||||
|
||||
Pin a specific version if needed: `"devx==0.49.2"` or `"devx>=0.49.2,<0.50"`.
|
||||
Pin a specific version if needed: `"devx==0.49.4"` or `"devx>=0.49.4,<0.50"`.
|
||||
|
||||
### Optional extras
|
||||
|
||||
|
||||
@@ -48,12 +48,12 @@ Add devx to your `pyproject.toml`:
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"devx>=0.49.2",
|
||||
"devx>=0.49.4",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"devx>=0.49.2",
|
||||
"devx>=0.49.4",
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
||||
|
||||
__version__ = "0.49.2"
|
||||
__version__ = "0.49.4"
|
||||
|
||||
+12
-2
@@ -15,6 +15,7 @@ from pathlib import Path
|
||||
|
||||
import click
|
||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
from devx.tokens import get_developer_token
|
||||
|
||||
@@ -56,13 +57,22 @@ def _install_pre_commit_hooks(bin_dir: str) -> None:
|
||||
|
||||
|
||||
def _install_ansible_collections(bin_dir: str) -> None:
|
||||
"""Install required Ansible Galaxy collections if requirements exist."""
|
||||
"""Install required Ansible Galaxy collections if requirements exist.
|
||||
|
||||
Retries up to 3 times with exponential backoff to handle transient
|
||||
network timeouts when contacting galaxy.ansible.com.
|
||||
"""
|
||||
galaxy = shutil.which("ansible-galaxy") or str(Path(bin_dir) / "ansible-galaxy")
|
||||
requirements = Path("ansible/requirements.yml")
|
||||
if not requirements.exists():
|
||||
click.echo(" ansible/requirements.yml not found — skipping collections.")
|
||||
return
|
||||
_run([galaxy, "collection", "install", "-r", str(requirements)])
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=2, min=2, max=10), reraise=True)
|
||||
def _do_install() -> None:
|
||||
_run([galaxy, "collection", "install", "-r", str(requirements)])
|
||||
|
||||
_do_install()
|
||||
|
||||
|
||||
def _configure_tea_login() -> None:
|
||||
|
||||
@@ -114,6 +114,49 @@ class TestInstallAnsibleCollections:
|
||||
_install_ansible_collections(".venv/bin")
|
||||
mock_run.assert_not_called()
|
||||
|
||||
@patch("tenacity.nap.time.sleep")
|
||||
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/ansible-galaxy")
|
||||
@patch("devx.tools.setup._run")
|
||||
def test_retries_on_transient_failure(
|
||||
self, mock_run: MagicMock, mock_which: MagicMock, mock_sleep: MagicMock, tmp_path: Path
|
||||
) -> None:
|
||||
"""ansible-galaxy install should retry on transient network errors."""
|
||||
import subprocess as _subprocess
|
||||
|
||||
req = tmp_path / "ansible" / "requirements.yml"
|
||||
req.parent.mkdir(parents=True)
|
||||
req.write_text("collections: []")
|
||||
# First call fails (timeout), second succeeds
|
||||
mock_run.side_effect = [
|
||||
_subprocess.CalledProcessError(1, ["ansible-galaxy", "collection", "install"]),
|
||||
None,
|
||||
]
|
||||
with patch("devx.tools.setup.Path") as mock_path:
|
||||
mock_path.return_value.exists.return_value = True
|
||||
mock_path.return_value.__str__ = lambda _: str(req)
|
||||
_install_ansible_collections(".venv/bin")
|
||||
assert mock_run.call_count == 2
|
||||
|
||||
@patch("tenacity.nap.time.sleep")
|
||||
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/ansible-galaxy")
|
||||
@patch("devx.tools.setup._run")
|
||||
def test_exhausts_retries_then_raises(
|
||||
self, mock_run: MagicMock, mock_which: MagicMock, mock_sleep: MagicMock, tmp_path: Path
|
||||
) -> None:
|
||||
"""After 3 attempts, the error should propagate."""
|
||||
import subprocess as _subprocess
|
||||
|
||||
req = tmp_path / "ansible" / "requirements.yml"
|
||||
req.parent.mkdir(parents=True)
|
||||
req.write_text("collections: []")
|
||||
mock_run.side_effect = _subprocess.CalledProcessError(1, ["ansible-galaxy"])
|
||||
with patch("devx.tools.setup.Path") as mock_path:
|
||||
mock_path.return_value.exists.return_value = True
|
||||
mock_path.return_value.__str__ = lambda _: str(req)
|
||||
with pytest.raises(_subprocess.CalledProcessError):
|
||||
_install_ansible_collections(".venv/bin")
|
||||
assert mock_run.call_count == 3
|
||||
|
||||
|
||||
class TestConfigureTeaLogin:
|
||||
@patch("devx.tools.setup.shutil.which", return_value=None)
|
||||
|
||||
Reference in New Issue
Block a user