Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement changes from generic module #4915

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d3da009
Add Logger class to handle loggers generation
QU3B1M Jan 30, 2024
8be8057
Apply a minimal logger usage to use as example
QU3B1M Jan 30, 2024
53279aa
Merge branch 'enhancement/4495-DTT1' into enhancement/4888-dtt1-it-3-…
QU3B1M Jan 31, 2024
dfba8e6
Implement logger on Ansible class
QU3B1M Jan 31, 2024
7e0f2b2
Fix path to logger config file
QU3B1M Jan 31, 2024
78896c0
Complete implementation of logger in Ansible class
QU3B1M Jan 31, 2024
8e97e22
Implement logger into SchemaValidator | Rename file to schema_validator
QU3B1M Jan 31, 2024
9fdc746
Update ansible logger name | Remove legacy comments
QU3B1M Jan 31, 2024
6b04d00
Styling improvements
QU3B1M Jan 31, 2024
c171edc
Add colors to console logs
QU3B1M Jan 31, 2024
2e17ace
Implement logger in Provision module
QU3B1M Jan 31, 2024
2650394
Replace logger handler with the defined on generics
QU3B1M Jan 31, 2024
2b27277
update workflow
QU3B1M Jan 31, 2024
5d62b3e
Add docstrings
QU3B1M Jan 31, 2024
3cdbf03
Add filter to get the logger name in uppercase
QU3B1M Feb 1, 2024
eb6b6b6
Implement logger in allocator files
QU3B1M Feb 1, 2024
30ae670
Implement logger in provision files
QU3B1M Feb 1, 2024
f3c6382
Implement logger in testing files
QU3B1M Feb 1, 2024
fc00c46
Fix workflow JSON schema and test.yaml
QU3B1M Feb 1, 2024
1d8ce86
Style changes
QU3B1M Feb 1, 2024
de2737d
Update deployability/modules/testing/testing.py
QU3B1M Feb 1, 2024
92d9d29
Add docstrings
QU3B1M Feb 1, 2024
68769f6
Add dosctrings to schema validator
QU3B1M Feb 1, 2024
ba7135c
Add requested docstrings
QU3B1M Feb 1, 2024
afb4c2a
Update deployability/modules/allocation/vagrant/provider.py
QU3B1M Feb 1, 2024
245b118
Update deployability/modules/allocation/aws/provider.py
QU3B1M Feb 1, 2024
4deb476
Update deployability/modules/testing/testing.py
QU3B1M Feb 1, 2024
49906e2
Rename logger file to utils in order to use it to store more utils fu…
QU3B1M Feb 5, 2024
bb20c1c
Update workflow_engine removing its dependency from the generic module
QU3B1M Feb 5, 2024
284083a
Merge pull request #4897 from wazuh/enhancement/4888-dtt1-it-3-genera…
fcaffieri Feb 5, 2024
0d6bc73
Merge remote-tracking branch 'origin/enhancement/4495-DTT1' into enha…
QU3B1M Feb 5, 2024
801fda8
Move schema_validator to workflow_engine
QU3B1M Feb 5, 2024
ff59996
Remove unused import
QU3B1M Feb 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions deployability/launchers/workflow_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,11 @@ def parse_arguments() -> argparse.Namespace:
parser.add_argument('--schema_file', required=False, type=str, help='Path to the schema file (YAML format)')
return parser.parse_args()


def setup_logger(log_level: str) -> None:
"""Setup logger."""
logger = logging.getLogger()
console_handler = colorlog.StreamHandler()
console_handler.setFormatter(colorlog.ColoredFormatter("%(log_color)s[%(asctime)s] [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S"))
logger.addHandler(console_handler)
logger.setLevel(log_level)


def main() -> None:
"""Main entry point."""

args = parse_arguments()
setup_logger(args.log_level)
processor = WorkflowProcessor(**dict(InputPayload(**vars(parse_arguments()))))
processor = WorkflowProcessor(**dict(InputPayload(**vars(args))))
processor.run()


Expand Down
1 change: 0 additions & 1 deletion deployability/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .provision import Provision
from .generic import Ansible
from .allocation import Allocator
from .generic import SchemaValidator
25 changes: 14 additions & 11 deletions deployability/modules/allocation/allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

from pathlib import Path

from .generic import Instance, Provider, models
from .aws.provider import AWSProvider, AWSConfig
from .generic import Instance, Provider, models
from .generic.utils import logger
from .vagrant.provider import VagrantProvider, VagrantConfig


