diff --git a/contaminate/__init__.py b/contaminate/__init__.py index 988d6005bed7b5332323aa618aafbd30d73624ac..1a21e96607e71925f4c412e6ff2310a3ca68a8f0 100644 --- a/contaminate/__init__.py +++ b/contaminate/__init__.py @@ -1,3 +1,11 @@ from .container import Container from .container_env import ContainerEnv -from .action import Return, Script, Shell, Upload, Download +from .action import ( + Action, + Return, + Script, + Shell, + Upload, + Download, + sequence_rules +) diff --git a/contaminate/action.py b/contaminate/action.py index 3cf408c00c243d3ccc1214e2d75175c4b7d9e3e4..41a80c4566a95ed366c89fb7ecad65bd4b97bbee 100644 --- a/contaminate/action.py +++ b/contaminate/action.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, Field from .container import Container from .container_env import ContainerEnv -from .podman import runcmd +import contaminate.podman as podman class AReturn(BaseModel): action_type : Literal["return"] = "return" @@ -31,25 +31,31 @@ class AShell(BaseModel): return c.run_shell(self.script) class AUpload(BaseModel): + """ Note: paths in Upload and Download + must be strings, since docker uses ending '/' + to denote moving inside a directory, as opposed to + overwriting. + """ action_type : Literal["upload"] = "upload" - src : Path # local - dst : Path # remote + src : str # local + dst : str # remote extract : bool = False # use ADD instead of COPY def __call__(self, c : Container) -> Container: - cmd = f"COPY {self.src} {self.dst}" + src = podman.quote(self.src) + dst = podman.quote(self.dst) + cmd = f'COPY ["{src}", "{dst}"]' if self.extract: - cmd = f"ADD {self.src} {self.dst}" + cmd = f'ADD ["{src}", "{dst}"]' return c.run_cmd(cmd) class ADownload(BaseModel): action_type : Literal["download"] = "download" - src : Path # remote - dst : Path # local + src : str # remote + dst : str # local def __call__(self, c : Container) -> Container: - runcmd("podman", "cp", - f"{c.name}:{self.src}", self.dst) + podman.download(c.name, self.src, self.dst) return c ActionT = Annotated[ @@ -90,11 +96,24 @@ def Script(script : str) -> Action: def Shell(script : str) -> Action: return Action(AShell(script = script)) -def Upload(src : Union[str,Path], dst : Union[str,Path], +def Upload(src : str, dst : str, extract : bool = False) -> Action: - return Action(AUpload(src=Path(src), - dst=Path(dst), extract=extract)) - -def Download(src : Union[str,Path], dst : Union[str,Path] - ) -> Action: - return Action(ADownload(src=Path(src), dst=Path(dst))) + return Action(AUpload(src=src, + dst=dst, extract=extract)) + +def Download(src : str, dst : str) -> Action: + return Action(ADownload(src=src, dst=dst)) + +# Run a sequence of rules, saving a json state ea. time. +def sequence_rules(rules : dict[str, Action], *steps : str) -> Container: + C = None + for s in steps: + result = Path(s + ".json") + if result.exists(): + C = Container.load(result) + else: + assert C is not None, f"Starting file '{result}' not present." + C = C.run( rules[s], name = s ) + C.store(result) + assert C is not None, "Empty rule set." + return C diff --git a/contaminate/podman.py b/contaminate/podman.py index b737887e2c82ffb7db459c730fcd516825f72b11..3151190feecd10d8e1887447de5d03d05662226c 100644 --- a/contaminate/podman.py +++ b/contaminate/podman.py @@ -5,6 +5,9 @@ import os import string from pathlib import Path +def quote(s) -> str: + return str(s).replace("\\", "\\\\").replace('"', '\"') + def to_oneliner(cmds): # process end-of-lines shell = "" @@ -79,3 +82,6 @@ def build(name : str, if fname != "": os.remove(fname) return "\n".join(ans) + +def download(name, src, dst): + runcmd("podman", "cp", f"{c.name}:{self.src}", self.dst)