81 lines
2.6 KiB
Python
81 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Merge a Dockerfile's split `apt-get update` + `apt-get install` into one RUN.
|
||
|
|
|
||
|
|
Why: several imported challenge Dockerfiles run
|
||
|
|
|
||
|
|
RUN apt-get -o Acquire::AllowInsecureRepositories=true update
|
||
|
|
RUN apt-get -y --allow-unauthenticated install -y ...
|
||
|
|
|
||
|
|
as two separate layers. The bundled apt-insecure.conf enables insecure/downgrade
|
||
|
|
repositories, and a second apt invocation in a later layer re-resolves against
|
||
|
|
whatever index the first one left behind — which on the EOL bullseye-security
|
||
|
|
mirror returns 404 for every package:
|
||
|
|
|
||
|
|
Err:1 http://deb.debian.org/debian-security bullseye-security/main amd64 libsystemd0
|
||
|
|
404 Not Found
|
||
|
|
|
||
|
|
Merging them into a single `update && install` layer (the pattern the working
|
||
|
|
gemastik18 challenges use) makes apt resolve once, from a fresh index.
|
||
|
|
|
||
|
|
Idempotent: a Dockerfile that already has the combined form is left untouched.
|
||
|
|
"""
|
||
|
|
import re
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
SERVICES = Path("/opt/gemastik18-final/services")
|
||
|
|
|
||
|
|
UPDATE_RE = re.compile(
|
||
|
|
r"^RUN\s+apt-get\s+(?P<opts>(?:-o\s+\S+\s+)*)update\s*$"
|
||
|
|
)
|
||
|
|
INSTALL_RE = re.compile(r"^RUN\s+(?P<rest>apt-get\s+.*\binstall\b.*)$")
|
||
|
|
|
||
|
|
|
||
|
|
def fix(text: str) -> tuple[str, bool]:
|
||
|
|
lines = text.splitlines()
|
||
|
|
out: list[str] = []
|
||
|
|
i = 0
|
||
|
|
changed = False
|
||
|
|
while i < len(lines):
|
||
|
|
m = UPDATE_RE.match(lines[i])
|
||
|
|
if not m:
|
||
|
|
out.append(lines[i])
|
||
|
|
i += 1
|
||
|
|
continue
|
||
|
|
# look ahead: is the very next non-empty line an install?
|
||
|
|
j = i + 1
|
||
|
|
while j < len(lines) and not lines[j].strip():
|
||
|
|
j += 1
|
||
|
|
if j < len(lines):
|
||
|
|
mi = INSTALL_RE.match(lines[j])
|
||
|
|
if mi:
|
||
|
|
out.append(f"RUN apt-get {m.group('opts')}update && \\")
|
||
|
|
out.append(f" {mi.group('rest')}")
|
||
|
|
changed = True
|
||
|
|
i = j + 1
|
||
|
|
continue
|
||
|
|
out.append(lines[i])
|
||
|
|
i += 1
|
||
|
|
return "\n".join(out) + ("\n" if text.endswith("\n") else ""), changed
|
||
|
|
|
||
|
|
|
||
|
|
def main(argv):
|
||
|
|
names = argv[1:] or [p.name for p in sorted(SERVICES.iterdir())
|
||
|
|
if p.is_dir() and (p / "Dockerfile").exists()]
|
||
|
|
changed = []
|
||
|
|
for name in names:
|
||
|
|
p = SERVICES / name / "Dockerfile"
|
||
|
|
if not p.exists():
|
||
|
|
continue
|
||
|
|
text = p.read_text()
|
||
|
|
new, did = fix(text)
|
||
|
|
if did:
|
||
|
|
p.write_text(new)
|
||
|
|
changed.append(name)
|
||
|
|
print(f"{name}: merged apt update+install into one RUN")
|
||
|
|
print("rewritten:", ", ".join(changed) if changed else "(none)")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main(sys.argv))
|