Expand All @@ -26,10 +27,10 @@ def run(cls, payload: models.InputPayload) -> None:
payload = models.InputPayload(**dict(payload))
# Detect the action and call the appropriate method.
if payload.action == 'create':
print(f"Creating instance at {payload.working_dir}")
logger.info(f"Creating instance at {payload.working_dir}")
return cls.__create(payload)
elif payload.action == 'delete':
print(f"Deleting instance from trackfile {payload.track_output}")
logger.info(f"Deleting instance from trackfile {payload.track_output}")
return cls.__delete(payload)

# Internal methods
Expand All @@ -48,10 +49,10 @@ def __create(cls, payload: models.CreationPayload):
config = cls.___get_custom_config(payload)
instance = provider.create_instance(
payload.working_dir, instance_params, config)
print(f"Instance {instance.identifier} created.")
# Start the instance
logger.info(f"Instance {instance.identifier} created.")
# Start the instance
instance.start()
print(f"Instance {instance.identifier} started.")
logger.info(f"Instance {instance.identifier} started.")
# Generate the inventory and track files.
cls.__generate_inventory(instance, payload.inventory_output)
cls.__generate_track_file(instance, payload.provider, payload.track_output)
Expand All @@ -71,7 +72,7 @@ def __delete(cls, payload: models.DeletionPayload) -> None:
track = models.TrackOutput(**yaml.safe_load(f))
provider = PROVIDERS[track.provider]()
provider.destroy_instance(track.instance_dir, track.identifier)
print(f"Instance {track.identifier} deleted.")
logger.info(f"Instance {track.identifier} deleted.")

@staticmethod
def ___get_custom_config(payload: models.CreationPayload) -> models.ProviderConfig | None:
Expand All @@ -85,11 +86,13 @@ def ___get_custom_config(payload: models.CreationPayload) -> models.ProviderConf
Returns:
ProviderConfig: The configuration object.
"""
if not payload.custom_provider_config:
config = payload.custom_provider_config
if not config:
return None
# Read the custom config file and validate it.
config_model: models.ProviderConfig = CONFIGS[payload.provider]
with open(payload.custom_provider_config, 'r') as f:
with open(config, 'r') as f:
logger.info(f"Using custom provider config from {config}")
config = config_model(**yaml.safe_load(f))
return config

Expand All @@ -112,7 +115,7 @@ def __generate_inventory(instance: Instance, inventory_path: Path) -> None:
ansible_ssh_private_key_file=str(ssh_config.private_key))
with open(inventory_path, 'w') as f:
yaml.dump(inventory.model_dump(), f)
print(f"\nInventory file generated at {inventory_path}")
logger.info(f"Inventory file generated at {inventory_path}")

@staticmethod
def __generate_track_file(instance: Instance, provider_name: str, track_path: Path) -> None:
Expand All @@ -133,4 +136,4 @@ def __generate_track_file(instance: Instance, provider_name: str, track_path: P
key_path=str(instance.credentials.key_path))
with open(track_path, 'w') as f:
yaml.dump(track.model_dump(), f)
print(f"\nTrack file generated at {track_path}")
logger.info(f"Track file generated at {track_path}")
5 changes: 5 additions & 0 deletions deployability/modules/allocation/aws/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path

from modules.allocation.generic import Credentials
from modules.allocation.generic.utils import logger


class AWSCredentials(Credentials):
Expand Down Expand Up @@ -46,6 +47,7 @@ def generate(self, base_dir: str | Path, name: str, overwrite: bool = False) ->

# Validate base directory
if not base_dir.exists():
logger.debug(f"Creating base directory: {base_dir}")
base_dir.mkdir(parents=True, exist_ok=True)
elif base_dir.is_file():
raise self.CredentialsError(f"Invalid base directory: {base_dir}")
Expand All @@ -57,6 +59,7 @@ def generate(self, base_dir: str | Path, name: str, overwrite: bool = False) ->
if not overwrite:
raise self.CredentialsError(f"Key pair {name} already exists.")
else:
logger.warning(f"Key pair {name} already exists. Overwriting.")
key_pair.delete()
except ClientError:
pass
Expand Down Expand Up @@ -112,6 +115,7 @@ def load(self, name: str) -> str:
def delete(self) -> None:
"""Deletes the key pair."""
if not self.name:
logger.warning(f"Key pair doesn't exist. Skipping deletion.")
return

try:
Expand All @@ -122,6 +126,7 @@ def delete(self) -> None:

# Remove the local private key file
if self.key_path:
logger.debug(f"Deleting private key: {self.key_path}")
Path(self.key_path).unlink()

# Clear instance attributes
Expand Down
3 changes: 3 additions & 0 deletions deployability/modules/allocation/aws/instance.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import boto3

from pathlib import Path
from modules.allocation.generic import Instance
from modules.allocation.generic.models import ConnectionInfo
from modules.allocation.generic.utils import logger
from .credentials import AWSCredentials


Expand Down Expand Up @@ -32,6 +34,7 @@ def __init__(self, path: str | Path, identifier: str, credentials: AWSCredential
self._client = boto3.resource('ec2')
self._instance = self._client.Instance(self.identifier)
if not self.credentials:
logger.debug(f"No credentials found. Loading from instance directory.")
self.credentials = self.__get_credentials()

def start(self) -> None:
Expand Down
6 changes: 6 additions & 0 deletions deployability/modules/allocation/aws/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from modules.allocation.generic import Provider
from modules.allocation.generic.models import CreationPayload
from modules.allocation.generic.utils import logger
from .credentials import AWSCredentials
from .instance import AWSInstance
from .models import AWSConfig
Expand Down Expand Up @@ -38,21 +39,25 @@ def _create_instance(cls, base_dir: Path, params: CreationPayload, config: AWSCo
temp_dir = base_dir / temp_id
credentials = AWSCredentials()
if not config:
logger.debug(f"No config provided. Generating from payload")
# Generate the credentials.
credentials.generate(temp_dir, temp_id.split('-')[-1] + '_key')
# Parse the config if it is not provided.
config = cls.__parse_config(params, credentials)
else:
logger.debug(f"Using provided config")
# Load the existing credentials.
credentials.load(config.key_name)
# Create the temp directory.
# TODO: Review this on the credentials refactor.
if not temp_dir.exists():
logger.debug(f"Creating temp directory: {temp_dir}")
temp_dir.mkdir(parents=True, exist_ok=True)
# Generate the instance.
instance_id = cls.__create_ec2_instance(config)
# Rename the temp directory to its real name.
instance_dir = Path(base_dir, instance_id)
logger.debug(f"Renaming temp {temp_dir} directory to {instance_dir}")
os.rename(temp_dir, instance_dir)
credentials.key_path = (instance_dir / credentials.name).with_suffix('.pem')

Expand Down Expand Up @@ -83,6 +88,7 @@ def _destroy_instance(cls, instance_dir: str, identifier: str) -> None:
"""
instance = AWSInstance(instance_dir, identifier)
if instance.credentials:
logger.debug(f"Deleting credentials: {instance.credentials.key_path}")
instance.credentials.delete()
instance.delete()

