feat(ansible): GIT_ASKPASS per-source HTTP token auth + settings UI
This commit is contained in:
+65
-23
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -48,6 +51,7 @@ def get_sources(ansible_cfg: dict) -> list[dict]:
|
||||
"url": src.get("url"),
|
||||
"branch": src.get("branch", "main"),
|
||||
"pull_interval_seconds": int(src.get("pull_interval_seconds", 3600)),
|
||||
"http_token": src.get("http_token", "") if src_type == "git" else "",
|
||||
})
|
||||
return result
|
||||
|
||||
@@ -99,27 +103,65 @@ def read_playbook(source_path: str, relative_path: str) -> str | None:
|
||||
|
||||
|
||||
async def git_pull(source: dict) -> None:
|
||||
"""Clone the git repo if absent; pull if already present."""
|
||||
"""Clone the git repo if absent; pull if already present.
|
||||
|
||||
When source['http_token'] is set, injects credentials via a temporary
|
||||
GIT_ASKPASS script so they never touch .git/config or process args.
|
||||
"""
|
||||
path = Path(source["path"])
|
||||
if not (path / ".git").exists():
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"git", "clone",
|
||||
"--branch", source["branch"],
|
||||
"--single-branch",
|
||||
source["url"], str(path),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
_, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
logger.error("git clone failed for %r: %s", source["name"], stderr.decode(errors="replace"))
|
||||
else:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"git", "-C", str(path), "pull",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
_, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
logger.error("git pull failed for %r: %s", source["name"], stderr.decode(errors="replace"))
|
||||
token = source.get("http_token", "")
|
||||
|
||||
env = None
|
||||
askpass_path = None
|
||||
if token:
|
||||
fd, askpass_path = tempfile.mkstemp(prefix="steward-askpass-", suffix=".sh")
|
||||
try:
|
||||
script = (
|
||||
"#!/bin/sh\n"
|
||||
'case "$1" in\n'
|
||||
' Username*) echo "oauth2" ;;\n'
|
||||
f' Password*) echo "{token}" ;;\n'
|
||||
"esac\n"
|
||||
)
|
||||
os.write(fd, script.encode())
|
||||
finally:
|
||||
os.close(fd)
|
||||
os.chmod(askpass_path, stat.S_IRWXU)
|
||||
env = {**os.environ, "GIT_ASKPASS": askpass_path}
|
||||
|
||||
try:
|
||||
if not (path / ".git").exists():
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"git", "clone",
|
||||
"--branch", source["branch"],
|
||||
"--single-branch",
|
||||
source["url"], str(path),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env=env,
|
||||
)
|
||||
_, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
logger.error(
|
||||
"git clone failed for %r: %s",
|
||||
source["name"],
|
||||
stderr.decode(errors="replace"),
|
||||
)
|
||||
else:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"git", "-C", str(path), "pull",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env=env,
|
||||
)
|
||||
_, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
logger.error(
|
||||
"git pull failed for %r: %s",
|
||||
source["name"],
|
||||
stderr.decode(errors="replace"),
|
||||
)
|
||||
finally:
|
||||
if askpass_path and os.path.exists(askpass_path):
|
||||
os.unlink(askpass_path)
|
||||
|
||||
Reference in New Issue
Block a user