fix: make dev tools executable
This commit is contained in:
@@ -3,7 +3,9 @@
|
|||||||
This directory supplies the ready-made `python` and `dotnet` scripts. They are not reserved:
|
This directory supplies the ready-made `python` and `dotnet` scripts. They are not reserved:
|
||||||
modify, replace, or remove them like any other script. Place other trusted executable install
|
modify, replace, or remove them like any other script. Place other trusted executable install
|
||||||
scripts here and add the desired file names to `AGENTCI_INSTALL_SCRIPTS`. Compose mounts the
|
scripts here and add the desired file names to `AGENTCI_INSTALL_SCRIPTS`. Compose mounts the
|
||||||
directory read-only at `/etc/agentci/install-scripts`.
|
directory read-only at `/etc/agentci/install-scripts`. Executable files run directly; files without
|
||||||
|
executable mode run as POSIX shell scripts through `/bin/sh` so bind mounts do not depend on host
|
||||||
|
file-mode preservation.
|
||||||
|
|
||||||
Scripts run from the cloned repository with a sanitized environment. They receive:
|
Scripts run from the cloned repository with a sanitized environment. They receive:
|
||||||
|
|
||||||
@@ -19,4 +21,4 @@ not persist into implementation turns.
|
|||||||
The configured scripts run in list order after the repository is cloned (or an existing agent PR
|
The configured scripts run in list order after the repository is cloned (or an existing agent PR
|
||||||
branch is synchronized) and before the first implementation turn for `implement`, `iterate`, and
|
branch is synchronized) and before the first implementation turn for `implement`, `iterate`, and
|
||||||
`fix`. They do not rerun between implementation and review-revision turns within one job. Scripts
|
`fix`. They do not rerun between implementation and review-revision turns within one job. Scripts
|
||||||
must be executable, idempotent, and must not expect AgentCI or Gitea credentials.
|
must be idempotent and must not expect AgentCI or Gitea credentials.
|
||||||
|
|||||||
@@ -55,19 +55,18 @@ class DevelopmentEnvironment:
|
|||||||
) from exc
|
) from exc
|
||||||
if resolved.parent != self.scripts_dir.resolve() or not resolved.is_file():
|
if resolved.parent != self.scripts_dir.resolve() or not resolved.is_file():
|
||||||
raise DevelopmentEnvironmentError(f"Install script {name!r} is not a regular file")
|
raise DevelopmentEnvironmentError(f"Install script {name!r} is not a regular file")
|
||||||
if not os.access(resolved, os.X_OK):
|
|
||||||
raise DevelopmentEnvironmentError(f"Install script {name!r} is not executable")
|
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
async def _run(self, name: str, script: Path, workspace: Path) -> None:
|
async def _run(self, name: str, script: Path, workspace: Path) -> None:
|
||||||
started = monotonic()
|
started = monotonic()
|
||||||
|
command = [str(script)] if os.access(script, os.X_OK) else ["/bin/sh", str(script)]
|
||||||
log.info(
|
log.info(
|
||||||
"development install script started",
|
"development install script started",
|
||||||
extra={"operation": "development.install", "script": name},
|
extra={"operation": "development.install", "script": name},
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
process = await asyncio.create_subprocess_exec(
|
process = await asyncio.create_subprocess_exec(
|
||||||
str(script),
|
*command,
|
||||||
cwd=workspace,
|
cwd=workspace,
|
||||||
env=self._environment(),
|
env=self._environment(),
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
|||||||
@@ -61,6 +61,19 @@ async def test_supplied_script_names_use_the_same_directory(tmp_path) -> None:
|
|||||||
assert (workspace / "selected").read_text() == "python\n"
|
assert (workspace / "selected").read_text() == "python\n"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_runs_non_executable_shell_script_from_bind_mount(tmp_path) -> None:
|
||||||
|
workspace = tmp_path / "workspace"
|
||||||
|
workspace.mkdir()
|
||||||
|
development = environment(tmp_path, ["mounted"])
|
||||||
|
mounted = development.scripts_dir / "mounted"
|
||||||
|
mounted.write_text("#!/bin/sh\nprintf 'mounted\\n' > selected\n")
|
||||||
|
mounted.chmod(0o644)
|
||||||
|
|
||||||
|
await development.prepare(workspace)
|
||||||
|
|
||||||
|
assert (workspace / "selected").read_text() == "mounted\n"
|
||||||
|
|
||||||
|
|
||||||
async def test_reports_script_failure_output(tmp_path) -> None:
|
async def test_reports_script_failure_output(tmp_path) -> None:
|
||||||
workspace = tmp_path / "workspace"
|
workspace = tmp_path / "workspace"
|
||||||
workspace.mkdir()
|
workspace.mkdir()
|
||||||
|
|||||||
Reference in New Issue
Block a user