Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33cfbb0f41 | ||
|
|
598238e4d6 | ||
|
|
925b99b7db | ||
|
|
16ed48bd26 | ||
|
|
3b0500164b | ||
|
|
00a44ec5dc | ||
|
|
b385c57621 | ||
|
|
3181b24f5e | ||
|
|
bc8478220c | ||
|
|
a568c0899f | ||
|
|
4216698ca8 |
@@ -23,6 +23,10 @@ on:
|
|||||||
- src/devx/**
|
- src/devx/**
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build-images
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
detect-type:
|
detect-type:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
|
|||||||
@@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
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.23.3] - 2026-06-27
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Correct clean_images delete URL and add retry with error handling
|
||||||
|
|
||||||
|
## [0.23.2] - 2026-06-27
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Add skip-ci flag to release commits and concurrency to build-images
|
||||||
|
|
||||||
|
## [0.23.1] - 2026-06-27
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Add rsync to ci-full image for molecule_docker
|
||||||
|
|
||||||
## [0.23.0] - 2026-06-27
|
## [0.23.0] - 2026-06-27
|
||||||
|
|
||||||
### 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?
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ FROM git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-quality:latest
|
|||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
# Install rsync (required by molecule_docker for file sync between host and test containers)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends rsync \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install devx[release,molecule,deploy] from local source
|
# Install devx[release,molecule,deploy] from local source
|
||||||
COPY . /tmp/devx
|
COPY . /tmp/devx
|
||||||
RUN pip install --no-cache-dir /tmp/devx[release,molecule,deploy] \
|
RUN pip install --no-cache-dir /tmp/devx[release,molecule,deploy] \
|
||||||
|
|||||||
+6
-6
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.23.0"
|
__version__ = "0.23.3"
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ def commit_release_changes(new_version: str) -> bool:
|
|||||||
if status.returncode == 0:
|
if status.returncode == 0:
|
||||||
click.echo(_("No staged changes — version and changelog already up to date."))
|
click.echo(_("No staged changes — version and changelog already up to date."))
|
||||||
return False
|
return False
|
||||||
run_cmd(["git", "commit", "--no-verify", "-m", f"release: v{new_version}"])
|
run_cmd(["git", "commit", "--no-verify", "-m", f"release: v{new_version} [skip ci]"])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -705,7 +705,7 @@ def main(dry_run: bool, skip_tests: bool, verify: bool) -> None:
|
|||||||
click.echo(_("\n[dry-run] Changelog:\n{changelog}", changelog=changelog))
|
click.echo(_("\n[dry-run] Changelog:\n{changelog}", changelog=changelog))
|
||||||
click.echo(_("[dry-run] Would update {init}", init=INIT_FILE))
|
click.echo(_("[dry-run] Would update {init}", init=INIT_FILE))
|
||||||
click.echo(_("[dry-run] Would update {changelog_file}", changelog_file=CHANGELOG_FILE))
|
click.echo(_("[dry-run] Would update {changelog_file}", changelog_file=CHANGELOG_FILE))
|
||||||
click.echo(_("[dry-run] Would commit: release: v{version}", version=new_version))
|
click.echo(_("[dry-run] Would commit: release: v{version} [skip ci]", version=new_version))
|
||||||
click.echo(_("[dry-run] Would push commit to master"))
|
click.echo(_("[dry-run] Would push commit to master"))
|
||||||
click.echo(_("[dry-run] Would create tag: v{version}", version=new_version))
|
click.echo(_("[dry-run] Would create tag: v{version}", version=new_version))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Authentication uses ``CI_GITEA_TOKEN`` environment variable.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@@ -85,15 +86,37 @@ def delete_package_version(
|
|||||||
token: str,
|
token: str,
|
||||||
*,
|
*,
|
||||||
timeout: int = 30,
|
timeout: int = 30,
|
||||||
|
package_type: str = "container",
|
||||||
|
max_retries: int = 3,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Delete a specific version of a container package.
|
"""Delete a specific version of a container package.
|
||||||
|
|
||||||
|
Uses the Gitea API endpoint ``DELETE /packages/{owner}/{type}/{name}/{version}``.
|
||||||
|
Retries on transient failures (5xx, timeouts) up to ``max_retries`` times.
|
||||||
|
|
||||||
Returns True on success, False on failure.
|
Returns True on success, False on failure.
|
||||||
"""
|
"""
|
||||||
url = f"{api_url}/packages/{owner}/{name}/{version}"
|
url = f"{api_url}/packages/{owner}/{package_type}/{name}/{version}"
|
||||||
headers = {"Authorization": f"token {token}"}
|
headers = {"Authorization": f"token {token}"}
|
||||||
resp = requests.delete(url, headers=headers, timeout=timeout)
|
for attempt in range(max_retries):
|
||||||
return resp.status_code in (204, 200)
|
try:
|
||||||
|
resp = requests.delete(url, headers=headers, timeout=timeout)
|
||||||
|
except requests.RequestException:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
time.sleep(2**attempt)
|
||||||
|
continue
|
||||||
|
return False
|
||||||
|
if resp.status_code in (204, 200):
|
||||||
|
return True
|
||||||
|
# 404 means already deleted — treat as success
|
||||||
|
if resp.status_code == 404:
|
||||||
|
return True
|
||||||
|
# 5xx is transient — retry
|
||||||
|
if 500 <= resp.status_code < 600 and attempt < max_retries - 1:
|
||||||
|
time.sleep(2**attempt)
|
||||||
|
continue
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def sort_versions_by_date(
|
def sort_versions_by_date(
|
||||||
@@ -171,6 +194,7 @@ def main(
|
|||||||
|
|
||||||
total_deleted = 0
|
total_deleted = 0
|
||||||
total_kept = 0
|
total_kept = 0
|
||||||
|
total_failed = 0
|
||||||
for name in names:
|
for name in names:
|
||||||
click.echo(f"\n{'=' * 60}")
|
click.echo(f"\n{'=' * 60}")
|
||||||
click.echo(f"Package: {owner}/{name}")
|
click.echo(f"Package: {owner}/{name}")
|
||||||
@@ -182,6 +206,7 @@ def main(
|
|||||||
_("Failed to list versions for {name}: {error}", name=name, error=exc),
|
_("Failed to list versions for {name}: {error}", name=name, error=exc),
|
||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
|
total_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not versions:
|
if not versions:
|
||||||
@@ -203,6 +228,7 @@ def main(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
deleted_count = 0
|
deleted_count = 0
|
||||||
|
failed_count = 0
|
||||||
for v in to_delete:
|
for v in to_delete:
|
||||||
version = str(v.get("version", ""))
|
version = str(v.get("version", ""))
|
||||||
if delete_package_version(base_url, owner, name, version, token):
|
if delete_package_version(base_url, owner, name, version, token):
|
||||||
@@ -210,11 +236,15 @@ def main(
|
|||||||
deleted_count += 1
|
deleted_count += 1
|
||||||
else:
|
else:
|
||||||
click.echo(f" FAILED to delete: {version}", err=True)
|
click.echo(f" FAILED to delete: {version}", err=True)
|
||||||
|
failed_count += 1
|
||||||
|
|
||||||
total_deleted += deleted_count
|
total_deleted += deleted_count
|
||||||
total_kept += kept_count
|
total_kept += kept_count
|
||||||
|
total_failed += failed_count
|
||||||
|
|
||||||
click.echo(f"\nDone. Deleted {total_deleted}, kept {total_kept}.")
|
click.echo(f"\nDone. Deleted {total_deleted}, kept {total_kept}, failed {total_failed}.")
|
||||||
|
if total_failed > 0:
|
||||||
|
raise click.ClickException(_("Failed to delete {count} image version(s)", count=total_failed))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
|||||||
@@ -1391,6 +1391,14 @@
|
|||||||
"ru": "CI_GITEA_TOKEN environment variable required",
|
"ru": "CI_GITEA_TOKEN environment variable required",
|
||||||
"zh": "CI_GITEA_TOKEN environment variable required"
|
"zh": "CI_GITEA_TOKEN environment variable required"
|
||||||
},
|
},
|
||||||
|
"Failed to delete {count} image version(s)": {
|
||||||
|
"bg": "Failed to delete {count} image version(s)",
|
||||||
|
"de": "Failed to delete {count} image version(s)",
|
||||||
|
"en": "Failed to delete {count} image version(s)",
|
||||||
|
"pl": "Failed to delete {count} image version(s)",
|
||||||
|
"ru": "Failed to delete {count} image version(s)",
|
||||||
|
"zh": "Failed to delete {count} image version(s)"
|
||||||
|
},
|
||||||
"CI_GITEA_TOKEN is not set. Required to create a PR.": {
|
"CI_GITEA_TOKEN is not set. Required to create a PR.": {
|
||||||
"bg": "CI_GITEA_TOKEN не е зададен. Необходим за създаване на PR.",
|
"bg": "CI_GITEA_TOKEN не е зададен. Необходим за създаване на PR.",
|
||||||
"de": "CI_GITEA_TOKEN nicht gesetzt. Erforderlich zum Erstellen eines PR.",
|
"de": "CI_GITEA_TOKEN nicht gesetzt. Erforderlich zum Erstellen eines PR.",
|
||||||
@@ -1799,13 +1807,13 @@
|
|||||||
"ru": "[check_test_coverage] No changed files to check.",
|
"ru": "[check_test_coverage] No changed files to check.",
|
||||||
"zh": "[check_test_coverage] No changed files to check."
|
"zh": "[check_test_coverage] No changed files to check."
|
||||||
},
|
},
|
||||||
"[dry-run] Would commit: release: v{version}": {
|
"[dry-run] Would commit: release: v{version} [skip ci]": {
|
||||||
"bg": "[dry-run] Would commit: release: v{version}",
|
"bg": "[dry-run] Would commit: release: v{version} [skip ci]",
|
||||||
"de": "[dry-run] Would commit: release: v{version}",
|
"de": "[dry-run] Would commit: release: v{version} [skip ci]",
|
||||||
"en": "[dry-run] Would commit: release: v{version}",
|
"en": "[dry-run] Would commit: release: v{version} [skip ci]",
|
||||||
"pl": "[dry-run] Utworzono by commit: release: v{version}",
|
"pl": "[dry-run] Utworzono by commit: release: v{version} [skip ci]",
|
||||||
"ru": "[dry-run] Would commit: release: v{version}",
|
"ru": "[dry-run] Would commit: release: v{version} [skip ci]",
|
||||||
"zh": "[dry-run] Would commit: release: v{version}"
|
"zh": "[dry-run] Would commit: release: v{version} [skip ci]"
|
||||||
},
|
},
|
||||||
"[dry-run] Would create tag: v{version}": {
|
"[dry-run] Would create tag: v{version}": {
|
||||||
"bg": "[dry-run] Would create tag: v{version}",
|
"bg": "[dry-run] Would create tag: v{version}",
|
||||||
|
|||||||
@@ -321,6 +321,25 @@ class TestCleanImagesAPI:
|
|||||||
from devx.tools.clean_images import delete_package_version
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
mock_resp = MagicMock(status_code=204)
|
mock_resp = MagicMock(status_code=204)
|
||||||
|
with patch("devx.tools.clean_images.requests.delete", return_value=mock_resp) as mock_del:
|
||||||
|
assert (
|
||||||
|
delete_package_version(
|
||||||
|
"https://git.example.com/api/v1",
|
||||||
|
"oblachno-oss",
|
||||||
|
"ci-base",
|
||||||
|
"0.1.0",
|
||||||
|
"token",
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
# Verify URL includes container type
|
||||||
|
url = mock_del.call_args.args[0]
|
||||||
|
assert "/container/" in url
|
||||||
|
|
||||||
|
def test_delete_package_version_404_treated_as_success(self) -> None:
|
||||||
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
|
mock_resp = MagicMock(status_code=404)
|
||||||
with patch("devx.tools.clean_images.requests.delete", return_value=mock_resp):
|
with patch("devx.tools.clean_images.requests.delete", return_value=mock_resp):
|
||||||
assert (
|
assert (
|
||||||
delete_package_version(
|
delete_package_version(
|
||||||
@@ -336,7 +355,7 @@ class TestCleanImagesAPI:
|
|||||||
def test_delete_package_version_failure(self) -> None:
|
def test_delete_package_version_failure(self) -> None:
|
||||||
from devx.tools.clean_images import delete_package_version
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
mock_resp = MagicMock(status_code=404)
|
mock_resp = MagicMock(status_code=403)
|
||||||
with patch("devx.tools.clean_images.requests.delete", return_value=mock_resp):
|
with patch("devx.tools.clean_images.requests.delete", return_value=mock_resp):
|
||||||
assert (
|
assert (
|
||||||
delete_package_version(
|
delete_package_version(
|
||||||
@@ -349,6 +368,110 @@ class TestCleanImagesAPI:
|
|||||||
is False
|
is False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_delete_package_version_retries_on_5xx(self) -> None:
|
||||||
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
|
responses = [
|
||||||
|
MagicMock(status_code=500),
|
||||||
|
MagicMock(status_code=502),
|
||||||
|
MagicMock(status_code=204),
|
||||||
|
]
|
||||||
|
with patch("devx.tools.clean_images.requests.delete", side_effect=responses):
|
||||||
|
with patch("devx.tools.clean_images.time.sleep"):
|
||||||
|
assert (
|
||||||
|
delete_package_version(
|
||||||
|
"https://git.example.com/api/v1",
|
||||||
|
"oblachno-oss",
|
||||||
|
"ci-base",
|
||||||
|
"0.1.0",
|
||||||
|
"token",
|
||||||
|
max_retries=3,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_package_version_retries_on_exception(self) -> None:
|
||||||
|
import requests as req
|
||||||
|
|
||||||
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
|
responses = [
|
||||||
|
req.ConnectionError("network down"),
|
||||||
|
MagicMock(status_code=204),
|
||||||
|
]
|
||||||
|
with patch("devx.tools.clean_images.requests.delete", side_effect=responses):
|
||||||
|
with patch("devx.tools.clean_images.time.sleep"):
|
||||||
|
assert (
|
||||||
|
delete_package_version(
|
||||||
|
"https://git.example.com/api/v1",
|
||||||
|
"oblachno-oss",
|
||||||
|
"ci-base",
|
||||||
|
"0.1.0",
|
||||||
|
"token",
|
||||||
|
max_retries=3,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_package_version_exhausts_retries_on_exception(self) -> None:
|
||||||
|
import requests as req
|
||||||
|
|
||||||
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"devx.tools.clean_images.requests.delete",
|
||||||
|
side_effect=req.ConnectionError("network down"),
|
||||||
|
):
|
||||||
|
with patch("devx.tools.clean_images.time.sleep"):
|
||||||
|
assert (
|
||||||
|
delete_package_version(
|
||||||
|
"https://git.example.com/api/v1",
|
||||||
|
"oblachno-oss",
|
||||||
|
"ci-base",
|
||||||
|
"0.1.0",
|
||||||
|
"token",
|
||||||
|
max_retries=2,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_package_version_exhausts_retries_on_5xx(self) -> None:
|
||||||
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"devx.tools.clean_images.requests.delete",
|
||||||
|
return_value=MagicMock(status_code=500),
|
||||||
|
):
|
||||||
|
with patch("devx.tools.clean_images.time.sleep"):
|
||||||
|
assert (
|
||||||
|
delete_package_version(
|
||||||
|
"https://git.example.com/api/v1",
|
||||||
|
"oblachno-oss",
|
||||||
|
"ci-base",
|
||||||
|
"0.1.0",
|
||||||
|
"token",
|
||||||
|
max_retries=2,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_package_version_zero_retries(self) -> None:
|
||||||
|
from devx.tools.clean_images import delete_package_version
|
||||||
|
|
||||||
|
with patch("devx.tools.clean_images.requests.delete") as mock_del:
|
||||||
|
assert (
|
||||||
|
delete_package_version(
|
||||||
|
"https://git.example.com/api/v1",
|
||||||
|
"oblachno-oss",
|
||||||
|
"ci-base",
|
||||||
|
"0.1.0",
|
||||||
|
"token",
|
||||||
|
max_retries=0,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
mock_del.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
class TestCLIBuildImage:
|
class TestCLIBuildImage:
|
||||||
def test_single_image_build(self, tmp_path: Path) -> None:
|
def test_single_image_build(self, tmp_path: Path) -> None:
|
||||||
@@ -557,7 +680,7 @@ class TestCLICleanImages:
|
|||||||
clean_main,
|
clean_main,
|
||||||
["--owner", "oblachno-oss", "--name", "ci-base", "--dry-run"],
|
["--owner", "oblachno-oss", "--name", "ci-base", "--dry-run"],
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code != 0
|
||||||
assert "Failed to list" in result.output
|
assert "Failed to list" in result.output
|
||||||
|
|
||||||
def test_delete_failure_in_cli(self) -> None:
|
def test_delete_failure_in_cli(self) -> None:
|
||||||
@@ -571,13 +694,15 @@ class TestCLICleanImages:
|
|||||||
{"version": "0.3.0", "created_at": "2025-03-01"},
|
{"version": "0.3.0", "created_at": "2025-03-01"},
|
||||||
]
|
]
|
||||||
list_resp.raise_for_status = MagicMock()
|
list_resp.raise_for_status = MagicMock()
|
||||||
delete_resp = MagicMock(status_code=500)
|
delete_resp = MagicMock(status_code=403)
|
||||||
with patch.dict("os.environ", {"CI_GITEA_TOKEN": "fake"}):
|
with patch.dict("os.environ", {"CI_GITEA_TOKEN": "fake"}):
|
||||||
with patch("devx.tools.clean_images.requests.get", return_value=list_resp):
|
with patch("devx.tools.clean_images.requests.get", return_value=list_resp):
|
||||||
with patch("devx.tools.clean_images.requests.delete", return_value=delete_resp):
|
with patch("devx.tools.clean_images.requests.delete", return_value=delete_resp):
|
||||||
result = runner.invoke(
|
with patch("devx.tools.clean_images.time.sleep"):
|
||||||
clean_main,
|
result = runner.invoke(
|
||||||
["--owner", "oblachno-oss", "--name", "ci-base", "--keep", "2"],
|
clean_main,
|
||||||
)
|
["--owner", "oblachno-oss", "--name", "ci-base", "--keep", "2"],
|
||||||
assert result.exit_code == 0
|
)
|
||||||
assert "FAILED" in result.output
|
assert result.exit_code != 0
|
||||||
|
assert "FAILED" in result.output
|
||||||
|
assert "failed" in result.output.lower()
|
||||||
|
|||||||
@@ -802,7 +802,7 @@ class TestCommitReleaseChanges:
|
|||||||
assert result is True
|
assert result is True
|
||||||
calls = [c.args[0] for c in mock_run_cmd.call_args_list]
|
calls = [c.args[0] for c in mock_run_cmd.call_args_list]
|
||||||
assert ["git", "add", "src/devx/__init__.py", "CHANGELOG.md"] in calls
|
assert ["git", "add", "src/devx/__init__.py", "CHANGELOG.md"] in calls
|
||||||
assert ["git", "commit", "--no-verify", "-m", "release: v0.2.0"] in calls
|
assert ["git", "commit", "--no-verify", "-m", "release: v0.2.0 [skip ci]"] in calls
|
||||||
|
|
||||||
@patch("devx.ci.release.run_cmd")
|
@patch("devx.ci.release.run_cmd")
|
||||||
def test_skips_when_no_changes(self, mock_run_cmd: MagicMock) -> None:
|
def test_skips_when_no_changes(self, mock_run_cmd: MagicMock) -> None:
|
||||||
@@ -811,7 +811,7 @@ class TestCommitReleaseChanges:
|
|||||||
result = commit_release_changes("0.1.0")
|
result = commit_release_changes("0.1.0")
|
||||||
assert result is False
|
assert result is False
|
||||||
calls = [c.args[0] for c in mock_run_cmd.call_args_list]
|
calls = [c.args[0] for c in mock_run_cmd.call_args_list]
|
||||||
assert ["git", "commit", "--no-verify", "-m", "release: v0.1.0"] not in calls
|
assert ["git", "commit", "--no-verify", "-m", "release: v0.1.0 [skip ci]"] not in calls
|
||||||
|
|
||||||
|
|
||||||
class TestCreateAndPushTag:
|
class TestCreateAndPushTag:
|
||||||
|
|||||||
Reference in New Issue
Block a user