Skip to content

Commit

Permalink
New run command implemented.
Browse files Browse the repository at this point in the history
  • Loading branch information
janosmurai committed May 6, 2024
1 parent 3cbce65 commit 3d0d4f9
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 169 deletions.
5 changes: 3 additions & 2 deletions dem/cli/command/add_task_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# dem/cli/command/add_task_cmd.py

from dem.core.platform import Platform
from dem.cli.console import stderr
from dem.cli.console import stderr, stdout

def execute(platform: Platform, dev_env_name: str, task_name: str, command: str) -> None:
""" Add a task to a Development Environment.
Expand All @@ -18,4 +18,5 @@ def execute(platform: Platform, dev_env_name: str, task_name: str, command: str)
stderr.print(f"[red]Error: Development Environment '{dev_env_name}' not found![/]")
return
dev_env.add_task(task_name, command)
platform.flush_dev_env_properties()
platform.flush_dev_env_properties()
stdout.print(f"[green]Task [bold]{task_name}[/bold] added to Development Environment [bold]{dev_env_name}[/bold]![/]")
100 changes: 61 additions & 39 deletions dem/cli/command/run_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,75 @@
from dem.core.dev_env import DevEnv
from dem.core.platform import Platform
from dem.cli.console import stdout, stderr
import typer
import typer, subprocess

def handle_missing_tool_images(missing_tool_images: set[str], dev_env_local: DevEnv,
platform: Platform) -> None:
""" Report an error to the user that some images are not available. Ask them if the DEM should
try to fix the Dev Env: abort if no, pull the missing tool images if yes.
def dev_env_health_check(platform: Platform, dev_env: DevEnv) -> None:
""" Check a Development Environment's tool images status and reinstall them if needed.
Args:
missing_tool_images -- the missing tool images
dev_env_local -- local Dev Env
platform -- provides the interface to pull the missing images
platform -- the Platform
dev_env -- the Development Environment
Raises:
typer.Abort -- if the Development Environment has missing tool images
"""
stderr.print("[red]Error: The following tool images are not available locally:[/]")
for missing_tool_image in missing_tool_images:
stderr.print("[red]" + missing_tool_image + "[/]")
typer.confirm("Should DEM try to fix the faulty Development Environment?", abort=True)
# Don't load the registry images to save time.
platform.local_only = True

platform.install_dev_env(dev_env_local)
stdout.print("[green]DEM fixed the " + dev_env_local.name + "![/]")
dev_env.assign_tool_image_instances(platform.tool_images)
dev_env_tool_status = dev_env.get_tool_image_status()

