Skip to content

Commit

Permalink
task validation
Browse files Browse the repository at this point in the history
  • Loading branch information
a1fred committed Nov 23, 2021
1 parent a2b9c87 commit 5aa1fef
Show file tree
Hide file tree
Showing 32 changed files with 289 additions and 262 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ qa:
poetry run mypy .

.PHONY: test
test: qa docs test_deps
test: docs qa dev
poetry run python3 -m pytest -x --cov-report term --cov=carnival -vv tests/

.PHONY: test_fast
test_fast:
test_fast: dev
poetry run python3 -m pytest -x --cov-report term --cov=carnival -vv -m "not slow" tests/

.PHONY: test_local
test_local:
test_local: dev
poetry run python3 -m pytest -x --cov-report term --cov=carnival -vv -m "not remote" tests/

.PHONY: dev
dev:
docker-compose -f testdata/docker-compose.yml up --build -d --remove-orphans --force-recreate
docker-compose -f testdata/docker-compose.yml up --build -d --remove-orphans

.PHONY: nodev
nodev:
Expand Down
4 changes: 2 additions & 2 deletions carnival/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from carnival.step import Step
from carnival.host import SSHHost, LocalHost
from carnival.task import Task, SimpleTask
from carnival.task import TaskBase, StepsTask
from carnival import cmd
from carnival import internal_tasks
from carnival.utils import log
Expand All @@ -20,7 +20,7 @@
__all__ = [
'Step',
'SSHHost', 'LocalHost',
'Task', 'SimpleTask',
'TaskBase', 'StepsTask',
'cmd',
'log',
'context_ref',
Expand Down
6 changes: 3 additions & 3 deletions carnival/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import click
import dotenv

from carnival.task import Task
from carnival.task import TaskBase
from carnival.tasks_loader import get_tasks

# Load dotenv first
Expand All @@ -23,11 +23,11 @@ def is_completion_script(complete_var: str) -> bool:
return os.getenv(complete_var, None) is not None


task_types: Dict[str, Type[Task]] = {}
task_types: Dict[str, Type[TaskBase]] = {}


def except_hook(type: Type[Any], value: Any, traceback: Any) -> None:
print(f"{type} was raised. You can use --debug flag to see full traceback.")
print(f"{type.__name__}: {value} \nYou can use --debug flag to see full traceback.")


def main() -> int:
Expand Down
6 changes: 3 additions & 3 deletions carnival/cmd/cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from typing import Any

from carnival import global_context
from carnival import connection
from invoke import Result # type: ignore


def _run_command(command: str, **kwargs: Any) -> Result:
assert global_context.conn is not None, "No connection"
return global_context.conn.run(command, **kwargs)
assert connection.conn is not None, "No connection"
return connection.conn.run(command, **kwargs)


def run(command: str, **kwargs: Any) -> Result:
Expand Down
16 changes: 8 additions & 8 deletions carnival/cmd/fs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Optional

from carnival import cmd, global_context
from carnival import cmd, connection
from invoke import Result # type: ignore
from patchwork import files # type:ignore

Expand Down Expand Up @@ -35,10 +35,10 @@ def is_file_contains(filename: str, text: str, exact: bool = False, escape: bool
"""
assert global_context.conn is not None, "No connection"
assert connection.conn is not None, "No connection"
return bool(files.contains(
global_context.conn,
runner=global_context.conn.run,
connection.conn,
runner=connection.conn.run,
filename=filename, text=text, exact=exact, escape=escape
))

Expand All @@ -50,8 +50,8 @@ def is_file_exists(path: str) -> bool:
:param path: путь до файла
"""
assert global_context.conn is not None, "No connection"
return bool(files.exists(global_context.conn, runner=global_context.conn.run, path=path))
assert connection.conn is not None, "No connection"
return bool(files.exists(connection.conn, runner=connection.conn.run, path=path))


def ensure_dir_exists(
Expand All @@ -70,5 +70,5 @@ def ensure_dir_exists(
:param group: группа
:param mode: права
"""
assert global_context.conn is not None, "No connection"
files.directory(global_context.conn, runner=global_context.conn.run, path=path, user=user, group=group, mode=mode)
assert connection.conn is not None, "No connection"
files.directory(connection.conn, runner=connection.conn.run, path=path, user=user, group=group, mode=mode)
10 changes: 5 additions & 5 deletions carnival/cmd/transfer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from io import BytesIO
from typing import Any, Iterable

from carnival import global_context
from carnival import connection
from carnival.templates import render
from fabric.transfer import Result, Transfer # type:ignore
from patchwork import transfers # type:ignore
Expand All @@ -18,7 +18,7 @@ def rsync(
<https://fabric-patchwork.readthedocs.io/en/latest/api/transfers.html#patchwork.transfers.rsync>
"""
return transfers.rsync(
c=global_context.conn,
c=connection.conn,
source=source,
target=target,
exclude=exclude,
Expand All @@ -38,7 +38,7 @@ def get(remote: str, local: str, preserve_mode: bool = True) -> Result:
:param local: путь куда сохранить файл
:param preserve_mode: сохранить права
"""
t = Transfer(global_context.conn)
t = Transfer(connection.conn)
return t.get(remote=remote, local=local, preserve_mode=preserve_mode)


Expand All @@ -51,7 +51,7 @@ def put(local: str, remote: str, preserve_mode: bool = True) -> Result:
:param remote: путь куда сохранить на сервере
:param preserve_mode: сохранить права
"""
t = Transfer(global_context.conn)
t = Transfer(connection.conn)
return t.put(local=local, remote=remote, preserve_mode=preserve_mode)


Expand All @@ -67,5 +67,5 @@ def put_template(template_path: str, remote: str, **context: Any) -> Result:
:param context: контекс для рендеринга jinja2
"""
filestr = render(template_path=template_path, **context)
t = Transfer(global_context.conn)
t = Transfer(connection.conn)
return t.put(local=BytesIO(filestr.encode()), remote=remote, preserve_mode=False)
10 changes: 7 additions & 3 deletions carnival/global_context.py → carnival/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@
from invoke import Context # type:ignore

from carnival.host import AnyHost
from carnival.exceptions import GlobalConnectionError


# noinspection PyTypeChecker
conn: Union[Connection, Context, None] = None
# noinspection PyTypeChecker
host: Optional[AnyHost] = None


class SetContext:
class SetConnection:
def __init__(self, h: AnyHost):
self.host = h

def __enter__(self) -> None:
global conn
global host

assert host is None, f"Cannot set context, while other context active: {host}"
assert conn is None, f"Cannot set context, while other context active: {conn}"
if host is not None:
raise GlobalConnectionError(f"Cannot set context, while other context active: {host}")
if conn is not None:
raise GlobalConnectionError(f"Cannot set context, while other context active: {conn}")

conn = self.host.connect()
host = self.host
Expand Down
54 changes: 26 additions & 28 deletions carnival/context.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,54 @@
import inspect
import os
from typing import Any, Callable, Dict, List
import typing

from carnival.exceptions import ContextBuilderError, ContextBuilderPassAllArgs

class ContextBuilderError(BaseException):
pass

def _get_arg_names(fn: typing.Callable[..., typing.Any]) -> typing.Dict[str, bool]:
arg_names: typing.Dict[str, bool] = {}

class PassAllArgs(BaseException):
pass
for arg_name, arg_parameter in inspect.signature(fn).parameters.items():
if arg_parameter.kind not in [
arg_parameter.KEYWORD_ONLY,
arg_parameter.POSITIONAL_OR_KEYWORD,
arg_parameter.VAR_KEYWORD,
]:
raise ContextBuilderError("only keyword parameters required for autocontext")

if arg_parameter.kind == arg_parameter.VAR_KEYWORD:
raise ContextBuilderPassAllArgs()

def _get_arg_names(fn: Callable[..., Any]) -> List[str]:
arg_names: List[str] = []
spec = inspect.getfullargspec(fn)

if spec.varargs is not None:
raise ValueError("*args is not supported for autocontext")

if spec.varkw is not None:
# Not actual if kwargs argument exists
raise PassAllArgs()

arg_names += spec.args
arg_names += spec.kwonlyargs
arg_names[arg_name] = arg_parameter.default == arg_parameter.empty

if 'self' in arg_names:
arg_names.remove('self')
arg_names.pop('self')

return arg_names


def build_kwargs(fn: Callable[..., Any], context: Dict[str, Any]) -> Dict[str, Any]:
def build_kwargs(
fn: typing.Callable[..., typing.Any],
context: typing.Dict[str, typing.Any],
) -> typing.Dict[str, typing.Any]:
try:
arg_names: List[str] = _get_arg_names(fn)
except PassAllArgs:
arg_names: typing.Dict[str, bool] = _get_arg_names(fn)
except ContextBuilderPassAllArgs:
# Pass all context if kwargs var exists
return context.copy()

kwargs = {}
for arg_name in arg_names:
# TODO: check if variable has default value
# if arg_name not in context:
# raise ContextBuilderError("Required context var '{arg_name}' is not present in context.")
for arg_name, is_arg_required in arg_names.items():
if arg_name not in context and is_arg_required:
raise ContextBuilderError(f"Variable '{arg_name}', is not present in context.")

if arg_name in context:
kwargs[arg_name] = context[arg_name]
return kwargs


def build_context(*context_chain: Dict[str, Any]) -> Dict[str, Any]:
run_context: Dict[str, Any] = {}
def build_context(*context_chain: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
run_context: typing.Dict[str, typing.Any] = {}

env_prefix = "CARNIVAL_CTX_"
# Build context from environment variables
Expand Down
24 changes: 24 additions & 0 deletions carnival/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@


class CarnivalException(BaseException):
"""
Base error class
"""


class ContextBuilderError(CarnivalException):
"""
Error when build context for step
"""


class ContextBuilderPassAllArgs(CarnivalException):
"""
Signal to send full context to step with **kwargs
"""


class GlobalConnectionError(CarnivalException):
"""
Global connection switching error
"""
44 changes: 8 additions & 36 deletions carnival/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,7 @@
подразумевая что вы будете использовать встроенные коллекции python и организуете
работу так, как будет удобно для вашей задачи.
В простом случае, можно передавать хосты прямо в коде файла `carnival_tasks.py`.
>>> class SetupFrontend(Task):
>>> def run(self, **kwargs):
>>> self.step(
>>> [Frontend(), ],
>>> [SSHHost("1.2.3.4", packages=["htop", ]), ],
>>> )
В более сложных, создать списки в файле `inventory.py`
>>> # inventory.py
>>> frontends = [
>>> LocalHost(),
>>> SSHHost("1.2.3.5"),
>>> ]
>>> # carnival_tasks.py
>>> import inventory as i
>>> class SetupFrontend(Task):
>>> def run(self, **kwargs):
>>> self.step([Frontend(), ], i.frontends)
В более сложных, создать списки в отдельном файле, например `inventory.py`
"""

import typing
Expand All @@ -41,6 +21,8 @@
class LocalHost:
"""
Локальный хост, работает по локальному терминалу
:param context: Контекст хоста
"""

def __init__(self, **context: typing.Any) -> None:
Expand All @@ -51,27 +33,14 @@ def __init__(self, **context: typing.Any) -> None:
def connect(self) -> LocalConnection:
return LocalConnection()

@property
def host(self) -> str:
"""
Remove user and port parts, return just address
"""

h = self.addr

if ':' in self.addr:
h = h.split(":", maxsplit=1)[0]

return h

def __str__(self) -> str:
return f"🖥 {self.host}"
return f"🖥 {self.addr}"

def __hash__(self) -> int:
return hash(self.addr)

def __repr__(self) -> str:
return f"<Host object {self.host}>"
return f"<Host object {self.addr}>"


class SSHHost(LocalHost):
Expand All @@ -98,6 +67,9 @@ def __init__(
:param ssh_gateway: Gateway
:param context: Контекст хоста
"""
if ":" in addr:
raise ValueError("Please set port in 'ssh_port' arg")

if "@" in addr:
raise ValueError("Please set user in 'ssh_user' arg")

Expand Down
Loading

0 comments on commit 5aa1fef

Please sign in to comment.