Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/control-plane.yml
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ jobs:
ref: ${{ inputs.sha }}
path: app-repo
fetch-depth: 0
submodules: ${{ steps.control-check-policy.outputs.checkout_submodules == 'true' && 'recursive' || 'false' }}
submodules: ${{ steps.control-check-policy.outputs.checkout_submodules == 'true' && inputs.event != 'pull_request' && 'recursive' || 'false' }}
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false

Expand Down Expand Up @@ -494,7 +494,7 @@ jobs:
ref: ${{ matrix.target.sha }}
path: app-repo
fetch-depth: 0
submodules: ${{ matrix.target.checkout_submodules == true && 'recursive' || 'false' }}
submodules: ${{ matrix.target.checkout_submodules == true && matrix.target.event != 'pull_request' && 'recursive' || 'false' }}
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false

Expand Down
4 changes: 1 addition & 3 deletions src/aio_fleet/poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ def poll_targets(
sha=sha,
event="pull_request",
source=f"pr:{number}",
checkout_submodules=bool(
repo.raw.get("checkout_submodules")
),
checkout_submodules=False,
publish=False,
)
)
Expand Down
18 changes: 14 additions & 4 deletions src/aio_fleet/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,20 @@ def _release_tag_sha_allowed(
subjects = git(
repo.path, "log", "--format=%s", f"{release_target_commit}..{sha}"
)
changed_files = git(
repo.path, "diff", "--name-only", f"{release_target_commit}..{sha}"
)
except (Exception, SystemExit):
return False
return bool(subjects.strip()) and all(
_RELEASE_FORMAT_SUBJECT.match(subject.strip())
for subject in subjects.splitlines()
if subject.strip()

subject_lines = [
subject.strip() for subject in subjects.splitlines() if subject.strip()
]
changed_paths = [
path.strip() for path in changed_files.splitlines() if path.strip()
]
return (
bool(subject_lines)
and all(_RELEASE_FORMAT_SUBJECT.match(subject) for subject in subject_lines)
and changed_paths == ["CHANGELOG.md"]
)
15 changes: 11 additions & 4 deletions src/aio_fleet/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,17 @@ def find_release_publish_target_commit(repo_path: Path, version: str) -> str:
if not git_is_ancestor(repo_path, release_target, head):
return release_target
subjects = git(repo_path, "log", "--format=%s", f"{release_target}..{head}")
if subjects.strip() and all(
RELEASE_FORMAT_SUBJECT.match(subject.strip())
for subject in subjects.splitlines()
if subject.strip()
changed_files = git(repo_path, "diff", "--name-only", f"{release_target}..{head}")
subject_lines = [
subject.strip() for subject in subjects.splitlines() if subject.strip()
]
changed_paths = [
path.strip() for path in changed_files.splitlines() if path.strip()
]
if (
subject_lines
and all(RELEASE_FORMAT_SUBJECT.match(subject) for subject in subject_lines)
and changed_paths == ["CHANGELOG.md"]
):
return head
return release_target
Expand Down
24 changes: 19 additions & 5 deletions src/aio_fleet/safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
"redis",
"volume",
}
GITHUB_CLI_TOKEN_KEYS = (
"AIO_FLEET_DASHBOARD_TOKEN",
"AIO_FLEET_UPSTREAM_TOKEN",
"AIO_FLEET_ISSUE_TOKEN",
"AIO_FLEET_WORKFLOW_TOKEN",
"AIO_FLEET_CHECK_TOKEN",
"APP_TOKEN",
"GH_TOKEN",
"GITHUB_TOKEN",
)


@dataclass(frozen=True)
Expand Down Expand Up @@ -646,9 +656,13 @@ def _gh_json(args: list[str], *, check: bool = True) -> Any:

def _gh_env() -> dict[str, str]:
env = dict(os.environ)
if not env.get("GH_TOKEN"):
for key in ("AIO_FLEET_WORKFLOW_TOKEN", "AIO_FLEET_CHECK_TOKEN", "APP_TOKEN"):
if env.get(key):
env["GH_TOKEN"] = env[key]
break
token = ""
for key in GITHUB_CLI_TOKEN_KEYS:
token = env.get(key, "").strip()
if token:
break
if token:
for key in GITHUB_CLI_TOKEN_KEYS:
env.pop(key, None)
env["GH_TOKEN"] = token
return env
4 changes: 2 additions & 2 deletions tests/test_control_plane_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ def test_app_code_checkouts_gate_submodule_checkout() -> None:

assert "checkout_submodules" in manual["with"]["submodules"] # nosec B101
assert (
"inputs.event != 'pull_request'" not in manual["with"]["submodules"]
"inputs.event != 'pull_request'" in manual["with"]["submodules"]
) # nosec B101
assert "checkout_submodules" in poll["with"]["submodules"] # nosec B101
assert (
"matrix.target.event != 'pull_request'" not in poll["with"]["submodules"]
"matrix.target.event != 'pull_request'" in poll["with"]["submodules"]
) # nosec B101


Expand Down
4 changes: 2 additions & 2 deletions tests/test_poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def test_poll_targets_require_same_repository_pr_identity(
]


def test_poll_targets_emit_checkout_submodules_for_same_repo_pr_and_main(
def test_poll_targets_emit_checkout_submodules_only_for_main(
tmp_path: Path, monkeypatch
) -> None:
manifest_path = _write_manifest(tmp_path, checkout_submodules=True)
Expand All @@ -94,7 +94,7 @@ def test_poll_targets_emit_checkout_submodules_for_same_repo_pr_and_main(
"pull_request",
"push",
]
assert targets[0].checkout_submodules is True # nosec B101
assert targets[0].checkout_submodules is False # nosec B101
assert targets[1].checkout_submodules is True # nosec B101


Expand Down
64 changes: 59 additions & 5 deletions tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,23 @@ def test_release_tag_allows_changelog_format_followup(monkeypatch) -> None:
registry, "find_release_target_commit", lambda *_args, **_kwargs: release_sha
)
monkeypatch.setattr(registry, "git_is_ancestor", lambda *_args: True)
monkeypatch.setattr(
registry,
"git",
lambda *_args: "chore(release): format sure changelog",
)

def fake_git(_path: Path, command: str, *args: str) -> str:
if (command, *args[:2]) == (
"log",
"--format=%s",
f"{release_sha}..{publish_sha}",
):
return "chore(release): format sure changelog"
if (command, *args[:2]) == (
"diff",
"--name-only",
f"{release_sha}..{publish_sha}",
):
return "CHANGELOG.md"
raise AssertionError(f"unexpected git call: {(command, *args)}")

monkeypatch.setattr(registry, "git", fake_git)

tags = registry.compute_registry_tags(repo, sha=publish_sha)

Expand Down Expand Up @@ -134,6 +146,48 @@ def test_release_tag_rejects_arbitrary_post_release_commit(monkeypatch) -> None:
assert "ghcr.io/jsonbored/sure-aio:0.7.0-aio.2" not in tags.ghcr # nosec B101


def test_release_tag_rejects_changelog_format_subject_with_runtime_changes(
monkeypatch,
) -> None:
repo = load_manifest(ROOT / "fleet.yml").repo("sure-aio")
release_sha = "c" * 40
publish_sha = "d" * 40

monkeypatch.setattr(
registry, "_read_component_upstream_version", lambda *_: "0.7.0"
)
monkeypatch.setattr(
registry, "latest_changelog_version", lambda *_args, **_kwargs: "0.7.0-aio.2"
)
monkeypatch.setattr(
registry, "find_release_target_commit", lambda *_args, **_kwargs: release_sha
)
monkeypatch.setattr(registry, "git_is_ancestor", lambda *_args: True)

def fake_git(_path: Path, command: str, *args: str) -> str:
if (command, *args[:2]) == (
"log",
"--format=%s",
f"{release_sha}..{publish_sha}",
):
return "chore(release): format sure changelog"
if (command, *args[:2]) == (
"diff",
"--name-only",
f"{release_sha}..{publish_sha}",
):
return "CHANGELOG.md\nDockerfile"
raise AssertionError(f"unexpected git call: {(command, *args)}")

monkeypatch.setattr(registry, "git", fake_git)

tags = registry.compute_registry_tags(repo, sha=publish_sha)

assert tags.release_package_tag == "" # nosec B101
assert "jsonbored/sure-aio:0.7.0-aio.2" not in tags.dockerhub # nosec B101
assert "ghcr.io/jsonbored/sure-aio:0.7.0-aio.2" not in tags.ghcr # nosec B101


def test_signoz_agent_publish_command_uses_component_context(monkeypatch) -> None:
repo = load_manifest(ROOT / "fleet.yml").repo("signoz-aio")

Expand Down
22 changes: 22 additions & 0 deletions tests/test_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,28 @@ def test_release_publish_target_rejects_arbitrary_post_release_commit(
)


def test_release_publish_target_rejects_changelog_subject_with_runtime_changes(
tmp_path: Path,
) -> None:
_init_repo(tmp_path)
(tmp_path / "CHANGELOG.md").write_text("## Unreleased\n\n- initial\n")
_commit(tmp_path, "feat(test): initial")
(tmp_path / "CHANGELOG.md").write_text(
"## v1.0.0-aio.1 - 2026-05-10\n\n- initial\n"
)
_commit(tmp_path, "chore(release): v1.0.0-aio.1")
release_commit = _git_output(tmp_path, "rev-parse", "HEAD")
(tmp_path / "CHANGELOG.md").write_text(
"## v1.0.0-aio.1 - 2026-05-10\n\n- initial\n\n"
)
(tmp_path / "Dockerfile").write_text("FROM scratch\n")
_commit(tmp_path, "chore(release): format test changelog")

assert ( # nosec B101
find_release_publish_target_commit(tmp_path, "v1.0.0-aio.1") == release_commit
)


def test_release_cli_supports_component_suffix(tmp_path: Path, capsys) -> None:
_init_repo(tmp_path)
(tmp_path / "components").mkdir()
Expand Down
28 changes: 28 additions & 0 deletions tests/test_safety.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import subprocess
from pathlib import Path

from aio_fleet import safety
Expand Down Expand Up @@ -79,6 +80,33 @@ def test_runtime_smoke_failure_blocks(tmp_path: Path, monkeypatch) -> None:
)


def test_safety_gh_reads_use_dashboard_token(monkeypatch) -> None:
captured_env: dict[str, str] = {}

def fake_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
nonlocal captured_env
captured_env = dict(kwargs.get("env") or {})
return subprocess.CompletedProcess(
args=args,
returncode=0,
stdout="[]",
stderr="",
)

monkeypatch.setenv("AIO_FLEET_DASHBOARD_TOKEN", "dashboard-token")
monkeypatch.setenv("AIO_FLEET_UPSTREAM_TOKEN", "upstream-token")
monkeypatch.setenv("AIO_FLEET_CHECK_TOKEN", "check-token")
monkeypatch.setenv("GH_TOKEN", "lower-priority-token")
monkeypatch.setenv("GITHUB_TOKEN", "repo-token")
monkeypatch.setattr(safety.subprocess, "run", fake_run)

assert safety._gh_json(["api", "repos/JSONbored/private"]) == [] # nosec B101
assert captured_env["GH_TOKEN"] == "dashboard-token" # nosec B101
assert "AIO_FLEET_DASHBOARD_TOKEN" not in captured_env # nosec B101
assert "AIO_FLEET_CHECK_TOKEN" not in captured_env # nosec B101
assert "GITHUB_TOKEN" not in captured_env # nosec B101


def test_missing_required_check_blocks(tmp_path: Path, monkeypatch) -> None:
repo = load_manifest(_manifest(tmp_path)).repo("example-aio")
_write_xml(tmp_path / "example-aio.xml", targets=["8080"])
Expand Down
Loading