def execute(platform: Platform, dev_env_name: str, container_arguments: list[str]) -> None:
""" Execute the run command in the given Dev Env context. If something is wrong with the Dev
Env the DEM can try to fix it.
if dev_env_tool_status == DevEnv.Status.UNAVAILABLE_IMAGE:
stderr.print("[red]Error: Required tool images are not available![/]")
stdout.print("Trying to locate the missing tool images...")
# Now we need to load the registry images.
platform.tool_images.update()
dev_env.assign_tool_image_instances(platform.tool_images)
# Get the tool status again.
dev_env_tool_status = dev_env.get_tool_image_status()
if dev_env_tool_status == DevEnv.Status.REINSTALL_NEEDED:
stdout.print("The missing images are available from the registries.")
typer.confirm("Should DEM reinstall the missing images?", abort=True)
platform.install_dev_env(dev_env)
stdout.print(f"[green]DEM successfully fixed the {dev_env.name} Development Environment![/]")
else:
stderr.print("[red]Error: The missing tool images could not be found in the registries![/]")
raise typer.Abort()

def execute(platform: Platform, dev_env_name: str, task_name: str) -> None:
""" Run a task in a Development Environment.
Args:
dev_env_name -- name of the Development Environment
container_arguments -- arguments passed to the container
platform -- the Platform
dev_env_name -- the Development Environment name
task_name -- the task name
Raises:
typer.Abort -- if the Development Environment has missing tool images
"""
if not dev_env_name:
if platform.default_dev_env_name:
dev_env_name = platform.default_dev_env_name
else:
stderr.print("[red]Error: Only one parameter is supplied but no default Dev Env is set! Please specify the Dev Env to run the task in or set a default one![/]")
return

dev_env = platform.get_dev_env_by_name(dev_env_name)
if dev_env is None:
stderr.print("[red]Error: Unknown Development Environment: " + dev_env_name + "[/]")
return

if not dev_env.is_installed:
stderr.print(f"[red]Error: Development Environment [bold]{dev_env_name}[/bold] is not installed![/]")
return

dev_env_local = platform.get_dev_env_by_name(dev_env_name)
dev_env_health_check(platform, dev_env)

if dev_env_local is None:
stderr.print("[red]Error: Unknown Development Environment: " + dev_env_name + "[/]")
raise typer.Abort()
else:
# Only the local images are needed.
Platform.local_only = True

missing_tool_images = set()
for tool in dev_env_local.tool_image_descriptors:
tool_image = tool["image_name"] + ":" + tool["image_version"]
# Check if the required tool image exists locally.
if tool_image not in platform.tool_images.get_local_ones().keys():
missing_tool_images.add(tool_image)

if missing_tool_images:
handle_missing_tool_images(missing_tool_images, dev_env_local, platform)

platform.container_engine.run(container_arguments)
if task_name not in dev_env.tasks:
stderr.print(f"[red]Error: Task [bold]{task_name}[/bold] not found in Development Environment [bold]{dev_env_name}[/bold]![/]")
return

stdout.print(f"[green]Running task [bold]{task_name}[/bold] in Development Environment [bold]{dev_env_name}[/bold]...[/]\n")
command = dev_env.tasks[task_name]
subprocess.run(command, shell=True)
stdout.print("")
24 changes: 13 additions & 11 deletions dem/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,21 +348,23 @@ def init(project_path: Annotated[str, typer.Argument(help="Path of the project."
else:
raise InternalError("Error: The platform hasn't been initialized properly!")

@typer_cli.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
def run(dev_env_name: Annotated[str, typer.Argument(help="Run the container in this Development Environment context",
autocompletion=autocomplete_dev_env_name)],
ctx: Annotated[typer.Context, typer.Option()]) -> None:
@typer_cli.command()
def run(dev_env_name: Annotated[str, typer.Argument(help="Name of the Development Environment to run the task in. If not set, the default Dev Env will be used.",
autocompletion=autocomplete_installed_dev_env_name)] = "",
task_name: Annotated[str, typer.Argument(help="The name of the task to run.",
autocompletion=autocomplete_task_name)] = "") -> None:
"""
Run the `docker run` command in the Development Environment's context with the given parameters.
This command can be used as the docker CLI one, except as first argument the name of the
Development Environment must be set.
Example: dem run dev_env --name test test_image_name:latest ls -la
Run the task of the Development Environment. The Dev Env must be installed.
See the documentation for the list of currently supported docker run parameters.
If the Dev Env is not specified, the default Dev Env will be used. If the default Dev Env is not
set, an error message will be printed.
"""
if platform:
run_cmd.execute(platform, dev_env_name, ctx.args)
# If only a single parameter is supplied, we assume it's the task name
if not task_name and dev_env_name:
task_name = dev_env_name
dev_env_name = ""
run_cmd.execute(platform, dev_env_name, task_name)
else:
raise InternalError("Error: The platform hasn't been initialized properly!")

Expand Down
1 change: 1 addition & 0 deletions dem/core/dev_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def assign_tool_image_instances(self, tool_images: ToolImages) -> None:
Exceptions:
ToolImageError -- if the Tool Image name is invalid
"""
self.tool_images = []
for tool_descriptor in self.tool_image_descriptors:
tool_image_name = tool_descriptor["image_name"] + ':' + tool_descriptor["image_version"]
tool_image = tool_images.all_tool_images.get(tool_image_name, ToolImage(tool_image_name))
Expand Down
6 changes: 3 additions & 3 deletions dem/core/tool_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self, container_engine: ContainerEngine, registries: Registries) ->

