easyrunner-cli 0.0.1.dev86__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- easyrunner_cli-0.0.1.dev86/PKG-INFO +50 -0
- easyrunner_cli-0.0.1.dev86/README.md +30 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/cloud_providers/__init__.py +0 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/cloud_providers/cloud_provider_base.py +194 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/cloud_providers/cloud_providers.py +14 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/cloud_providers/hetzner_provider.py +77 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/command_executor.py +99 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/command_executor_local.py +150 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/__init__.py +15 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/__init__.py +39 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/archive_commands.py +63 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/caddy_api_curl_commands.py +19 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/caddy_commands.py +19 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/command_base.py +41 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/curl_commands.py +19 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/dir_commands.py +52 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/docker_compose_commands.py +48 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/file_commands.py +282 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/git_commands.py +117 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/ip_tables_commands.py +161 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/ip_tables_persistent_commands.py +34 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/null_command.py +22 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/os_package_manager_commands.py +62 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/podman_commands.py +146 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/ssh_agent_commands.py +141 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/ssh_keygen_commands.py +60 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/systemctl_commands.py +146 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/user_commands.py +134 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/base/utility_commands.py +25 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/runnable_command_string.py +21 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/__init__.py +31 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/archive_commands_ubuntu.py +28 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/caddy_api_curl_commands_ubuntu.py +209 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/caddy_commands_container_ubuntu.py +63 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/curl_commands_ubuntu.py +369 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/dir_commands_ubuntu.py +21 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/docker_compose_commands_ubuntu.py +34 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/file_commands_ubuntu.py +11 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/git_commands_ubuntu.py +24 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/ip_tables_commands_ubuntu.py +9 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/ip_tables_persistent_commands_ubuntu.py +9 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/os_package_manager_commands_ubuntu.py +66 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/podman_commands_ubuntu.py +29 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/ssh_agent_commands_ubuntu.py +20 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/ssh_keygen_commands_ubuntu.py +31 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/systemctl_commands_ubuntu.py +17 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/user_commands_ubuntu.py +20 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/commands/ubuntu/utility_commands_ubuntu.py +20 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/format_utils.py +15 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/http_client.py +185 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/known_host_ssh_keys.py +21 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/__init__.py +28 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/cloud_firewall_base.py +11 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/cloud_resource_api_base.py +10 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/cloud_resource_pulumi_base.py +16 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/cloud_virtual_machine_base.py +20 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/github/github_api_client.py +135 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/github/github_api_client_dtos.py +76 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/github/github_repo.py +145 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/hetzner/__init__.py +9 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/hetzner/hetzner_firewall.py +82 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/hetzner/hetzner_firewall_rule.py +14 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/hetzner/hetzner_resource_factory.py +76 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/hetzner/hetzner_stack.py +360 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/cloud_resources/hetzner/hetzner_virtual_machine.py +72 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/__init__.py +25 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/caddy.py +349 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/directory.py +232 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/docker_compose.py +18 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/file.py +203 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/git_repo.py +231 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/host_server_ubuntu.py +3448 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/ip_tables.py +260 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/os_package_manager.py +59 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/os_resource_base.py +20 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/podman.py +235 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/podman_network.py +64 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/ssh_agent.py +723 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/systemd_service.py +112 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/os_resources/user.py +269 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/resource_base.py +5 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/resources/web_security_scanner.py +954 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/ssh.py +284 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/ssh_key.py +340 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/__init__.py +11 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/data_models/__init__.py +4 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/data_models/app.py +37 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/data_models/database_dto_base.py +27 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/data_models/server.py +37 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/db_config.py +25 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/db_ctx.py +94 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/easyrunner_store.py +90 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/json_encoder.py +16 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/object_id.py +48 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/store/uuid7.py +26 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/tool_paths.py +50 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/__init__.py +23 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/caddy/caddy_config.py +19 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/caddy/caddy_site.py +16 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/compose_project/__init__.py +12 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/compose_project/compose_network.py +36 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/compose_project/compose_project.py +128 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/compose_project/compose_service.py +46 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/compose_project/compose_volume.py +22 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/cpu_arch_types.py +7 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/dir_info.py +38 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/dto_base.py +49 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/exec_result.py +57 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/file_info.py +60 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/json.py +80 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/jsonobject_to_dataclass.py +90 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/os_type.py +8 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/podman_network_driver.py +8 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/security_scan_result.py +54 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/ssh_key_type.py +7 -0
- easyrunner_cli-0.0.1.dev86/easyrunner/types/vm_config.py +28 -0
- easyrunner_cli-0.0.1.dev86/pyproject.toml +67 -0
- easyrunner_cli-0.0.1.dev86/source/__init__.py +19 -0
- easyrunner_cli-0.0.1.dev86/source/app_sub_command.py +557 -0
- easyrunner_cli-0.0.1.dev86/source/auth/__init__.py +12 -0
- easyrunner_cli-0.0.1.dev86/source/auth/auth_sub_command.py +226 -0
- easyrunner_cli-0.0.1.dev86/source/auth/github_oauth_config.py +24 -0
- easyrunner_cli-0.0.1.dev86/source/auth/github_oauth_flow.py +165 -0
- easyrunner_cli-0.0.1.dev86/source/auth/github_token_manager.py +226 -0
- easyrunner_cli-0.0.1.dev86/source/auth/oauth_callback_server.py +109 -0
- easyrunner_cli-0.0.1.dev86/source/auth_sub_command.py +226 -0
- easyrunner_cli-0.0.1.dev86/source/infrastructure_deps.py +107 -0
- easyrunner_cli-0.0.1.dev86/source/license_sub_command.py +198 -0
- easyrunner_cli-0.0.1.dev86/source/licensing/__init__.py +5 -0
- easyrunner_cli-0.0.1.dev86/source/licensing/license_manager.py +207 -0
- easyrunner_cli-0.0.1.dev86/source/main.py +101 -0
- easyrunner_cli-0.0.1.dev86/source/servers_sub_command.py +1515 -0
- easyrunner_cli-0.0.1.dev86/source/ssh_config.py +76 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easyrunner_cli
|
|
3
|
+
Version: 0.0.1.dev86
|
|
4
|
+
Summary: EasyRunner CLI.
|
|
5
|
+
Author: Janaka Abeywardhana
|
|
6
|
+
Author-email: contact@janaka.co.uk
|
|
7
|
+
Requires-Python: >=3.13,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Requires-Dist: cryptography (>=44.0.0,<45.0.0)
|
|
12
|
+
Requires-Dist: keyring (>=25.6.0,<26.0.0)
|
|
13
|
+
Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
|
|
14
|
+
Requires-Dist: pyobjc-framework-Security (>=10.3.1,<11.0.0) ; sys_platform == "darwin"
|
|
15
|
+
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
16
|
+
Requires-Dist: typer (>=0.15.1,<0.16.0)
|
|
17
|
+
Project-URL: Homepage, https://github.com/janaka/easyrunner
|
|
18
|
+
Project-URL: Repository, https://github.com/janaka/easyrunner
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# EasyRunner CLI
|
|
22
|
+
|
|
23
|
+
Application hosting platform that run on a single server.
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2024 - 2025 Janaka Abeywardhana
|
|
26
|
+
|
|
27
|
+
## Contribution
|
|
28
|
+
|
|
29
|
+
Setup python tools on a new machine
|
|
30
|
+
|
|
31
|
+
- `brew install pyenv` - python virtual environment manager
|
|
32
|
+
- `brew install pipx` - pipx python package manager, for install poetry
|
|
33
|
+
- `pipx install poetry` (pipx installs global packages in isolated environments)
|
|
34
|
+
- add `export PATH="$HOME/.local/bin:$PATH"` to ~/.zshrc for poetry.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Setup python environment for an application
|
|
38
|
+
|
|
39
|
+
- `pyenv install 3.13` install this version of python.
|
|
40
|
+
- `pyenv local` show the version in this environment
|
|
41
|
+
- `poetry env use $(pyenv which python)` to create a poetry environment in this project for dependencies. the `.venv`
|
|
42
|
+
- `source $(poetry env info --path)/bin/activate` to activate the environment (avail on path etc.)
|
|
43
|
+
- `poetry config virtualenvs.in-project true`
|
|
44
|
+
- `poetry install`
|
|
45
|
+
|
|
46
|
+
if the location of the repo changes on your local machine then the virtual env will get disconnected. Therefore remove and recreate
|
|
47
|
+
|
|
48
|
+
- `poetry env remove $(poetry env list --full-path | grep -Eo '/.*')` Remove the current Poetry environment to force a clean rebuild:
|
|
49
|
+
- `poetry install` Recreate the environment and install dependencies
|
|
50
|
+
- `source $(poetry env info --path)/bin/activate` activate the environment
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# EasyRunner CLI
|
|
2
|
+
|
|
3
|
+
Application hosting platform that run on a single server.
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2024 - 2025 Janaka Abeywardhana
|
|
6
|
+
|
|
7
|
+
## Contribution
|
|
8
|
+
|
|
9
|
+
Setup python tools on a new machine
|
|
10
|
+
|
|
11
|
+
- `brew install pyenv` - python virtual environment manager
|
|
12
|
+
- `brew install pipx` - pipx python package manager, for install poetry
|
|
13
|
+
- `pipx install poetry` (pipx installs global packages in isolated environments)
|
|
14
|
+
- add `export PATH="$HOME/.local/bin:$PATH"` to ~/.zshrc for poetry.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Setup python environment for an application
|
|
18
|
+
|
|
19
|
+
- `pyenv install 3.13` install this version of python.
|
|
20
|
+
- `pyenv local` show the version in this environment
|
|
21
|
+
- `poetry env use $(pyenv which python)` to create a poetry environment in this project for dependencies. the `.venv`
|
|
22
|
+
- `source $(poetry env info --path)/bin/activate` to activate the environment (avail on path etc.)
|
|
23
|
+
- `poetry config virtualenvs.in-project true`
|
|
24
|
+
- `poetry install`
|
|
25
|
+
|
|
26
|
+
if the location of the repo changes on your local machine then the virtual env will get disconnected. Therefore remove and recreate
|
|
27
|
+
|
|
28
|
+
- `poetry env remove $(poetry env list --full-path | grep -Eo '/.*')` Remove the current Poetry environment to force a clean rebuild:
|
|
29
|
+
- `poetry install` Recreate the environment and install dependencies
|
|
30
|
+
- `source $(poetry env info --path)/bin/activate` activate the environment
|
|
File without changes
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import pulumi.automation as auto
|
|
6
|
+
|
|
7
|
+
from ..tool_paths import EasyRunnerPaths
|
|
8
|
+
from ..types.exec_result import ExecResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CloudProviderBase(ABC):
|
|
12
|
+
"""Base class for cloud provider implementations."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self, api_key: str, region: str, project_name: Optional[str] = None
|
|
16
|
+
) -> None:
|
|
17
|
+
self.api_key = api_key
|
|
18
|
+
self.region = region
|
|
19
|
+
self.project_name = project_name or f"easyrunner-{self.name()}"
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def _create_provider(self) -> Any:
|
|
23
|
+
"""Create the cloud provider instance."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def _get_pulumi_command(self) -> str:
|
|
27
|
+
"""Get the Pulumi root directory for PulumiCommand, which will append /bin/pulumi."""
|
|
28
|
+
return str(EasyRunnerPaths.get_pulumi_root())
|
|
29
|
+
|
|
30
|
+
def _create_workspace_options(
|
|
31
|
+
self, backend_url: Optional[str] = None
|
|
32
|
+
) -> auto.LocalWorkspaceOptions:
|
|
33
|
+
"""Create workspace options with custom Pulumi command and optional backend."""
|
|
34
|
+
pulumi_command_path = self._get_pulumi_command()
|
|
35
|
+
|
|
36
|
+
# Set default backend to EasyRunner's local backend if not provided
|
|
37
|
+
if backend_url is None or backend_url == "":
|
|
38
|
+
backend_url = f"file://{EasyRunnerPaths.get_pulumi_local_backend_dir()}"
|
|
39
|
+
|
|
40
|
+
# Set Pulumi config passphrase if not already set
|
|
41
|
+
if "PULUMI_CONFIG_PASSPHRASE" not in os.environ:
|
|
42
|
+
os.environ["PULUMI_CONFIG_PASSPHRASE"] = (
|
|
43
|
+
"klshdf324£$@::D£$mcmoWERXakhk234%basdmnbqqvffadqWEQAO[]£N!#sdff"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return auto.LocalWorkspaceOptions(
|
|
47
|
+
pulumi_command=auto.PulumiCommand(pulumi_command_path),
|
|
48
|
+
pulumi_home=str(
|
|
49
|
+
EasyRunnerPaths.get_pulumi_root()
|
|
50
|
+
), # Control where all Pulumi data is stored
|
|
51
|
+
secrets_provider="passphrase",
|
|
52
|
+
work_dir=str(
|
|
53
|
+
EasyRunnerPaths.get_pulumi_local_backend_dir()
|
|
54
|
+
), # Set workspace directory
|
|
55
|
+
project_settings=auto.ProjectSettings(
|
|
56
|
+
name=self.project_name,
|
|
57
|
+
runtime="python",
|
|
58
|
+
backend=auto.ProjectBackend(url=backend_url),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _ensure_cloud_tools_available(self) -> ExecResult:
|
|
63
|
+
"""Ensure cloud tools are available before executing Pulumi operations."""
|
|
64
|
+
from easyrunner_cli.source.infrastructure_deps import InfrastructureDependencies
|
|
65
|
+
|
|
66
|
+
return InfrastructureDependencies.ensure_cloud_tools_available()
|
|
67
|
+
|
|
68
|
+
def get_provider_instance(self) -> Any:
|
|
69
|
+
"""Get the provider instance for programmatic use."""
|
|
70
|
+
|
|
71
|
+
return self._create_provider()
|
|
72
|
+
|
|
73
|
+
def _convert_pulumi_outputs_to_dict(
|
|
74
|
+
self, outputs: auto.OutputMap
|
|
75
|
+
) -> dict[str, Any]:
|
|
76
|
+
"""Convert Pulumi OutputMap to standard dictionary with extracted values.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
outputs: Pulumi OutputMap from stack.outputs()
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Dictionary with extracted values from Pulumi OutputValue objects
|
|
83
|
+
"""
|
|
84
|
+
result: dict[str, Any] = {}
|
|
85
|
+
for key, output_value in outputs.items():
|
|
86
|
+
# Pulumi automation API returns OutputValue objects with a 'value' field
|
|
87
|
+
result[key] = (
|
|
88
|
+
output_value.value if hasattr(output_value, "value") else output_value
|
|
89
|
+
)
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def name(self) -> str:
|
|
94
|
+
"""Get the name of the cloud provider."""
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
# def execute_pulumi_program(
|
|
98
|
+
# self,
|
|
99
|
+
# program: Callable[[], None],
|
|
100
|
+
# stack_name: str,
|
|
101
|
+
# backend_url: Optional[str] = None,
|
|
102
|
+
# ) -> ExecResult:
|
|
103
|
+
# """Execute a Pulumi program using EasyRunner's Pulumi installation.
|
|
104
|
+
|
|
105
|
+
# Args:
|
|
106
|
+
# program: The Pulumi program function to execute
|
|
107
|
+
# stack_name: Name of the stack
|
|
108
|
+
# backend_url: Optional backend URL (e.g., s3://bucket-name for S3 backend)
|
|
109
|
+
# Returns:
|
|
110
|
+
# ExecResult[dict[str, Any]]: The result of the Pulumi program execution stack.outputs as a dictionary.
|
|
111
|
+
# """
|
|
112
|
+
# # Ensure cloud tools are available before executing
|
|
113
|
+
# deps_result = self._ensure_cloud_tools_available()
|
|
114
|
+
# if not deps_result.success:
|
|
115
|
+
# return deps_result
|
|
116
|
+
|
|
117
|
+
# try:
|
|
118
|
+
# # Create or select stack with custom workspace options
|
|
119
|
+
# stack = auto.create_or_select_stack(
|
|
120
|
+
# stack_name=stack_name,
|
|
121
|
+
# project_name=self.project_name,
|
|
122
|
+
# program=program,
|
|
123
|
+
# opts=self._create_workspace_options(backend_url=backend_url),
|
|
124
|
+
# )
|
|
125
|
+
|
|
126
|
+
# # Deploy
|
|
127
|
+
# stack.up()
|
|
128
|
+
|
|
129
|
+
# result = ExecResult[dict[str, Any]](
|
|
130
|
+
# success=True,
|
|
131
|
+
# return_code=0,
|
|
132
|
+
# stdout="Stack deployed successfully",
|
|
133
|
+
# stderr=None,
|
|
134
|
+
# )
|
|
135
|
+
|
|
136
|
+
# # Convert Pulumi OutputMap to standard dict
|
|
137
|
+
# result.result = self._convert_pulumi_outputs_to_dict(stack.outputs())
|
|
138
|
+
|
|
139
|
+
# return result
|
|
140
|
+
|
|
141
|
+
# except Exception as e:
|
|
142
|
+
# return ExecResult(success=False, return_code=1, stdout=None, stderr=str(e))
|
|
143
|
+
|
|
144
|
+
# def destroy_pulumi_stack(
|
|
145
|
+
# self, stack_name: str, backend_url: Optional[str] = None
|
|
146
|
+
# ) -> ExecResult:
|
|
147
|
+
# """Destroy a Pulumi stack using EasyRunner's Pulumi installation."""
|
|
148
|
+
# try:
|
|
149
|
+
|
|
150
|
+
# def dummy_program() -> None:
|
|
151
|
+
# """Empty program required for stack selection."""
|
|
152
|
+
# pass
|
|
153
|
+
|
|
154
|
+
# stack = auto.select_stack(
|
|
155
|
+
# stack_name=stack_name,
|
|
156
|
+
# project_name=self.project_name,
|
|
157
|
+
# program=dummy_program,
|
|
158
|
+
# opts=self._create_workspace_options(backend_url=backend_url),
|
|
159
|
+
# )
|
|
160
|
+
|
|
161
|
+
# stack.destroy()
|
|
162
|
+
|
|
163
|
+
# return ExecResult(
|
|
164
|
+
# success=True,
|
|
165
|
+
# return_code=0,
|
|
166
|
+
# stdout=f"Stack {stack_name} destroyed",
|
|
167
|
+
# stderr=None,
|
|
168
|
+
# )
|
|
169
|
+
|
|
170
|
+
# except Exception as e:
|
|
171
|
+
# return ExecResult(success=False, return_code=1, stdout=None, stderr=str(e))
|
|
172
|
+
|
|
173
|
+
def get_stack_outputs(
|
|
174
|
+
self, stack_name: str, backend_url: Optional[str] = None
|
|
175
|
+
) -> Optional[dict[str, Any]]:
|
|
176
|
+
"""Get outputs from a Pulumi stack using EasyRunner's Pulumi installation."""
|
|
177
|
+
try:
|
|
178
|
+
stack = auto.select_stack(
|
|
179
|
+
stack_name=stack_name,
|
|
180
|
+
project_name=self.project_name,
|
|
181
|
+
opts=self._create_workspace_options(backend_url=backend_url),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
outputs = stack.outputs()
|
|
185
|
+
result = self._convert_pulumi_outputs_to_dict(outputs)
|
|
186
|
+
return result if result else None
|
|
187
|
+
|
|
188
|
+
except Exception:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
# @abstractmethod
|
|
192
|
+
# def create_select_state_backend_bucket(self) -> ExecResult:
|
|
193
|
+
# """Setup the Pulumi state backend."""
|
|
194
|
+
# pass
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CloudProviders(str, Enum):
|
|
5
|
+
"""Supported cloud providers for automated server creation."""
|
|
6
|
+
#AZURE = "azure"
|
|
7
|
+
#AWS = "aws"
|
|
8
|
+
#DIGITAL_OCEAN = "digitalocean"
|
|
9
|
+
#LINODE = "linode"
|
|
10
|
+
#VULTR = "vultr"
|
|
11
|
+
HETZNER = "hetzner"
|
|
12
|
+
#GCP = "gcp"
|
|
13
|
+
#OVH = "ovh"
|
|
14
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from .cloud_provider_base import CloudProviderBase
|
|
5
|
+
from .cloud_providers import CloudProviders
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
import pulumi_hcloud
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HetznerProvider(CloudProviderBase):
|
|
12
|
+
"""Hetzner cloud provider implementation using Pulumi."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, api_key: str, region: str = "nbg1"):
|
|
15
|
+
super().__init__(api_key, region)
|
|
16
|
+
# Store the API key for programmatic provider configuration
|
|
17
|
+
self._hetzner_token = api_key
|
|
18
|
+
|
|
19
|
+
def _create_provider(self) -> "pulumi_hcloud.Provider":
|
|
20
|
+
"""Create Hetzner provider with explicit token configuration."""
|
|
21
|
+
import pulumi_hcloud as hetzner
|
|
22
|
+
|
|
23
|
+
return hetzner.Provider("hetzner-provider", token=self._hetzner_token)
|
|
24
|
+
|
|
25
|
+
def name(self) -> str:
|
|
26
|
+
"""Get the name of the cloud provider."""
|
|
27
|
+
return CloudProviders.HETZNER.value
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_env(cls, region: str = "nbg1") -> "HetznerProvider":
|
|
31
|
+
"""Create HetznerProvider instance from HETZNER_API_KEY environment variable."""
|
|
32
|
+
api_key = os.getenv("HETZNER_API_KEY")
|
|
33
|
+
if not api_key:
|
|
34
|
+
raise ValueError("HETZNER_API_KEY environment variable is required")
|
|
35
|
+
return cls(api_key=api_key, region=region)
|
|
36
|
+
|
|
37
|
+
# def create_select_state_backend_bucket(self) -> ExecResult:
|
|
38
|
+
# """Setup Pulumi state backend - local for initial setup, S3 for production.
|
|
39
|
+
|
|
40
|
+
# Retrurns:
|
|
41
|
+
# ExecResult[dict[str, Any]]
|
|
42
|
+
|
|
43
|
+
# results keys:
|
|
44
|
+
# - bucket_name: Name of the created S3 bucket for state storage.
|
|
45
|
+
# """
|
|
46
|
+
|
|
47
|
+
# # Create S3 compatible object storage backend using the base class execute_pulumi_program method
|
|
48
|
+
# def create_state_backend_object_store() -> None:
|
|
49
|
+
# """there currently no pulumi object storage support for Hetzner as it's a new service."""
|
|
50
|
+
# pass
|
|
51
|
+
|
|
52
|
+
# # Execute the program using local backend (for creating the S3 bucket)
|
|
53
|
+
# result: ExecResult[dict[str, Any]] = self.execute_pulumi_program(
|
|
54
|
+
# program=create_state_backend_object_store,
|
|
55
|
+
# stack_name="state-backend",
|
|
56
|
+
# backend_url=None, # Use local backend to create the S3 bucket
|
|
57
|
+
# )
|
|
58
|
+
|
|
59
|
+
# if not result.success:
|
|
60
|
+
# return result
|
|
61
|
+
|
|
62
|
+
# # Get the bucket name from outputs
|
|
63
|
+
# outputs = self.get_stack_outputs(
|
|
64
|
+
# stack_name="state-backend",
|
|
65
|
+
# backend_url=None,
|
|
66
|
+
# )
|
|
67
|
+
|
|
68
|
+
# bucket_name = ""
|
|
69
|
+
# if outputs and "state_bucket_name" in outputs:
|
|
70
|
+
# bucket_name = outputs["state_bucket_name"]
|
|
71
|
+
|
|
72
|
+
# return ExecResult(
|
|
73
|
+
# success=True,
|
|
74
|
+
# return_code=0,
|
|
75
|
+
# stdout=f"S3 state bucket created: {bucket_name}. Use backend URL: s3://{bucket_name}",
|
|
76
|
+
# stderr=None,
|
|
77
|
+
# )
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from fabric import Result as InvokeResult
|
|
6
|
+
from fabric.transfer import Result as TransferResult
|
|
7
|
+
|
|
8
|
+
from .commands.runnable_command_string import RunnableCommandString
|
|
9
|
+
from .ssh import Ssh
|
|
10
|
+
from .types.exec_result import ExecResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommandExecutor:
|
|
14
|
+
"""This class is responsible for executing commands on the remote machine"""
|
|
15
|
+
def __init__(self, ssh_client: Ssh):
|
|
16
|
+
# setup logger for this class with correct logger namespace hierarchy
|
|
17
|
+
self._logger: logging.Logger = logging.getLogger(__name__)
|
|
18
|
+
# Critical for libs to prevent log messages from propagating to the root logger and causing dup logs and config issues.
|
|
19
|
+
self._logger.addHandler(logging.NullHandler())
|
|
20
|
+
|
|
21
|
+
self.ssh_client: Ssh = ssh_client
|
|
22
|
+
|
|
23
|
+
def execute(self, command: RunnableCommandString) -> ExecResult:
|
|
24
|
+
"""
|
|
25
|
+
Execute a command on the remote host.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
command: Command to execute
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
if command.output_to_file is not None:
|
|
32
|
+
redirect_operator = ">>" if command.append_or_overwrite == "APPEND" else ">"
|
|
33
|
+
command = RunnableCommandString(
|
|
34
|
+
command=f"{command.command} {redirect_operator} {command.output_to_file}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
with self.ssh_client as ssh:
|
|
38
|
+
result: InvokeResult = (
|
|
39
|
+
ssh.run_sudo(command=command)
|
|
40
|
+
if command.sudo
|
|
41
|
+
else ssh.run(command=command)
|
|
42
|
+
)
|
|
43
|
+
mapped_result = self._map_invoke_result(result, command=command)
|
|
44
|
+
|
|
45
|
+
self._logger.debug(
|
|
46
|
+
"Command Executed > '%r', On Remote Host: '%s:%s'\n\n",
|
|
47
|
+
mapped_result,
|
|
48
|
+
ssh.hostname,
|
|
49
|
+
ssh.port,
|
|
50
|
+
)
|
|
51
|
+
return mapped_result
|
|
52
|
+
|
|
53
|
+
def put_file(self, source: Path | str | BytesIO, remote_path: str) -> ExecResult:
|
|
54
|
+
"""
|
|
55
|
+
Transfer a file from local machine where this method is executed to remote host.
|
|
56
|
+
|
|
57
|
+
See https://docs.fabfile.org/en/latest/api/transfer.html#fabric.transfer.Transfer.put
|
|
58
|
+
Args:
|
|
59
|
+
local_path: Path to local file or a file-like object as io.StringIO(content_str).
|
|
60
|
+
remote_path: Remote destination path
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
with self.ssh_client as ssh:
|
|
64
|
+
self._logger.debug(
|
|
65
|
+
"Transferring file to remote host. Remote path: '%s'",
|
|
66
|
+
remote_path,
|
|
67
|
+
)
|
|
68
|
+
result: TransferResult = ssh.connection.put(
|
|
69
|
+
local=source, remote=remote_path, preserve_mode=False
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return ExecResult(
|
|
73
|
+
stdout=f"Transferring file '{result.local}' to remote '{result.remote}' successfully",
|
|
74
|
+
return_code=0,
|
|
75
|
+
success=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
return ExecResult(
|
|
80
|
+
stdout="",
|
|
81
|
+
stderr=str(e),
|
|
82
|
+
return_code=1,
|
|
83
|
+
success=False,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# raise RuntimeError(
|
|
87
|
+
# f"CommandExecutor.put_file() - Failed to transfer file to remote host: {str(e)}"
|
|
88
|
+
# )
|
|
89
|
+
|
|
90
|
+
def _map_invoke_result(
|
|
91
|
+
self, result: InvokeResult, command: RunnableCommandString
|
|
92
|
+
) -> ExecResult:
|
|
93
|
+
return ExecResult(
|
|
94
|
+
stdout=result.stdout,
|
|
95
|
+
stderr=result.stderr,
|
|
96
|
+
return_code=result.return_code,
|
|
97
|
+
success=result.return_code == 0,
|
|
98
|
+
command=command,
|
|
99
|
+
)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from invoke import run
|
|
6
|
+
from invoke.runners import Result as InvokeResult
|
|
7
|
+
|
|
8
|
+
from .commands.runnable_command_string import RunnableCommandString
|
|
9
|
+
from .types.exec_result import ExecResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CommandExecutorLocal:
|
|
13
|
+
""" This class is responsible for executing commands on the local machine"""
|
|
14
|
+
def __init__(self, debug: bool = False, silent: bool = True):
|
|
15
|
+
# setup logger for this class with correct logger namespace hierarchy
|
|
16
|
+
self._logger: logging.Logger = logging.getLogger(__name__)
|
|
17
|
+
# Critical for libs to prevent log messages from propagating to the root logger and causing dup logs and config issues.
|
|
18
|
+
self._logger.addHandler(logging.NullHandler())
|
|
19
|
+
|
|
20
|
+
self.debug: bool = debug
|
|
21
|
+
self.silent: bool = silent
|
|
22
|
+
|
|
23
|
+
# Configure hide behavior like your SSH class
|
|
24
|
+
if self.debug:
|
|
25
|
+
self._hide: bool | str = False
|
|
26
|
+
elif self.silent:
|
|
27
|
+
self._hide = "both"
|
|
28
|
+
else:
|
|
29
|
+
# self._hide = False
|
|
30
|
+
self._hide = "both"
|
|
31
|
+
|
|
32
|
+
def execute(self, command: RunnableCommandString) -> ExecResult:
|
|
33
|
+
"""
|
|
34
|
+
Execute a command on the local machine.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
command: Command to execute
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
if command.output_to_file is not None:
|
|
41
|
+
redirect_operator = ">>" if command.append_or_overwrite == "APPEND" else ">"
|
|
42
|
+
command = RunnableCommandString(
|
|
43
|
+
command=f"{command.command} {redirect_operator} {command.output_to_file}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Build the actual command to execute
|
|
47
|
+
cmd = f"sudo {command.command}" if command.sudo else command.command
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
result: InvokeResult | None = run(
|
|
51
|
+
command=cmd,
|
|
52
|
+
env=command.env,
|
|
53
|
+
hide=self._hide,
|
|
54
|
+
warn=True # Don't raise on non-zero exit codes
|
|
55
|
+
)
|
|
56
|
+
mapped_result = self._map_invoke_result(result, command=command)
|
|
57
|
+
|
|
58
|
+
self._logger.debug(
|
|
59
|
+
"Command Executed > '%r', On Local Machine\n\n",
|
|
60
|
+
mapped_result,
|
|
61
|
+
)
|
|
62
|
+
return mapped_result
|
|
63
|
+
except Exception as e:
|
|
64
|
+
self._logger.error(
|
|
65
|
+
"Command didn't reach an exit code. Command: %s", cmd
|
|
66
|
+
)
|
|
67
|
+
return ExecResult(
|
|
68
|
+
stdout="",
|
|
69
|
+
stderr=str(e),
|
|
70
|
+
return_code=1,
|
|
71
|
+
success=False,
|
|
72
|
+
command=command,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def put_file(self, source: Path | str | BytesIO, remote_path: str) -> ExecResult:
|
|
76
|
+
"""
|
|
77
|
+
Copy a file locally from source to destination.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
source: Path to source file or a file-like object as BytesIO.
|
|
81
|
+
remote_path: Local destination path
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
dest_path = Path(remote_path)
|
|
85
|
+
|
|
86
|
+
# Create parent directories if they don't exist
|
|
87
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
|
|
89
|
+
if isinstance(source, (str, Path)):
|
|
90
|
+
# Copy from file path
|
|
91
|
+
source_path = Path(source)
|
|
92
|
+
if not source_path.exists():
|
|
93
|
+
return ExecResult(
|
|
94
|
+
stdout="",
|
|
95
|
+
stderr=f"Source file does not exist: {source}",
|
|
96
|
+
return_code=1,
|
|
97
|
+
success=False,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
import shutil
|
|
101
|
+
shutil.copy2(source_path, dest_path)
|
|
102
|
+
self._logger.debug(
|
|
103
|
+
"File copied locally from '%s' to '%s'",
|
|
104
|
+
source_path,
|
|
105
|
+
dest_path,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
elif isinstance(source, BytesIO):
|
|
109
|
+
# Write from BytesIO object
|
|
110
|
+
with open(dest_path, 'wb') as f:
|
|
111
|
+
f.write(source.getvalue())
|
|
112
|
+
self._logger.debug(
|
|
113
|
+
"Content written to file '%s' from BytesIO object",
|
|
114
|
+
dest_path,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return ExecResult(
|
|
118
|
+
stdout=f"File copied successfully from '{source}' to '{dest_path}'",
|
|
119
|
+
stderr="",
|
|
120
|
+
return_code=0,
|
|
121
|
+
success=True,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
return ExecResult(
|
|
126
|
+
stdout="",
|
|
127
|
+
stderr=str(e),
|
|
128
|
+
return_code=1,
|
|
129
|
+
success=False,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _map_invoke_result(
|
|
133
|
+
self, result: InvokeResult | None, command: RunnableCommandString
|
|
134
|
+
) -> ExecResult:
|
|
135
|
+
if result is None:
|
|
136
|
+
return ExecResult(
|
|
137
|
+
stdout="",
|
|
138
|
+
stderr="InvokeResult was None",
|
|
139
|
+
return_code=1,
|
|
140
|
+
success=False,
|
|
141
|
+
command=command,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return ExecResult(
|
|
145
|
+
stdout=result.stdout or "",
|
|
146
|
+
stderr=result.stderr or "",
|
|
147
|
+
return_code=result.return_code,
|
|
148
|
+
success=result.return_code == 0,
|
|
149
|
+
command=command,
|
|
150
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# from .base.caddy_commands import CaddyCommands
|
|
2
|
+
# from .base.command_base import CommandBase
|
|
3
|
+
# from .base.docker_compose_commands import DockerComposeCommands
|
|
4
|
+
# from .base.git_commands import GitCommands
|
|
5
|
+
# from .base.os_package_manager_commands import OsPackageManagerCommands
|
|
6
|
+
# from .base.podman_commands import PodmanCommands
|
|
7
|
+
|
|
8
|
+
# __all__ = [
|
|
9
|
+
# "CaddyCommands",
|
|
10
|
+
# "CommandBase",
|
|
11
|
+
# "DockerComposeCommands",
|
|
12
|
+
# "PodmanCommands",
|
|
13
|
+
# "GitCommands",
|
|
14
|
+
# "OsPackageManagerCommands",
|
|
15
|
+
# ]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from .archive_commands import ArchiveCommands
|
|
2
|
+
from .caddy_api_curl_commands import CaddyApiCurlCommands
|
|
3
|
+
from .caddy_commands import CaddyCommands
|
|
4
|
+
from .command_base import CommandBase
|
|
5
|
+
from .curl_commands import CurlCommands
|
|
6
|
+
from .dir_commands import DirCommands
|
|
7
|
+
from .docker_compose_commands import DockerComposeCommands
|
|
8
|
+
from .file_commands import FileCommands
|
|
9
|
+
from .git_commands import GitCommands
|
|
10
|
+
from .ip_tables_commands import IpTablesCommands
|
|
11
|
+
from .ip_tables_persistent_commands import IpTablesPersistentCommands
|
|
12
|
+
from .null_command import NullCommand
|
|
13
|
+
from .os_package_manager_commands import OsPackageManagerCommands
|
|
14
|
+
from .podman_commands import PodmanCommands
|
|
15
|
+
from .ssh_agent_commands import SshAgentCommands
|
|
16
|
+
from .ssh_keygen_commands import SshKeygenCommands
|
|
17
|
+
from .systemctl_commands import SystemctlCommands
|
|
18
|
+
from .utility_commands import UtilityCommands
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ArchiveCommands",
|
|
22
|
+
"CaddyApiCurlCommands",
|
|
23
|
+
"CaddyCommands",
|
|
24
|
+
"CommandBase",
|
|
25
|
+
"CurlCommands",
|
|
26
|
+
"DirCommands",
|
|
27
|
+
"DockerComposeCommands",
|
|
28
|
+
"FileCommands",
|
|
29
|
+
"GitCommands",
|
|
30
|
+
"IpTablesCommands",
|
|
31
|
+
"IpTablesPersistentCommands",
|
|
32
|
+
"NullCommand",
|
|
33
|
+
"OsPackageManagerCommands",
|
|
34
|
+
"PodmanCommands",
|
|
35
|
+
"SshAgentCommands",
|
|
36
|
+
"SystemctlCommands",
|
|
37
|
+
"UtilityCommands",
|
|
38
|
+
"SshKeygenCommands",
|
|
39
|
+
]
|