Expand Down
5 changes: 3 additions & 2 deletions deployability/modules/allocation/generic/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from .instance import Instance
from .models import CreationPayload, ProviderConfig
from .utils import logger


class Provider(ABC):
Expand Down Expand Up @@ -76,11 +77,11 @@ def load_instance(cls, instance_dir: str | Path, instance_id: str) -> Instance:
Instance: The loaded instance.

Raises:
Exception: If the instance directory does not exist.
ValueError: If the instance directory does not exist.
"""
instance_dir = Path(instance_dir)
if not instance_dir.exists():
raise Exception(f"Instance path {instance_dir} does not exist")
raise ValueError(f"Instance path {instance_dir} does not exist")
return cls._load_instance(instance_dir, instance_id)

@classmethod
Expand Down
3 changes: 3 additions & 0 deletions deployability/modules/allocation/generic/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from modules.generic.logger import Logger

logger = Logger("allocator").get_logger()
13 changes: 10 additions & 3 deletions deployability/modules/allocation/vagrant/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

from modules.allocation.generic import Credentials
from modules.allocation.generic.utils import logger


class VagrantCredentials(Credentials):
Expand Down Expand Up @@ -36,9 +37,12 @@ def generate(self, base_dir: str | Path, name: str) -> Path:
CredentialsError: This exception is raised if there's an error during the key creation process.
"""
if self.key_path and self.key_id:
logger.warning(f"Key pair already exists: {self.key_path}")
return self.key_path

base_dir = Path(base_dir)
if not base_dir.exists():
logger.debug(f"Creating base directory: {base_dir}")
base_dir.mkdir(parents=True, exist_ok=True)
elif Path(base_dir).is_file():
raise self.CredentialsError(f"Invalid base directory: {base_dir}")
Expand All @@ -47,6 +51,7 @@ def generate(self, base_dir: str | Path, name: str) -> Path:
public_key_path = private_key_path.with_suffix(".pub")
# Delete the existing key pair if it exists.
if private_key_path.exists():
logger.warning(f"Key pair already exists: {private_key_path}")
return self.load(base_dir, name)
elif private_key_path.exists():
private_key_path.unlink()
Expand All @@ -63,8 +68,8 @@ def generate(self, base_dir: str | Path, name: str) -> Path:
capture_output=True, text=True)
os.chmod(private_key_path, 0o600)
if output.returncode != 0:
raise self.CredentialsError(
f"Error creating key pair: {output.stderr}")
raise self.CredentialsError(f"Error creating key pair: {output.stderr}")

