RL-Explorer / app /dockerfile.py
AdithyaSK's picture
AdithyaSK HF Staff
Deploy HF RL Explorer
599974a verified
Raw History Blame Contribute Delete
5.15 kB
"""Running a Dockerfile-only task on an HF Sandbox, which starts from a prebuilt image and builds nothing.
Most task Dockerfiles are one stage: FROM an image, then RUN, COPY, WORKDIR, ENV and ARG. Those can be replayed in
the sandbox itself: start it from the FROM image, run each RUN there, upload each COPY from the task's environment
folder, and keep ENV and WORKDIR for every later command (the agent's and the grader's too). What can't be replayed
(several stages, COPY --from, heredocs, ADD from a URL) is refused with the reason, so the run panel can say why.
"""
from __future__ import annotations
import json
import re
import shlex
from dataclasses import dataclass, field
IGNORED = {"CMD", "ENTRYPOINT", "EXPOSE", "LABEL", "HEALTHCHECK", "VOLUME", "STOPSIGNAL", "MAINTAINER"}
@dataclass
class Plan:
base: str
steps: list[tuple[str, object]] = field(default_factory=list) # ("run", cmd) ("copy", (srcs, dst)) ("workdir", p) ("env", {k: v})
class NotReplayable(ValueError):
pass
def _lines(text: str) -> list[str]:
"""Instructions, one per entry: comments dropped, continuation lines joined."""
out, cur = [], ""
for raw in text.splitlines():
line = raw.rstrip()
if not cur and (not line.strip() or line.lstrip().startswith("#")):
continue
if cur and line.lstrip().startswith("#"): # a comment inside a continued instruction
continue
if line.endswith("\\"):
cur += line[:-1] + " "
continue
cur += line
out.append(cur.strip())
cur = ""
if cur.strip():
out.append(cur.strip())
return out
def _subst(s: str, args: dict[str, str]) -> str:
return re.sub(r"\$\{(\w+)(?::-([^}]*))?\}|\$(\w+)",
lambda m: args.get(m.group(1) or m.group(3), m.group(2) or "") if (m.group(1) or m.group(3)) in args or m.group(2) is not None
else m.group(0), s)
def _pairs(rest: str) -> dict[str, str]:
"""ENV/ARG bodies: `K=V K2="v 2"` or the old `K V` form."""
parts = shlex.split(rest)
if parts and "=" not in parts[0]:
return {parts[0]: " ".join(parts[1:])}
return dict(p.split("=", 1) if "=" in p else (p, "") for p in parts)
def plan(text: str) -> Plan:
"""The replay plan for a Dockerfile, or NotReplayable with the reason."""
if re.search(r"<<-?\s*['\"]?\w+", text):
raise NotReplayable("its Dockerfile uses a heredoc")
args: dict[str, str] = {}
base = None
steps: list[tuple[str, object]] = []
for line in _lines(text):
op, _, rest = line.partition(" ")
op, rest = op.upper(), rest.strip()
if op == "FROM":
if base:
raise NotReplayable("its Dockerfile has several stages")
base = _subst(rest.split()[0] if not rest.startswith("--") else rest.split()[1], args)
continue
if op == "ARG":
for k, v in _pairs(rest).items():
args.setdefault(k, v)
continue
if base is None:
raise NotReplayable("its Dockerfile has no FROM")
rest = _subst(rest, args)
if op == "RUN":
if rest.startswith("--"):
flags = re.match(r"((?:--\S+\s+)+)(.*)", rest, re.S)
rest = flags.group(2) if flags else rest
if rest.startswith("["):
try:
rest = " ".join(shlex.quote(x) for x in json.loads(rest))
except ValueError:
raise NotReplayable("a RUN line it can't read")
steps.append(("run", rest))
elif op in ("COPY", "ADD"):
parts = [p for p in (json.loads(rest) if rest.startswith("[") else shlex.split(rest))]
flags = [p for p in parts if p.startswith("--")]
if any(f.startswith("--from") for f in flags):
raise NotReplayable("its Dockerfile copies from another stage")
parts = [p for p in parts if not p.startswith("--")]
if len(parts) < 2:
raise NotReplayable("a COPY line it can't read")
if any(re.match(r"https?://", p) for p in parts[:-1]):
raise NotReplayable("its Dockerfile adds a file from a URL")
steps.append(("copy", (parts[:-1], parts[-1])))
elif op == "WORKDIR":
steps.append(("workdir", rest))
elif op == "ENV":
steps.append(("env", _pairs(rest)))
elif op in IGNORED or op == "ONBUILD":
continue
else:
raise NotReplayable(f"its Dockerfile uses {op}")
if not base:
raise NotReplayable("its Dockerfile has no FROM")
if base.lower() == "scratch":
raise NotReplayable("its Dockerfile starts FROM scratch")
return Plan(base=base, steps=steps)
def check(text: str | None) -> tuple[bool, str]:
"""Whether a task with this Dockerfile can run here, and if not, why."""
if not text:
return False, "it has neither a prebuilt image nor a Dockerfile"
try:
plan(text)
return True, ""
except NotReplayable as e:
return False, str(e)