def update(self, local_only: bool = False, registry_only: bool = False,
reg_selection: list[str] = []) -> None:
""" Update the list of available tools.
""" Update the list of available tools. If the tool image already exists, it will be updated.
Args:
local_only -- update the local tools only
Expand All @@ -64,7 +64,7 @@ def update(self, local_only: bool = False, registry_only: bool = False,
registry_tool_image_names = self.registries.list_repos(reg_selection)

for tool_image_name in local_tool_image_names:
tool_image = ToolImage(tool_image_name)
tool_image = self.all_tool_images.get(tool_image_name, ToolImage(tool_image_name))
if tool_image_name in registry_tool_image_names:
tool_image.availability = ToolImage.LOCAL_AND_REGISTRY
else:
Expand All @@ -73,7 +73,7 @@ def update(self, local_only: bool = False, registry_only: bool = False,

for tool_image_name in registry_tool_image_names:
if tool_image_name not in local_tool_image_names:
tool_image = ToolImage(tool_image_name)
tool_image = self.all_tool_images.get(tool_image_name, ToolImage(tool_image_name))
tool_image.availability = ToolImage.REGISTRY_ONLY
self.all_tool_images[tool_image_name] = tool_image

Expand Down
18 changes: 5 additions & 13 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,27 +386,19 @@ Rename the Development Environment.

## **`dem run DEV_ENV_NAME *`**

:warning: Experimental feature!


**Description:**

Run a container in the context of a Development Environment. (Experimental feature)

This command works the same way as the `docker run`, but with some restrictions, and the first
argument is the name of the Development Environment.
Run the task of the Development Environment. The Dev Env must be installed.

:warning: The supported docker run options:
`-p, --name, -v, --privileged, --rm, --name, -d`
See the [Docker documentation](https://docs.docker.com/engine/reference/commandline/run/) for more
info.
If the Dev Env is not specified, the default Dev Env will be used. If the default Dev Env is not
set, an error message will be printed.

**Arguments:**

| Argument | Description | Required |
|------------------|----------------------------------------------------------|----------------:|
| `DEV_ENV_NAME` | Name of the Development Environment. | :material-check:|
| `*` | Variable-length argument list for `docker run` command. | |
| `DEV_ENV_NAME` | Name of the Development Environment to run the task in. If not set, the default Dev Env will be used. |
| `TASK_NAME` | The name of the task to run. | :material-check:|

---

Expand Down
4 changes: 3 additions & 1 deletion tests/cli/test_add_task_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
# the stdout.
runner = CliRunner(mix_stderr=False)

def test_add_task_cmd() -> None:
@patch("dem.cli.command.add_task_cmd.stdout.print")
def test_add_task_cmd(mock_stdout_print: MagicMock) -> None:
# Setup
mock_platform = MagicMock()
main.platform = mock_platform
Expand All @@ -32,6 +33,7 @@ def test_add_task_cmd() -> None:
mock_platform.get_dev_env_by_name.assert_called_once_with("my-dev-env")
mock_dev_env.add_task.assert_called_once_with("my-task", "my-command")
mock_platform.flush_dev_env_properties.assert_called_once()
mock_stdout_print.assert_called_once_with("[green]Task [bold]my-task[/bold] added to Development Environment [bold]my-dev-env[/bold]![/]")

@patch("dem.cli.command.add_task_cmd.stderr.print")
def test_execute_dev_env_not_found(mock_stderr_print: MagicMock) -> None:
Expand Down
4 changes: 2 additions & 2 deletions tests/cli/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def test_platform_not_initialized() -> None:
test_name = "test_name"
test_url = "test_url"
test_command = "test_command"
mock_ctx = MagicMock()
test_task_name = "test_task_name"
main.platform = None

units_to_test = {
Expand All @@ -249,7 +249,7 @@ def test_platform_not_initialized() -> None:
main.uninstall: [test_dev_env_name],
main.assign: [test_dev_env_name, test_path],
main.init: [test_path],
main.run: [test_dev_env_name, mock_ctx],
main.run: [test_dev_env_name, test_task_name],
main.add_reg: [test_name, test_url],
main.list_reg: [],
main.del_reg: [test_name],
Expand Down
Loading

0 comments on commit 3d0d4f9

Please sign in to comment.