Skip to content

Commit fe6152f

Browse files
authored
Merge pull request #49 from Mresyzz/fix/cleanup-ai-and-harden-edge-cases
fix: remove .ai directory and harden discovery, runner temp file cleanup, and report line endings
2 parents 9244506 + 00e9b86 commit fe6152f

6 files changed

Lines changed: 160 additions & 204 deletions

File tree

‎.ai/v0.9.2_issues_report.md‎

Lines changed: 0 additions & 150 deletions
This file was deleted.

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ venv.bak/
5050
*~
5151
.DS_Store
5252
Thumbs.db
53+
.ai/

‎src/opsscript_gate/cli.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,8 @@ def publish_output(output: str, filename: str | None) -> bool:
489489
try:
490490
path = Path(filename)
491491
path.parent.mkdir(parents=True, exist_ok=True)
492-
path.write_text(output + "\n", encoding="utf-8")
492+
with path.open("w", encoding="utf-8", newline="\n") as stream:
493+
stream.write(output + "\n")
493494
except OSError as err:
494495
sys.stderr.write(f"Error writing report: {err}\n")
495496
return False

‎src/opsscript_gate/discovery.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def is_shell_script(file_path: Path, max_size_bytes: int = MAX_SCRIPT_SIZE_BYTES
7878
Check if a file is a candidate shell script:
7979
- Must be a regular file, never a symlink.
8080
- File size must not exceed max_size_bytes.
81+
- Must be readable by the current process.
8182
- Extension is .sh or .bash, OR the first line is a recognized shell shebang.
8283
"""
8384
try:
@@ -87,14 +88,13 @@ def is_shell_script(file_path: Path, max_size_bytes: int = MAX_SCRIPT_SIZE_BYTES
8788
if stat.st_size == 0 or stat.st_size > max_size_bytes:
8889
return False
8990

91+
with open(file_path, "rb") as f:
92+
first_line_bytes = f.readline(256)
93+
9094
ext = file_path.suffix.lower()
9195
if ext in (".sh", ".bash"):
9296
return True
9397

94-
# If extension is not .sh/.bash, inspect the first line
95-
with open(file_path, "rb") as f:
96-
first_line_bytes = f.readline(256)
97-
9898
first_line = first_line_bytes.decode("utf-8-sig", errors="ignore").strip()
9999
return any(first_line == prefix or first_line.startswith(prefix + " ")
100100
for prefix in SHELL_SHEBANG_PREFIXES)

‎src/opsscript_gate/runner.py‎

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,8 @@ def run_on_distro(
832832

833833
# Line-ending defense & Windows-safe path preparation
834834
own_temp_file: tempfile.NamedTemporaryFile | None = None
835+
container = None
836+
start_time = time.perf_counter()
835837
if prepared_script_path is not None:
836838
mount_src = prepared_script_path
837839
else:
@@ -847,35 +849,31 @@ def run_on_distro(
847849
error_message=f"Failed to read/prepare script: {exc}",
848850
)
849851

850-
safe_mount_src = normalize_host_path_for_docker(mount_src)
851852
try:
852-
container_command = _wrap_with_package_setup(command, package_list)
853-
except (TypeError, ValueError) as exc:
854-
return SingleResult(
855-
distro=distro,
856-
status=DistroStatus.ERROR,
857-
exit_code=None,
858-
duration=0.0,
859-
output_snippet="",
860-
error_message=str(exc),
861-
)
853+
safe_mount_src = normalize_host_path_for_docker(mount_src)
854+
try:
855+
container_command = _wrap_with_package_setup(command, package_list)
856+
except (TypeError, ValueError) as exc:
857+
return SingleResult(
858+
distro=distro,
859+
status=DistroStatus.ERROR,
860+
exit_code=None,
861+
duration=0.0,
862+
output_snippet="",
863+
error_message=str(exc),
864+
)
862865

863-
# Security: Strict unprivileged options and read-only mount
864-
volumes = {
865-
safe_mount_src: {
866-
"bind": "/tmp/target_script.sh",
867-
"mode": "ro",
866+
# Security: Strict unprivileged options and read-only mount
867+
volumes = {
868+
safe_mount_src: {
869+
"bind": "/tmp/target_script.sh",
870+
"mode": "ro",
871+
}
872+
}
873+
environment = {
874+
"DEBIAN_FRONTEND": "noninteractive",
875+
"CI": "true",
868876
}
869-
}
870-
environment = {
871-
"DEBIAN_FRONTEND": "noninteractive",
872-
"CI": "true",
873-
}
874-
875-
container = None
876-
start_time = time.perf_counter()
877-
878-
try:
879877
create_kwargs = {
880878
"image": distro,
881879
"command": container_command,
@@ -1076,13 +1074,46 @@ def run_matrix(
10761074
except ValueError:
10771075
mode = None
10781076

1077+
distro_list = _normalize_matrix(matrix)
1078+
10791079
abs_script = os.path.abspath(script_path)
1080+
if not os.path.exists(abs_script):
1081+
return RunReport(
1082+
results=[
1083+
SingleResult(
1084+
distro=d,
1085+
status=DistroStatus.ERROR,
1086+
exit_code=None,
1087+
duration=0.0,
1088+
output_snippet="",
1089+
error_message=f"Target script does not exist: {abs_script}",
1090+
)
1091+
for d in distro_list
1092+
],
1093+
total_duration=0.0,
1094+
all_passed=False,
1095+
)
1096+
if not os.path.isfile(abs_script):
1097+
return RunReport(
1098+
results=[
1099+
SingleResult(
1100+
distro=d,
1101+
status=DistroStatus.ERROR,
1102+
exit_code=None,
1103+
duration=0.0,
1104+
output_snippet="",
1105+
error_message=f"Target script path is not a regular file: {abs_script}",
1106+
)
1107+
for d in distro_list
1108+
],
1109+
total_duration=0.0,
1110+
all_passed=False,
1111+
)
1112+
10801113
parsed_shebang: ShebangParseResult | None = None
10811114
if mode is not None and mode != ShellMode.POSIX:
1082-
if os.path.isfile(abs_script):
1083-
parsed_shebang = inspect_shebang(abs_script)
1115+
parsed_shebang = inspect_shebang(abs_script)
10841116

1085-
distro_list = _normalize_matrix(matrix)
10861117
if isinstance(packages, (str, bytes)):
10871118
raise ValueError("packages must be a list of package names")
10881119
package_list = list(packages or [])
@@ -1102,26 +1133,23 @@ def run_matrix(
11021133

11031134
# Prepare script once for all parallel matrix workers
11041135
shared_temp_file: tempfile.NamedTemporaryFile | None = None
1105-
prepared_script: str = abs_script
1106-
if os.path.isfile(abs_script):
1107-
try:
1108-
prepared_script, shared_temp_file = prepare_script(abs_script)
1109-
except Exception as exc:
1110-
return RunReport(
1111-
results=[
1112-
SingleResult(
1113-
distro=d,
1114-
status=DistroStatus.ERROR,
1115-
exit_code=None,
1116-
duration=0.0,
1117-
output_snippet="",
1118-
error_message=f"Failed to read/prepare script: {exc}",
1119-
)
1120-
for d in distro_list
1121-
],
1122-
total_duration=0.0,
1123-
all_passed=False,
1124-
)
1136+
try:
1137+
prepared_script, shared_temp_file = prepare_script(abs_script)
1138+
except Exception as exc:
1139+
return RunReport(
1140+
results=[
1141+
SingleResult(
1142+
distro=d,
1143+
status=DistroStatus.ERROR,
1144+
exit_code=None,
1145+
duration=0.0,
1146+
output_snippet="",
1147+
error_message=f"Failed to read/prepare script: {exc}",
1148+
)
1149+
for d in distro_list
1150+
],
1151+
total_duration=0.0,
1152+
)
11251153

11261154
start_total = time.perf_counter()
11271155
results: list[SingleResult] = [None] * len(distro_list) # type: ignore

0 commit comments

Comments
 (0)