Skip to content

Commit

Permalink
some refactoring, 1.4 prepare, task context overload hook
Browse files Browse the repository at this point in the history
  • Loading branch information
a1fred committed Nov 23, 2021
1 parent 636f4c9 commit a2b9c87
Show file tree
Hide file tree
Showing 23 changed files with 262 additions and 200 deletions.
18 changes: 16 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
.PHONY: dist clean qa test dev nodev todos install docs test_deps
.PHONY: all
all: test_deps qa docs test

clean:
.PHONY: clean
clean: nodev
rm -rf carnival.egg-info dist build __pycache__ .mypy_cache .pytest_cache .coverage

.PHONY: test_deps
test_deps:
poetry install --no-root

.PHONY: qa
qa:
poetry run flake8 .
poetry run mypy .

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

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

.PHONY: test_local
test_local:
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

.PHONY: nodev
nodev:
docker-compose -f testdata/docker-compose.yml rm -sf
ssh-keygen -R [127.0.0.1]:22222
ssh-keygen -R [127.0.0.1]:22223

.PHONY: todos
todos:
grep -r TODO carnival

.PHONY: docs
docs:
poetry run make -C docs html

.PHONY: dist
dist:
poetry publish --build
git tag `poetry version -s`
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ from carnival import Step, Host, Task, cmd
class Deploy(Task):
def run(self):
self.step(
DeployFrontend(),
Host("1.2.3.5", ssh_user="root", can="context", additional="give"),
[DeployFrontend(), ],
[Host("1.2.3.5", ssh_user="root", can="context", additional="give"), ],
)

