fmartingr
/
jeeves
Archived
1
0
Fork 0

Arguments

This commit is contained in:
Felipe Martin 2020-04-29 20:08:06 +02:00
parent 33af22443e
commit 8a44efc464
Signed by: fmartingr
GPG Key ID: 716BC147715E716F
6 changed files with 97 additions and 27 deletions

View File

@ -24,7 +24,8 @@ def main():
default=False, default=False,
help="Display output for flow", help="Display output for flow",
) )
def execute(defintinion_file, print_output): @click.option("--argument", "-a", "defined_arguments", multiple=True, default=[])
def execute(defintinion_file, print_output, defined_arguments=list):
extension = os.path.splitext(defintinion_file.name)[1][1:] extension = os.path.splitext(defintinion_file.name)[1][1:]
if not hasattr(FlowParser, f"from_{extension}"): if not hasattr(FlowParser, f"from_{extension}"):
@ -35,8 +36,13 @@ def execute(defintinion_file, print_output):
flow = getattr(FlowParser, f"from_{extension}")(defintinion_file.read()) flow = getattr(FlowParser, f"from_{extension}")(defintinion_file.read())
arguments = {}
for argument in defined_arguments:
key, value = argument.split("=")
arguments[key] = value
title(f"Running flow: {flow.name}") title(f"Running flow: {flow.name}")
executor = Executor(flow) executor = Executor(flow=flow, defined_arguments=arguments)
for n, step in enumerate(executor.steps, start=1): for n, step in enumerate(executor.steps, start=1):
click.echo( click.echo(
@ -44,12 +50,14 @@ def execute(defintinion_file, print_output):
nl=False, nl=False,
) )
result = executor.execute_step(step) result = executor.execute_step(step)
if not result.success: if not result.success:
error(f"Executing step [{n}/{executor.step_count}]: {step.task.name}") error(f"Executing step [{n}/{executor.step_count}]: {step.task.name}")
if print_output:
click.echo(result.output)
break break
else: else:
success(f"Running step [{n}/{executor.step_count}]: {step.task.name}") success(f"Running step [{n}/{executor.step_count}]: {step.task.name}")
if print_output: if print_output:
title("Full output:") click.echo(result.output)
click.echo("\n".join(executor._execution.output))

View File

@ -1,6 +1,7 @@
import logging import logging
from abc import abstractmethod from abc import abstractmethod
from jinja2 import Template
import pydantic import pydantic
@ -8,11 +9,20 @@ class Action:
id = "" id = ""
class Parameters(pydantic.BaseModel): class Parameters(pydantic.BaseModel):
pass PARSE_WITH_ARGUMENTS = set()
def __init__(self, parameters=None): def __init__(self, parameters=None):
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
self.parameters = self.Parameters(**(parameters or {})) self.parameters = self.Parameters(**(parameters or {}))
self.parsed_parameters = {}
def parse_parameters_with_arguments(self, **arguments):
"""
Returns a dict with the parameters parsed in base of the provided arguments.
Parsing using jinja2 template themes only on the fields defined on the `Parameters.PARSE_WITH_ARGUMENTS`.
"""
for parameter_name in self.parameters.PARSE_WITH_ARGUMENTS:
self.parsed_parameters[parameter_name] = Template(self.parameters.dict()[parameter_name]).render(**arguments)
@abstractmethod @abstractmethod
def execute(self, workspace, **kwargs): def execute(self, workspace, **kwargs):

View File

