deploy: add git-pull source for host deployment
Host can now fetch code directly from GitHub instead of receiving a VM-built tarball, bypassing the VM->host scp/kex failures and stale-tar risk. Design: VM sends only two small scripts (host_git_fetch.sh + host_deploy.sh, sha-verified via the existing transfer chain, now with a chunk_key fallback that does not need SSH_PASSWORD); host clones via a read-only deploy key (SSH only, HTTPS refused, key must exist with mode 0600), checks out the exact 40-char commit, verifies HEAD, builds a tar in the same format as the VM release (DEPLOYED_COMMIT + RELEASE.json, member names without ./ prefix so host_deploy.sh tar -O works), then calls the unchanged host_deploy.sh for staging swap, backup, health and rollback. Git fetch tolerates the known intermittent host->GitHub degradation: 120s timeout, 3 attempts, 10s sleep, live untouched until host_deploy runs. VM gates: clean tree, tests, commit must be on origin/main, SSH repo only, full sha only; --source tar remains the default. Tests: deploy suite OK, including 6 new host_git_fetch simulations (success with clean tar members, unknown commit, key permissions/missing, HTTPS refusal, short sha) and 4 new deploy.py git-mode helpers plus chunk_key fallback; git-mode dry-run verified against HEAD 630d14f.
This commit is contained in:
1 parent
630d14fe07
commit
e8e256f6e5
5 files changed
+542
-7
No files matched your search
@@ -82,6 +82,71 @@ class TestDeployPure(unittest.TestCase):
|
||||
self.assertIsNone(deploy_mod.live_commit("Warning: something\n"))
|
||||
|
||||
|
||||
class TestGitModeHelpers(unittest.TestCase):
|
||||
"""--source git 的纯逻辑门禁:完整 sha、SSH 形式、origin/main
|
||||
归属。主机只能拉到已推的提交,未推的 HEAD 必须在 VM 侧拦住。"""
|
||||
|
||||
def test_is_full_sha(self):
|
||||
self.assertTrue(deploy_mod.is_full_sha("a" * 40))
|
||||
self.assertFalse(deploy_mod.is_full_sha("a" * 7))
|
||||
self.assertFalse(deploy_mod.is_full_sha("g" * 40))
|
||||
self.assertFalse(deploy_mod.is_full_sha(""))
|
||||
self.assertFalse(deploy_mod.is_full_sha(None))
|
||||
|
||||
def test_is_ssh_git_url(self):
|
||||
self.assertTrue(deploy_mod.is_ssh_git_url(
|
||||
"git@github.com:openqj/codex-app-server.git"))
|
||||
self.assertTrue(deploy_mod.is_ssh_git_url(
|
||||
"ssh://git@github.com/openqj/codex-app-server.git"))
|
||||
self.assertFalse(deploy_mod.is_ssh_git_url(
|
||||
"https://github.com/openqj/codex-app-server.git"))
|
||||
self.assertFalse(deploy_mod.is_ssh_git_url(""))
|
||||
|
||||
def test_commit_on_origin_main_uses_merge_base(self):
|
||||
calls = []
|
||||
|
||||
def fake_run(cmd, cwd=None, check=True):
|
||||
calls.append(cmd)
|
||||
return subprocess.CompletedProcess(args=cmd, returncode=0,
|
||||
stdout="", stderr="")
|
||||
|
||||
with mock.patch.object(deploy_mod, "run", side_effect=fake_run):
|
||||
self.assertTrue(deploy_mod.commit_on_origin_main("a" * 40))
|
||||
self.assertIn("merge-base", calls[0])
|
||||
self.assertIn("origin/main", calls[0])
|
||||
|
||||
def test_transfer_falls_back_to_chunk_key(self):
|
||||
"""scp 失败后走 chunk_key 分片(密钥版),不依赖 SSH_PASSWORD;
|
||||
验远端 sha 通过即成功,不应退出。"""
|
||||
calls = []
|
||||
|
||||
def fake_run(cmd, cwd=None, check=True, **kwargs):
|
||||
calls.append(cmd)
|
||||
if cmd[0] == "scp":
|
||||
return subprocess.CompletedProcess(args=cmd, returncode=1,
|
||||
stdout="", stderr="reset")
|
||||
return subprocess.CompletedProcess(args=cmd, returncode=0,
|
||||
stdout="", stderr="")
|
||||
|
||||
def fake_helper(name):
|
||||
return "/helpers/chunk_send_key_explicit.py" \
|
||||
if name == "chunk_send_key_explicit.py" else None
|
||||
|
||||
with mock.patch.object(deploy_mod.subprocess, "run",
|
||||
side_effect=fake_run):
|
||||
with mock.patch.object(deploy_mod, "helper_path",
|
||||
side_effect=fake_helper):
|
||||
with mock.patch.object(deploy_mod, "ssh_cmd",
|
||||
return_value=["ssh", "dummy"]):
|
||||
with mock.patch.object(
|
||||
deploy_mod, "remote_sha",
|
||||
return_value="deadbeef"):
|
||||
deploy_mod.transfer("h", "u", "/tmp/a",
|
||||
"/tmp/b", "deadbeef")
|
||||
self.assertTrue(any("chunk_send_key_explicit.py" in x
|
||||
for c in calls for x in c))
|
||||
|
||||
|
||||
class TestDefaultTestSuites(unittest.TestCase):
|
||||
"""P2-ops 回归:部署前默认清单必须含 deploy/tests,
|
||||
且 run_tests() 确实按 discover -s 逐套跑(不是包路径)。"""
|
||||
|
||||
Reference in new issue
Block a user