self.step(
DeployFrontend(),
[DeployFrontend(), ],
[
Host("root@1.2.3.6", can="give", additional="context"),
Host("root@1.2.3.7", can="context", additional="give"),
Expand Down
4 changes: 2 additions & 2 deletions carnival/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys

from carnival.step import Step
from carnival.host import Host, SSHHost, LocalHost
from carnival.host import SSHHost, LocalHost
from carnival.task import Task, SimpleTask
from carnival import cmd
from carnival import internal_tasks
Expand All @@ -19,7 +19,7 @@

__all__ = [
'Step',
'Host', 'SSHHost', 'LocalHost',
'SSHHost', 'LocalHost',
'Task', 'SimpleTask',
'cmd',
'log',
Expand Down
6 changes: 2 additions & 4 deletions carnival/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def main() -> int:
>>> $ poetry run python -m carnival --help
>>> Usage: python -m carnival [OPTIONS] {help|test}...
>>> Options:
>>> -d, --dry_run Simulate run
>>> --debug Turn on debug mode
>>> --help Show this message and exit.
"""
Expand All @@ -48,17 +47,16 @@ def main() -> int:
)

@click.command()
@click.option('-d', '--dry_run', is_flag=True, default=False, help="Simulate run")
@click.option('--debug', is_flag=True, default=False, help="Turn on debug mode")
@click.argument('tasks', required=True, type=click.Choice(list(task_types.keys())), nargs=-1)
def cli(dry_run: bool, debug: bool, tasks: Iterable[str]) -> None:
def cli(debug: bool, tasks: Iterable[str]) -> None:
if debug is True:
print("Debug mode on.")
else:
sys.excepthook = except_hook

for task in tasks:
task_types[task](dry_run=dry_run).run()
task_types[task]().run()

cli(complete_var=complete_var)
return 0
40 changes: 22 additions & 18 deletions carnival/context.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import inspect
import os
from itertools import chain
from typing import TYPE_CHECKING, Any, Callable, Dict, List
from typing import Any, Callable, Dict, List

if TYPE_CHECKING:
from carnival.host import AnyHost
from carnival.step import Step

class ContextBuilderError(BaseException):
pass


class PassAllArgs(BaseException):
Expand Down Expand Up @@ -40,29 +39,34 @@ def build_kwargs(fn: Callable[..., Any], context: Dict[str, Any]) -> Dict[str, A
return context.copy()

kwargs = {}
for context_name, context_val in context.items():
if context_name in arg_names:
kwargs[context_name] = context_val
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.")

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


def build_context(step: 'Step', host: 'AnyHost') -> Dict[str, Any]:
run_context: Dict[str, Any] = {'host': host}
def build_context(*context_chain: Dict[str, Any]) -> Dict[str, Any]:
run_context: Dict[str, Any] = {}

env_prefix = "CARNIVAL_CTX_"
# Build context from environment variables
for env_name, env_val in os.environ.items():
if env_name.startswith(env_prefix):
run_context[env_name[len(env_prefix):]] = env_val

for var_name, var_val in chain(host.context.items(), step.context.items()):
if isinstance(var_val, context_ref):
try:
run_context[var_name] = run_context[var_val.context_var_name]
except KeyError as e:
raise KeyError(f"There is no '{var_val.context_var_name}' variable in context") from e
else:
run_context[var_name] = var_val
for context_item in context_chain:
for var_name, var_val in context_item.items():
if isinstance(var_val, context_ref):
try:
run_context[var_name] = run_context[var_val.context_var_name]
except KeyError as e:
raise ContextBuilderError(f"There is no '{var_val.context_var_name}' variable in context") from e
else:
run_context[var_name] = var_val

return run_context

Expand Down
97 changes: 18 additions & 79 deletions carnival/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
>>> class SetupFrontend(Task):
>>> def run(self, **kwargs):
>>> self.step(Frontend(), SSHHost("1.2.3.4", packages=["htop", ]))
>>> self.step(
>>> [Frontend(), ],
>>> [SSHHost("1.2.3.4", packages=["htop", ]), ],
>>> )
В более сложных, создать списки в файле `inventory.py`
Expand All @@ -22,38 +25,28 @@
>>> import inventory as i
>>> class SetupFrontend(Task):
>>> def run(self, **kwargs):
>>> self.step(Frontend(), i.frontends)
>>> self.step([Frontend(), ], i.frontends)
"""

from typing import Any, Optional, Union
import warnings
import typing

from fabric.connection import Connection as SSHConnection # type: ignore
from invoke.context import Context as LocalConnection # type: ignore
from paramiko.client import MissingHostKeyPolicy, AutoAddPolicy # type: ignore
from paramiko.client import MissingHostKeyPolicy, AutoAddPolicy


AnyConnection = Union[SSHConnection, LocalConnection]

"""
Список адресов которые трактуются как локальное соединение
.. deprecated:: 1.4
Host is deprecated, use LocalHost or SSHHost explicitly
"""
LOCAL_ADDRS = [
'local',
'localhost',
]
AnyConnection = typing.Union[SSHConnection, LocalConnection]


class LocalHost:
"""
Локальный хост, работает по локальному терминалу
"""

def __init__(self, **context: Any) -> None:
def __init__(self, **context: typing.Any) -> None:
self.addr = "local"
self.context = context
self.context['host'] = self

def connect(self) -> LocalConnection:
return LocalConnection()
Expand Down Expand Up @@ -89,12 +82,12 @@ class SSHHost(LocalHost):
def __init__(
self,
addr: str,
ssh_user: Optional[str] = None, ssh_password: Optional[str] = None, ssh_port: int = 22,
ssh_gateway: Optional['SSHHost'] = None,
ssh_user: typing.Optional[str] = None, ssh_password: typing.Optional[str] = None, ssh_port: int = 22,
ssh_gateway: typing.Optional['SSHHost'] = None,
ssh_connect_timeout: int = 10,
missing_host_key_policy: MissingHostKeyPolicy = AutoAddPolicy,
missing_host_key_policy: typing.Type[MissingHostKeyPolicy] = AutoAddPolicy,

**context: Any
**context: typing.Any
):
"""
:param addr: Адрес сервера
Expand All @@ -108,26 +101,16 @@ def __init__(
if "@" in addr:
raise ValueError("Please set user in 'ssh_user' arg")

super().__init__(**context)

self.addr = addr
self.ssh_port = ssh_port
self.context = context
self.ssh_user = ssh_user
self.ssh_password = ssh_password
self.ssh_connect_timeout = ssh_connect_timeout
self.ssh_gateway: Optional['SSHHost'] = ssh_gateway
self.ssh_gateway: typing.Optional['SSHHost'] = ssh_gateway
self.missing_host_key_policy = missing_host_key_policy

def is_connection_local(self) -> bool:
"""
Check if host's connection is local
"""
warnings.warn(
"is_connection_local is deprecated, use LocalHost or SSHHost explicitly",
DeprecationWarning,
stacklevel=2,
)
return self.host.lower() in LOCAL_ADDRS

def connect(self) -> SSHConnection:
gateway = None
if self.ssh_gateway:
Expand All @@ -148,48 +131,4 @@ def connect(self) -> SSHConnection:
return conn


AnyHost = Union[LocalHost, SSHHost]


class Host(SSHHost):
"""
:param addr: Адрес сервера для SSH или "local" для локального соединения
:param ssh_user: Пользователь SSH
:param ssh_password: Пароль SSH
:param ssh_port: SSH порт
:param ssh_connect_timeout: SSH таймаут соединения
:param ssh_gateway: Gateway
:param context: Контекст хоста
.. deprecated:: 1.4
Host is deprecated, use LocalHost or SSHHost explicitly
"""
def __init__(
self,
addr: str,
ssh_user: Optional[str] = None, ssh_password: Optional[str] = None, ssh_port: int = 22,
ssh_gateway: Optional['SSHHost'] = None, ssh_connect_timeout: int = 10,
missing_host_key_policy: MissingHostKeyPolicy = AutoAddPolicy,
**context: Any
):
warnings.warn(
"Host is deprecated, use LocalHost or SSHHost explicitly",
DeprecationWarning,
stacklevel=2,
)

if "@" in addr:
ssh_user, addr = addr.split("@", maxsplit=1)

super().__init__(
addr,
ssh_user=ssh_user, ssh_password=ssh_password, ssh_port=ssh_port,
ssh_gateway=ssh_gateway, ssh_connect_timeout=ssh_connect_timeout,
missing_host_key_policy=missing_host_key_policy, **context
)

def connect(self) -> AnyConnection:
if self.addr in LOCAL_ADDRS:
return LocalConnection()

return super().connect()
AnyHost = typing.Union[LocalHost, SSHHost]
16 changes: 10 additions & 6 deletions carnival/step.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import abc
from typing import Any, no_type_check
import typing

from carnival.context import build_context, build_kwargs
from carnival.host import AnyHost


class Step:
Expand All @@ -28,19 +27,24 @@ class Step:
>>> ...
"""
def __init__(self, **context: Any):
def __init__(self, **context: typing.Any):
"""
:param context: Переменные контекста, назначенные при вызове Шага
"""
self.context = context

def run_with_context(self, host: AnyHost) -> Any:
context = build_context(self, host)
def run_with_context(self, host_ctx: typing.Dict[str, typing.Any]) -> typing.Any:
"""
Выполнить шаг
:param host_ctx: конекст хоста, (`AnyHost.context`)
"""
context = build_context(host_ctx, self.context)
kwargs = build_kwargs(self.run, context)
return self.run(**kwargs) # type: ignore

@abc.abstractmethod
@no_type_check
@typing.no_type_check
def run(self, **kwargs) -> None:
"""
Метод который нужно определить для выполнения комманд
Expand Down
Loading

0 comments on commit a2b9c87

Please sign in to comment.