@ -1,4 +1,5 @@
from typing import Text import os.path
from typing import Text, Optional
import docker import docker
import pydantic import pydantic
@ -8,57 +9,96 @@ from jeeves.core.objects import Result
from .base import Action from .base import Action
class DockerAction(Action): class DockerBuildAction(Action):
""" """
.. automethod:: _run_container
""" """
id = "contrib/docker" id = "contrib/docker/build"
verbose_name = "Build docker image"
class Parameters(pydantic.BaseModel):
"""
"""
dockerfile_path: Text
image_name: Text
tag: Text = "latest"
def execute(self, **kwargs):
workspace = kwargs.get("workspace")
client = docker.from_env()
client.images.build(
path=workspace,
fileobj=os.path.join(workspace, self.parameters.dockerfile_path),
)
class DockerRunAction(Action):
"""
"""
id = "contrib/docker/run"
verbose_name = "Execute docker container" verbose_name = "Execute docker container"
class Parameters(pydantic.BaseModel): class Parameters(pydantic.BaseModel):
""" """
+----------------+------+-----------+----------------------------------------------+ +----------------------+------+-----------+--------------------------------------------------------------+
| Parameter name | Type | Mandatory | Description | | Parameter name | Type | Mandatory | Description |
+================+======+===========+==============================================+ +======================+======+===========+==============================================================+
| ``image`` | text | no | Image to run (defaults to ``alpine:latest``) | | ``image`` | text | no | Image to run (defaults to ``alpine:latest``) |
| ``command`` | text | yes | The command to be executed | | ``command`` | text | yes | The command to be executed |
+----------------+------+-----------+----------------------------------------------+ | ``entrypoint`` | text | no | The entrypoint to use (defaults in image) |
| ``tty`` | bool | no | Allocate pseudo-tty (defaults to ``false``) |
| ``remove_container`` | text | no | Select to remove container after run (defaults to ``true`` ) |
+----------------------+------+-----------+--------------------------------------------------------------+
""" """
image: Text = "alpine:latest" image: Text = "alpine:latest"
command: Text command: Text
entrypoint: Optional[Text] = None
tty: bool = False
remove_container: bool = True remove_container: bool = True
def _run_container(self): PARSE_WITH_ARGUMENTS = {"command", "image"}
"""
"""
pass
def execute(self, **kwargs): def execute(self, **kwargs):
workspace = kwargs.get("workspace") workspace = kwargs.get("workspace")
arguments = kwargs.get("arguments")
image = self.parameters.image image = self.parameters.image
command = self.parameters.command
environment = {"WORKSPACE_PATH": "/workspace"} environment = {"WORKSPACE_PATH": "/workspace"}
# Add arguments as uppercase environment variables prefixed with JEEVES_
if arguments:
for key, value in arguments.items():
environment[f"JEEVES_{key.upper}"] = value
client = docker.from_env() client = docker.from_env()
self.logger.info("Pulling image...") self.logger.info("Pulling image...")
try: try:
client.images.get(image) client.images.get(image)
except docker.errors.ImageNotFound: except docker.errors.ImageNotFound:
self.logger.error("Image does not exist") self.logger.error(f"Image '{image}' does not exist")
return Result(success=False) return Result(success=False)
self.logger.info("Execute command in container...") run_kwargs = dict(
container = client.containers.run(
image=image, image=image,
command=command, command=self.parsed_parameters["command"],
detach=True, detach=True,
environment=environment, environment=environment,
tty=self.parameters.tty,
volumes={"/workspace": {"bind": str(workspace.path), "mode": "rw"}}, volumes={"/workspace": {"bind": str(workspace.path), "mode": "rw"}},
working_dir="/workspace",
) )
if self.parameters.entrypoint:
run_kwargs["entrypoint"] = self.parameters.entrypoint
self.logger.info("Execute command in container...")
container = client.containers.run(**run_kwargs)
try: try:
result = container.wait(timeout=30, condition="not-running") result = container.wait(timeout=30, condition="not-running")
logs = container.logs() logs = container.logs()

View File

@ -1,14 +1,21 @@
import traceback import traceback
from typing import Dict
from jeeves.core.objects import Flow, Result, Execution, ExecutionStep from jeeves.core.objects import Flow, Result, Execution, ExecutionStep
from jeeves.core.registry import ActionRegistry from jeeves.core.registry import ActionRegistry
class Executor: class Executor:
def __init__(self, flow: Flow): def __init__(self, flow: Flow, defined_arguments: Dict = None):
defined_arguments = defined_arguments or {}
self.step_count = len(flow.tasks) self.step_count = len(flow.tasks)
self._flow: Flow = flow self._flow: Flow = flow
self._execution = Execution(flow=flow, steps=self._get_steps(flow)) self._execution = Execution(flow=flow, steps=self._get_steps(flow))
self._arguments = {}
if self._flow.arguments:
for argument in self._flow.arguments:
# TODO: What happens if not default?
self._arguments[argument.name] = defined_arguments.get(argument.name, argument.default)
@property @property
def steps(self): def steps(self):
@ -22,6 +29,7 @@ class Executor:
def execute_step(self, step: ExecutionStep): def execute_step(self, step: ExecutionStep):
try: try:
action = ActionRegistry.get_action_cls(step.task.type)(parameters=step.task.parameters) action = ActionRegistry.get_action_cls(step.task.type)(parameters=step.task.parameters)
action.parse_parameters_with_arguments(**self._arguments)
step.result = action.execute(workspace=self._execution.workspace) step.result = action.execute(workspace=self._execution.workspace)
except Exception as error: except Exception as error:
# Catch unhandled exceptions, mark the result as unsuccessful # Catch unhandled exceptions, mark the result as unsuccessful

View File

@ -32,6 +32,7 @@ class Argument(BaseObject):
class Flow(BaseObject): class Flow(BaseObject):
name: Text name: Text
tasks: List[Task] = field(default_factory=list) tasks: List[Task] = field(default_factory=list)
arguments: Optional[List[Argument]] = None
class ExecutionStep(BaseObject): class ExecutionStep(BaseObject):
@ -60,12 +61,15 @@ class Execution(BaseObject):
flow: Flow flow: Flow
steps: List[ExecutionStep] steps: List[ExecutionStep]
workspace: Workspace = None # type: ignore workspace: Workspace = None # type: ignore
arguments: Optional[List[Argument]] = None
success: bool = False success: bool = False
@pydantic.validator("workspace", pre=True, always=True) @pydantic.validator("workspace", pre=True, always=True)
def workspace_default(cls, v): # pylint: disable=no-self-argument def workspace_default(cls, v): # pylint: disable=no-self-argument
return v or Workspace() return v or Workspace()
# TODO: @pydantic.validator("arguments")
@property @property
def output(self): def output(self):
for step in self.steps: for step in self.steps:

View File

@ -23,5 +23,5 @@ def test_registry_get_action_ok():
def test_registry_namespace_conflict_ok(): def test_registry_namespace_conflict_ok():
ActionRegistry.register_action(StubSuccessAction) ActionRegistry.register_action(StubSuccessAction)
with pytest.raises(ActionRegistry.ActionNamespaceConflict): with pytest.raises(ActionRegistry.ActionIDConflict):
ActionRegistry.register_action(StubSuccessAction) ActionRegistry.register_action(StubSuccessAction)