Commit 22bb14bf authored by David M. Rogers's avatar David M. Rogers
Browse files

Updated Action behavior.

parent 1b245ace
Loading
Loading
Loading
Loading
+18 −7
Original line number Diff line number Diff line
@@ -31,20 +31,31 @@ To instantiate it, create a container environment,
then a base container with that environment.
Then apply the action.

    from contaminate import Container, ContainerEnv, apply
    from contaminate import Container, ContainerEnv

    env = ContainerEnv(system="localhost",
                       arch="linux/x86_64",
                       bind=["/home"])
    c = Container(name="ubuntu:16.04", env=env, value="")
    c2 = apply(c, build_essentials)
    c2 = c.run(build_essentials)

    print(c2.name)
    print(c2.name) # ubuntu.1

You can also run commands directly in the container
You can also chain/compose commands with `apply`,
run commands directly in the container
using the Shell action, and copy files from
the container using the Download action,

    apply(c2, Shell(script="w"))
    apply(c2, Download(src="/etc/hosts", dst="hostfile"))
    steps = Shell(script="w") \
          .apply(Download(src="/etc/hosts", dst="hostfile"))
    c2.run(steps)

The coontainer build process makes use of
`podman build`, so the images are stored based
on podman's conventions.  You can save and load
the extended container environment information
using:

    c2.write("my_container.json")
    c2 = Container.read("my_container.json")
+1 −1
Original line number Diff line number Diff line
from .container import Container
from .container_env import ContainerEnv
from .action import Return, Script, Shell, Upload, Download, applyM
from .action import Return, Script, Shell, Upload, Download
+53 −20
Original line number Diff line number Diff line
from enum import Enum
from typing import Literal, Union, Callable
from typing_extensions import Annotated
from pathlib import Path

from pydantic import BaseModel, Field
@@ -8,29 +9,29 @@ from .container import Container
from .container_env import ContainerEnv
from .podman import runcmd

class Return(BaseModel):
    action_type : Literal["return"]
class AReturn(BaseModel):
    action_type : Literal["return"] = "return"
    value       : str

    def __call__(self, c : Container) -> Container:
        return c.with_value(self.value)

class Script(BaseModel):
    action_type : Literal["script"]
class AScript(BaseModel):
    action_type : Literal["script"] = "script"
    script      : str

    def __call__(self, c : Container) -> Container:
        return c.run_cmd(self.script)

class Shell(BaseModel):
    action_type : Literal["shell"]
class AShell(BaseModel):
    action_type : Literal["shell"] = "shell"
    script      : str

    def __call__(self, c : Container) -> Container:
        return c.run_shell(self.script)

class Upload(BaseModel):
    action_type : Literal["upload"]
class AUpload(BaseModel):
    action_type : Literal["upload"] = "upload"
    src         : Path # local
    dst         : Path # remote
    extract     : bool = False # use ADD instead of COPY
@@ -41,8 +42,8 @@ class Upload(BaseModel):
            cmd = f"ADD {self.src} {self.dst}"
        return c.run_cmd(cmd)

class Download(BaseModel):
    action_type : Literal["download"]
class ADownload(BaseModel):
    action_type : Literal["download"] = "download"
    src         : Path # remote
    dst         : Path # local

@@ -51,17 +52,49 @@ class Download(BaseModel):
               f"{c.name}:{self.src}", self.dst)
        return c

Actions = Union[Return, Script,
                  Shell, Upload, Download]
class Action(BaseModel):
    action: Actions = Field(...,
                            discriminator="action_type")
ActionT = Annotated[
              Union[AReturn, AScript,
                    AShell,  AUpload, ADownload],
              Field(discriminator="action_type")]

class Action:
    def __init__(self,
                 run : Callable[[Container], Container]):
        self.action = run

    def __call__(self, c : Container) -> Container:
        return self.action(c)

def apply(c : Container, action : Action) -> Container:
    return action(c)
    def apply(self, *acts : "Action") -> "Action":
        def run(c : Container) -> Container:
            c = self(c)
            for act in acts:
                c = act(c)
            return c
        return Action(run)
    
    def applyM(self, k : Callable[[str], "Action"]
              ) -> "Action":
        def run(c : Container) -> Container:
            c = self(c)    # first run this action
            A = k(c.value) # extract the next action
            return A( c )
        return Action(run)

def Return(value : str) -> Action:
    return Action(AReturn(value=value))

def Script(script : str) -> Action:
    return Action(AScript(script=script))

def Shell(script : str) -> Action:
    return Action(AShell(script = script))

def Upload(src : Union[str,Path], dst : Union[str,Path],
           extract : bool = False) -> Action:
    return Action(AUpload(src=Path(src),
                          dst=Path(dst), extract=extract))

def applyM(c : Container, k : Callable[[str], Action]) -> Container:
    action = k(c.value)
    return action(c)
def Download(src : Union[str,Path], dst : Union[str,Path]
             ) -> Action:
    return Action(ADownload(src=Path(src), dst=Path(dst)))
+25 −0
Original line number Diff line number Diff line
from typing import Optional, Union
from pathlib import Path
import json

from pydantic import BaseModel

@@ -6,6 +8,9 @@ from .container_env import ContainerEnv
import contaminate.podman as podman

def incr_name(name : str) -> str:
    s = name.split(":", 1)
    if len(s) > 1:
        name = s[0]
    s = name.rsplit(".", 1)
    if len(s) == 1:
        return f"{name}.1"
@@ -21,6 +26,26 @@ class Container(BaseModel):
    env    : ContainerEnv  # run-environment needed to use container
    value  : str           # current container output text

    @classmethod
    def read(cls, fname : Union[str,Path]) -> "Container":
        with open(fname, "r", encoding="utf-8") as f:
            return Container.model_validate_json(f.read())

    def write(self, fname : Union[str,Path]) -> None:
        Path(fname).write_text(
                    self.model_dump_json(indent=4))

    def run(self, *actions, name: Optional[str] = None
           ) -> "Container":
        assert len(actions) > 0, "inaction not implemented"
        c = self.copy()
        end = len(actions)-1
        for i, a in enumerate(actions):
            if name is not None and i == end: # rename on last step
                c.name = name
            c = a(c)
        return c

    def with_value(self, val : str) -> "Container":
        return Container(name=self.name, #path=self.path,
                         env=self.env, value=val)