diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index a5c8ebdf..5df55bfe 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -11,7 +11,10 @@
- no redundant code - move repeated logic into helper functions
- use type hints to specify the expected types of function arguments and return values
-- check `ruff.toml` for formatting rules
-- always lint changes using `ruff check`
+- check `pyproject.toml` for formatting rules
+- always lint changes using `uv run ruff check`
- tests should be placed in `tests/` directory, follow the existing structure and code style
-- to run a test always use `bash scripts/run_tests.sh tests/path_to_test.py -k [TEST_NAME]` command
\ No newline at end of file
+- always use `uv` to run all commands in the repo (e.g., `uv run ruff`, `uv run pytest`, etc.)
+- for running tests, export environment variables in the terminal before running the tests: `. ./scripts/export_env.sh`
+
+- additional external context is located in context directory
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 87a0d8f8..65232943 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,3 +125,4 @@ tests/.skale/node_data/node_options.json
tests/.skale/config/nginx.conf.j2
.zed
+uv.lock
\ No newline at end of file
diff --git a/node_cli/cli/fair_boot.py b/node_cli/cli/fair_boot.py
index f5dce9b1..d5ff6039 100644
--- a/node_cli/cli/fair_boot.py
+++ b/node_cli/cli/fair_boot.py
@@ -88,6 +88,6 @@ def signature_boot(validator_id):
@streamed_cmd
def update_node(env_file, pull_config_for_schain):
update(
- env_filepath=env_file,
+ config_file=env_file,
pull_config_for_schain=pull_config_for_schain,
)
diff --git a/node_cli/cli/fair_node.py b/node_cli/cli/fair_node.py
index 56ab9d75..efdf3149 100644
--- a/node_cli/cli/fair_node.py
+++ b/node_cli/cli/fair_node.py
@@ -57,10 +57,10 @@ def fair_node_info(format):
@node.command('init', help='Initialize regular Fair node')
-@click.argument('env_filepath')
+@click.argument('config_file')
@streamed_cmd
-def init_node(env_filepath: str):
- init_fair(node_mode=NodeMode.ACTIVE, env_filepath=env_filepath)
+def init_node(config_file: str):
+ init_fair(node_mode=NodeMode.ACTIVE, config_file=config_file)
@node.command('register', help=TEXTS['fair']['node']['register']['help'])
@@ -70,7 +70,7 @@ def register(ip: str) -> None:
@node.command('update', help='Update Fair node')
-@click.argument('env_filepath')
+@click.argument('config_file')
@click.option(
'--yes',
is_flag=True,
@@ -88,10 +88,10 @@ def register(ip: str) -> None:
is_flag=True,
)
@streamed_cmd
-def update_node(env_filepath: str, pull_config_for_schain, force_skaled_start: bool):
+def update_node(config_file: str, pull_config_for_schain, force_skaled_start: bool):
update_fair(
node_mode=NodeMode.ACTIVE,
- env_filepath=env_filepath,
+ config_file=config_file,
pull_config_for_schain=pull_config_for_schain,
force_skaled_start=force_skaled_start,
)
@@ -106,7 +106,7 @@ def backup_node(backup_folder_path):
@node.command('restore', help='Restore Fair node from a backup file.')
@click.argument('backup_path')
-@click.argument('env_file')
+@click.argument('config_file')
@click.option(
'--config-only',
help='Only restore configuration files in .skale and artifacts',
@@ -114,12 +114,12 @@ def backup_node(backup_folder_path):
hidden=True,
)
@streamed_cmd
-def restore_node(backup_path, env_file, config_only):
- restore_fair(backup_path, env_file, config_only)
+def restore_node(backup_path, config_file, config_only):
+ restore_fair(backup_path, config_file, config_only)
@node.command('migrate', help='Switch from boot to regular Fair node.')
-@click.argument('env_filepath')
+@click.argument('config_file')
@click.option(
'--yes',
is_flag=True,
@@ -128,8 +128,8 @@ def restore_node(backup_path, env_file, config_only):
prompt='Are you sure you want to migrate to regular Fair node? The action cannot be undone',
)
@streamed_cmd
-def migrate_node(env_filepath: str) -> None:
- migrate_from_boot(env_filepath=env_filepath)
+def migrate_node(config_file: str) -> None:
+ migrate_from_boot(config_file=config_file)
@node.command('repair', help='Toggle fair chain repair mode')
@@ -221,7 +221,7 @@ def turn_off_node() -> None:
expose_value=False,
prompt='Are you sure you want to turn on the node?',
)
-@click.argument('env_filepath')
+@click.argument('config_file')
@streamed_cmd
-def turn_on_node(env_filepath: str) -> None:
- turn_on_fair(env_file=env_filepath, node_type=TYPE)
+def turn_on_node(config_file: str) -> None:
+ turn_on_fair(env_file=config_file, node_type=TYPE)
diff --git a/node_cli/cli/node.py b/node_cli/cli/node.py
index 55304356..b34fd155 100644
--- a/node_cli/cli/node.py
+++ b/node_cli/cli/node.py
@@ -17,8 +17,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from typing import get_args
+
import click
+from skale_core.types import EnvType
from node_cli.cli.info import TYPE
from node_cli.core.node import (
cleanup as cleanup_skale,
@@ -38,7 +41,6 @@
run_checks,
)
from node_cli.configs import DEFAULT_NODE_BASE_PORT
-from node_cli.configs.user import ALLOWED_ENV_TYPES
from node_cli.core.node_options import upsert_node_mode
from node_cli.utils.decorators import check_inited
from node_cli.utils.helper import abort_if_false, streamed_cmd, IP_TYPE
@@ -85,10 +87,10 @@ def register_node(name, ip, port, domain):
@node.command('init', help='Initialize SKALE node')
-@click.argument('env_file')
+@click.argument('config_file')
@streamed_cmd
-def init_node(env_file):
- init(env_filepath=env_file, node_type=TYPE)
+def init_node(config_file):
+ init(config_file=config_file, node_type=TYPE)
@node.command('update', help='Update node from .env file')
@@ -101,12 +103,12 @@ def init_node(env_file):
)
@click.option('--pull-config', 'pull_config_for_schain', hidden=True, type=str)
@click.option('--unsafe', 'unsafe_ok', help='Allow unsafe update', hidden=True, is_flag=True)
-@click.argument('env_file')
+@click.argument('config_file')
@streamed_cmd
-def update_node(env_file, pull_config_for_schain, unsafe_ok):
+def update_node(config_file, pull_config_for_schain, unsafe_ok):
update(
node_mode=NodeMode.ACTIVE,
- env_filepath=env_file,
+ config_file=config_file,
pull_config_for_schain=pull_config_for_schain,
node_type=TYPE,
unsafe_ok=unsafe_ok,
@@ -143,7 +145,7 @@ def backup_node(backup_folder_path):
def restore_node(backup_path, env_file, no_snapshot, config_only):
restore(
backup_path=backup_path,
- env_filepath=env_file,
+ config_file=env_file,
no_snapshot=no_snapshot,
config_only=config_only,
node_type=TYPE,
@@ -227,7 +229,7 @@ def _set_domain_name(domain):
@click.option(
'--network',
'-n',
- type=click.Choice(ALLOWED_ENV_TYPES),
+ type=click.Choice(get_args(EnvType)),
default='mainnet',
help='Network to check',
)
diff --git a/node_cli/cli/passive_fair_node.py b/node_cli/cli/passive_fair_node.py
index 83eb1bd1..7bac3581 100644
--- a/node_cli/cli/passive_fair_node.py
+++ b/node_cli/cli/passive_fair_node.py
@@ -49,7 +49,7 @@ def passive_node():
@passive_node.command('init', help='Initialize a passive Fair node')
-@click.argument('env_filepath')
+@click.argument('config_file')
@click.option('--id', required=True, type=int, help=TEXTS['fair']['node']['setup']['id'])
@click.option('--indexer', help=TEXTS['passive_node']['init']['indexer'], is_flag=True)
@click.option('--archive', help=TEXTS['passive_node']['init']['archive'], is_flag=True)
@@ -61,7 +61,7 @@ def passive_node():
)
@streamed_cmd
def init_passive_node(
- env_filepath: str, id: int, indexer: bool, archive: bool, snapshot: str | None
+ config_file: str, id: int, indexer: bool, archive: bool, snapshot: str | None
):
if indexer and archive:
error_exit('Cannot use both --indexer and --archive options')
@@ -69,7 +69,7 @@ def init_passive_node(
error_exit('Cannot use any for indexer/archive node')
init_fair(
node_mode=NodeMode.PASSIVE,
- env_filepath=env_filepath,
+ config_file=config_file,
node_id=id,
indexer=indexer,
archive=archive,
@@ -78,7 +78,7 @@ def init_passive_node(
@passive_node.command('update', help='Update Fair node')
-@click.argument('env_filepath')
+@click.argument('config_file')
@click.option(
'--yes',
is_flag=True,
@@ -96,10 +96,10 @@ def init_passive_node(
is_flag=True,
)
@streamed_cmd
-def update_node(env_filepath: str, pull_config_for_schain, force_skaled_start: bool):
+def update_node(config_file: str, pull_config_for_schain, force_skaled_start: bool):
update_fair(
node_mode=NodeMode.PASSIVE,
- env_filepath=env_filepath,
+ config_file=config_file,
pull_config_for_schain=pull_config_for_schain,
force_skaled_start=force_skaled_start,
)
@@ -146,7 +146,7 @@ def turn_off_node() -> None:
expose_value=False,
prompt='Are you sure you want to turn on the node?',
)
-@click.argument('env_filepath')
+@click.argument('config_file')
@streamed_cmd
-def turn_on_node(env_filepath: str) -> None:
- turn_on_fair(env_file=env_filepath, node_type=TYPE)
+def turn_on_node(config_file: str) -> None:
+ turn_on_fair(env_file=config_file, node_type=TYPE)
diff --git a/node_cli/cli/schains.py b/node_cli/cli/schains.py
index 803ea754..6bb1fce2 100644
--- a/node_cli/cli/schains.py
+++ b/node_cli/cli/schains.py
@@ -21,6 +21,8 @@
import click
+from skale_core.settings import get_settings
+
from node_cli.utils.helper import abort_if_false, URL_TYPE
from node_cli.core.schains import (
describe,
@@ -104,8 +106,12 @@ def info_(schain_name: str, json_format: bool) -> None:
@click.argument('schain_name')
@click.argument('snapshot_path')
@click.option('--schain-type', default='medium')
-@click.option('--env-type', default=None)
-def restore(
- schain_name: str, snapshot_path: str, schain_type: str, env_type: Optional[str]
-) -> None:
- restore_schain_from_snapshot(schain_name, snapshot_path, node_type=TYPE)
+def restore(schain_name: str, snapshot_path: str, schain_type: str) -> None:
+ settings = get_settings()
+ restore_schain_from_snapshot(
+ schain_name,
+ snapshot_path,
+ node_type=TYPE,
+ env_type=settings.env_type,
+ schain_type=schain_type,
+ )
diff --git a/node_cli/configs/__init__.py b/node_cli/configs/__init__.py
index 35d776d8..7ed6afee 100644
--- a/node_cli/configs/__init__.py
+++ b/node_cli/configs/__init__.py
@@ -19,6 +19,7 @@
import os
import sys
+from pathlib import Path
from node_cli.utils.global_config import read_g_config
@@ -43,17 +44,19 @@
NODE_DATA_PATH = os.path.join(SKALE_DIR, 'node_data')
SCHAIN_NODE_DATA_PATH = os.path.join(NODE_DATA_PATH, 'schains')
NODE_CLI_STATUS_FILENAME = 'node_cli.status'
+
+SETTINGS_DIR = Path(NODE_DATA_PATH) / 'settings'
+NODE_SETTINGS_PATH = SETTINGS_DIR / 'node.toml'
+INTERNAL_SETTINGS_PATH = SETTINGS_DIR / 'internal.toml'
+
NODE_CONFIG_PATH = os.path.join(NODE_DATA_PATH, 'node_config.json')
CONTAINER_CONFIG_PATH = os.path.join(SKALE_DIR, 'config')
CONTAINER_CONFIG_TMP_PATH = os.path.join(SKALE_TMP_DIR, 'config')
CONTRACTS_PATH = os.path.join(SKALE_DIR, 'contracts_info')
REPORTS_PATH = os.path.join(SKALE_DIR, 'reports')
BACKUP_CONTRACTS_PATH = os.path.join(SKALE_DIR, '.old_contracts_info')
-INIT_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env')
SKALE_RUN_DIR = '/var/run/skale'
-SGX_CERTIFICATES_DIR_NAME = 'sgx_certs'
-
COMPOSE_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'docker-compose.yml')
FAIR_COMPOSE_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'docker-compose-fair.yml')
STATIC_PARAMS_FILEPATH = os.path.join(CONTAINER_CONFIG_PATH, 'static_params.yaml')
@@ -73,9 +76,6 @@
SGX_CERTS_PATH = os.path.join(NODE_DATA_PATH, 'sgx_certs')
SCHAINS_DATA_PATH = os.path.join(NODE_DATA_PATH, 'schains')
-CURRENT_FILE_LOCATION = os.path.dirname(os.path.realpath(__file__))
-DOTENV_FILEPATH = os.path.join(os.path.dirname(CURRENT_FILE_LOCATION), '.env')
-
SRC_FILEBEAT_CONFIG_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'filebeat.yml')
FILEBEAT_CONFIG_PATH = os.path.join(NODE_DATA_PATH, 'filebeat.yml')
@@ -95,9 +95,6 @@
IPTABLES_RULES_STATE_FILEPATH = os.path.join(IPTABLES_DIR, 'rules.v4')
DEFAULT_SSH_PORT = 22
-FLASK_SECRET_KEY_FILENAME = 'flask_db_key.txt'
-FLASK_SECRET_KEY_FILE = os.path.join(NODE_DATA_PATH, FLASK_SECRET_KEY_FILENAME)
-
DOCKER_CONFIG_FILEPATH = '/etc/docker/daemon.json'
HIDE_STREAM_LOG = os.getenv('HIDE_STREAM_LOG')
diff --git a/node_cli/configs/alias_address_validation.py b/node_cli/configs/alias_address_validation.py
deleted file mode 100644
index ffdbb2ee..00000000
--- a/node_cli/configs/alias_address_validation.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of node-cli
-#
-# Copyright (C) 2025-Present SKALE Labs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from enum import Enum
-from typing import Dict, Optional
-
-import requests
-
-from node_cli.utils.helper import error_exit, is_contract_address
-
-
-METADATA_URL: str = (
- 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/'
- 'refs/heads/deployments/metadata.json'
-)
-
-
-class ContractType(Enum):
- """Contract types supported by the system using skale-contracts library."""
-
- IMA = 'mainnet-ima'
- MANAGER = 'skale-manager'
-
-
-def validate_alias_or_address(
- alias_or_address: str, contract_type: ContractType, endpoint: str
-) -> None:
- if is_contract_address(alias_or_address):
- validate_contract_address(alias_or_address, endpoint)
- else:
- validate_contract_alias(alias_or_address, contract_type, endpoint)
-
-
-def validate_contract_address(contract_address: str, endpoint: str) -> None:
- try:
- response = requests.post(
- endpoint,
- json={
- 'jsonrpc': '2.0',
- 'method': 'eth_getCode',
- 'params': [contract_address, 'latest'],
- 'id': 1,
- },
- )
- if response.status_code != 200:
- error_exit(f'Failed to verify contract at address {contract_address}')
- result = response.json().get('result')
- if not result or result in ['0x', '0x0']:
- error_exit(f'No contract code found at address {contract_address}')
- except requests.RequestException as e:
- error_exit(f'Failed to validate contract address: {str(e)}')
-
-
-def get_deployment_url(alias: str, contract_type: ContractType, network_path: str) -> str:
- return (
- f'https://raw.githubusercontent.com/skalenetwork/skale-contracts/'
- f'refs/heads/deployments/{network_path}/{contract_type.value}/{alias}.json'
- )
-
-
-def validate_contract_alias(alias: str, contract_type: ContractType, endpoint: str) -> None:
- try:
- chain_id = get_chain_id(endpoint)
- metadata = get_network_metadata()
- networks = metadata.get('networks', [])
- network_path: Optional[str] = None
- for net in networks:
- if net.get('chainId') == chain_id:
- network_path = net.get('path')
- break
- if not network_path:
- error_exit(f'Network with chain ID {chain_id} not found in metadata')
- if not isinstance(network_path, str):
- error_exit(f'Invalid network path type: {network_path}')
- deployment_url = get_deployment_url(alias, contract_type, network_path)
- if requests.get(deployment_url).status_code != 200:
- error_exit(f"Contract alias '{alias}' not found for {contract_type.value}")
- except requests.RequestException as e:
- error_exit(f"Failed to validate contract alias '{alias}': {str(e)}")
-
-
-def get_chain_id(endpoint: str) -> int:
- try:
- response = requests.post(
- endpoint,
- json={'jsonrpc': '2.0', 'method': 'eth_chainId', 'params': [], 'id': 1},
- )
- if response.status_code != 200:
- error_exit('Failed to get chain ID from endpoint')
- return int(response.json()['result'], 16)
- except requests.RequestException as e:
- error_exit(f'Failed to get chain ID: {str(e)}')
-
-
-def get_network_metadata() -> Dict:
- try:
- response = requests.get(METADATA_URL)
- if response.status_code != 200:
- error_exit('Failed to fetch networks metadata')
- return response.json()
- except requests.RequestException as e:
- error_exit(f'Failed to fetch networks metadata: {str(e)}')
diff --git a/node_cli/configs/user.py b/node_cli/configs/user.py
deleted file mode 100644
index a4f52c2c..00000000
--- a/node_cli/configs/user.py
+++ /dev/null
@@ -1,216 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of node-cli
-#
-# Copyright (C) 2019-Present SKALE Labs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import inspect
-import os
-from abc import ABC
-from dataclasses import dataclass
-from typing import Dict, NamedTuple
-
-from dotenv.main import DotEnv
-
-from node_cli.configs import CONTAINER_CONFIG_PATH, SKALE_DIR
-from node_cli.configs.alias_address_validation import ContractType, validate_alias_or_address
-from node_cli.utils.helper import error_exit
-from node_cli.utils.node_type import NodeMode, NodeType
-from node_cli.core.node_options import (
- active_fair,
- active_skale,
- passive_skale,
- passive_fair,
-)
-
-SKALE_DIR_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env')
-CONFIGS_ENV_FILEPATH = os.path.join(CONTAINER_CONFIG_PATH, '.env')
-
-ALLOWED_ENV_TYPES = ['mainnet', 'testnet', 'qanet', 'devnet']
-
-
-class ValidationResult(NamedTuple):
- result: bool
- missing: set
- extra: set
-
-
-@dataclass(kw_only=True)
-class BaseUserConfig(ABC):
- node_version: str
- env_type: str
- filebeat_host: str
- block_device: str
-
- container_configs_dir: str = ''
- skip_docker_config: str = ''
- skip_docker_cleanup: str = ''
-
- def to_env(self) -> Dict[str, str]:
- result = {}
- for field_name, field_value in self.__dict__.items():
- upper_key = field_name.upper()
- result[upper_key] = str(field_value) if field_value is not None else ''
- return result
-
- @classmethod
- def validate_params(cls, params: Dict) -> ValidationResult:
- parameters = inspect.signature(cls.__init__).parameters
- missing = []
- keys = params.keys()
- expected_keys = {
- name.upper()
- for name, value in parameters.items()
- if name != 'self' and value.default == inspect._empty
- }
- optional_keys = {
- name.upper()
- for name, value in parameters.items()
- if name != 'self' and value.default != inspect._empty
- }
- missing = expected_keys - keys
- extra = keys - expected_keys - optional_keys
- return ValidationResult(missing == set() and extra == set(), missing, extra)
-
-
-@dataclass
-class FairUserConfig(BaseUserConfig):
- fair_contracts: str
- boot_endpoint: str
- sgx_server_url: str
- enforce_btrfs: str = ''
- telegraf: str = ''
- influx_url: str = ''
-
-
-@dataclass
-class PassiveFairUserConfig(BaseUserConfig):
- fair_contracts: str
- boot_endpoint: str
- enforce_btrfs: str = ''
-
-
-@dataclass
-class FairBootUserConfig(BaseUserConfig):
- endpoint: str
- manager_contracts: str
- ima_contracts: str
- sgx_server_url: str
- enforce_btrfs: str = ''
-
-
-@dataclass
-class SkaleUserConfig(BaseUserConfig):
- endpoint: str
- manager_contracts: str
- ima_contracts: str
- docker_lvmpy_version: str
- sgx_server_url: str
- monitoring_containers: str = ''
- telegraf: str = ''
- influx_url: str = ''
- tg_api_key: str = ''
- tg_chat_id: str = ''
- disable_dry_run: str = ''
- default_gas_limit: str = ''
- default_gas_price_wei: str = ''
- bite: str = ''
-
-
-@dataclass
-class PassiveSkaleUserConfig(BaseUserConfig):
- endpoint: str
- manager_contracts: str
- schain_name: str = ''
- ima_contracts: str = ''
- enforce_btrfs: str = ''
- bite: str = ''
-
-
-def get_validated_user_config(
- node_type: NodeType,
- node_mode: NodeMode,
- env_filepath: str = SKALE_DIR_ENV_FILEPATH,
- is_fair_boot: bool = False,
- skip_user_conf_validation: bool = False,
-) -> BaseUserConfig:
- params = parse_env_file(env_filepath)
- user_config_class = get_user_config_class(
- node_type=node_type,
- node_mode=node_mode,
- is_fair_boot=is_fair_boot,
- )
- _, missing_params, extra_params = user_config_class.validate_params(params)
-
- if len(missing_params) > 0:
- error_exit(f'Missing required parameters: {missing_params}')
-
- if len(extra_params) > 0:
- error_exit(f'Extra parameters: {extra_params}')
-
- params = to_lower_keys(params)
- user_config = user_config_class(**params)
- if not skip_user_conf_validation:
- validate_user_config(user_config)
-
- return user_config
-
-
-def validate_user_config(user_config: BaseUserConfig) -> None:
- validate_env_type(env_type=user_config.env_type)
-
- if not isinstance(user_config, FairUserConfig) and not isinstance(
- user_config, PassiveFairUserConfig
- ):
- validate_alias_or_address(
- user_config.manager_contracts, ContractType.MANAGER, user_config.endpoint
- )
-
- if isinstance(user_config, (SkaleUserConfig, FairBootUserConfig)):
- validate_alias_or_address(user_config.ima_contracts, ContractType.IMA, user_config.endpoint)
-
-
-def to_lower_keys(params: Dict[str, str]) -> Dict[str, str]:
- return {key.lower(): value for key, value in params.items()}
-
-
-def parse_env_file(env_filepath: str) -> Dict:
- if not os.path.isfile(env_filepath):
- error_exit(f'Failed to load environment from {env_filepath}')
- return DotEnv(env_filepath).dict()
-
-
-def get_user_config_class(
- node_type: NodeType,
- node_mode: NodeMode,
- is_fair_boot: bool,
-) -> type[BaseUserConfig]:
- if node_type == NodeType.FAIR and is_fair_boot:
- user_config_class = FairBootUserConfig
- elif passive_fair(node_type, node_mode):
- user_config_class = PassiveFairUserConfig
- elif active_fair(node_type, node_mode):
- user_config_class = FairUserConfig
- elif passive_skale(node_type, node_mode):
- user_config_class = PassiveSkaleUserConfig
- elif active_skale(node_type, node_mode):
- user_config_class = SkaleUserConfig
- return user_config_class
-
-
-def validate_env_type(env_type: str) -> None:
- if env_type not in ALLOWED_ENV_TYPES:
- error_exit(f'Allowed ENV_TYPE values are {ALLOWED_ENV_TYPES}. Actual: "{env_type}"')
diff --git a/node_cli/core/checks.py b/node_cli/core/checks.py
index a4c439c9..0424577f 100644
--- a/node_cli/core/checks.py
+++ b/node_cli/core/checks.py
@@ -48,6 +48,8 @@
from debian import debian_support
from packaging.version import parse as version_parse
+from skale_core.types import EnvType
+
from node_cli.configs import (
CHECK_REPORT_PATH,
CONTAINER_CONFIG_PATH,
@@ -471,7 +473,7 @@ def run_checks(
disk: str,
node_type: NodeType,
node_mode: NodeMode,
- env_type: str = 'mainnet',
+ env_type: EnvType = 'mainnet',
config_path: str = CONTAINER_CONFIG_PATH,
check_type: CheckType = CheckType.ALL,
) -> ResultList:
diff --git a/node_cli/core/host.py b/node_cli/core/host.py
index da4be640..a040b8c4 100644
--- a/node_cli/core/host.py
+++ b/node_cli/core/host.py
@@ -19,9 +19,11 @@
import logging
import os
-from shutil import copyfile, chown
+from shutil import chown
from urllib.parse import urlparse
+from skale_core.types import EnvType
+
from node_cli.core.resources import update_resource_allocation
from node_cli.utils.helper import error_exit
@@ -39,6 +41,7 @@
SGX_CERTS_PATH,
REPORTS_PATH,
REDIS_DATA_PATH,
+ SETTINGS_DIR,
SCHAINS_DATA_PATH,
LOG_PATH,
REMOVED_CONTAINERS_FOLDER_PATH,
@@ -50,7 +53,6 @@
NGINX_CONFIG_FILEPATH,
)
from node_cli.configs.cli_logger import LOG_DATA_PATH
-from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH
from node_cli.core.nftables import NFTablesManager
from node_cli.utils.helper import safe_mkdir
@@ -73,29 +75,11 @@ def fix_url(url):
return False
-def get_flask_secret_key() -> str:
- secret_key_filepath = os.path.join(NODE_DATA_PATH, 'flask_db_key.txt')
-
- if not os.path.exists(secret_key_filepath):
- error_exit(f'Flask secret key file not found at {secret_key_filepath}')
-
- try:
- with open(secret_key_filepath, 'r') as key_file:
- secret_key = key_file.read().strip()
- return secret_key
- except (IOError, OSError) as e:
- error_exit(f'Failed to read Flask secret key: {e}')
-
-
-def prepare_host(env_filepath: str, env_type: str, allocation: bool = False) -> None:
- if not env_filepath or not env_type:
- error_exit('Missing required parameters for host initialization')
-
+def prepare_host(env_type: EnvType, allocation: bool = False) -> None:
try:
logger.info('Preparing host started')
make_dirs()
chown(REDIS_DATA_PATH, user=999, group=1000)
- save_env_params(env_filepath)
if allocation:
update_resource_allocation(env_type)
@@ -121,6 +105,7 @@ def make_dirs():
LOG_PATH,
REPORTS_PATH,
REDIS_DATA_PATH,
+ SETTINGS_DIR,
SKALE_RUN_DIR,
SKALE_STATE_DIR,
SKALE_TMP_DIR,
@@ -128,16 +113,6 @@ def make_dirs():
safe_mkdir(dir_path)
-def save_env_params(env_filepath: str) -> None:
- copyfile(env_filepath, SKALE_DIR_ENV_FILEPATH)
-
-
-def link_env_file():
- if not (os.path.islink(CONFIGS_ENV_FILEPATH) or os.path.isfile(CONFIGS_ENV_FILEPATH)):
- logger.info('Creating symlink %s → %s', SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH)
- os.symlink(SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH)
-
-
def init_logs_dir():
safe_mkdir(LOG_DATA_PATH)
safe_mkdir(REMOVED_CONTAINERS_FOLDER_PATH)
diff --git a/node_cli/core/node.py b/node_cli/core/node.py
index 2128d43f..7c9e2409 100644
--- a/node_cli/core/node.py
+++ b/node_cli/core/node.py
@@ -33,7 +33,6 @@
BACKUP_ARCHIVE_NAME,
CONTAINER_CONFIG_PATH,
FILESTORAGE_MAPPING,
- INIT_ENV_FILEPATH,
LOG_PATH,
RESTORE_SLEEP_TIMEOUT,
SCHAINS_MNT_DIR_REGULAR,
@@ -43,9 +42,8 @@
TM_INIT_TIMEOUT,
)
from node_cli.configs.cli_logger import LOG_DATA_PATH as CLI_LOG_DATA_PATH
-from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH, get_validated_user_config
from node_cli.core.checks import run_checks as run_host_checks
-from node_cli.core.host import get_flask_secret_key, is_node_inited, save_env_params
+from node_cli.core.host import is_node_inited
from node_cli.core.resources import update_resource_allocation
from node_cli.core.node_options import (
active_fair,
@@ -89,6 +87,8 @@
print_node_cmd_error,
print_node_info,
)
+from node_cli.utils.settings import validate_and_save_node_settings
+from skale_core.settings import get_settings
from node_cli.utils.texts import safe_load_texts
logger = logging.getLogger(__name__)
@@ -152,51 +152,64 @@ def register_node(name, p2p_ip, public_ip, port, domain_name):
@check_not_inited
-def init(env_filepath: str, node_type: NodeType) -> None:
+def init(config_file: str, node_type: NodeType) -> None:
node_mode = NodeMode.ACTIVE
- env = compose_node_env(env_filepath=env_filepath, node_type=node_type, node_mode=node_mode)
+ settings = validate_and_save_node_settings(config_file, node_type, node_mode)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
- init_op(env_filepath=env_filepath, env=env, node_mode=node_mode)
+ init_op(settings=settings, compose_env=compose_env, node_mode=node_mode)
logger.info('Waiting for containers initialization')
time.sleep(TM_INIT_TIMEOUT)
if not is_base_containers_alive(node_type=node_type, node_mode=node_mode):
error_exit('Containers are not running', exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR)
logger.info('Generating resource allocation file ...')
- update_resource_allocation(env['ENV_TYPE'])
+ update_resource_allocation(settings.env_type)
logger.info('Init procedure finished')
@check_not_inited
-def restore(backup_path, env_filepath, node_type: NodeType, no_snapshot=False, config_only=False):
+def restore(
+ backup_path: str,
+ config_file: str,
+ node_type: NodeType,
+ no_snapshot: bool = False,
+ config_only: bool = False,
+):
node_mode = NodeMode.ACTIVE
- env = compose_node_env(env_filepath=env_filepath, node_type=node_type, node_mode=node_mode)
- if env is None:
- return
- save_env_params(env_filepath)
- env['SKALE_DIR'] = SKALE_DIR
-
- if not no_snapshot:
- logger.info('Adding BACKUP_RUN to env ...')
- env['BACKUP_RUN'] = 'True' # should be str
+ settings = validate_and_save_node_settings(config_file, node_type, node_mode)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
- restored_ok = restore_op(env, backup_path, node_type=node_type, config_only=config_only)
+ restored_ok = restore_op(
+ settings=settings,
+ compose_env=compose_env,
+ backup_path=backup_path,
+ node_type=node_type,
+ config_only=config_only,
+ backup_run=not no_snapshot,
+ )
if not restored_ok:
error_exit('Restore operation failed', exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR)
time.sleep(RESTORE_SLEEP_TIMEOUT)
logger.info('Generating resource allocation file ...')
- update_resource_allocation(env['ENV_TYPE'])
+ update_resource_allocation(settings.env_type)
print('Node is restored from backup')
@check_not_inited
def init_passive(
- env_filepath: str, indexer: bool, archive: bool, snapshot: bool, snapshot_from: Optional[str]
+ config_file: str, indexer: bool, archive: bool, snapshot: bool, snapshot_from: Optional[str]
) -> None:
node_mode = NodeMode.PASSIVE
- env = compose_node_env(env_filepath, node_type=NodeType.SKALE, node_mode=node_mode)
- if env is None:
- return
- init_passive_op(env_filepath, env, indexer, archive, snapshot, snapshot_from)
+ settings = validate_and_save_node_settings(config_file, NodeType.SKALE, node_mode)
+ compose_env = compose_node_env(node_type=NodeType.SKALE, node_mode=node_mode)
+ init_passive_op(
+ settings=settings,
+ compose_env=compose_env,
+ indexer=indexer,
+ archive=archive,
+ snapshot=snapshot,
+ snapshot_from=snapshot_from,
+ )
logger.info('Waiting for containers initialization')
time.sleep(TM_INIT_TIMEOUT)
if not is_base_containers_alive(node_type=NodeType.SKALE, node_mode=node_mode):
@@ -206,13 +219,14 @@ def init_passive(
@check_inited
@check_user
-def update_passive(env_filepath: str) -> None:
+def update_passive(config_file: str) -> None:
logger.info('Node update started')
prev_version = CliMetaManager().get_meta_info().version
if (__version__ == 'test' or __version__.startswith('2.6')) and prev_version == '2.5.0':
migrate_2_6()
- env = compose_node_env(env_filepath, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
- update_ok = update_passive_op(env_filepath, env)
+ settings = validate_and_save_node_settings(config_file, NodeType.SKALE, NodeMode.PASSIVE)
+ compose_env = compose_node_env(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
+ update_ok = update_passive_op(settings=settings, compose_env=compose_env)
if update_ok:
logger.info('Waiting for containers initialization')
time.sleep(TM_INIT_TIMEOUT)
@@ -227,76 +241,31 @@ def update_passive(env_filepath: str) -> None:
@check_user
def cleanup(node_mode: NodeMode, prune: bool = False) -> None:
node_mode = upsert_node_mode(node_mode=node_mode)
- env = compose_node_env(
- SKALE_DIR_ENV_FILEPATH,
- save=False,
- node_type=NodeType.SKALE,
- node_mode=node_mode,
- skip_user_conf_validation=True,
- )
- cleanup_skale_op(node_mode=node_mode, env=env, prune=prune)
+ env = compose_node_env(NodeType.SKALE, node_mode)
+ cleanup_skale_op(node_mode=node_mode, compose_env=env, prune=prune)
logger.info('SKALE node was cleaned up, all containers and data removed')
-def compose_node_env(
- env_filepath: str,
- node_type: NodeType,
- node_mode: NodeMode,
- inited_node: bool = False,
- sync_schains: Optional[bool] = None,
- pull_config_for_schain: Optional[str] = None,
- save: bool = True,
- is_fair_boot: bool = False,
- skip_user_conf_validation: bool = False,
-) -> dict[str, str]:
- if env_filepath is not None:
- user_config = get_validated_user_config(
- node_type=node_type,
- node_mode=node_mode,
- env_filepath=env_filepath,
- is_fair_boot=is_fair_boot,
- skip_user_conf_validation=skip_user_conf_validation,
- )
- if save:
- save_env_params(env_filepath)
- else:
- user_config = get_validated_user_config(
- node_type=node_type,
- node_mode=node_mode,
- env_filepath=INIT_ENV_FILEPATH,
- is_fair_boot=is_fair_boot,
- skip_user_conf_validation=skip_user_conf_validation,
- )
-
+def compose_node_env(node_type: NodeType, node_mode: NodeMode) -> dict[str, str]:
+ st = get_settings()
if node_mode == NodeMode.PASSIVE or node_type == NodeType.FAIR:
mnt_dir = SCHAINS_MNT_DIR_SINGLE_CHAIN
else:
mnt_dir = SCHAINS_MNT_DIR_REGULAR
-
env = {
'SKALE_DIR': SKALE_DIR,
'SCHAINS_MNT_DIR': mnt_dir,
'FILESTORAGE_MAPPING': FILESTORAGE_MAPPING,
'SKALE_LIB_PATH': SKALE_STATE_DIR,
- **user_config.to_env(),
+ 'FILEBEAT_HOST': st.filebeat_host,
}
-
- if inited_node and not node_mode == NodeMode.PASSIVE:
- env['FLASK_SECRET_KEY'] = get_flask_secret_key()
-
- if sync_schains and not node_mode == NodeMode.PASSIVE:
- env['BACKUP_RUN'] = 'True'
-
- if pull_config_for_schain:
- env['PULL_CONFIG_FOR_SCHAIN'] = pull_config_for_schain
-
return {k: v for k, v in env.items() if v != ''}
@check_inited
@check_user
def update(
- env_filepath: str,
+ config_file: str,
pull_config_for_schain: Optional[str],
node_type: NodeType,
node_mode: NodeMode,
@@ -312,15 +281,9 @@ def update(
if (__version__ == 'test' or __version__.startswith('2.6')) and prev_version == '2.5.0':
migrate_2_6()
logger.info('Node update started')
- env = compose_node_env(
- env_filepath,
- inited_node=True,
- sync_schains=False,
- pull_config_for_schain=pull_config_for_schain,
- node_type=node_type,
- node_mode=node_mode,
- )
- update_ok = update_op(env_filepath, env, node_mode=node_mode)
+ settings = validate_and_save_node_settings(config_file, node_type, node_mode)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
+ update_ok = update_op(settings=settings, compose_env=compose_env, node_mode=node_mode)
if update_ok:
logger.info('Waiting for containers initialization')
time.sleep(TM_INIT_TIMEOUT)
@@ -433,24 +396,24 @@ def turn_off(node_type: NodeType, maintenance_on: bool = False, unsafe_ok: bool
error_exit(error_msg, exit_code=CLIExitCodes.UNSAFE_UPDATE)
if maintenance_on:
set_maintenance_mode_on()
- env = compose_node_env(
- SKALE_DIR_ENV_FILEPATH, save=False, node_type=node_type, node_mode=node_mode
- )
- turn_off_op(node_type=node_type, node_mode=node_mode, env=env)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
+ turn_off_op(compose_env=compose_env, node_type=node_type, node_mode=node_mode)
@check_inited
@check_user
-def turn_on(maintenance_off, sync_schains, env_file, node_type: NodeType) -> None:
+def turn_on(maintenance_off: bool, sync_schains: bool, env_file: str, node_type: NodeType) -> None:
node_mode = upsert_node_mode()
- env = compose_node_env(
- env_file,
- inited_node=True,
- sync_schains=sync_schains,
+ settings = validate_and_save_node_settings(env_file, node_type, node_mode)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
+ backup_run = sync_schains and node_mode != NodeMode.PASSIVE
+ turn_on_op(
+ settings=settings,
+ compose_env=compose_env,
node_type=node_type,
node_mode=node_mode,
+ backup_run=backup_run,
)
- turn_on_op(env=env, node_type=node_type, node_mode=node_mode)
logger.info('Waiting for containers initialization')
time.sleep(TM_INIT_TIMEOUT)
if not is_base_containers_alive(node_type=node_type, node_mode=node_mode):
@@ -545,8 +508,8 @@ def run_checks(
return
if disk is None:
- env_config = get_validated_user_config(node_type=node_type, node_mode=node_mode)
- disk = env_config.block_device
+ settings = get_settings()
+ disk = settings.block_device
failed_checks = run_host_checks(disk, node_type, node_mode, network, container_config_path)
if not failed_checks:
print('Requirements checking successfully finished!')
diff --git a/node_cli/core/resources.py b/node_cli/core/resources.py
index 430eee86..34f4cac5 100644
--- a/node_cli/core/resources.py
+++ b/node_cli/core/resources.py
@@ -24,7 +24,9 @@
import psutil
-from node_cli.configs.user import get_validated_user_config
+from skale_core.types import EnvType
+
+from node_cli.utils.settings import validate_and_save_node_settings
from node_cli.utils.docker_utils import ensure_volume
from node_cli.utils.schain_types import SchainTypes
from node_cli.utils.helper import write_json, read_json, run_cmd, safe_load_yml
@@ -73,7 +75,7 @@ def get_resource_allocation_info():
return None
-def compose_resource_allocation_config(env_type: str, params_by_env_type: Dict = None) -> Dict:
+def compose_resource_allocation_config(env_type: EnvType, params_by_env_type: Dict = None) -> Dict:
params_by_env_type = params_by_env_type or safe_load_yml(STATIC_PARAMS_FILEPATH)
common_config = params_by_env_type['common']
schain_cpu_alloc, ima_cpu_alloc = get_cpu_alloc(common_config)
@@ -93,22 +95,20 @@ def compose_resource_allocation_config(env_type: str, params_by_env_type: Dict =
def generate_resource_allocation_config(
- env_file,
+ env_file: str,
node_type: NodeType,
node_mode: NodeMode,
- force=False,
+ force: bool = False,
) -> None:
if not force and os.path.isfile(RESOURCE_ALLOCATION_FILEPATH):
msg = 'Resource allocation file already exists'
logger.debug(msg)
print(msg)
return
- user_config = get_validated_user_config(
- node_type=node_type, node_mode=node_mode, env_filepath=env_file
- )
+ settings = validate_and_save_node_settings(env_file, node_type, node_mode)
logger.info('Generating resource allocation file ...')
try:
- update_resource_allocation(user_config.env_type)
+ update_resource_allocation(settings.env_type)
except Exception as e:
logger.exception(e)
print("Can't generate resource allocation file, check out CLI logs")
@@ -116,7 +116,7 @@ def generate_resource_allocation_config(
print(f'Resource allocation file generated: {RESOURCE_ALLOCATION_FILEPATH}')
-def update_resource_allocation(env_type: str) -> None:
+def update_resource_allocation(env_type: EnvType) -> None:
resource_allocation_config = compose_resource_allocation_config(env_type)
write_json(RESOURCE_ALLOCATION_FILEPATH, resource_allocation_config)
diff --git a/node_cli/core/schains.py b/node_cli/core/schains.py
index 64c16031..8aac42f1 100644
--- a/node_cli/core/schains.py
+++ b/node_cli/core/schains.py
@@ -26,6 +26,8 @@
from pathlib import Path
from typing import Dict, Optional
+from skale_core.types import EnvType
+
from lvmpy.src.core import mount, volume_mountpoint
from node_cli.configs import (
ALLOCATION_FILEPATH,
@@ -34,7 +36,6 @@
SCHAIN_NODE_DATA_PATH,
SCHAINS_MNT_DIR_SINGLE_CHAIN,
)
-from node_cli.configs.user import get_validated_user_config
from node_cli.utils.docker_utils import ensure_volume, is_volume_exists
from node_cli.utils.exit_codes import CLIExitCodes
from node_cli.utils.helper import (
@@ -214,12 +215,9 @@ def restore_schain_from_snapshot(
schain: str,
snapshot_path: str,
node_type: NodeType,
- env_type: Optional[str] = None,
+ env_type: EnvType,
schain_type: str = 'medium',
) -> None:
- if env_type is None:
- user_config = get_validated_user_config(node_type=node_type)
- env_type = user_config.env_type
ensure_schain_volume(schain, schain_type, env_type)
block_number = get_block_number_from_path(snapshot_path)
if block_number == -1:
@@ -242,12 +240,12 @@ def get_schains_by_artifacts() -> str:
return '\n'.join(os.listdir(SCHAIN_NODE_DATA_PATH))
-def get_schain_volume_size(schain_type: str, env_type: str) -> int:
+def get_schain_volume_size(schain_type: str, env_type: EnvType) -> int:
alloc = safe_load_yml(ALLOCATION_FILEPATH)
return alloc[env_type]['disk'][schain_type]
-def ensure_schain_volume(schain: str, schain_type: str, env_type: str) -> None:
+def ensure_schain_volume(schain: str, schain_type: str, env_type: EnvType) -> None:
if not is_volume_exists(schain):
size = get_schain_volume_size(schain_type, env_type)
ensure_volume(schain, size)
@@ -313,4 +311,4 @@ def cleanup_lvm_datadir():
cleanup_dir_content('/mnt/')
logger.info('Removing LVM volume group "schains"...')
run_cmd(['sudo', 'lvremove', '-f', 'schains'], check_code=False)
- logger.info('Active node cleanup finished.')
\ No newline at end of file
+ logger.info('Active node cleanup finished.')
diff --git a/node_cli/core/static_config.py b/node_cli/core/static_config.py
index a93a7a60..06e0e7f4 100644
--- a/node_cli/core/static_config.py
+++ b/node_cli/core/static_config.py
@@ -28,10 +28,12 @@
)
from node_cli.utils.node_type import NodeType
+from skale_core.types import EnvType
+
def get_static_params(
node_type: NodeType,
- env_type: str = 'mainnet',
+ env_type: EnvType = 'mainnet',
config_path: str = CONTAINER_CONFIG_PATH,
) -> dict:
if node_type == NodeType.FAIR:
@@ -46,7 +48,7 @@ def get_static_params(
return ydata['envs'][env_type]
-def get_fair_chain_name(env: dict) -> str:
+def get_fair_chain_name(env_type: EnvType) -> str:
node_type = NodeType.FAIR
- params = get_static_params(node_type, env['ENV_TYPE'])
+ params = get_static_params(node_type, env_type)
return params['info']['chain_name']
diff --git a/node_cli/fair/active.py b/node_cli/fair/active.py
index 963686fe..2ec42bde 100644
--- a/node_cli/fair/active.py
+++ b/node_cli/fair/active.py
@@ -22,8 +22,8 @@
import time
from typing import cast
-from node_cli.configs import DEFAULT_SKALED_BASE_PORT, RESTORE_SLEEP_TIMEOUT, SKALE_DIR
-from node_cli.core.host import is_node_inited, save_env_params
+from node_cli.configs import DEFAULT_SKALED_BASE_PORT, RESTORE_SLEEP_TIMEOUT
+from node_cli.core.host import is_node_inited
from node_cli.core.node import compose_node_env, is_base_containers_alive
from node_cli.operations import (
FairUpdateType,
@@ -35,6 +35,7 @@
from node_cli.utils.helper import error_exit, get_request, post_request
from node_cli.utils.node_type import NodeMode, NodeType
from node_cli.utils.print_formatters import print_node_cmd_error, print_node_info_fair
+from node_cli.utils.settings import validate_and_save_node_settings
from node_cli.utils.texts import safe_load_texts
logger = logging.getLogger(__name__)
@@ -63,19 +64,14 @@ def get_node_info(format):
@check_inited
@check_user
def migrate_from_boot(
- env_filepath: str,
+ config_file: str,
) -> None:
logger.info('Migrating from boot to fair node...')
- env = compose_node_env(
- env_filepath,
- inited_node=True,
- sync_schains=False,
- node_type=NodeType.FAIR,
- node_mode=NodeMode.ACTIVE,
- )
+ settings = validate_and_save_node_settings(config_file, NodeType.FAIR, NodeMode.ACTIVE)
+ compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
migrate_ok = update_fair_op(
- env_filepath,
- env,
+ settings=settings,
+ compose_env=compose_env,
node_mode=NodeMode.ACTIVE,
update_type=FairUpdateType.FROM_BOOT,
force_skaled_start=False,
@@ -145,16 +141,17 @@ def exit() -> None:
@check_not_inited
-def restore(backup_path, env_filepath, config_only=False):
+def restore(backup_path: str, config_file: str, config_only: bool = False):
node_mode = NodeMode.ACTIVE
- env = compose_node_env(env_filepath, node_type=NodeType.FAIR, node_mode=node_mode)
- if env is None:
- return
- save_env_params(env_filepath)
- env['SKALE_DIR'] = SKALE_DIR
+ settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode)
+ compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode)
restored_ok = restore_fair_op(
- node_mode=node_mode, env=env, backup_path=backup_path, config_only=config_only
+ node_mode=node_mode,
+ settings=settings,
+ compose_env=compose_env,
+ backup_path=backup_path,
+ config_only=config_only,
)
if not restored_ok:
error_exit('Restore operation failed', exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR)
diff --git a/node_cli/fair/boot.py b/node_cli/fair/boot.py
index a6415f75..d9da98fa 100644
--- a/node_cli/fair/boot.py
+++ b/node_cli/fair/boot.py
@@ -30,22 +30,19 @@
from node_cli.utils.helper import error_exit
from node_cli.utils.node_type import NodeMode, NodeType
from node_cli.utils.print_formatters import print_node_cmd_error
+from node_cli.utils.settings import validate_and_save_node_settings
logger = logging.getLogger(__name__)
@check_not_inited
-def init(env_filepath: str) -> None:
+def init(config_file: str) -> None:
node_mode = NodeMode.ACTIVE
node_type = NodeType.FAIR
- env = compose_node_env(
- env_filepath,
- node_type=node_type,
- node_mode=node_mode,
- is_fair_boot=True,
- )
+ settings = validate_and_save_node_settings(config_file, node_type, node_mode)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
- init_fair_boot_op(env_filepath, env, node_mode)
+ init_fair_boot_op(settings=settings, compose_env=compose_env, node_mode=node_mode)
logger.info('Waiting for fair containers initialization')
time.sleep(TM_INIT_TIMEOUT)
if not is_base_containers_alive(node_type=node_type, node_mode=node_mode, is_fair_boot=True):
@@ -55,19 +52,16 @@ def init(env_filepath: str) -> None:
@check_inited
@check_user
-def update(env_filepath: str, pull_config_for_schain: str) -> None:
+def update(config_file: str, pull_config_for_schain: str) -> None:
logger.info('Fair boot node update started')
node_mode = upsert_node_mode(node_mode=NodeMode.ACTIVE)
- env = compose_node_env(
- env_filepath,
- inited_node=True,
- sync_schains=False,
- pull_config_for_schain=pull_config_for_schain,
- node_type=NodeType.FAIR,
- node_mode=node_mode,
- is_fair_boot=True,
+ settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode)
+ compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode)
+ migrate_ok = update_fair_boot_op(
+ settings=settings,
+ compose_env=compose_env,
+ node_mode=NodeMode.ACTIVE,
)
- migrate_ok = update_fair_boot_op(env_filepath, env, node_mode=NodeMode.ACTIVE)
if migrate_ok:
logger.info('Waiting for containers initialization')
time.sleep(TM_INIT_TIMEOUT)
diff --git a/node_cli/fair/common.py b/node_cli/fair/common.py
index 968891d4..98f10a7f 100644
--- a/node_cli/fair/common.py
+++ b/node_cli/fair/common.py
@@ -20,9 +20,7 @@
import logging
import time
-from node_cli.configs import INIT_TIMEOUT, SKALE_DIR, TM_INIT_TIMEOUT
-from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH
-from node_cli.core.host import save_env_params
+from node_cli.configs import INIT_TIMEOUT, TM_INIT_TIMEOUT
from node_cli.core.node import compose_node_env, is_base_containers_alive
from node_cli.core.node_options import upsert_node_mode
from node_cli.fair.passive import setup_fair_passive
@@ -40,7 +38,9 @@
from node_cli.utils.helper import error_exit
from node_cli.utils.node_type import NodeMode, NodeType
from node_cli.utils.print_formatters import print_node_cmd_error
+from node_cli.utils.settings import validate_and_save_node_settings
from node_cli.utils.texts import safe_load_texts
+from skale_core.settings import get_settings
logger = logging.getLogger(__name__)
TEXTS = safe_load_texts()
@@ -49,21 +49,18 @@
@check_not_inited
def init(
node_mode: NodeMode,
- env_filepath: str,
+ config_file: str,
node_id: int | None = None,
indexer: bool = False,
archive: bool = False,
snapshot: str | None = None,
) -> None:
- env = compose_node_env(env_filepath, node_type=NodeType.FAIR, node_mode=node_mode)
- if env is None:
- return
- save_env_params(env_filepath)
- env['SKALE_DIR'] = SKALE_DIR
+ settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode)
+ compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode)
init_ok = init_fair_op(
- env_filepath,
- env,
+ settings=settings,
+ compose_env=compose_env,
node_mode=node_mode,
indexer=indexer,
archive=archive,
@@ -79,17 +76,12 @@ def init(
print('Fair node is initialized')
+@check_inited
@check_user
def cleanup(node_mode: NodeMode, prune: bool = False) -> None:
node_mode = upsert_node_mode(node_mode=node_mode)
- env = compose_node_env(
- SKALE_DIR_ENV_FILEPATH,
- save=False,
- node_type=NodeType.FAIR,
- node_mode=node_mode,
- skip_user_conf_validation=True,
- )
- cleanup_fair_op(node_mode=node_mode, env=env, prune=prune)
+ compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode)
+ cleanup_fair_op(node_mode=node_mode, compose_env=compose_env, prune=prune)
logger.info('Fair node was cleaned up, all containers and data removed')
@@ -97,29 +89,23 @@ def cleanup(node_mode: NodeMode, prune: bool = False) -> None:
@check_user
def update(
node_mode: NodeMode,
- env_filepath: str,
+ config_file: str,
pull_config_for_schain: str | None = None,
force_skaled_start: bool = False,
) -> None:
logger.info(
'Updating fair node: %s, pull_config_for_schain: %s, force_skaled_start: %s',
- env_filepath,
+ config_file,
pull_config_for_schain,
force_skaled_start,
)
node_mode = upsert_node_mode(node_mode=node_mode)
- env = compose_node_env(
- env_filepath,
- inited_node=True,
- sync_schains=False,
- node_type=NodeType.FAIR,
- node_mode=node_mode,
- pull_config_for_schain=pull_config_for_schain,
- )
+ settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode)
+ compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode)
update_ok = update_fair_op(
- env_filepath,
- env,
+ settings=settings,
+ compose_env=compose_env,
node_mode=node_mode,
update_type=FairUpdateType.REGULAR,
force_skaled_start=force_skaled_start,
@@ -133,34 +119,25 @@ def update(
def repair_chain(snapshot_from: str = 'any') -> None:
- node_mode = upsert_node_mode()
- env = compose_node_env(
- SKALE_DIR_ENV_FILEPATH, save=False, node_type=NodeType.FAIR, node_mode=node_mode
- )
- repair_fair_op(env=env, snapshot_from=snapshot_from)
+ settings = get_settings()
+ repair_fair_op(env_type=settings.env_type, snapshot_from=snapshot_from)
@check_inited
@check_user
def turn_off(node_type: NodeType) -> None:
node_mode = upsert_node_mode()
- env = compose_node_env(
- SKALE_DIR_ENV_FILEPATH, save=False, node_type=node_type, node_mode=node_mode
- )
- turn_off_op(node_type=node_type, node_mode=node_mode, env=env)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
+ turn_off_op(compose_env=compose_env, node_type=node_type, node_mode=node_mode)
@check_inited
@check_user
-def turn_on(env_file, node_type: NodeType) -> None:
+def turn_on(env_file: str, node_type: NodeType) -> None:
node_mode = upsert_node_mode()
- env = compose_node_env(
- env_file,
- inited_node=True,
- node_type=node_type,
- node_mode=node_mode,
- )
- turn_on_op(env=env, node_type=node_type, node_mode=node_mode)
+ settings = validate_and_save_node_settings(env_file, node_type, node_mode)
+ compose_env = compose_node_env(node_type=node_type, node_mode=node_mode)
+ turn_on_op(settings=settings, compose_env=compose_env, node_type=node_type, node_mode=node_mode)
logger.info('Waiting for containers initialization')
time.sleep(TM_INIT_TIMEOUT)
if not is_base_containers_alive(node_type=node_type, node_mode=node_mode):
diff --git a/node_cli/fair/record/chain_record.py b/node_cli/fair/record/chain_record.py
index d4ac740c..b6d48886 100644
--- a/node_cli/fair/record/chain_record.py
+++ b/node_cli/fair/record/chain_record.py
@@ -22,6 +22,8 @@
from typing import cast
from datetime import datetime
+from skale_core.types import EnvType
+
from node_cli.core.static_config import get_fair_chain_name
from node_cli.fair.record.redis_record import FlatRedisRecord, FieldInfo
@@ -78,18 +80,17 @@ def set_force_skaled_start(self, value: bool) -> None:
self._set_field('force_skaled_start', value)
-def get_fair_chain_record(env: dict) -> ChainRecord:
- return ChainRecord(get_fair_chain_name(env))
+def get_fair_chain_record(env_type: EnvType) -> ChainRecord:
+ return ChainRecord(get_fair_chain_name(env_type))
-def migrate_chain_record(env: dict) -> None:
- version = env['NODE_VERSION']
- logger.info('Migrating fair chain record, setting config version to %s', version)
- record = get_fair_chain_record(env)
- record.set_config_version(version)
+def migrate_chain_record(env_type: EnvType, node_version: str) -> None:
+ logger.info('Migrating fair chain record, setting config version to %s', node_version)
+ record = get_fair_chain_record(env_type)
+ record.set_config_version(node_version)
-def update_chain_record(env: dict, force_skaled_start: bool) -> None:
- record = get_fair_chain_record(env)
+def update_chain_record(env_type: EnvType, force_skaled_start: bool) -> None:
+ record = get_fair_chain_record(env_type)
record.set_force_skaled_start(force_skaled_start)
logger.info('Updated fair chain record with force_skaled_start=%s', force_skaled_start)
diff --git a/node_cli/operations/base.py b/node_cli/operations/base.py
index 1a64d585..803fe299 100644
--- a/node_cli/operations/base.py
+++ b/node_cli/operations/base.py
@@ -20,10 +20,12 @@
import functools
import logging
import time
-from typing import Dict, Optional
+from typing import Optional
import distro
+from skale_core.settings import BaseNodeSettings, SkalePassiveSettings, SkaleSettings, get_settings
+
from node_cli.cli.info import TYPE, VERSION
from node_cli.configs import (
CONTAINER_CONFIG_PATH,
@@ -37,7 +39,6 @@
from node_cli.core.docker_config import cleanup_docker_configuration, configure_docker
from node_cli.core.host import (
ensure_btrfs_kernel_module_autoloaded,
- link_env_file,
prepare_host,
)
from node_cli.core.nftables import configure_nftables
@@ -53,7 +54,7 @@
cleanup_no_lvm_datadir,
update_node_cli_schain_status,
)
-from node_cli.operations.common import configure_filebeat, configure_flask, unpack_backup_archive
+from node_cli.operations.common import configure_filebeat, unpack_backup_archive
from node_cli.operations.config_repo import (
download_skale_node,
sync_skale_node,
@@ -72,23 +73,26 @@
remove_dynamic_containers,
system_prune,
)
-from node_cli.utils.helper import cleanup_dir_content, rm_dir, str_to_bool
+from node_cli.utils.helper import cleanup_dir_content, rm_dir
from node_cli.utils.meta import CliMetaManager, FairCliMetaManager
from node_cli.utils.node_type import NodeMode, NodeType
from node_cli.utils.print_formatters import print_failed_requirements_checks
+from node_cli.utils.settings import save_internal_settings
logger = logging.getLogger(__name__)
def checked_host(func):
@functools.wraps(func)
- def wrapper(env_filepath: str, env: Dict, node_mode: NodeMode, *args, **kwargs):
- download_skale_node(env.get('NODE_VERSION'), env.get('CONTAINER_CONFIGS_DIR'))
+ def wrapper(
+ settings: BaseNodeSettings, compose_env: dict, node_mode: NodeMode, *args, **kwargs
+ ):
+ download_skale_node(settings.node_version, settings.container_configs_dir or None)
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
node_mode,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_TMP_PATH,
check_type=CheckType.PREINSTALL,
)
@@ -96,15 +100,15 @@ def wrapper(env_filepath: str, env: Dict, node_mode: NodeMode, *args, **kwargs):
print_failed_requirements_checks(failed_checks)
return False
- result = func(env_filepath, env, node_mode, *args, **kwargs)
+ result = func(settings, compose_env, node_mode, *args, **kwargs)
if not result:
return result
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
node_mode,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_PATH,
check_type=CheckType.POSTINSTALL,
)
@@ -117,112 +121,118 @@ def wrapper(env_filepath: str, env: Dict, node_mode: NodeMode, *args, **kwargs):
@checked_host
-def update(env_filepath: str, env: Dict, node_mode: NodeMode) -> bool:
- compose_rm(node_type=NodeType.SKALE, node_mode=node_mode, env=env)
+def update(settings: BaseNodeSettings, compose_env: dict, node_mode: NodeMode) -> bool:
+ compose_rm(node_type=NodeType.SKALE, node_mode=node_mode, env=compose_env)
remove_dynamic_containers()
sync_skale_node()
ensure_btrfs_kernel_module_autoloaded()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
- lvmpy_install(env)
+ lvmpy_install(settings.block_device)
generate_nginx_config()
- prepare_host(env_filepath, env['ENV_TYPE'], allocation=True)
- init_shared_space_volume(env['ENV_TYPE'])
+ prepare_host(settings.env_type, allocation=True)
+ save_internal_settings(node_type=NodeType.SKALE, node_mode=node_mode)
+ init_shared_space_volume(settings.env_type)
meta_manager = CliMetaManager()
current_stream = meta_manager.get_meta_info().config_stream
- skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True'
- if not skip_cleanup and current_stream != env['NODE_VERSION']:
+ if not settings.skip_docker_cleanup and current_stream != settings.node_version:
logger.info(
'Stream version was changed from %s to %s',
current_stream,
- env['NODE_VERSION'],
+ settings.node_version,
)
docker_cleanup()
+ skale_settings = get_settings(SkaleSettings)
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
- env['DOCKER_LVMPY_VERSION'],
+ settings.node_version,
+ skale_settings.docker_lvmpy_version,
distro.id(),
distro.version(),
)
- update_images(env=env, node_type=NodeType.SKALE, node_mode=node_mode)
- compose_up(env=env, node_type=NodeType.SKALE, node_mode=node_mode)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.SKALE,
+ node_mode=node_mode,
+ )
+ compose_up(env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=node_mode)
return True
@checked_host
-def init(env_filepath: str, env: dict, node_mode: NodeMode) -> None:
+def init(settings: BaseNodeSettings, compose_env: dict, node_mode: NodeMode) -> None:
sync_skale_node()
ensure_btrfs_kernel_module_autoloaded()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
- prepare_host(env_filepath, env_type=env['ENV_TYPE'])
- link_env_file()
+ prepare_host(env_type=settings.env_type)
+ save_internal_settings(node_type=NodeType.SKALE, node_mode=node_mode)
mark_active_node()
configure_filebeat()
- configure_flask()
generate_nginx_config()
- lvmpy_install(env)
- init_shared_space_volume(env['ENV_TYPE'])
+ lvmpy_install(settings.block_device)
+ init_shared_space_volume(settings.env_type)
+ skale_settings = get_settings(SkaleSettings)
meta_manager = CliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
- env['DOCKER_LVMPY_VERSION'],
+ settings.node_version,
+ skale_settings.docker_lvmpy_version,
distro.id(),
distro.version(),
)
- update_resource_allocation(env_type=env['ENV_TYPE'])
- update_images(env=env, node_type=NodeType.SKALE, node_mode=node_mode)
- compose_up(env=env, node_type=NodeType.SKALE, node_mode=node_mode)
+ update_resource_allocation(env_type=settings.env_type)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.SKALE,
+ node_mode=node_mode,
+ )
+ compose_up(env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=node_mode)
def init_passive(
- env_filepath: str,
- env: dict,
+ settings: BaseNodeSettings,
+ compose_env: dict,
indexer: bool,
archive: bool,
snapshot: bool,
snapshot_from: Optional[str],
) -> None:
- cleanup_volume_artifacts(env['BLOCK_DEVICE'])
- download_skale_node(env.get('NODE_VERSION'), env.get('CONTAINER_CONFIGS_DIR'))
+ cleanup_volume_artifacts(settings.block_device)
+ download_skale_node(settings.node_version, settings.container_configs_dir or None)
sync_skale_node()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
- prepare_host(
- env_filepath,
- env_type=env['ENV_TYPE'],
- )
+ prepare_host(env_type=settings.env_type)
+ save_internal_settings(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
NodeMode.PASSIVE,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_PATH,
- check_type=CheckType.PREINSTALL
+ check_type=CheckType.PREINSTALL,
)
if failed_checks:
print_failed_requirements_checks(failed_checks)
@@ -230,57 +240,66 @@ def init_passive(
set_passive_node_options(archive=archive, indexer=indexer)
ensure_filestorage_mapping()
- link_env_file()
generate_nginx_config()
- prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True')
+ passive_settings = get_settings(SkalePassiveSettings)
+ prepare_block_device(settings.block_device, force=passive_settings.enforce_btrfs)
meta_manager = CliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
None,
distro.id(),
distro.version(),
)
- update_resource_allocation(env_type=env['ENV_TYPE'])
+ update_resource_allocation(env_type=settings.env_type)
- schain_name = env['SCHAIN_NAME']
- if snapshot or snapshot_from:
+ if passive_settings.schain_name and (snapshot or snapshot_from):
ts = int(time.time())
- update_node_cli_schain_status(schain_name, repair_ts=ts, snapshot_from=snapshot_from)
+ update_node_cli_schain_status(
+ passive_settings.schain_name, repair_ts=ts, snapshot_from=snapshot_from
+ )
- update_images(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
- compose_up(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.SKALE,
+ node_mode=NodeMode.PASSIVE,
+ )
+ compose_up(
+ env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE
+ )
-def update_passive(env_filepath: str, env: Dict) -> bool:
- compose_rm(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
+def update_passive(settings: BaseNodeSettings, compose_env: dict) -> bool:
+ compose_rm(env=compose_env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
remove_dynamic_containers()
- cleanup_volume_artifacts(env['BLOCK_DEVICE'])
- download_skale_node(env['NODE_VERSION'], env.get('CONTAINER_CONFIGS_DIR'))
+ cleanup_volume_artifacts(settings.block_device)
+ download_skale_node(settings.node_version, settings.container_configs_dir or None)
sync_skale_node()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
ensure_filestorage_mapping()
- prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True')
+ passive_settings = get_settings(SkalePassiveSettings)
+ prepare_block_device(settings.block_device, force=passive_settings.enforce_btrfs)
generate_nginx_config()
- prepare_host(env_filepath, env['ENV_TYPE'], allocation=True)
+ prepare_host(settings.env_type, allocation=True)
+ save_internal_settings(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
NodeMode.PASSIVE,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_PATH,
- check_type=CheckType.PREINSTALL
+ check_type=CheckType.PREINSTALL,
)
if failed_checks:
print_failed_requirements_checks(failed_checks)
@@ -288,56 +307,82 @@ def update_passive(env_filepath: str, env: Dict) -> bool:
meta_manager = CliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
None,
distro.id(),
distro.version(),
)
- update_images(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
- compose_up(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.SKALE,
+ node_mode=NodeMode.PASSIVE,
+ )
+ compose_up(
+ env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE
+ )
return True
-def turn_off(env: dict, node_type: NodeType, node_mode: NodeMode) -> None:
+def turn_off(compose_env: dict, node_type: NodeType, node_mode: NodeMode) -> None:
logger.info('Turning off the node...')
- compose_rm(env=env, node_type=node_type, node_mode=node_mode)
+ compose_rm(env=compose_env, node_type=node_type, node_mode=node_mode)
remove_dynamic_containers()
logger.info('Node was successfully turned off')
-def turn_on(env: dict, node_type: NodeType, node_mode: NodeMode) -> None:
+def turn_on(
+ settings: BaseNodeSettings,
+ compose_env: dict,
+ node_type: NodeType,
+ node_mode: NodeMode,
+ backup_run: bool = False,
+) -> None:
logger.info('Turning on the node...')
if node_type == NodeType.FAIR:
meta_manager = FairCliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
distro.id(),
distro.version(),
)
else:
+ skale_settings = get_settings((SkaleSettings, SkalePassiveSettings))
+ docker_lvmpy_version = (
+ skale_settings.docker_lvmpy_version
+ if isinstance(skale_settings, SkaleSettings)
+ else None
+ )
meta_manager = CliMetaManager()
meta_manager.update_meta(
- VERSION, env['NODE_VERSION'], env['DOCKER_LVMPY_VERSION'], distro.id(), distro.version()
+ VERSION, settings.node_version, docker_lvmpy_version, distro.id(), distro.version()
)
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
+ save_internal_settings(node_type=node_type, node_mode=node_mode, backup_run=backup_run)
logger.info('Launching containers on the node...')
- compose_up(env=env, node_type=node_type, node_mode=node_mode)
+ compose_up(env=compose_env, settings=settings, node_type=node_type, node_mode=node_mode)
-def restore(env, backup_path, node_type: NodeType, config_only=False):
+def restore(
+ settings: BaseNodeSettings,
+ compose_env: dict,
+ backup_path: str,
+ node_type: NodeType,
+ config_only: bool = False,
+ backup_run: bool = False,
+) -> bool:
node_mode = upsert_node_mode(node_mode=NodeMode.ACTIVE)
unpack_backup_archive(backup_path)
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
node_mode,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_PATH,
check_type=CheckType.PREINSTALL,
)
@@ -347,32 +392,32 @@ def restore(env, backup_path, node_type: NodeType, config_only=False):
ensure_btrfs_kernel_module_autoloaded()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
- link_env_file()
- lvmpy_install(env)
- init_shared_space_volume(env['ENV_TYPE'])
+ lvmpy_install(settings.block_device)
+ init_shared_space_volume(settings.env_type)
+ skale_settings = get_settings(SkaleSettings)
meta_manager = CliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
- env['DOCKER_LVMPY_VERSION'],
+ settings.node_version,
+ skale_settings.docker_lvmpy_version,
distro.id(),
distro.version(),
)
+ save_internal_settings(node_type=node_type, node_mode=node_mode, backup_run=backup_run)
if not config_only:
- compose_up(env=env, node_type=node_type, node_mode=node_mode)
+ compose_up(env=compose_env, settings=settings, node_type=node_type, node_mode=node_mode)
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
node_mode,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_PATH,
check_type=CheckType.POSTINSTALL,
)
@@ -382,19 +427,20 @@ def restore(env, backup_path, node_type: NodeType, config_only=False):
return True
-def cleanup_passive(env, schain_name: str) -> None:
- turn_off(env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
+def cleanup_passive(compose_env: dict, schain_name: str) -> None:
+ turn_off(compose_env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE)
cleanup_no_lvm_datadir(chain_name=schain_name)
rm_dir(GLOBAL_SKALE_DIR)
rm_dir(SKALE_DIR)
-def cleanup(node_mode: NodeMode, env: dict, prune: bool = False) -> None:
- turn_off(env, node_type=NodeType.SKALE, node_mode=node_mode)
+def cleanup(
+ node_mode: NodeMode, compose_env: dict, schain_name: Optional[str] = None, prune: bool = False
+) -> None:
+ turn_off(compose_env, node_type=NodeType.SKALE, node_mode=node_mode)
if prune:
system_prune()
if node_mode == NodeMode.PASSIVE:
- schain_name = env['SCHAIN_NAME']
cleanup_no_lvm_datadir(chain_name=schain_name)
else:
cleanup_lvm_datadir()
diff --git a/node_cli/operations/common.py b/node_cli/operations/common.py
index 7c876fa8..c595a3b8 100644
--- a/node_cli/operations/common.py
+++ b/node_cli/operations/common.py
@@ -19,7 +19,6 @@
import logging
import os
-import secrets
import shutil
import stat
import tarfile
@@ -27,7 +26,6 @@
from node_cli.configs import (
FILEBEAT_CONFIG_PATH,
- FLASK_SECRET_KEY_FILE,
G_CONF_HOME,
SRC_FILEBEAT_CONFIG_PATH,
)
@@ -43,17 +41,6 @@ def configure_filebeat():
logger.info('Filebeat configured')
-def configure_flask():
- if os.path.isfile(FLASK_SECRET_KEY_FILE):
- logger.info('Flask secret key already exists')
- else:
- logger.info('Generating Flask secret key...')
- flask_secret_key = secrets.token_urlsafe(16)
- with open(FLASK_SECRET_KEY_FILE, 'w') as f:
- f.write(flask_secret_key)
- logger.info('Flask secret key generated and saved')
-
-
def unpack_backup_archive(backup_path: str) -> None:
logger.info('Unpacking backup archive...')
with tarfile.open(backup_path) as tar:
diff --git a/node_cli/operations/config_repo.py b/node_cli/operations/config_repo.py
index b8456db6..19cf284a 100644
--- a/node_cli/operations/config_repo.py
+++ b/node_cli/operations/config_repo.py
@@ -33,12 +33,13 @@
logger = logging.getLogger(__name__)
-def update_images(env: dict, node_type: NodeType, node_mode: NodeMode) -> None:
- local = env.get('CONTAINER_CONFIGS_DIR') != ''
- if local:
- compose_build(env=env, node_type=node_type, node_mode=node_mode)
+def update_images(
+ compose_env: dict, container_configs_dir: str, node_type: NodeType, node_mode: NodeMode
+) -> None:
+ if container_configs_dir:
+ compose_build(env=compose_env, node_type=node_type, node_mode=node_mode)
else:
- compose_pull(env=env, node_type=node_type, node_mode=node_mode)
+ compose_pull(env=compose_env, node_type=node_type, node_mode=node_mode)
def download_skale_node(stream: Optional[str] = None, src: Optional[str] = None) -> None:
diff --git a/node_cli/operations/docker_lvmpy.py b/node_cli/operations/docker_lvmpy.py
index c21328f8..eb307d55 100644
--- a/node_cli/operations/docker_lvmpy.py
+++ b/node_cli/operations/docker_lvmpy.py
@@ -56,12 +56,10 @@ def sync_docker_lvmpy_repo(env):
sync_repo(DOCKER_LVMPY_REPO_URL, DOCKER_LVMPY_PATH, env['DOCKER_LVMPY_VERSION'])
-def lvmpy_install(env):
+def lvmpy_install(block_device: str) -> None:
ensure_filestorage_mapping()
logging.info('Configuring and starting lvmpy')
- setup_lvmpy(
- block_device=env['BLOCK_DEVICE'], volume_group=VOLUME_GROUP, exec_start=LVMPY_RUN_CMD
- )
+ setup_lvmpy(block_device=block_device, volume_group=VOLUME_GROUP, exec_start=LVMPY_RUN_CMD)
init_healing_cron()
logger.info('docker-lvmpy is configured and started')
diff --git a/node_cli/operations/fair.py b/node_cli/operations/fair.py
index f1e62305..3a48fdbd 100644
--- a/node_cli/operations/fair.py
+++ b/node_cli/operations/fair.py
@@ -23,6 +23,9 @@
import distro
+from skale_core.settings import BaseNodeSettings, FairBaseSettings, FairSettings, get_settings
+from skale_core.types import EnvType
+
from node_cli.cli.info import TYPE, VERSION
from node_cli.configs import (
CONTAINER_CONFIG_PATH,
@@ -33,7 +36,7 @@
from node_cli.core.checks import CheckType
from node_cli.core.checks import run_checks as run_host_checks
from node_cli.core.docker_config import cleanup_docker_configuration, configure_docker
-from node_cli.core.host import ensure_btrfs_kernel_module_autoloaded, link_env_file, prepare_host
+from node_cli.core.host import ensure_btrfs_kernel_module_autoloaded, prepare_host
from node_cli.core.nftables import configure_nftables
from node_cli.core.nginx import generate_nginx_config
from node_cli.core.schains import cleanup_no_lvm_datadir
@@ -46,7 +49,7 @@
)
from node_cli.migrations.fair.from_boot import migrate_nftables_from_boot
from node_cli.operations.base import checked_host, turn_off
-from node_cli.operations.common import configure_filebeat, configure_flask, unpack_backup_archive
+from node_cli.operations.common import configure_filebeat, unpack_backup_archive
from node_cli.operations.config_repo import (
sync_skale_node,
update_images,
@@ -66,10 +69,11 @@
system_prune,
wait_for_container,
)
-from node_cli.utils.helper import cleanup_dir_content, rm_dir, str_to_bool
+from node_cli.utils.helper import cleanup_dir_content, rm_dir
from node_cli.utils.meta import FairCliMetaManager
from node_cli.utils.print_formatters import print_failed_requirements_checks
from node_cli.utils.node_type import NodeMode, NodeType
+from node_cli.utils.settings import save_internal_settings
logger = logging.getLogger(__name__)
@@ -81,42 +85,56 @@ class FairUpdateType(Enum):
@checked_host
-def init_fair_boot(env_filepath: str, env: dict) -> None:
+def init_fair_boot(
+ settings: BaseNodeSettings,
+ compose_env: dict,
+ node_mode: NodeMode,
+) -> None:
sync_skale_node()
- cleanup_volume_artifacts(env['BLOCK_DEVICE'])
+ cleanup_volume_artifacts(settings.block_device)
ensure_btrfs_kernel_module_autoloaded()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
- prepare_host(env_filepath, env_type=env['ENV_TYPE'])
- link_env_file()
+ prepare_host(env_type=settings.env_type)
+ save_internal_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
mark_active_node()
configure_filebeat()
- configure_flask()
generate_nginx_config()
- prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True')
+ fair_settings = get_settings((FairSettings, FairBaseSettings))
+ prepare_block_device(settings.block_device, force=fair_settings.enforce_btrfs)
meta_manager = FairCliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
distro.id(),
distro.version(),
)
- update_images(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.FAIR,
+ node_mode=NodeMode.ACTIVE,
+ )
- compose_up(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE, is_fair_boot=True)
+ compose_up(
+ env=compose_env,
+ settings=settings,
+ node_type=NodeType.FAIR,
+ node_mode=NodeMode.ACTIVE,
+ is_fair_boot=True,
+ )
@checked_host
def init(
- env_filepath: str,
- env: dict,
+ settings: BaseNodeSettings,
+ compose_env: dict,
node_mode: NodeMode,
indexer: bool,
archive: bool,
@@ -124,24 +142,33 @@ def init(
) -> bool:
sync_skale_node()
ensure_btrfs_kernel_module_autoloaded()
- cleanup_volume_artifacts(env['BLOCK_DEVICE'])
+ cleanup_volume_artifacts(settings.block_device)
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
configure_nftables()
configure_filebeat()
- configure_flask()
generate_nginx_config()
- prepare_host(env_filepath, env_type=env['ENV_TYPE'])
- link_env_file()
+ prepare_host(env_type=settings.env_type)
+ save_internal_settings(node_type=NodeType.FAIR, node_mode=node_mode)
- prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True')
+ fair_settings = get_settings((FairSettings, FairBaseSettings))
+ prepare_block_device(settings.block_device, force=fair_settings.enforce_btrfs)
- update_images(env=env, node_type=NodeType.FAIR, node_mode=node_mode)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.FAIR,
+ node_mode=node_mode,
+ )
compose_up(
- env=env, node_type=NodeType.FAIR, node_mode=node_mode, services=list(REDIS_SERVICE_DICT)
+ env=compose_env,
+ settings=settings,
+ node_type=NodeType.FAIR,
+ node_mode=node_mode,
+ services=list(REDIS_SERVICE_DICT),
)
upsert_node_mode(node_mode=node_mode)
@@ -151,129 +178,159 @@ def init(
if snapshot:
logger.info('Waiting %s seconds for redis to start', REDIS_START_TIMEOUT)
time.sleep(REDIS_START_TIMEOUT)
- trigger_skaled_snapshot_mode(env=env, snapshot_from=snapshot)
+ trigger_skaled_snapshot_mode(env_type=settings.env_type, snapshot_from=snapshot)
meta_manager = FairCliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
distro.id(),
distro.version(),
)
- compose_up(env=env, node_type=NodeType.FAIR, node_mode=node_mode)
+ compose_up(env=compose_env, settings=settings, node_type=NodeType.FAIR, node_mode=node_mode)
wait_for_container(BASE_PASSIVE_FAIR_COMPOSE_SERVICES['api'])
time.sleep(REDIS_START_TIMEOUT)
return True
@checked_host
-def update_fair_boot(env_filepath: str, env: dict, node_mode: NodeMode = NodeMode.ACTIVE) -> bool:
- compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=env)
+def update_fair_boot(
+ settings: BaseNodeSettings,
+ compose_env: dict,
+ node_mode: NodeMode = NodeMode.ACTIVE,
+) -> bool:
+ compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=compose_env)
remove_dynamic_containers()
- cleanup_volume_artifacts(env['BLOCK_DEVICE'])
+ cleanup_volume_artifacts(settings.block_device)
sync_skale_node()
ensure_btrfs_kernel_module_autoloaded()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
generate_nginx_config()
- prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True')
+ fair_settings = get_settings((FairSettings, FairBaseSettings))
+ prepare_block_device(settings.block_device, force=fair_settings.enforce_btrfs)
- prepare_host(env_filepath, env['ENV_TYPE'])
+ prepare_host(settings.env_type)
+ save_internal_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
meta_manager = FairCliMetaManager()
current_stream = meta_manager.get_meta_info().config_stream
- skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True'
- if not skip_cleanup and current_stream != env['NODE_VERSION']:
+ if not settings.skip_docker_cleanup and current_stream != settings.node_version:
logger.info(
'Stream version was changed from %s to %s',
current_stream,
- env['NODE_VERSION'],
+ settings.node_version,
)
docker_cleanup()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
distro.id(),
distro.version(),
)
- update_images(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
- compose_up(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE, is_fair_boot=True)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.FAIR,
+ node_mode=NodeMode.ACTIVE,
+ )
+ compose_up(
+ env=compose_env,
+ settings=settings,
+ node_type=NodeType.FAIR,
+ node_mode=NodeMode.ACTIVE,
+ is_fair_boot=True,
+ )
return True
@checked_host
def update(
- env_filepath: str,
- env: dict,
+ settings: BaseNodeSettings,
+ compose_env: dict,
node_mode: NodeMode,
update_type: FairUpdateType,
force_skaled_start: bool,
) -> bool:
- compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=env)
+ compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=compose_env)
if update_type not in (FairUpdateType.INFRA_ONLY, FairUpdateType.FROM_BOOT):
remove_dynamic_containers()
sync_skale_node()
ensure_btrfs_kernel_module_autoloaded()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
configure_nftables()
generate_nginx_config()
- prepare_host(env_filepath, env['ENV_TYPE'], allocation=True)
+ prepare_host(settings.env_type, allocation=True)
+ save_internal_settings(node_type=NodeType.FAIR, node_mode=node_mode)
meta_manager = FairCliMetaManager()
current_stream = meta_manager.get_meta_info().config_stream
- skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True'
- if not skip_cleanup and current_stream != env['NODE_VERSION']:
+ if not settings.skip_docker_cleanup and current_stream != settings.node_version:
logger.info(
'Stream version was changed from %s to %s',
current_stream,
- env['NODE_VERSION'],
+ settings.node_version,
)
docker_cleanup()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
distro.id(),
distro.version(),
)
- fair_chain_name = get_fair_chain_name(env)
+ fair_chain_name = get_fair_chain_name(settings.env_type)
if update_type == FairUpdateType.FROM_BOOT:
migrate_nftables_from_boot(chain_name=fair_chain_name)
- update_images(env=env, node_type=NodeType.FAIR, node_mode=node_mode)
+ update_images(
+ compose_env=compose_env,
+ container_configs_dir=settings.container_configs_dir,
+ node_type=NodeType.FAIR,
+ node_mode=node_mode,
+ )
compose_up(
- env=env, node_type=NodeType.FAIR, node_mode=node_mode, services=list(REDIS_SERVICE_DICT)
+ env=compose_env,
+ settings=settings,
+ node_type=NodeType.FAIR,
+ node_mode=node_mode,
+ services=list(REDIS_SERVICE_DICT),
)
wait_for_container(REDIS_SERVICE_DICT['redis'])
time.sleep(REDIS_START_TIMEOUT)
if update_type == FairUpdateType.FROM_BOOT:
- migrate_chain_record(env)
- update_chain_record(env, force_skaled_start=force_skaled_start)
- compose_up(env=env, node_type=NodeType.FAIR, node_mode=node_mode)
+ migrate_chain_record(settings.env_type, settings.node_version)
+ update_chain_record(settings.env_type, force_skaled_start=force_skaled_start)
+ compose_up(env=compose_env, settings=settings, node_type=NodeType.FAIR, node_mode=node_mode)
return True
-def restore(node_mode: NodeMode, env, backup_path, config_only=False):
+def restore(
+ node_mode: NodeMode,
+ settings: BaseNodeSettings,
+ compose_env: dict,
+ backup_path: str,
+ config_only: bool = False,
+) -> bool:
unpack_backup_archive(backup_path)
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
node_mode,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_PATH,
check_type=CheckType.PREINSTALL,
)
@@ -283,30 +340,27 @@ def restore(node_mode: NodeMode, env, backup_path, config_only=False):
ensure_btrfs_kernel_module_autoloaded()
- if env.get('SKIP_DOCKER_CONFIG') != 'True':
+ if not settings.skip_docker_config:
configure_docker()
- enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False'))
- configure_nftables(enable_monitoring=enable_monitoring)
-
- link_env_file()
+ configure_nftables(enable_monitoring=settings.monitoring_containers)
meta_manager = FairCliMetaManager()
meta_manager.update_meta(
VERSION,
- env['NODE_VERSION'],
+ settings.node_version,
distro.id(),
distro.version(),
)
if not config_only:
- compose_up(env=env, node_type=NodeType.FAIR, node_mode=node_mode)
+ compose_up(env=compose_env, settings=settings, node_type=NodeType.FAIR, node_mode=node_mode)
failed_checks = run_host_checks(
- env['BLOCK_DEVICE'],
+ settings.block_device,
TYPE,
node_mode,
- env['ENV_TYPE'],
+ settings.env_type,
CONTAINER_CONFIG_PATH,
check_type=CheckType.POSTINSTALL,
)
@@ -316,8 +370,8 @@ def restore(node_mode: NodeMode, env, backup_path, config_only=False):
return True
-def cleanup(node_mode: NodeMode, env: dict, prune: bool = False) -> None:
- turn_off(env, node_type=NodeType.FAIR, node_mode=node_mode)
+def cleanup(node_mode: NodeMode, compose_env: dict, prune: bool = False) -> None:
+ turn_off(compose_env, node_type=NodeType.FAIR, node_mode=node_mode)
if prune:
system_prune()
cleanup_no_lvm_datadir()
@@ -327,15 +381,15 @@ def cleanup(node_mode: NodeMode, env: dict, prune: bool = False) -> None:
cleanup_docker_configuration()
-def trigger_skaled_snapshot_mode(env: dict, snapshot_from: str = 'any') -> None:
- record = get_fair_chain_record(env)
+def trigger_skaled_snapshot_mode(env_type: EnvType, snapshot_from: str = 'any') -> None:
+ record = get_fair_chain_record(env_type)
if not snapshot_from:
snapshot_from = 'any'
logger.info('Triggering skaled snapshot mode, snapshot_from: %s', snapshot_from)
record.set_snapshot_from(snapshot_from)
-def repair(env: dict, snapshot_from: str = 'any') -> None:
+def repair(env_type: EnvType, snapshot_from: str = 'any') -> None:
logger.info('Starting fair node repair')
container_name = 'sk_admin'
if is_admin_running():
@@ -346,7 +400,7 @@ def repair(env: dict, snapshot_from: str = 'any') -> None:
logger.info('Cleaning up datadir')
cleanup_no_lvm_datadir()
logger.info('Requesting fair node repair')
- trigger_skaled_snapshot_mode(env=env, snapshot_from=snapshot_from)
+ trigger_skaled_snapshot_mode(env_type=env_type, snapshot_from=snapshot_from)
logger.info('Starting admin')
start_container_by_name(container_name=container_name)
logger.info('Fair node repair completed successfully')
diff --git a/node_cli/utils/docker_utils.py b/node_cli/utils/docker_utils.py
index 695a1253..98a4946c 100644
--- a/node_cli/utils/docker_utils.py
+++ b/node_cli/utils/docker_utils.py
@@ -29,15 +29,16 @@
from docker.errors import NotFound
from docker.models.containers import Container
+from skale_core.settings import BaseNodeSettings
+
from node_cli.configs import (
COMPOSE_PATH,
FAIR_COMPOSE_PATH,
NGINX_CONTAINER_NAME,
REMOVED_CONTAINERS_FOLDER_PATH,
- SGX_CERTIFICATES_DIR_NAME,
)
from node_cli.core.node_options import active_fair, active_skale, passive_fair, passive_skale
-from node_cli.utils.helper import run_cmd, str_to_bool
+from node_cli.utils.helper import run_cmd
from node_cli.utils.node_type import NodeMode, NodeType
logger = logging.getLogger(__name__)
@@ -327,6 +328,7 @@ def get_up_compose_cmd(
def compose_up(
env,
+ settings: BaseNodeSettings,
node_type: NodeType,
node_mode: NodeMode,
is_fair_boot: bool = False,
@@ -338,9 +340,6 @@ def compose_up(
run_cmd(cmd=get_up_compose_cmd(node_type=node_type, node_mode=node_mode), env=env)
return
- if 'SGX_CERTIFICATES_DIR_NAME' not in env:
- env['SGX_CERTIFICATES_DIR_NAME'] = SGX_CERTIFICATES_DIR_NAME
-
if active_fair(node_type, node_mode):
logger.info('Running fair base set of containers')
if is_fair_boot:
@@ -368,7 +367,7 @@ def compose_up(
logger.debug('Launching skale node containers with env %s', env)
run_cmd(cmd=get_up_compose_cmd(node_type=node_type, node_mode=node_mode), env=env)
- if 'TG_API_KEY' in env and 'TG_CHAT_ID' in env:
+ if settings.tg_api_key and settings.tg_chat_id:
logger.info('Running containers for Telegram notifications')
run_cmd(
cmd=get_up_compose_cmd(
@@ -379,7 +378,7 @@ def compose_up(
env=env,
)
- if str_to_bool(env.get('MONITORING_CONTAINERS', 'False')):
+ if settings.monitoring_containers:
logger.info('Running monitoring containers')
run_cmd(
cmd=get_up_compose_cmd(
diff --git a/node_cli/utils/helper.py b/node_cli/utils/helper.py
index dd1a7151..8a642aee 100644
--- a/node_cli/utils/helper.py
+++ b/node_cli/utils/helper.py
@@ -30,6 +30,7 @@
import urllib.parse
import urllib.request
import uuid
+from pathlib import Path
from functools import wraps
from logging import Formatter, StreamHandler
from typing import Any, NoReturn, Optional
@@ -85,13 +86,13 @@ def write_json(path: str, content: dict) -> None:
json.dump(content, outfile, indent=4)
-def save_json(path: str, content: dict) -> None:
+def save_json(path: str | Path, content: dict) -> None:
tmp_path = get_tmp_path(path)
write_json(tmp_path, content)
shutil.move(tmp_path, path)
-def init_file(path, content=None):
+def init_file(path: str | Path, content=None):
if not os.path.exists(path):
write_json(path, content)
@@ -143,16 +144,6 @@ def get_username():
return os.environ.get('USERNAME') or os.environ.get('USER')
-def str_to_bool(val: str) -> bool:
- val = val.lower()
- if val in ('y', 'yes', 't', 'true', 'on', '1'):
- return True
- elif val in ('n', 'no', 'f', 'false', 'off', '0'):
- return False
- else:
- raise ValueError(f'Invalid truth value {val!r}')
-
-
def error_exit(error_payload: Any, exit_code: CLIExitCodes = CLIExitCodes.FAILURE) -> NoReturn:
"""Print error message and exit the program with specified exit code.
@@ -339,7 +330,7 @@ def cleanup_dir_content(folder: str) -> None:
shutil.rmtree(file_path)
-def safe_mkdir(path: str, print_res: bool = False) -> None:
+def safe_mkdir(path: str | Path, print_res: bool = False) -> None:
if os.path.exists(path):
logger.debug(f'Directory {path} already exists')
return
@@ -411,8 +402,8 @@ def convert(self, value, param, ctx):
IP_TYPE = IpType()
-def get_tmp_path(path: str) -> str:
- base, ext = os.path.splitext(path)
+def get_tmp_path(path: str | Path) -> str:
+ base, ext = os.path.splitext(str(path))
salt = uuid.uuid4().hex[:5]
return base + salt + '.tmp' + ext
@@ -425,10 +416,6 @@ def get_ssh_port(ssh_service_name='ssh'):
return DEFAULT_SSH_PORT
-def is_contract_address(value: str) -> bool:
- return bool(re.fullmatch(r'0x[a-fA-F0-9]{40}', value))
-
-
def is_btrfs_subvolume(path: str) -> bool:
"""Check if the given path is a Btrfs subvolume."""
try:
diff --git a/node_cli/utils/node_type.py b/node_cli/utils/node_type.py
index bf3f6d4f..754a1d69 100644
--- a/node_cli/utils/node_type.py
+++ b/node_cli/utils/node_type.py
@@ -20,9 +20,9 @@
from enum import Enum
-class NodeType(Enum):
- SKALE = 0
- FAIR = 1
+class NodeType(str, Enum):
+ SKALE = 'skale'
+ FAIR = 'fair'
class NodeMode(str, Enum):
diff --git a/node_cli/utils/print_formatters.py b/node_cli/utils/print_formatters.py
index 7edec113..1da07d51 100644
--- a/node_cli/utils/print_formatters.py
+++ b/node_cli/utils/print_formatters.py
@@ -339,9 +339,6 @@ def format_timestamp(value):
return str(value)
-1
-
-
def print_chain_record(record):
print(
inspect.cleandoc(f"""
diff --git a/node_cli/utils/settings.py b/node_cli/utils/settings.py
new file mode 100644
index 00000000..8e7f957c
--- /dev/null
+++ b/node_cli/utils/settings.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of node-cli
+#
+# Copyright (C) 2026 SKALE Labs
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import tomllib
+
+from dotenv.main import DotEnv
+
+from skale_core.settings import (
+ SETTINGS_MAP,
+ BaseNodeSettings,
+ FairBaseSettings,
+ FairSettings,
+ InternalSettings,
+ SkalePassiveSettings,
+ SkaleSettings,
+ write_internal_settings_file,
+ write_node_settings_file,
+)
+
+from node_cli.configs import INTERNAL_SETTINGS_PATH, NODE_SETTINGS_PATH, SKALE_DIR
+from node_cli.utils.node_type import NodeMode, NodeType
+
+InternalSettings.model_config['toml_file'] = INTERNAL_SETTINGS_PATH
+SkaleSettings.model_config['toml_file'] = NODE_SETTINGS_PATH
+SkalePassiveSettings.model_config['toml_file'] = NODE_SETTINGS_PATH
+FairSettings.model_config['toml_file'] = NODE_SETTINGS_PATH
+FairBaseSettings.model_config['toml_file'] = NODE_SETTINGS_PATH
+
+
+def load_config_file(filepath: str) -> dict:
+ if filepath.endswith('.toml'):
+ with open(filepath, 'rb') as f:
+ return tomllib.load(f)
+ return {k.lower(): v for k, v in DotEnv(filepath).dict().items()}
+
+
+def validate_and_save_node_settings(
+ config_filepath: str,
+ node_type: NodeType,
+ node_mode: NodeMode,
+) -> BaseNodeSettings:
+ data = load_config_file(config_filepath)
+ settings_type = SETTINGS_MAP[(node_type.value, node_mode.value)]
+ write_node_settings_file(path=NODE_SETTINGS_PATH, settings_type=settings_type, data=data)
+ return settings_type()
+
+
+def save_internal_settings(
+ node_type: NodeType,
+ node_mode: NodeMode,
+ backup_run: bool = False,
+ pull_config_for_schain: str | None = None,
+) -> None:
+ data = {
+ 'node_type': node_type.value,
+ 'node_mode': node_mode.value,
+ 'skale_dir_host': str(SKALE_DIR),
+ 'backup_run': backup_run,
+ 'pull_config_for_schain': pull_config_for_schain,
+ }
+ write_internal_settings_file(path=INTERNAL_SETTINGS_PATH, data=data)
diff --git a/pyproject.toml b/pyproject.toml
index cf813bfa..4ab3258d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,15 +10,13 @@ readme = "README.md"
requires-python = ">=3.13"
license = { file = "LICENSE" }
keywords = ["skale", "cli"]
-authors = [
- { name = "SKALE Labs", email = "support@skalelabs.com" }
-]
+authors = [{ name = "SKALE Labs", email = "support@skalelabs.com" }]
classifiers = [
- "Development Status :: 5 - Production/Stable",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: GNU Affero General Public License v3",
- "Natural Language :: English",
- "Programming Language :: Python :: 3.13",
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: GNU Affero General Public License v3",
+ "Natural Language :: English",
+ "Programming Language :: Python :: 3.13",
]
dependencies = [
@@ -40,13 +38,14 @@ dependencies = [
"MarkupSafe==3.0.3",
"Flask==3.1.2",
"itsdangerous==2.2.0",
- "cryptography==46.0.3",
+ "cryptography==46.0.5",
"filelock==3.20.0",
"sh==2.2.2",
"python-crontab==3.3.0",
"requests-mock==1.12.1",
- "redis==7.1.0",
- "PyInstaller==6.16.0",
+ "redis==7.1.1",
+ "PyInstaller==6.18.0",
+ "skale.py-core==7.13.dev1",
]
[project.urls]
@@ -78,11 +77,13 @@ target-version = "py313"
[tool.ruff.format]
quote-style = "single"
+[tool.uv]
+prerelease = "allow"
+
+
[tool.pytest.ini_options]
log_cli = false
log_cli_level = "INFO"
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
-filterwarnings = [
- "ignore::DeprecationWarning",
-]
+filterwarnings = ["ignore::DeprecationWarning"]
diff --git a/scripts/export_env.sh b/scripts/export_env.sh
new file mode 100644
index 00000000..af30b4ab
--- /dev/null
+++ b/scripts/export_env.sh
@@ -0,0 +1,8 @@
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+PROJECT_DIR=$(dirname $DIR)
+
+export LVMPY_LOG_DIR="$PROJECT_DIR/tests/"
+export HIDE_STREAM_LOG=true
+export TEST_HOME_DIR="$PROJECT_DIR/tests/"
+export GLOBAL_SKALE_DIR="$PROJECT_DIR/tests/etc/skale"
+export DOTENV_FILEPATH='tests/test-env'
\ No newline at end of file
diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh
index efc72c6c..1d3a3fe6 100755
--- a/scripts/run_tests.sh
+++ b/scripts/run_tests.sh
@@ -3,9 +3,6 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PROJECT_DIR=$(dirname $DIR)
-LVMPY_LOG_DIR="$PROJECT_DIR/tests/" \
- HIDE_STREAM_LOG=true \
- TEST_HOME_DIR="$PROJECT_DIR/tests/" \
- GLOBAL_SKALE_DIR="$PROJECT_DIR/tests/etc/skale" \
- DOTENV_FILEPATH='tests/test-env' \
- py.test --cov=$PROJECT_DIR/ --ignore=tests/core/nftables_test.py --ignore=tests/core/migration_test.py tests/ $@
+. "$DIR/export_env.sh"
+
+py.test --cov=$PROJECT_DIR/ --ignore=tests/core/nftables_test.py --ignore=tests/core/migration_test.py tests/ $@
diff --git a/tests/.skale/config/docker-compose-fair.yml b/tests/.skale/config/docker-compose-fair.yml
new file mode 100644
index 00000000..c09f2c08
--- /dev/null
+++ b/tests/.skale/config/docker-compose-fair.yml
@@ -0,0 +1,5 @@
+services:
+ test:
+ container_name: test
+ image: alpine:latest
+ network_mode: host
diff --git a/tests/cli/fair_cli_test.py b/tests/cli/fair_cli_test.py
index 3c6b9ef5..14e2aee9 100644
--- a/tests/cli/fair_cli_test.py
+++ b/tests/cli/fair_cli_test.py
@@ -20,6 +20,7 @@
from tests.helper import run_command, subprocess_run_mock
from tests.resources_test import BIG_DISK_SIZE
+
@mock.patch('node_cli.cli.fair_node.restore_fair')
def test_fair_node_restore(mock_restore_core, valid_env_file, tmp_path):
runner = CliRunner()
@@ -105,7 +106,7 @@ def test_fair_node_migrate(mock_migrate_core, valid_env_file):
result = runner.invoke(migrate_node, ['--yes', valid_env_file])
assert result.exit_code == 0, f'Output: {result.output}\nException: {result.exception}'
- mock_migrate_core.assert_called_once_with(env_filepath=valid_env_file)
+ mock_migrate_core.assert_called_once_with(config_file=valid_env_file)
@mock.patch('node_cli.cli.fair_node.exit_fair')
@@ -136,4 +137,5 @@ def test_cleanup_node(mocked_g_config, inited_node):
result = run_command(cleanup_node, ['--yes'])
assert result.exit_code == 0
cleanup_mock.assert_called_once_with(
- node_mode=NodeMode.ACTIVE, prune=False, env={'SCHAIN_NAME': 'test'})
+ node_mode=NodeMode.ACTIVE, prune=False, compose_env={'SCHAIN_NAME': 'test'}
+ )
diff --git a/tests/cli/fair_passive_node_test.py b/tests/cli/fair_passive_node_test.py
index a8b7818a..00c61ca0 100644
--- a/tests/cli/fair_passive_node_test.py
+++ b/tests/cli/fair_passive_node_test.py
@@ -15,15 +15,12 @@
init_default_logger()
-def test_init_fair_passive(mocked_g_config, tmp_path):
- env_file = tmp_path / 'test-env'
- env_file.write_text('')
+def test_init_fair_passive(mocked_g_config, fair_passive_settings, fair_passive_user_conf):
pathlib.Path(SKALE_DIR).mkdir(parents=True, exist_ok=True)
with (
mock.patch('subprocess.run', new=subprocess_run_mock),
mock.patch('node_cli.fair.common.init_fair_op', return_value=True),
mock.patch('node_cli.fair.common.compose_node_env', return_value={}),
- mock.patch('node_cli.fair.common.save_env_params'),
mock.patch('node_cli.fair.passive.setup_fair_passive'),
mock.patch('node_cli.fair.common.time.sleep'),
mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True),
@@ -34,7 +31,7 @@ def test_init_fair_passive(mocked_g_config, tmp_path):
result = run_command(
init_passive_node,
[
- env_file.as_posix(),
+ fair_passive_user_conf.as_posix(),
'--id',
'1',
],
@@ -42,15 +39,14 @@ def test_init_fair_passive(mocked_g_config, tmp_path):
assert result.exit_code == 0
-def test_init_fair_passive_snapshot_any(mocked_g_config, tmp_path):
- env_file = tmp_path / 'test-env'
- env_file.write_text('')
+def test_init_fair_passive_snapshot_any(
+ mocked_g_config, fair_passive_settings, fair_passive_user_conf
+):
pathlib.Path(SKALE_DIR).mkdir(parents=True, exist_ok=True)
with (
mock.patch('subprocess.run', new=subprocess_run_mock),
mock.patch('node_cli.fair.common.init_fair_op', return_value=True),
mock.patch('node_cli.fair.common.compose_node_env', return_value={}),
- mock.patch('node_cli.fair.common.save_env_params'),
mock.patch('node_cli.fair.passive.setup_fair_passive'),
mock.patch('node_cli.fair.common.time.sleep'),
mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True),
@@ -61,7 +57,7 @@ def test_init_fair_passive_snapshot_any(mocked_g_config, tmp_path):
result = run_command(
init_passive_node,
[
- env_file.as_posix(),
+ fair_passive_user_conf.as_posix(),
'--id',
'2',
'--snapshot',
@@ -71,9 +67,9 @@ def test_init_fair_passive_snapshot_any(mocked_g_config, tmp_path):
assert result.exit_code == 0
-def test_update_fair_passive(mocked_g_config, tmp_path, clean_node_options):
- env_file = tmp_path / 'test-env'
- env_file.write_text('')
+def test_update_fair_passive(
+ mocked_g_config, fair_passive_settings, fair_passive_user_conf, clean_node_options
+):
pathlib.Path(NODE_DATA_PATH).mkdir(parents=True, exist_ok=True)
with (
mock.patch('subprocess.run', new=subprocess_run_mock),
@@ -84,7 +80,7 @@ def test_update_fair_passive(mocked_g_config, tmp_path, clean_node_options):
mock.patch('node_cli.operations.base.configure_nftables'),
mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True),
):
- result = run_command(update_node, [env_file.as_posix(), '--yes'])
+ result = run_command(update_node, [fair_passive_user_conf.as_posix(), '--yes'])
assert result.exit_code == 0
@@ -107,4 +103,5 @@ def test_cleanup_node(mocked_g_config):
result = run_command(cleanup_node, ['--yes'])
assert result.exit_code == 0
cleanup_mock.assert_called_once_with(
- node_mode=NodeMode.PASSIVE, prune=False, env={'SCHAIN_NAME': 'test'})
+ node_mode=NodeMode.PASSIVE, compose_env={'SCHAIN_NAME': 'test'}, prune=False
+ )
diff --git a/tests/cli/node_test.py b/tests/cli/node_test.py
index b31cddc0..a0c064f0 100644
--- a/tests/cli/node_test.py
+++ b/tests/cli/node_test.py
@@ -347,18 +347,17 @@ def test_restore(request, node_type, node_mode, test_user_conf, mocked_g_config,
return_value=CliMeta(version='2.4.0', config_stream='3.0.2'),
),
patch('node_cli.operations.base.configure_nftables'),
- patch('node_cli.configs.user.validate_alias_or_address'),
):
user_conf_path = request.getfixturevalue(test_user_conf).as_posix()
result = run_command(restore_node, [backup_path, user_conf_path])
assert result.exit_code == 0
assert 'Node is restored from backup\n' in result.output # noqa
- assert mock_restore_op.call_args[0][0].get('BACKUP_RUN') == 'True'
+ assert mock_restore_op.call_args.kwargs.get('backup_run') is True
result = run_command(restore_node, [backup_path, user_conf_path, '--no-snapshot'])
assert result.exit_code == 0
assert 'Node is restored from backup\n' in result.output # noqa
- assert mock_restore_op.call_args[0][0].get('BACKUP_RUN') is None
+ assert mock_restore_op.call_args.kwargs.get('backup_run') is False
def test_maintenance_on():
@@ -385,14 +384,12 @@ def test_maintenance_off(mocked_g_config):
)
-def test_turn_off_maintenance_on(mocked_g_config, regular_user_conf, active_node_option):
+def test_turn_off_maintenance_on(mocked_g_config, regular_user_conf, active_node_option, skale_active_settings):
resp_mock = response_mock(requests.codes.ok, {'status': 'ok', 'payload': None})
with (
mock.patch('subprocess.run', new=subprocess_run_mock),
- mock.patch('node_cli.core.node.SKALE_DIR_ENV_FILEPATH', regular_user_conf.as_posix()),
mock.patch('node_cli.core.node.turn_off_op'),
mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True),
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
mock.patch('node_cli.cli.node.TYPE', NodeType.SKALE),
):
result = run_command_mock(
@@ -418,15 +415,13 @@ def test_turn_off_maintenance_on(mocked_g_config, regular_user_conf, active_node
assert result.exit_code == CLIExitCodes.UNSAFE_UPDATE
-def test_turn_on_maintenance_off(mocked_g_config, regular_user_conf, active_node_option):
+def test_turn_on_maintenance_off(mocked_g_config, regular_user_conf, active_node_option, skale_active_settings):
resp_mock = response_mock(requests.codes.ok, {'status': 'ok', 'payload': None})
with (
mock.patch('subprocess.run', new=subprocess_run_mock),
- mock.patch('node_cli.core.node.get_flask_secret_key'),
mock.patch('node_cli.core.node.turn_on_op'),
mock.patch('node_cli.core.node.is_base_containers_alive'),
mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True),
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
mock.patch('node_cli.cli.node.TYPE', NodeType.SKALE),
):
result = run_command_mock(
@@ -475,6 +470,7 @@ def test_node_version(meta_file_v2):
== "{'version': '0.1.1', 'config_stream': 'develop', 'docker_lvmpy_version': '1.1.2'}\n"
)
+
def test_cleanup_node(mocked_g_config):
pathlib.Path(SKALE_DIR).mkdir(parents=True, exist_ok=True)
@@ -493,4 +489,4 @@ def test_cleanup_node(mocked_g_config):
):
result = run_command(cleanup_node, ['--yes'])
assert result.exit_code == 0
- cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, env={})
\ No newline at end of file
+ cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, compose_env={})
diff --git a/tests/cli/passive_node_test.py b/tests/cli/passive_node_test.py
index c9ea9527..e8419220 100644
--- a/tests/cli/passive_node_test.py
+++ b/tests/cli/passive_node_test.py
@@ -45,7 +45,6 @@ def test_init_passive(mocked_g_config, clean_node_options, passive_user_conf):
mock.patch('node_cli.core.resources.get_disk_size', return_value=BIG_DISK_SIZE),
mock.patch('node_cli.operations.base.configure_nftables'),
mock.patch('node_cli.utils.decorators.is_node_inited', return_value=False),
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
):
result = run_command(_init_passive, [passive_user_conf.as_posix()])
@@ -66,9 +65,11 @@ def test_init_passive_archive(mocked_g_config, clean_node_options, passive_user_
mock.patch('node_cli.operations.base.sync_skale_node'),
mock.patch('node_cli.operations.base.configure_docker'),
mock.patch('node_cli.operations.base.prepare_host'),
+ mock.patch('node_cli.operations.base.save_internal_settings'),
+ mock.patch('node_cli.operations.base.run_host_checks', return_value=[]),
mock.patch('node_cli.operations.base.ensure_filestorage_mapping'),
- mock.patch('node_cli.operations.base.link_env_file'),
mock.patch('node_cli.operations.base.generate_nginx_config'),
+ mock.patch('node_cli.operations.base.get_settings'),
mock.patch('node_cli.operations.base.prepare_block_device'),
mock.patch('node_cli.operations.base.CliMetaManager.update_meta'),
mock.patch('node_cli.operations.base.update_resource_allocation'),
@@ -77,7 +78,6 @@ def test_init_passive_archive(mocked_g_config, clean_node_options, passive_user_
mock.patch('node_cli.core.resources.get_disk_size', return_value=BIG_DISK_SIZE),
mock.patch('node_cli.operations.base.configure_nftables'),
mock.patch('node_cli.utils.decorators.is_node_inited', return_value=False),
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
mock.patch('node_cli.cli.node.TYPE', NodeType.SKALE),
):
result = run_command(_init_passive, [passive_user_conf.as_posix(), '--archive'])
@@ -121,7 +121,6 @@ def test_update_passive(passive_user_conf, mocked_g_config):
'node_cli.core.node.CliMetaManager.get_meta_info',
return_value=CliMeta(version='2.6.0', config_stream='3.0.2'),
),
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
):
result = run_command(_update_passive, [passive_user_conf.as_posix(), '--yes'])
assert result.exit_code == 0
@@ -146,4 +145,5 @@ def test_cleanup_node(mocked_g_config):
result = run_command(cleanup_node, ['--yes'])
assert result.exit_code == 0
cleanup_mock.assert_called_once_with(
- node_mode=NodeMode.PASSIVE, prune=False, env={'SCHAIN_NAME': 'test'})
+ node_mode=NodeMode.PASSIVE, prune=False, compose_env={'SCHAIN_NAME': 'test'}
+ )
diff --git a/tests/configs/configs_env_validate_test.py b/tests/configs/configs_env_validate_test.py
deleted file mode 100644
index 2db057ff..00000000
--- a/tests/configs/configs_env_validate_test.py
+++ /dev/null
@@ -1,189 +0,0 @@
-import os
-from typing import Optional
-
-import pytest
-import requests
-
-from node_cli.configs.alias_address_validation import (
- ContractType,
- get_chain_id,
- get_network_metadata,
- validate_alias_or_address,
- validate_contract_address,
- validate_contract_alias,
-)
-from node_cli.configs.user import (
- ALLOWED_ENV_TYPES,
- FairBootUserConfig,
- FairUserConfig,
- PassiveFairUserConfig,
- SkaleUserConfig,
- PassiveSkaleUserConfig,
- get_user_config_class,
- get_validated_user_config,
- validate_env_type,
-)
-from node_cli.utils.node_type import NodeType, NodeMode
-
-ENDPOINT = 'http://localhost:8545'
-
-
-class FakeResponse:
- def __init__(self, status_code: int, json_data: Optional[dict] = None):
- self.status_code = status_code
- self._json_data = json_data or {}
-
- def json(self):
- return self._json_data
-
-
-@pytest.mark.parametrize(
- 'node_type, node_mode, is_fair_boot, expected_type',
- [
- (NodeType.SKALE, NodeMode.ACTIVE, False, SkaleUserConfig),
- (NodeType.SKALE, NodeMode.PASSIVE, False, PassiveSkaleUserConfig),
- (NodeType.FAIR, NodeMode.ACTIVE, True, FairBootUserConfig),
- (NodeType.FAIR, NodeMode.ACTIVE, False, FairUserConfig),
- (NodeType.FAIR, NodeMode.PASSIVE, False, PassiveFairUserConfig),
- ],
- ids=['skale_active', 'skale_passive', 'fair_boot', 'fair_active', 'fair_passive'],
-)
-def test_build_env_params_keys(node_type, node_mode, is_fair_boot, expected_type):
- env_type = get_user_config_class(
- node_type=node_type, node_mode=node_mode, is_fair_boot=is_fair_boot
- )
- assert env_type == expected_type
-
-
-@pytest.mark.parametrize(
- 'env_types, should_fail',
- [
- (ALLOWED_ENV_TYPES, False),
- (['invalid'], True),
- ],
- ids=[
- 'correct_env',
- 'invalid_env',
- ],
-)
-def test_env_types(env_types, should_fail):
- for env_type in env_types:
- if should_fail:
- with pytest.raises(SystemExit):
- validate_env_type(env_type=env_type)
- else:
- validate_env_type(env_type=env_type)
-
-
-def test_get_chain_id_success(monkeypatch):
- fake_response = FakeResponse(200, {'result': '0x1'})
-
- def fake_post(url, json):
- return fake_response
-
- monkeypatch.setattr(requests, 'post', fake_post)
- assert get_chain_id(ENDPOINT) == 1
-
-
-def test_get_chain_id_failure(monkeypatch):
- fake_response = FakeResponse(404)
-
- def fake_post(url, json):
- return fake_response
-
- monkeypatch.setattr(requests, 'post', fake_post)
- with pytest.raises(SystemExit):
- get_chain_id(ENDPOINT)
-
-
-@pytest.mark.parametrize(
- 'metadata,status_code,should_raise',
- [
- ({'networks': [{'chainId': 1, 'path': 'mainnet'}]}, 200, False),
- (None, 404, True),
- ],
-)
-def test_get_network_metadata(requests_mock, metadata, status_code, should_raise):
- metadata_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/metadata.json'
- requests_mock.get(metadata_url, json=metadata, status_code=status_code)
-
- if should_raise:
- with pytest.raises(SystemExit):
- get_network_metadata()
- else:
- assert get_network_metadata() == metadata
-
-
-@pytest.mark.parametrize(
- 'code,should_raise',
- [
- ('0x123', False),
- ('0x', True),
- ],
-)
-def test_validate_contract_address(requests_mock, code, should_raise):
- requests_mock.post(ENDPOINT, json={'result': code})
- addr = '0x' + 'a' * 40
- if should_raise:
- with pytest.raises(SystemExit):
- validate_contract_address(addr, ENDPOINT)
- else:
- validate_contract_address(addr, ENDPOINT)
-
-
-@pytest.mark.parametrize(
- 'networks,should_raise',
- [
- ([{'chainId': 1, 'path': 'mainnet'}], False),
- ([], True),
- ],
-)
-def test_validate_contract_alias(requests_mock, networks, should_raise):
- requests_mock.post(ENDPOINT, json={'result': '0x1'})
- metadata_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/metadata.json'
- requests_mock.get(metadata_url, json={'networks': networks}, status_code=200)
-
- if not should_raise:
- alias_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/mainnet/skale-manager/test-alias.json'
- requests_mock.get(alias_url, status_code=200)
- validate_contract_alias('test-alias', ContractType.MANAGER, ENDPOINT)
- else:
- with pytest.raises(SystemExit):
- validate_contract_alias('test-alias', ContractType.MANAGER, ENDPOINT)
-
-
-def test_validate_env_alias_or_address_with_address(requests_mock):
- addr = '0x' + 'b' * 40
- requests_mock.post(ENDPOINT, json={'result': '0x1'})
- validate_alias_or_address(addr, ContractType.IMA, ENDPOINT)
-
-
-def test_validate_env_alias_or_address_with_alias(requests_mock):
- requests_mock.post(ENDPOINT, json={'result': '0x1'})
- metadata_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/metadata.json'
- metadata = {'networks': [{'chainId': 1, 'path': 'mainnet'}]}
- requests_mock.get(metadata_url, json=metadata, status_code=200)
- alias_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/mainnet/mainnet-ima/test-alias.json'
- requests_mock.get(alias_url, status_code=200)
- validate_alias_or_address('test-alias', ContractType.IMA, ENDPOINT)
-
-
-def test_get_validated_env_config_missing_file():
- with pytest.raises(SystemExit):
- get_validated_user_config(
- env_filepath='nonexistent.env', node_type=NodeType.SKALE, node_mode=NodeMode.ACTIVE
- )
-
-
-def test_get_validated_env_config_unreadable_file(tmp_path):
- env_file = tmp_path / 'unreadable.env'
- env_file.touch()
- original_mode = env_file.stat().st_mode
- try:
- os.chmod(env_file, 0o000)
- with pytest.raises(PermissionError):
- get_validated_user_config(
- env_filepath=str(env_file), node_type=NodeType.SKALE, node_mode=NodeMode.ACTIVE
- )
- finally:
- os.chmod(env_file, original_mode)
diff --git a/tests/conftest.py b/tests/conftest.py
index 5434e697..8b610d96 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -48,8 +48,40 @@
from node_cli.utils.docker_utils import docker_client
from node_cli.utils.global_config import generate_g_config_file
from node_cli.utils.node_type import NodeMode
+from tests.fixtures.settings import ( # noqa: F401
+ INTERNAL_FAIR_ACTIVE,
+ INTERNAL_FAIR_PASSIVE,
+ INTERNAL_SKALE_ACTIVE,
+ INTERNAL_SKALE_PASSIVE,
+ NODE_FAIR_ACTIVE,
+ NODE_FAIR_PASSIVE,
+ NODE_SKALE_ACTIVE,
+ NODE_SKALE_PASSIVE,
+ _cleanup_settings,
+ _write_settings,
+ fair_active_settings,
+ fair_passive_settings,
+ skale_active_settings,
+ skale_passive_settings,
+)
from tests.helper import TEST_META_V1, TEST_META_V2, TEST_META_V3, TEST_SCHAINS_MNT_DIR_SINGLE_CHAIN
+TIMEOUT_PATCHES = [
+ 'node_cli.configs.TM_INIT_TIMEOUT',
+ 'node_cli.configs.RESTORE_SLEEP_TIMEOUT',
+ 'node_cli.configs.INIT_TIMEOUT',
+]
+
+
+@pytest.fixture(autouse=True, scope='session')
+def _fast_timeouts():
+ patchers = [mock.patch(target, 1) for target in TIMEOUT_PATCHES]
+ for p in patchers:
+ p.start()
+ yield
+ for p in patchers:
+ p.stop()
+
@pytest.fixture()
def tmp_dir_path():
@@ -290,20 +322,13 @@ def tmp_passive_datadir():
def valid_env_params():
return {
'ENDPOINT': 'http://localhost:8545',
- 'IMA_ENDPOINT': 'http://127.0.01',
- 'DB_USER': 'user',
- 'DB_PASSWORD': 'pass',
- 'DB_PORT': '3307',
'NODE_VERSION': 'master',
'FILEBEAT_HOST': '127.0.0.1:3010',
- 'SGX_SERVER_URL': 'http://127.0.0.1',
+ 'SGX_URL': 'http://127.0.0.1',
'BLOCK_DEVICE': '/dev/sss',
- 'DOCKER_LVMPY_VERSION': 'master',
'ENV_TYPE': 'devnet',
- 'SCHAIN_NAME': 'test',
'ENFORCE_BTRFS': 'False',
- 'MANAGER_CONTRACTS': 'test-manager',
- 'IMA_CONTRACTS': 'test-ima',
+ 'FAIR_CONTRACTS': 'test-fair',
}
@@ -319,6 +344,7 @@ def valid_env_file(valid_env_params):
finally:
if file_name:
os.unlink(file_name)
+ _cleanup_settings()
@pytest.fixture
@@ -356,12 +382,13 @@ def set_env_var(name, value):
@pytest.fixture
def regular_user_conf(tmp_path):
test_env_path = pathlib.Path(tmp_path / 'test-env')
+ _write_settings(INTERNAL_SKALE_ACTIVE, NODE_SKALE_ACTIVE)
try:
test_env = """
ENDPOINT=http://localhost:8545
NODE_VERSION='main'
FILEBEAT_HOST=127.0.0.1:3010
- SGX_SERVER_URL=http://127.0.0.1
+ SGX_URL=http://127.0.0.1
BLOCK_DEVICE=/dev/sss
DOCKER_LVMPY_VERSION='master'
ENV_TYPE='devnet'
@@ -373,17 +400,19 @@ def regular_user_conf(tmp_path):
yield test_env_path
finally:
test_env_path.unlink()
+ _cleanup_settings()
@pytest.fixture
def fair_user_conf(tmp_path):
test_env_path = pathlib.Path(tmp_path / 'test-env')
+ _write_settings(INTERNAL_FAIR_ACTIVE, NODE_FAIR_ACTIVE)
try:
test_env = """
- BOOT_ENDPOINT=http://localhost:8545
+ ENDPOINT=http://localhost:8545
NODE_VERSION='main'
FILEBEAT_HOST=127.0.0.1:3010
- SGX_SERVER_URL=http://127.0.0.1
+ SGX_URL=http://127.0.0.1
BLOCK_DEVICE=/dev/sss
ENV_TYPE='devnet'
ENFORCE_BTRFS=False
@@ -394,32 +423,57 @@ def fair_user_conf(tmp_path):
yield test_env_path
finally:
test_env_path.unlink()
+ _cleanup_settings()
@pytest.fixture
def fair_boot_user_conf(tmp_path):
test_env_path = pathlib.Path(tmp_path / 'test-env')
+ _write_settings(INTERNAL_FAIR_ACTIVE, NODE_FAIR_ACTIVE)
try:
test_env = """
ENDPOINT=http://localhost:8545
NODE_VERSION='main'
FILEBEAT_HOST=127.0.0.1:3010
- SGX_SERVER_URL=http://127.0.0.1
+ SGX_URL=http://127.0.0.1
BLOCK_DEVICE=/dev/sss
ENV_TYPE='devnet'
- MANAGER_CONTRACTS='test-manager'
- IMA_CONTRACTS='test-ima'
+ FAIR_CONTRACTS='test-fair'
+ """
+ with open(test_env_path, 'w') as env_file:
+ env_file.write(test_env)
+ yield test_env_path
+ finally:
+ test_env_path.unlink()
+ _cleanup_settings()
+
+
+@pytest.fixture
+def fair_passive_user_conf(tmp_path):
+ test_env_path = pathlib.Path(tmp_path / 'test-env')
+ _write_settings(INTERNAL_FAIR_PASSIVE, NODE_FAIR_PASSIVE)
+ try:
+ test_env = """
+ ENDPOINT=http://localhost:8545
+ NODE_VERSION='main'
+ FILEBEAT_HOST=127.0.0.1:3010
+ BLOCK_DEVICE=/dev/sss
+ ENV_TYPE='devnet'
+ ENFORCE_BTRFS=False
+ FAIR_CONTRACTS='test-fair'
"""
with open(test_env_path, 'w') as env_file:
env_file.write(test_env)
yield test_env_path
finally:
test_env_path.unlink()
+ _cleanup_settings()
@pytest.fixture
def passive_user_conf(tmp_path):
test_env_path = pathlib.Path(tmp_path / 'test-env')
+ _write_settings(INTERNAL_SKALE_PASSIVE, NODE_SKALE_PASSIVE)
try:
test_env = """
ENDPOINT=http://localhost:8545
@@ -430,12 +484,14 @@ def passive_user_conf(tmp_path):
SCHAIN_NAME='test-schain'
ENFORCE_BTRFS=False
MANAGER_CONTRACTS='test-manager'
+ IMA_CONTRACTS='test-ima'
"""
with open(test_env_path, 'w') as env_file:
env_file.write(test_env)
yield test_env_path
finally:
test_env_path.unlink()
+ _cleanup_settings()
@pytest.fixture
diff --git a/tests/core/core_node_test.py b/tests/core/core_node_test.py
index 953e7ca5..b28719d4 100644
--- a/tests/core/core_node_test.py
+++ b/tests/core/core_node_test.py
@@ -10,9 +10,13 @@
import pytest
import requests
-from node_cli.configs import NODE_DATA_PATH, SCHAINS_MNT_DIR_REGULAR, SCHAINS_MNT_DIR_SINGLE_CHAIN
+from node_cli.configs import (
+ NODE_DATA_PATH,
+ SCHAINS_MNT_DIR_REGULAR,
+ SCHAINS_MNT_DIR_SINGLE_CHAIN,
+)
from node_cli.configs.resource_allocation import RESOURCE_ALLOCATION_FILEPATH
-from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH
+
from node_cli.core.node import (
cleanup,
compose_node_env,
@@ -165,112 +169,38 @@ def test_is_base_containers_alive_empty(node_type, node_mode, is_boot):
@pytest.mark.parametrize(
- (
- 'node_type, node_mode, test_user_conf, is_boot, inited_node, sync_schains,'
- 'expected_mnt_dir, expect_flask_key, expect_backup_run'
- ),
+ 'node_type, node_mode, expected_mnt_dir',
[
(
NodeType.SKALE,
NodeMode.ACTIVE,
- 'regular_user_conf',
- False,
- True,
- False,
- SCHAINS_MNT_DIR_REGULAR,
- True,
- False,
- ),
- (
- NodeType.SKALE,
- NodeMode.ACTIVE,
- 'regular_user_conf',
- False,
- True,
- True,
SCHAINS_MNT_DIR_REGULAR,
- True,
- True,
),
(
NodeType.SKALE,
NodeMode.PASSIVE,
- 'passive_user_conf',
- False,
- False,
- False,
- SCHAINS_MNT_DIR_SINGLE_CHAIN,
- False,
- False,
- ),
- (
- NodeType.FAIR,
- NodeMode.ACTIVE,
- 'fair_boot_user_conf',
- True,
- True,
- False,
SCHAINS_MNT_DIR_SINGLE_CHAIN,
- True,
- False,
),
(
NodeType.FAIR,
NodeMode.ACTIVE,
- 'fair_user_conf',
- False,
- True,
- False,
SCHAINS_MNT_DIR_SINGLE_CHAIN,
- True,
- False,
),
],
ids=[
'regular',
- 'regular_passive_flag',
'passive',
- 'fair_boot',
- 'fair_regular',
+ 'fair',
],
)
-def test_compose_node_env(
- request,
- node_type,
- node_mode,
- test_user_conf,
- is_boot,
- inited_node,
- sync_schains,
- expected_mnt_dir,
- expect_flask_key,
- expect_backup_run,
-):
- user_config_path = request.getfixturevalue(test_user_conf)
-
- with (
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
- mock.patch('node_cli.core.node.save_env_params'),
- mock.patch('node_cli.core.node.get_flask_secret_key', return_value='mock_secret'),
- ):
- result_env = compose_node_env(
- env_filepath=user_config_path.as_posix(),
- inited_node=inited_node,
- sync_schains=sync_schains,
- node_type=node_type,
- node_mode=node_mode,
- is_fair_boot=is_boot,
- save=True,
- )
+def test_compose_node_env(node_type, node_mode, expected_mnt_dir, regular_user_conf):
+ result_env = compose_node_env(
+ node_type=node_type,
+ node_mode=node_mode,
+ )
assert result_env['SCHAINS_MNT_DIR'] == expected_mnt_dir
- assert (
- 'FLASK_SECRET_KEY' in result_env and result_env['FLASK_SECRET_KEY'] is not None
- ) == expect_flask_key
- if expect_flask_key:
- assert result_env['FLASK_SECRET_KEY'] == 'mock_secret'
- should_have_backup = sync_schains and node_mode != NodeMode.PASSIVE
- assert ('BACKUP_RUN' in result_env and result_env['BACKUP_RUN'] == 'True') == should_have_backup
+ assert 'BACKUP_RUN' not in result_env
@pytest.fixture
@@ -346,9 +276,8 @@ def test_init_node(regular_user_conf, no_resource_file): # todo: write new init
mock.patch('node_cli.core.node.init_op'),
mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True),
mock.patch('node_cli.utils.helper.post_request', resp_mock),
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
):
- init(env_filepath=regular_user_conf.as_posix(), node_type=NodeType.SKALE)
+ init(config_file=regular_user_conf.as_posix(), node_type=NodeType.SKALE)
assert os.path.isfile(RESOURCE_ALLOCATION_FILEPATH)
@@ -358,8 +287,6 @@ def test_update_node(regular_user_conf, mocked_g_config, resource_file, inited_n
with (
mock.patch('subprocess.run', new=subprocess_run_mock),
mock.patch('node_cli.core.node.update_op'),
- mock.patch('node_cli.core.node.get_flask_secret_key'),
- mock.patch('node_cli.core.node.save_env_params'),
mock.patch('node_cli.operations.base.configure_nftables'),
mock.patch('node_cli.core.host.prepare_host'),
mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True),
@@ -370,7 +297,6 @@ def test_update_node(regular_user_conf, mocked_g_config, resource_file, inited_n
'node_cli.core.node.CliMetaManager.get_meta_info',
return_value=CliMeta(version='2.6.0', config_stream='3.0.2'),
),
- mock.patch('node_cli.configs.user.validate_alias_or_address'),
):
with mock.patch(
'node_cli.utils.helper.requests.get', return_value=safe_update_api_response()
@@ -492,12 +418,7 @@ def test_cleanup_success(
cleanup(node_mode=NodeMode.ACTIVE)
- mock_compose_env.assert_called_once_with(
- SKALE_DIR_ENV_FILEPATH,
- save=False,
- node_type=NodeType.SKALE,
- node_mode=NodeMode.ACTIVE,
- skip_user_conf_validation=True,
- )
+ mock_compose_env.assert_called_once_with(NodeType.SKALE, NodeMode.ACTIVE)
mock_cleanup_skale_op.assert_called_once_with(
- node_mode=NodeMode.ACTIVE, env=mock_env, prune=False)
\ No newline at end of file
+ node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False
+ )
diff --git a/tests/fair/fair_node_test.py b/tests/fair/fair_node_test.py
index a0408caa..6554cd3f 100644
--- a/tests/fair/fair_node_test.py
+++ b/tests/fair/fair_node_test.py
@@ -2,8 +2,6 @@
import pytest
-from node_cli.configs import SKALE_DIR
-from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH
from node_cli.fair.boot import init as init_boot
from node_cli.fair.boot import update
from node_cli.fair.common import cleanup
@@ -14,11 +12,9 @@
@mock.patch('node_cli.fair.active.time.sleep')
@mock.patch('node_cli.fair.active.restore_fair_op')
-@mock.patch('node_cli.fair.active.save_env_params')
@mock.patch('node_cli.fair.active.compose_node_env')
def test_restore_fair(
mock_compose_env,
- mock_save_env,
mock_restore_op,
mock_sleep,
valid_env_file,
@@ -32,14 +28,11 @@ def test_restore_fair(
restore(backup_path, valid_env_file)
- mock_compose_env.assert_called_once_with(
- valid_env_file, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE
- )
- mock_save_env.assert_called_once_with(valid_env_file)
- expected_env = {**mock_env, 'SKALE_DIR': SKALE_DIR}
+ mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
mock_restore_op.assert_called_once_with(
node_mode=NodeMode.ACTIVE,
- env=expected_env,
+ settings=mock.ANY,
+ compose_env=mock_env,
backup_path=backup_path,
config_only=False,
)
@@ -63,16 +56,11 @@ def test_init_fair_boot(
init_boot(valid_env_file)
- mock_compose_env.assert_called_once_with(
- valid_env_file,
- node_type=NodeType.FAIR,
- node_mode=NodeMode.ACTIVE,
- is_fair_boot=True,
- )
+ mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
mock_init_op.assert_called_once_with(
- valid_env_file,
- mock_env,
- NodeMode.ACTIVE,
+ settings=mock.ANY,
+ compose_env=mock_env,
+ node_mode=NodeMode.ACTIVE,
)
mock_sleep.assert_called_once()
mock_is_alive.assert_called_once_with(
@@ -103,18 +91,10 @@ def test_update_fair_boot(
update(valid_env_file, pull_config_for_schain)
- mock_compose_env.assert_called_once_with(
- valid_env_file,
- inited_node=True,
- sync_schains=False,
- pull_config_for_schain=pull_config_for_schain,
- node_type=NodeType.FAIR,
- node_mode=NodeMode.ACTIVE,
- is_fair_boot=True,
- )
+ mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
mock_update_op.assert_called_once_with(
- valid_env_file,
- mock_env,
+ settings=mock.ANY,
+ compose_env=mock_env,
node_mode=NodeMode.ACTIVE,
)
mock_sleep.assert_called_once()
@@ -141,16 +121,10 @@ def test_migrate_from_boot(
migrate_from_boot(valid_env_file)
- mock_compose_env.assert_called_once_with(
- valid_env_file,
- inited_node=True,
- sync_schains=False,
- node_type=NodeType.FAIR,
- node_mode=NodeMode.ACTIVE,
- )
+ mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
mock_migrate_op.assert_called_once_with(
- valid_env_file,
- mock_env,
+ settings=mock.ANY,
+ compose_env=mock_env,
node_mode=NodeMode.ACTIVE,
update_type=FairUpdateType.FROM_BOOT,
force_skaled_start=False,
@@ -164,25 +138,20 @@ def test_cleanup_success(
mock_compose_env,
mock_cleanup_fair_op,
mock_is_user_valid,
- inited_node,
resource_alloc,
meta_file_v3,
active_node_option,
+ inited_node,
):
mock_env = {'ENV_TYPE': 'devnet'}
mock_compose_env.return_value = mock_env
cleanup(node_mode=NodeMode.ACTIVE)
- mock_compose_env.assert_called_once_with(
- SKALE_DIR_ENV_FILEPATH,
- save=False,
- node_type=NodeType.FAIR,
- node_mode=NodeMode.ACTIVE,
- skip_user_conf_validation=True,
- )
+ mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE)
mock_cleanup_fair_op.assert_called_once_with(
- node_mode=NodeMode.ACTIVE, env=mock_env, prune=False)
+ node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False
+ )
@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True)
@@ -192,10 +161,10 @@ def test_cleanup_calls_operations_in_correct_order(
mock_compose_env,
mock_cleanup_fair_op,
mock_is_user_valid,
- inited_node,
resource_alloc,
meta_file_v3,
active_node_option,
+ inited_node,
):
from node_cli.fair.common import cleanup
@@ -210,12 +179,10 @@ def test_cleanup_calls_operations_in_correct_order(
expected_calls = [
mock.call.compose_env(
- mock.ANY,
- save=False,
- node_type=mock.ANY,
+ node_type=NodeType.FAIR,
node_mode=NodeMode.ACTIVE,
- skip_user_conf_validation=True),
- mock.call.cleanup_fair_op(node_mode=NodeMode.ACTIVE, env=mock_env, prune=False),
+ ),
+ mock.call.cleanup_fair_op(node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False),
]
manager.assert_has_calls(expected_calls, any_order=False)
@@ -227,10 +194,10 @@ def test_cleanup_continues_after_fair_op_error(
mock_compose_env,
mock_cleanup_fair_op,
mock_is_user_valid,
- inited_node,
resource_alloc,
meta_file_v3,
active_node_option,
+ inited_node,
):
mock_env = {'ENV_TYPE': 'devnet'}
mock_compose_env.return_value = mock_env
@@ -240,15 +207,16 @@ def test_cleanup_continues_after_fair_op_error(
mock_compose_env.assert_called_once()
mock_cleanup_fair_op.assert_called_once_with(
- node_mode=NodeMode.ACTIVE, env=mock_env, prune=False)
+ node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False
+ )
@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=False)
def test_cleanup_fails_when_user_invalid(
mock_is_user_valid,
- inited_node,
resource_alloc,
meta_file_v3,
+ inited_node,
):
"""Test that cleanup fails when user validation fails"""
import pytest
@@ -259,11 +227,12 @@ def test_cleanup_fails_when_user_invalid(
cleanup(node_mode=NodeMode.ACTIVE)
-def test_cleanup_fails_when_not_inited(ensure_meta_removed, active_node_option):
+def test_cleanup_fails_when_not_inited(ensure_meta_removed, active_node_option, fair_user_conf):
import pytest
- with pytest.raises(SystemExit):
- cleanup(node_mode=NodeMode.ACTIVE)
+ with mock.patch('node_cli.operations.cleanup_fair_op', return_value=None):
+ with pytest.raises(SystemExit):
+ cleanup(node_mode=NodeMode.ACTIVE)
@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True)
@@ -275,10 +244,10 @@ def test_cleanup_logs_success_message(
mock_compose_env,
mock_cleanup_fair_op,
mock_is_user_valid,
- inited_node,
resource_alloc,
meta_file_v3,
active_node_option,
+ inited_node,
):
mock_env = {'ENV_TYPE': 'devnet'}
mock_compose_env.return_value = mock_env
diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/fixtures/settings.py b/tests/fixtures/settings.py
new file mode 100644
index 00000000..55b080b2
--- /dev/null
+++ b/tests/fixtures/settings.py
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of node-cli
+#
+# Copyright (C) 2026-Present SKALE Labs
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import pytest
+import tomli_w
+
+from skale_core.settings import get_internal_settings
+
+from node_cli.configs import INTERNAL_SETTINGS_PATH, NODE_SETTINGS_PATH
+
+SKALE_DIR_HOST = './skale-data/'
+
+INTERNAL_SKALE_ACTIVE = {
+ 'node_type': 'skale',
+ 'node_mode': 'active',
+ 'skale_dir_host': SKALE_DIR_HOST,
+}
+
+INTERNAL_SKALE_PASSIVE = {
+ 'node_type': 'skale',
+ 'node_mode': 'passive',
+ 'skale_dir_host': SKALE_DIR_HOST,
+}
+
+INTERNAL_FAIR_ACTIVE = {
+ 'node_type': 'fair',
+ 'node_mode': 'active',
+ 'skale_dir_host': SKALE_DIR_HOST,
+}
+
+INTERNAL_FAIR_PASSIVE = {
+ 'node_type': 'fair',
+ 'node_mode': 'passive',
+ 'skale_dir_host': SKALE_DIR_HOST,
+}
+
+_BASE_NODE = {
+ 'env_type': 'devnet',
+ 'endpoint': 'http://127.0.0.1:8545',
+ 'container_stop_timeout': 1,
+ 'tg_api_key': '123',
+ 'tg_chat_id': '-1231232',
+ 'node_version': '0.0.0',
+ 'block_device': '/dev/sda',
+}
+
+NODE_SKALE_ACTIVE = {
+ **_BASE_NODE,
+ 'sgx_url': 'https://localhost:1026',
+ 'docker_lvmpy_version': '0.0.0',
+ 'manager_contracts': 'test-manager',
+ 'ima_contracts': 'test-ima',
+}
+
+NODE_SKALE_PASSIVE = {
+ **_BASE_NODE,
+ 'manager_contracts': 'test-manager',
+ 'ima_contracts': 'test-ima',
+ 'schain_name': 'test-schain',
+ 'enforce_btrfs': False,
+}
+
+NODE_FAIR_ACTIVE = {
+ **_BASE_NODE,
+ 'sgx_url': 'https://localhost:1026',
+ 'fair_contracts': 'test-fair',
+ 'enforce_btrfs': False,
+}
+
+NODE_FAIR_PASSIVE = {
+ **_BASE_NODE,
+ 'fair_contracts': 'test-fair',
+ 'enforce_btrfs': False,
+}
+
+
+def _write_settings(internal: dict, node: dict) -> None:
+ INTERNAL_SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True)
+ INTERNAL_SETTINGS_PATH.write_bytes(tomli_w.dumps(internal).encode())
+ NODE_SETTINGS_PATH.write_bytes(tomli_w.dumps(node).encode())
+ get_internal_settings.cache_clear()
+
+
+def _cleanup_settings() -> None:
+ INTERNAL_SETTINGS_PATH.unlink(missing_ok=True)
+ NODE_SETTINGS_PATH.unlink(missing_ok=True)
+ get_internal_settings.cache_clear()
+
+
+@pytest.fixture
+def skale_active_settings():
+ _write_settings(INTERNAL_SKALE_ACTIVE, NODE_SKALE_ACTIVE)
+ yield
+ _cleanup_settings()
+
+
+@pytest.fixture
+def skale_passive_settings():
+ _write_settings(INTERNAL_SKALE_PASSIVE, NODE_SKALE_PASSIVE)
+ yield
+ _cleanup_settings()
+
+
+@pytest.fixture
+def fair_active_settings():
+ _write_settings(INTERNAL_FAIR_ACTIVE, NODE_FAIR_ACTIVE)
+ yield
+ _cleanup_settings()
+
+
+@pytest.fixture
+def fair_passive_settings():
+ _write_settings(INTERNAL_FAIR_PASSIVE, NODE_FAIR_PASSIVE)
+ yield
+ _cleanup_settings()
diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py
new file mode 100644
index 00000000..e7b3f4be
--- /dev/null
+++ b/tests/utils/settings_test.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of node-cli
+#
+# Copyright (C) 2026 SKALE Labs
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+