diff --git a/changelog/current.md b/changelog/current.md index 3e12dc5f1..81ef3b0bf 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -13,6 +13,7 @@ Record image-affecting changes to `manager/`, `worker/`, `copaw/`, `openclaw-bas **Bug Fixes** +- **CoPaw Worker k8s storage**: CoPaw workers in self-hosted k8s deployments now fall back to static MinIO alias setup when `MC_HOST_hiclaw` is not injected, restoring startup `mc mirror` behavior while preserving cloud STS mode. - **CoPaw Worker heartbeat**: CoPaw worker templates now seed heartbeat at a 10-minute interval so Team Leader agents created from the worker template can run heartbeat turns without requiring an explicit Team CR heartbeat spec. - **Helm CRDs**: Removed unsupported `propertyNames` schema fields from Worker and Team CRDs so Kubernetes API servers accept the chart CRDs. - **CoPaw local runtime paths**: CoPaw direct-run defaults now honor `COPAW_INSTALL_DIR` and `COPAW_WORKING_DIR` before falling back to local home-directory paths, while container entrypoints can continue to pass explicit directories. diff --git a/copaw/src/copaw_worker/sync.py b/copaw/src/copaw_worker/sync.py index 93ee19e9a..ea715b2f9 100644 --- a/copaw/src/copaw_worker/sync.py +++ b/copaw/src/copaw_worker/sync.py @@ -279,7 +279,8 @@ def _ensure_alias(self) -> None: Cloud mode (RRSA/STS): refresh credentials before every mc batch via the shared shell function (lazy, no-op when token is valid). - Local mode: set mc alias once with static credentials. + Local and self-hosted k8s modes: set mc alias once with static + credentials, unless k8s already injected ``MC_HOST_hiclaw``. """ runtime = os.environ.get("HICLAW_RUNTIME", "") mc_host_set = bool(os.environ.get(f"MC_HOST_{_MC_ALIAS}")) @@ -297,8 +298,11 @@ def _ensure_alias(self) -> None: mc_host_set, controller_url, ) - if self._k8s_mode: - logger.info("_ensure_alias: k8s mode, skipping mc alias set (mc-wrapper handles credentials)") + if self._k8s_mode and mc_host_set: + logger.info( + "_ensure_alias: k8s mode with MC_HOST_%s, skipping static alias set", + _MC_ALIAS, + ) self._alias_set = True return if self._cloud_mode: @@ -314,7 +318,7 @@ def _ensure_alias(self) -> None: if self._alias_set: logger.info("_ensure_alias: credential path=static, alias already set") return - # Local mode: static credentials, set alias once + # Local and self-hosted k8s modes use static credentials. if self.endpoint.startswith("http"): url = self.endpoint else: diff --git a/copaw/tests/test_worker_sync.py b/copaw/tests/test_worker_sync.py index 5f057d3ec..79abbcb48 100644 --- a/copaw/tests/test_worker_sync.py +++ b/copaw/tests/test_worker_sync.py @@ -7,10 +7,13 @@ from copaw_worker.sync import FileSync -def test_ensure_alias_skips_static_alias_in_k8s_mode(monkeypatch, tmp_path): +def test_ensure_alias_skips_static_alias_in_k8s_mode_when_mc_host_exists( + monkeypatch, tmp_path +): calls = [] monkeypatch.setenv("HICLAW_RUNTIME", "k8s") + monkeypatch.setenv("MC_HOST_hiclaw", "http://token@example.com") monkeypatch.setattr(sync, "_mc", lambda *args, **_kwargs: calls.append(args)) fs = FileSync( @@ -28,6 +31,32 @@ def test_ensure_alias_skips_static_alias_in_k8s_mode(monkeypatch, tmp_path): assert calls == [] +def test_ensure_alias_falls_back_to_static_alias_in_local_k8s( + monkeypatch, tmp_path +): + calls = [] + + monkeypatch.setenv("HICLAW_RUNTIME", "k8s") + monkeypatch.delenv("MC_HOST_hiclaw", raising=False) + monkeypatch.setattr(sync, "_mc", lambda *args, **_kwargs: calls.append(args)) + + fs = FileSync( + endpoint="minio:9000", + access_key="tt", + secret_key="secret", + bucket="hiclaw", + worker_name="tt", + local_dir=tmp_path, + ) + + fs._ensure_alias() + + assert fs._alias_set is True + assert calls == [ + ("alias", "set", "hiclaw", "http://minio:9000", "tt", "secret") + ] + + def test_filesync_fallback_uses_copaw_working_dir_parent(monkeypatch, tmp_path): working_dir = tmp_path / "alice" / ".copaw" monkeypatch.setenv("COPAW_WORKING_DIR", str(working_dir))