# Save instance attributes.
self.name = name
self.key_id = name
Expand All @@ -83,7 +88,7 @@ def load(self, path: str | Path) -> None:
"""
key_path = Path(path)
if not key_path.exists() or not key_path.is_file():
raise self.CredentialsError(f"Invalid path {key_path}.")
raise self.CredentialsError(f"Invalid key path {key_path}.")
self.key_path = key_path
self.name = key_path.name
self.key_id = key_path.name
Expand All @@ -93,6 +98,8 @@ def delete(self) -> None:
Deletes the key pair from the file system.
"""
if not self.key_path.exists():
logger.warning(f"Key pair doesn't exist: {self.key_path}.\
Skipping deletion.")
return
Path(self.key_path).unlink()
Path(self.key_path.with_suffix(".pub")).unlink()
Expand Down
15 changes: 11 additions & 4 deletions deployability/modules/allocation/vagrant/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from modules.allocation.generic import Instance
from modules.allocation.generic.models import ConnectionInfo
from modules.allocation.generic.utils import logger
from .credentials import VagrantCredentials


Expand Down Expand Up @@ -65,6 +66,8 @@ def delete(self) -> None:
None
"""
if "not created" in self.status():
logger.warning(f"Instance {self.identifier} is not created.\
Skipping deletion.")
return
self.__run_vagrant_command(['destroy', '-f'])

Expand All @@ -86,6 +89,8 @@ def ssh_connection_info(self) -> ConnectionInfo:
ConnectionInfo: The SSH configuration of the VM.
"""
if not 'running' in self.status():
logger.debug(f"Instance {self.identifier} is not running.\
Starting it.")
self.start()
output = self.__run_vagrant_command('ssh-config')
patterns = {'hostname': r'HostName (.*)',
Expand All @@ -99,8 +104,10 @@ def ssh_connection_info(self) -> ConnectionInfo:
if match:
ssh_config[key] = str(match.group(1)).strip("\r")
else:
raise ValueError(f"Couldn't find {key} in vagrant ssh-config")
logger.error(f"Couldn't find {key} in ssh-config")
return None
if self.credentials:
logger.debug(f"Using provided credentials")
ssh_config['private_key'] = str(self.credentials.key_path)
return ConnectionInfo(**ssh_config)

Expand All @@ -124,11 +131,11 @@ def __run_vagrant_command(self, command: str | list) -> str:
stderr=subprocess.PIPE)

if stderr := output.stderr.decode("utf-8"):
print(stderr)
print(output.stdout.decode("utf-8"))
logger.error(f"Command failed: {stderr}")
return None
return output.stdout.decode("utf-8")
except subprocess.CalledProcessError as e:
print(e)
logger.error(f"Command failed: {e.stderr}")
return None

def __parse_vagrant_status(self, message: str) -> str:
Expand Down
9 changes: 7 additions & 2 deletions deployability/modules/allocation/vagrant/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from modules.allocation.generic import Provider
from modules.allocation.generic.models import CreationPayload
from modules.allocation.generic.utils import logger
from .credentials import VagrantCredentials
from .instance import VagrantInstance
from .models import VagrantConfig
Expand Down Expand Up @@ -40,14 +41,17 @@ def _create_instance(cls, base_dir: Path, params: CreationPayload, config: Vagra
instance_dir.mkdir(parents=True, exist_ok=True)
credentials = VagrantCredentials()
if not config:
logger.debug(f"No config provided. Generating from payload")
# Generate the credentials.
credentials.generate(instance_dir, 'instance_key')
# Parse the config if it is not provided.
config = cls.__parse_config(params, credentials)
else:
logger.debug(f"Using provided config")
credentials.load(config.public_key)
# Create the Vagrantfile.
cls.__create_vagrantfile(instance_dir, config)
logger.debug(f"Vagrantfile created. Creating instance.")
return VagrantInstance(instance_dir, instance_id, credentials)

@staticmethod
Expand All @@ -64,8 +68,8 @@ def _load_instance(instance_dir: Path, identifier: str) -> VagrantInstance:
"""
return VagrantInstance(instance_dir, identifier)

@staticmethod
def _destroy_instance(instance_dir: Path, identifier: str) -> None:
@classmethod
def _destroy_instance(cls, instance_dir: Path, identifier: str) -> None:
"""
Destroys a Vagrant instance.

Expand All @@ -77,6 +81,7 @@ def _destroy_instance(instance_dir: Path, identifier: str) -> None:
None
"""
instance = VagrantInstance(instance_dir, identifier)
logger.debug(f"Destroying instance {identifier}")
instance.delete()

@classmethod
Expand Down
1 change: 0 additions & 1 deletion deployability/modules/generic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from .ansible import Ansible, Inventory
from .schemaValidator import SchemaValidator
Loading