mirror of
https://github.com/basnijholt/compose-farm.git
synced 2026-02-09 08:42:17 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eac9338352 | ||
|
|
667931dc80 |
@@ -110,6 +110,10 @@ Browser tests are marked with `@pytest.mark.browser`. They use Playwright to tes
|
||||
Use `gh release create` to create releases. The tag is created automatically.
|
||||
|
||||
```bash
|
||||
# IMPORTANT: Ensure you're on latest origin/main before releasing!
|
||||
git fetch origin
|
||||
git checkout origin/main
|
||||
|
||||
# Check current version
|
||||
git tag --sort=-v:refname | head -1
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Annotated, TypeVar
|
||||
|
||||
@@ -68,6 +69,21 @@ ServiceOption = Annotated[
|
||||
_MISSING_PATH_PREVIEW_LIMIT = 2
|
||||
_STATS_PREVIEW_LIMIT = 3 # Max number of pending migrations to show by name
|
||||
|
||||
# Environment variable to identify the web stack (for self-update ordering)
|
||||
CF_WEB_STACK = os.environ.get("CF_WEB_STACK", "")
|
||||
|
||||
|
||||
def sort_web_stack_last(stacks: list[str]) -> list[str]:
|
||||
"""Move the web stack to the end of the list.
|
||||
|
||||
When updating all stacks, the web UI stack (compose-farm) should be updated
|
||||
last. Otherwise, the container restarts mid-process and cancels remaining
|
||||
updates. The CF_WEB_STACK env var identifies the web stack.
|
||||
"""
|
||||
if CF_WEB_STACK and CF_WEB_STACK in stacks:
|
||||
return [s for s in stacks if s != CF_WEB_STACK] + [CF_WEB_STACK]
|
||||
return stacks
|
||||
|
||||
|
||||
def format_host(host: str | list[str]) -> str:
|
||||
"""Format a host value for display."""
|
||||
|
||||
@@ -23,6 +23,7 @@ from compose_farm.cli.common import (
|
||||
maybe_regenerate_traefik,
|
||||
report_results,
|
||||
run_async,
|
||||
sort_web_stack_last,
|
||||
validate_host_for_stack,
|
||||
validate_stacks,
|
||||
)
|
||||
@@ -171,6 +172,8 @@ def restart(
|
||||
raw = True
|
||||
results = run_async(run_on_stacks(cfg, stack_list, f"restart {service}", raw=raw))
|
||||
else:
|
||||
# Sort web stack last to avoid self-restart canceling remaining restarts
|
||||
stack_list = sort_web_stack_last(stack_list)
|
||||
raw = len(stack_list) == 1
|
||||
results = run_async(run_sequential_on_stacks(cfg, stack_list, ["down", "up -d"], raw=raw))
|
||||
maybe_regenerate_traefik(cfg, results)
|
||||
@@ -206,6 +209,8 @@ def update(
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Sort web stack last to avoid self-restart canceling remaining updates
|
||||
stack_list = sort_web_stack_last(stack_list)
|
||||
raw = len(stack_list) == 1
|
||||
results = run_async(
|
||||
run_sequential_on_stacks(
|
||||
@@ -328,6 +333,11 @@ def apply( # noqa: C901, PLR0912, PLR0915 (multi-phase reconciliation needs the
|
||||
console.print(f"\n{MSG_DRY_RUN}")
|
||||
return
|
||||
|
||||
# Sort web stack last in each phase to avoid self-restart canceling remaining work
|
||||
migrations = sort_web_stack_last(migrations)
|
||||
missing = sort_web_stack_last(missing)
|
||||
to_refresh = sort_web_stack_last(to_refresh)
|
||||
|
||||
# Execute changes
|
||||
console.print()
|
||||
all_results = []
|
||||
|
||||
@@ -437,3 +437,71 @@ class TestDownOrphaned:
|
||||
)
|
||||
|
||||
assert exc_info.value.exit_code == 1
|
||||
|
||||
|
||||
class TestSortWebStackLast:
|
||||
"""Tests for the sort_web_stack_last helper."""
|
||||
|
||||
def test_no_web_stack_env(self) -> None:
|
||||
"""When CF_WEB_STACK is not set, list is unchanged."""
|
||||
from compose_farm.cli import common
|
||||
|
||||
original = common.CF_WEB_STACK
|
||||
try:
|
||||
common.CF_WEB_STACK = ""
|
||||
stacks = ["a", "b", "c"]
|
||||
result = common.sort_web_stack_last(stacks)
|
||||
assert result == ["a", "b", "c"]
|
||||
finally:
|
||||
common.CF_WEB_STACK = original
|
||||
|
||||
def test_web_stack_not_in_list(self) -> None:
|
||||
"""When web stack is not in list, list is unchanged."""
|
||||
from compose_farm.cli import common
|
||||
|
||||
original = common.CF_WEB_STACK
|
||||
try:
|
||||
common.CF_WEB_STACK = "webstack"
|
||||
stacks = ["a", "b", "c"]
|
||||
result = common.sort_web_stack_last(stacks)
|
||||
assert result == ["a", "b", "c"]
|
||||
finally:
|
||||
common.CF_WEB_STACK = original
|
||||
|
||||
def test_web_stack_moved_to_end(self) -> None:
|
||||
"""When web stack is in list, it's moved to the end."""
|
||||
from compose_farm.cli import common
|
||||
|
||||
original = common.CF_WEB_STACK
|
||||
try:
|
||||
common.CF_WEB_STACK = "webstack"
|
||||
stacks = ["a", "webstack", "b", "c"]
|
||||
result = common.sort_web_stack_last(stacks)
|
||||
assert result == ["a", "b", "c", "webstack"]
|
||||
finally:
|
||||
common.CF_WEB_STACK = original
|
||||
|
||||
def test_web_stack_already_last(self) -> None:
|
||||
"""When web stack is already last, list is unchanged."""
|
||||
from compose_farm.cli import common
|
||||
|
||||
original = common.CF_WEB_STACK
|
||||
try:
|
||||
common.CF_WEB_STACK = "webstack"
|
||||
stacks = ["a", "b", "webstack"]
|
||||
result = common.sort_web_stack_last(stacks)
|
||||
assert result == ["a", "b", "webstack"]
|
||||
finally:
|
||||
common.CF_WEB_STACK = original
|
||||
|
||||
def test_empty_list(self) -> None:
|
||||
"""Empty list returns empty list."""
|
||||
from compose_farm.cli import common
|
||||
|
||||
original = common.CF_WEB_STACK
|
||||
try:
|
||||
common.CF_WEB_STACK = "webstack"
|
||||
result = common.sort_web_stack_last([])
|
||||
assert result == []
|
||||
finally:
|
||||
common.CF_WEB_STACK = original
|
||||
|
||||
Reference in New Issue
Block a user