Fix NFS backup destinations broken by the 0.40.0 refactor (0.40.1)
_ensure_helper_image moved to stack_assets_service, but backup_destination_service imports it lazily inside _nfs_run/_nfs_helper, so nothing failed at import time — every NFS destination operation raised ImportError at runtime instead. The helper is now a public ensure_helper_image() and the NFS helpers import it from its new home. Verified: every services/ and routers/ module imports, and both NFS helper paths run through to a Docker call instead of ImportError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -288,10 +288,10 @@ def _nfs_run(volume: str, command: list[str]) -> str:
|
|||||||
|
|
||||||
from config import settings
|
from config import settings
|
||||||
from docker_client import DockerError, get_client
|
from docker_client import DockerError, get_client
|
||||||
from services.backup_service import _ensure_helper_image
|
from services.stack_assets_service import ensure_helper_image
|
||||||
|
|
||||||
client = get_client()
|
client = get_client()
|
||||||
_ensure_helper_image(client)
|
ensure_helper_image(client)
|
||||||
try:
|
try:
|
||||||
out = client.containers.run(
|
out = client.containers.run(
|
||||||
settings.BACKUP_HELPER_IMAGE,
|
settings.BACKUP_HELPER_IMAGE,
|
||||||
@@ -314,10 +314,10 @@ def _nfs_helper(volume: str):
|
|||||||
|
|
||||||
from config import settings
|
from config import settings
|
||||||
from docker_client import DockerError, get_client, safe_call
|
from docker_client import DockerError, get_client, safe_call
|
||||||
from services.backup_service import _ensure_helper_image
|
from services.stack_assets_service import ensure_helper_image
|
||||||
|
|
||||||
client = get_client()
|
client = get_client()
|
||||||
_ensure_helper_image(client)
|
ensure_helper_image(client)
|
||||||
try:
|
try:
|
||||||
return safe_call(
|
return safe_call(
|
||||||
client.containers.create,
|
client.containers.create,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class AssetError(Exception):
|
|||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
def _ensure_helper_image(client) -> None:
|
def ensure_helper_image(client) -> None:
|
||||||
image = settings.BACKUP_HELPER_IMAGE
|
image = settings.BACKUP_HELPER_IMAGE
|
||||||
try:
|
try:
|
||||||
safe_call(client.images.get, image)
|
safe_call(client.images.get, image)
|
||||||
@@ -104,7 +104,7 @@ def inspect_paths(paths: list[str]) -> dict[str, dict]:
|
|||||||
if not unique:
|
if not unique:
|
||||||
return {}
|
return {}
|
||||||
client = get_client()
|
client = get_client()
|
||||||
_ensure_helper_image(client)
|
ensure_helper_image(client)
|
||||||
mounts = {p: {"bind": f"/m/{i}", "mode": "ro"} for i, p in enumerate(unique)}
|
mounts = {p: {"bind": f"/m/{i}", "mode": "ro"} for i, p in enumerate(unique)}
|
||||||
script_parts = []
|
script_parts = []
|
||||||
for i in range(len(unique)):
|
for i in range(len(unique)):
|
||||||
@@ -149,7 +149,7 @@ def inspect_paths(paths: list[str]) -> dict[str, dict]:
|
|||||||
def export_path(source: str, kind: str, dest_file: str) -> int:
|
def export_path(source: str, kind: str, dest_file: str) -> int:
|
||||||
"""Tar a host path (dir contents, or a single file) into ``dest_file``."""
|
"""Tar a host path (dir contents, or a single file) into ``dest_file``."""
|
||||||
client = get_client()
|
client = get_client()
|
||||||
_ensure_helper_image(client)
|
ensure_helper_image(client)
|
||||||
if kind == "file":
|
if kind == "file":
|
||||||
parent, base = _split(source)
|
parent, base = _split(source)
|
||||||
if not base:
|
if not base:
|
||||||
@@ -176,7 +176,7 @@ def export_path(source: str, kind: str, dest_file: str) -> int:
|
|||||||
def import_path(source: str, kind: str, src_file: str) -> None:
|
def import_path(source: str, kind: str, src_file: str) -> None:
|
||||||
"""Unpack an archive produced by :func:`export_path` back to its host path."""
|
"""Unpack an archive produced by :func:`export_path` back to its host path."""
|
||||||
client = get_client()
|
client = get_client()
|
||||||
_ensure_helper_image(client)
|
ensure_helper_image(client)
|
||||||
if kind == "file":
|
if kind == "file":
|
||||||
parent, _base = _split(source)
|
parent, _base = _split(source)
|
||||||
container = _create_helper(client, {parent: {"bind": "/dst", "mode": "rw"}})
|
container = _create_helper(client, {parent: {"bind": "/dst", "mode": "rw"}})
|
||||||
@@ -192,7 +192,7 @@ def import_path(source: str, kind: str, src_file: str) -> None:
|
|||||||
def export_volume(full_name: str, dest_file: str) -> int:
|
def export_volume(full_name: str, dest_file: str) -> int:
|
||||||
"""Stream a named volume's contents into ``dest_file`` (never into RAM)."""
|
"""Stream a named volume's contents into ``dest_file`` (never into RAM)."""
|
||||||
client = get_client()
|
client = get_client()
|
||||||
_ensure_helper_image(client)
|
ensure_helper_image(client)
|
||||||
container = _create_helper(client, {full_name: {"bind": "/v", "mode": "ro"}})
|
container = _create_helper(client, {full_name: {"bind": "/v", "mode": "ro"}})
|
||||||
written = 0
|
written = 0
|
||||||
try:
|
try:
|
||||||
@@ -209,7 +209,7 @@ def export_volume(full_name: str, dest_file: str) -> int:
|
|||||||
def import_volume(full_name: str, labels: dict, src_file: str, wipe: bool = True) -> None:
|
def import_volume(full_name: str, labels: dict, src_file: str, wipe: bool = True) -> None:
|
||||||
"""Restore a volume from an archive, optionally clearing it first."""
|
"""Restore a volume from an archive, optionally clearing it first."""
|
||||||
client = get_client()
|
client = get_client()
|
||||||
_ensure_helper_image(client)
|
ensure_helper_image(client)
|
||||||
existed = True
|
existed = True
|
||||||
try:
|
try:
|
||||||
safe_call(client.volumes.get, full_name)
|
safe_call(client.volumes.get, full_name)
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
"""Single source of truth for the StackPilot release version."""
|
"""Single source of truth for the StackPilot release version."""
|
||||||
|
|
||||||
APP_VERSION = "0.40.0"
|
APP_VERSION = "0.40.1"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "stackpilot-frontend",
|
"name": "stackpilot-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.40.0",
|
"version": "0.40.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Reference in New Issue
Block a user