From ee2bd03e3e2882d3fb143c37d5ce00c7aee8725c Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Fri, 17 Sep 2021 15:41:21 +0200 Subject: [PATCH 01/41] add: Add vagrant deployer for Windows #1900 Some extra temporary files has been added to test new changes --- .../qa_ctl/deployment/windows/config.yaml | 25 ++ .../deployment/windows/requirements.txt | 5 + .../deployment/windows/vagrant_deployer.py | 237 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml create mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt create mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml new file mode 100644 index 0000000000..c783f9a359 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml @@ -0,0 +1,25 @@ +deployment: + host_1: + provider: + vagrant: + enabled: true + vagrantfile_path: /tmp + vagrant_box: qactl/centos_8 + vm_memory: 1024 + vm_cpu: 1 + vm_name: manager_test_general_settings_enabled_1631885562.550129 + vm_system: linux + label: manager_test_general_settings_enabled_1631885562.550129 + vm_ip: 10.150.50.4 + host_2: + provider: + vagrant: + enabled: true + vagrantfile_path: /tmp + vagrant_box: qactl/ubuntu_20_04 + vm_memory: 1024 + vm_cpu: 1 + vm_name: manager_test_general_settings_enabled_1631885562.550129 + vm_system: linux + label: manager_test_general_settings_enabled_1631885562.550129 + vm_ip: 10.150.50.5 \ No newline at end of file diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt new file mode 100644 index 0000000000..a1df77e970 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt @@ -0,0 +1,5 @@ +requests +lockfile +setuptools +python-vagrant +pyyaml diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py new file mode 100644 index 0000000000..2521b3c990 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py @@ -0,0 +1,237 @@ +import yaml +import vagrant +import argparse +import os +import json +import logging +import threading + +import requests + +from tempfile import gettempdir +from datetime import datetime + + +LOGGER = logging.getLogger('vagrant_deployer') +TMP_FILES_PATH = os.path.join(gettempdir(), 'vagrant_deployer') +VAGRANTFILE_TEMPLATE_URL = 'https://raw.githubusercontent.com/wazuh/wazuh-qa/1900-qa-ctl-windows/deps/wazuh_testing/' \ + 'wazuh_testing/qa_ctl/deployment/vagrantfile_template.txt' + + +class ThreadExecutor(threading.Thread): + """Class which allows us to upload the thread exception to the parent process. + + This is useful to cause the pytest test to fail in the event of an exception or failure in any of the threads. + + Args: + function (callable): Function to run in the thread. + parameters (dict): Function parameters. Used as kwargs in the callable function. + + Attributes: + function (callable): Function to run in the thread. + parameters (dict): Function parameters. Used as kwargs in the callable function. + exception (Exception): Thread exception in case it has occurred. + """ + def __init__(self, function, parameters={}): + super().__init__() + self.function = function + self.exception = None + self.parameters = parameters + self._return = None + + def _run(self): + """Run the target function with its parameters in the thread""" + self._return = self.function(**self.parameters) + + def run(self): + """Overwrite run function of threading Thread module. + + Launch the target function and catch the exception in case it occurs. + """ + self.exc = None + try: + self._run() + except Exception as e: + self.exception = e + + def join(self): + """Overwrite join function of threading Thread module. + + Raises the exception to the parent in case it was raised when executing the target function. + + Raises: + Exception: Target function exception if ocurrs + """ + super(ThreadExecutor, self).join() + if self.exception: + raise self.exception + + return self._return + + +def read_parameters(): + parser = argparse.ArgumentParser() + + parser.add_argument('-c', '--config', type=str, action='store', required=True, dest='config', + help='Path to the configuration file.') + + parser.add_argument('-d', '--debug', action='store_true', + help='Persistent instance mode. Do not destroy the instances once the process has finished.') + + parameters = parser.parse_args() + + return parameters + + +def set_logging(debug_mode=False): + logging_level = logging.DEBUG if debug_mode else logging.INFO + + LOGGER.setLevel(logging_level) + + handler = logging.StreamHandler() + handler.setLevel(logging_level) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + LOGGER.addHandler(handler) + + +def process_config_file(configuration_file_path, debug_mode=False): + def _read_configuration_data(configuration_file_path): + with open(configuration_file_path) as config_file_fd: + configuration_data = yaml.safe_load(config_file_fd) + + return configuration_data + + LOGGER.debug(f"Processing {configuration_file_path} configuration file") + + instance_list = _read_configuration_data(configuration_file_path)['deployment'] + instances_data = [] + + for host in instance_list: + for provider in instance_list[host]['provider']: + data = instance_list[host]['provider'][provider] + if not data['enabled']: + continue + + if provider == 'vagrant': + vm_name = data['vm_name'].replace('_', '-') + instances_data.append({'vagrantfile_path': data['vagrantfile_path'], 'box': data['vagrant_box'], + 'label': data['label'], 'name': vm_name, 'cpus': data['vm_cpu'], + 'memory': data['vm_memory'], 'system': data['vm_system'], + 'ip': data['vm_ip'], 'quiet_out': not debug_mode}) + else: + raise ValueError("This tool can only deploy vagrant machines") + + LOGGER.debug(f"The {configuration_file_path} configuration file has been processed successfully") + + return instances_data + + +def download_vagrantfile_template(vagrantfile_template_file_path): + if not os.path.exists(vagrantfile_template_file_path): + LOGGER.debug(f"Downloading Vagrantfile template file from {vagrantfile_template_file_path}") + + with open(vagrantfile_template_file_path, 'w') as f: + f.write((requests.get(VAGRANTFILE_TEMPLATE_URL)).text) + + LOGGER.debug(f"The Vagrantfile template has been downloaded successfully") + + +def create_vagrantfile(instance_data, vagrantfile_template_file_path): + def _get_box_url(box_name): + box_mapping = { + 'qactl/ubuntu_20_04': 'https://s3.amazonaws.com/ci.wazuh.com/qa/boxes/QACTL_ubuntu20_04.box', + 'qactl/centos_8': 'https://s3.amazonaws.com/ci.wazuh.com/qa/boxes/QACTL_centos_8.box' + } + + return box_mapping[box_name] + + def _read_vagrantfile_template(vagrantfile_template_file_path): + with open(vagrantfile_template_file_path, 'r') as template_fd: + return template_fd.readlines() + + def _parse_instance_data(instance_data): + return json.dumps({ + instance_data['name']: { + 'box_image': instance_data['box'], + 'box_url': _get_box_url(instance_data['box']), + 'vm_label': instance_data['label'], + 'cpus': instance_data['cpus'], + 'memory': instance_data['memory'], + 'system': instance_data['system'], + 'private_ip': instance_data['ip'] + } + }) + + def _write_vagrantfile(instance_data, vagrantfile_file_path, vagrantfile_template_file_path): + LOGGER.debug(f"Writing Vagrantfile for {instance_data['name']} instance in {vagrantfile_file_path} path") + + REPLACE_PATTERN = 'json_box = {}\n' + read_lines = _read_vagrantfile_template(vagrantfile_template_file_path) + replace_line = read_lines.index(REPLACE_PATTERN) + read_lines[replace_line] = REPLACE_PATTERN.format(f"'{_parse_instance_data(instance_data)}'") + + with open(vagrantfile_file_path, 'w') as vagrantfile_fd: + vagrantfile_fd.writelines(read_lines) + + LOGGER.debug(f"Vagrantfile for {instance_data['name']} instance has been written sucessfully") + + vagrantfile_path = os.path.join(TMP_FILES_PATH, instance_data['name']) + + if not os.path.exists(vagrantfile_path): + os.makedirs(vagrantfile_path) + LOGGER.debug(f"{vagrantfile_path} path has been created") + + _write_vagrantfile(instance_data, os.path.join(vagrantfile_path, 'Vagrantfile'), vagrantfile_template_file_path) + + +def deploy(instances_data, vagrantfile_template_file_path): + def __threads_runner(threads): + for runner_thread in threads: + runner_thread.start() + + for runner_thread in threads: + runner_thread.join() + + def _deploy_instance(instance_data, vagrantfile_template_file_path): + LOGGER.debug(f"Deploying {instance_data['name']} instance") + + create_vagrantfile(instance_data, vagrantfile_template_file_path) + vagrantfile_path = os.path.join(TMP_FILES_PATH, instance_data['name']) + vagrant_instance = vagrant.Vagrant(root=vagrantfile_path, quiet_stdout=instance_data['quiet_out'], + quiet_stderr=False) + vagrant_instance.up() + + LOGGER.debug(f"The {instance_data['name']} instance has been deployed successfully") + + LOGGER.info(f"Deploying {len(instances_data)} instances") + + __threads_runner( + [ThreadExecutor(_deploy_instance, {'instance_data': instance, + 'vagrantfile_template_file_path': vagrantfile_template_file_path}) + for instance in instances_data]) + + LOGGER.info(f"The {len(instances_data)} instances has been deployed sucessfully") + + +def main(): + parameters = read_parameters() + + set_logging(True if parameters.debug else False) + + instances_data = process_config_file(parameters.config) + + if not os.path.exists(TMP_FILES_PATH): + os.makedirs(TMP_FILES_PATH) + LOGGER.debug(f"{TMP_FILES_PATH} path has been created") + + vagrantfile_template_file_path = os.path.join(TMP_FILES_PATH, 'vagrantfile_template.txt') + + download_vagrantfile_template(vagrantfile_template_file_path) + + deploy(instances_data, vagrantfile_template_file_path) + + +if __name__ == '__main__': + main() From fcd84c2b7ab03a0a7b0c3a664a8355d3b0644f6d Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Mon, 20 Sep 2021 17:12:48 +0200 Subject: [PATCH 02/41] add: Add ansible module to launch common ansible tasks #1900 --- .../qa_ctl/provisioning/ansible/ansible.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py new file mode 100644 index 0000000000..327d0d6fe2 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py @@ -0,0 +1,79 @@ +from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask +from wazuh_testing.qa_ctl.provisioning.ansible.ansible_runner import AnsibleRunner + + +def _ansible_runner(inventory_file_path, playbook_parameters, ansible_output=False): + tasks_result = AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, output=ansible_output) + + return tasks_result + + +def copy_files_to_remote(inventory_file_path, hosts, files_path, dest_path, become=False, ansible_output=False): + tasks_list = [ + AnsibleTask({ + 'name': f"Create path {dest_path} when system is not Windows", + 'file': { + 'path': dest_path, + 'state': 'directory', + 'mode': '0775' + }, + 'when': 'ansible_distribution is not search("Microsoft Windows")' + }), + AnsibleTask({ + 'name': 'Copy files to remote when system is not Windows', + 'copy': { + 'src': "{{ item.src }}", + 'dest': "{{ item.dest }}/" + }, + 'with_items': [{'src': file, 'dest': dest_path} for file in files_path], + 'when': 'ansible_distribution is not search("Microsoft Windows")' + }), + AnsibleTask({ + 'name': f"Create {dest_path} path when system is Windows", + 'win_file': { + 'path': dest_path, + 'state': 'directory', + 'mode': '0775' + }, + 'when': 'ansible_distribution is search("Microsoft Windows")' + }), + AnsibleTask({ + 'name': 'Copy files to remote when system is Windows', + 'win_copy': { + 'src': "{{ item.src }}", + 'dest': "{{ item.dest }}/" + }, + 'with_items': [{'src': file, 'dest': dest_path} for file in files_path], + 'when': 'ansible_distribution is search("Microsoft Windows")' + }) + ] + + return _ansible_runner(inventory_file_path, {'tasks_list': tasks_list, 'hosts': hosts, 'gather_facts': True, + 'become': become}, ansible_output) + + +def remove_paths(inventory_file_path, hosts, paths_to_delete, become=False, ansible_output=False): + tasks_list = [ + AnsibleTask({ + 'name': f"Delete {paths_to_delete} path (when system is not Windows)", + 'file': { + 'state': 'absent', + 'path': "{{ item }}" + }, + 'with_items': paths_to_delete, + 'when': 'ansible_distribution is not search("Microsoft Windows")' + }), + AnsibleTask({ + 'name': f"Delete {paths_to_delete} path (when system is not Windows)", + 'win_file': { + 'state': 'absent', + 'path': "{{ item }}" + }, + 'with_items': paths_to_delete, + 'when': 'ansible_distribution is search("Microsoft Windows")' + }), + ] + + return _ansible_runner(inventory_file_path, {'tasks_list': tasks_list, 'hosts': hosts, 'gather_facts': True, + 'become': become}, ansible_output) + From b110b8aba2786f885826dea7ba416a61eae4b85c Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Mon, 20 Sep 2021 17:16:08 +0200 Subject: [PATCH 03/41] add: Add support for WinRM in AnsibleInventory class #1900 --- .../qa_ctl/provisioning/ansible/ansible_inventory.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_inventory.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_inventory.py index 312b9b0721..edaef16a09 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_inventory.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_inventory.py @@ -44,12 +44,19 @@ def __setup_data__(self): 'ansible_password': instance.connection_user_password, 'ansible_connection': instance.connection_method, 'ansible_port': instance.connection_port, - 'ansible_python_interpreter': instance.ansible_python_interpreter, 'ansible_ssh_private_key_file': instance.ssh_private_key_file_path, + 'vars': instance.host_vars, 'ansible_ssh_common_args': "-o UserKnownHostsFile=/dev/null" + } + if instance.connection_method == 'winrm': + host_info.update({ + 'ansible_winrm_transport': 'basic', + 'ansible_winrm_server_cert_validation': 'ignore' + }) + # Remove ansible vars with None value host_info = {key: value for key, value in host_info.items() if value is not None} From 863ace235c1c0490cfbb7f747b07c553b20ea060 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Mon, 20 Sep 2021 17:17:04 +0200 Subject: [PATCH 04/41] add: Add vagrant deployer script to setup data files #1900 --- deps/wazuh_testing/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deps/wazuh_testing/setup.py b/deps/wazuh_testing/setup.py index 7c85932876..9fb034c48f 100644 --- a/deps/wazuh_testing/setup.py +++ b/deps/wazuh_testing/setup.py @@ -24,7 +24,8 @@ 'qa_ctl/deployment/dockerfiles/*', 'qa_ctl/deployment/vagrantfile_template.txt', 'qa_ctl/provisioning/wazuh_deployment/templates/preloaded_vars.conf.j2', - 'data/qactl_conf_validator_schema.json' + 'data/qactl_conf_validator_schema.json', + 'qa_ctl/deployment/windows/vagrant_deployer.py' ] scripts_list = [ From 2f12b18ddda9218ff90c3be4a67bf21413ec5f0e Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Mon, 20 Sep 2021 17:18:07 +0200 Subject: [PATCH 05/41] add: Add windows deployment provisioning to QA infraestructure class #1900 Improvements will be continued --- .../qa_ctl/deployment/qa_infraestructure.py | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py index 983e11fbd0..b99b147227 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py @@ -3,20 +3,28 @@ # This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 import ipaddress import docker +import os +import yaml + +from tempfile import gettempdir from wazuh_testing.qa_ctl.deployment.docker_wrapper import DockerWrapper from wazuh_testing.qa_ctl.deployment.vagrant_wrapper import VagrantWrapper +from wazuh_testing.qa_ctl.provisioning.ansible.ansible import copy_files_to_remote, remove_paths from wazuh_testing.tools.thread_executor import ThreadExecutor from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging from wazuh_testing.tools.exceptions import QAValueError +from wazuh_testing.tools.time import get_current_timestamp +from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance +from wazuh_testing.qa_ctl.provisioning.ansible.ansible_inventory import AnsibleInventory class QAInfraestructure: """Class to handle multiples instances objects. Args: - instance_list (dict): Dictionary with the information of the instances. Must follow the format of the yaml + deployment_data (dict): Dictionary with the information of the instances. Must follow the format of the yaml template. qa_ctl_configuration (QACTLConfiguration): QACTL configuration. @@ -34,17 +42,19 @@ class QAInfraestructure: DOCKER_NETWORK_NAME = 'wazuh_net' LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, instance_list, qa_ctl_configuration): + def __init__(self, deployment_data, qa_ctl_configuration, windows_deployment=False): + self.deployment_data = deployment_data self.qa_ctl_configuration = qa_ctl_configuration self.instances = [] self.docker_client = None self.docker_network = None self.network_address = None + self.windows_deployment = windows_deployment QAInfraestructure.LOGGER.debug('Processing deployment configuration') - for host in instance_list: - for provider in instance_list[host]['provider']: - data = instance_list[host]['provider'][provider] + for host in deployment_data: + for provider in deployment_data[host]['provider']: + data = deployment_data[host]['provider'][provider] if not data['enabled']: continue @@ -119,10 +129,42 @@ def __threads_runner(self, threads): def run(self): """Execute the run method on every configured instance.""" + self.run_unix_deployment() if not self.windows_deployment else self.run_windows_deployment() + + + def run_unix_deployment(self): + QAInfraestructure.LOGGER.info('Running Unix deployment') QAInfraestructure.LOGGER.info(f"Starting {len(self.instances)} instances deployment") self.__threads_runner([ThreadExecutor(instance.run) for instance in self.instances]) QAInfraestructure.LOGGER.info('The instances deployment has finished sucessfully') + def run_windows_deployment(self): + QAInfraestructure.LOGGER.info('Running Windows deployment') + + # Generate deployment configuration file + config_file_path = f"{gettempdir()}/qa_ctl_deployment_config_{get_current_timestamp()}.yaml" + + with open(config_file_path, 'w') as config_file: + config_file.write(yaml.dump({'deployment': self.deployment_data}, allow_unicode=True, sort_keys=False)) + + user = 'vagrant' + user_password = 'vagrant' + + inventory = AnsibleInventory([AnsibleInstance('172.16.1.13', connection_user=user, + connection_user_password=user_password, + connection_method='winrm', + connection_port='5986')]) + + deployer_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'windows', 'vagrant_deployer.py') + + QAInfraestructure.LOGGER.info('Provisioning deployment files to Windows host') + + copy_files_to_remote(inventory.inventory_file_path, '172.16.1.13', [deployer_file_path, config_file_path], + 'C:\\qa_ctl', ansible_output=True)#self.qa_ctl_configuration.ansible_output) + + QAInfraestructure.LOGGER.info('Provisioning has been done successfully') + + def halt(self): """Execute the 'halt' method on every configured instance.""" QAInfraestructure.LOGGER.info(f"Stopping {len(self.instances)} instances") From 6307a4784b07db6cff22f47e3f8bc98a2027afeb Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Tue, 21 Sep 2021 12:47:53 +0200 Subject: [PATCH 06/41] add: Create and use AnsibleException to manage ansible exceptions #1900 --- .../qa_ctl/provisioning/ansible/ansible_runner.py | 7 +++++-- deps/wazuh_testing/wazuh_testing/tools/exceptions.py | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py index 7721a9e2da..5d40693712 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py @@ -6,6 +6,7 @@ from wazuh_testing.qa_ctl.provisioning.ansible.ansible_playbook import AnsiblePlaybook from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging +from wazuh_testing.tools.exceptions import AnsibleException class AnsibleRunner: @@ -46,7 +47,7 @@ def run(self): ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0: - raise Exception(f"The playbook execution has failed. RC = {ansible_output.rc}") + raise AnsibleException(f"The playbook execution has failed. RC = {ansible_output.rc}") return ansible_output @@ -69,12 +70,14 @@ def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_er quiet = not output try: + AnsibleRunner.LOGGER.debug(f"Running {ansible_playbook.playbook_file_path} ansible-playbook with " + f"{ansible_inventory_path} inventory") runner = ansible_runner.run(playbook=ansible_playbook.playbook_file_path, inventory=ansible_inventory_path, quiet=quiet) ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0 and raise_on_error: - raise Exception(f'Failed: {ansible_output}') + raise AnsibleException(f'Failed: {ansible_output}') return ansible_output diff --git a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py index 36c1be9160..ae367afc25 100644 --- a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py +++ b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py @@ -1,4 +1,8 @@ +class AnsibleException(Exception): + def __init__(self, message): + super().__init__(message) + class QAValueError(Exception): def __init__(self, message, logger=None): self.message = f"\033[91m{message}\033[0m" From 21903dfe7459d5021d01b0acf8d6547b8cc233e8 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Tue, 21 Sep 2021 13:08:36 +0200 Subject: [PATCH 07/41] add: Add new common ansible tasks to ansible module #1900 --- .../qa_ctl/provisioning/ansible/ansible.py | 116 +++++++++++++++++- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py index 327d0d6fe2..f2ececf63d 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py @@ -1,17 +1,43 @@ from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask from wazuh_testing.qa_ctl.provisioning.ansible.ansible_runner import AnsibleRunner +from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance +from wazuh_testing.qa_ctl.provisioning.ansible.ansible_inventory import AnsibleInventory +from wazuh_testing.tools.exceptions import AnsibleException def _ansible_runner(inventory_file_path, playbook_parameters, ansible_output=False): + """Ansible runner method. Launch the playbook tasks with the indicated host. + + Args: + inventory_file_path (str): Path where is located the inventory file. + playbook_parameters (dict): Playbook parameters to create and launch. + ansible_output (boolean): True for showing ansible output, False otherwise. + + Returns: + AnsibleOutput: Result of the ansible run. + """ tasks_result = AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, output=ansible_output) return tasks_result def copy_files_to_remote(inventory_file_path, hosts, files_path, dest_path, become=False, ansible_output=False): + """Copy local files to remote hosts. + + Args: + inventory_file_path (str): Path where is located the inventory file. + hosts (str): Inventory hosts where to run the tasks. + files_path (list(str)): Files path of the files that will be copied to the remote host. + dest_path (str): Path of the remote host where place the copied files. + become (boolean): True if the tasks have to be launch as root user, False otherwise + ansible_output (boolean): True for showing ansible output, False otherwise. + + Returns: + AnsibleOutput: Result of the ansible run. + """ tasks_list = [ AnsibleTask({ - 'name': f"Create path {dest_path} when system is not Windows", + 'name': f"Create path {dest_path} for UNIX systems", 'file': { 'path': dest_path, 'state': 'directory', @@ -20,7 +46,7 @@ def copy_files_to_remote(inventory_file_path, hosts, files_path, dest_path, beco 'when': 'ansible_distribution is not search("Microsoft Windows")' }), AnsibleTask({ - 'name': 'Copy files to remote when system is not Windows', + 'name': 'Copy files to remote for UNIX systems', 'copy': { 'src': "{{ item.src }}", 'dest': "{{ item.dest }}/" @@ -29,7 +55,7 @@ def copy_files_to_remote(inventory_file_path, hosts, files_path, dest_path, beco 'when': 'ansible_distribution is not search("Microsoft Windows")' }), AnsibleTask({ - 'name': f"Create {dest_path} path when system is Windows", + 'name': f"Create {dest_path} path for Windows systems", 'win_file': { 'path': dest_path, 'state': 'directory', @@ -38,7 +64,7 @@ def copy_files_to_remote(inventory_file_path, hosts, files_path, dest_path, beco 'when': 'ansible_distribution is search("Microsoft Windows")' }), AnsibleTask({ - 'name': 'Copy files to remote when system is Windows', + 'name': 'Copy files to remote for Windows systems', 'win_copy': { 'src': "{{ item.src }}", 'dest': "{{ item.dest }}/" @@ -53,9 +79,21 @@ def copy_files_to_remote(inventory_file_path, hosts, files_path, dest_path, beco def remove_paths(inventory_file_path, hosts, paths_to_delete, become=False, ansible_output=False): + """Remove folders recursively in remote hosts. + + Args: + inventory_file_path (str): Path where is located the inventory file. + hosts (str): Inventory hosts where to run the tasks. + paths_to_delete (list(str)): Paths of the folders to delete recursively. + become (boolean): True if the tasks have to be launch as root user, False otherwise + ansible_output (boolean): True for showing ansible output, False otherwise. + + Returns: + AnsibleOutput: Result of the ansible run. + """ tasks_list = [ AnsibleTask({ - 'name': f"Delete {paths_to_delete} path (when system is not Windows)", + 'name': f"Delete {paths_to_delete} path for UNIX systems", 'file': { 'state': 'absent', 'path': "{{ item }}" @@ -64,7 +102,7 @@ def remove_paths(inventory_file_path, hosts, paths_to_delete, become=False, ansi 'when': 'ansible_distribution is not search("Microsoft Windows")' }), AnsibleTask({ - 'name': f"Delete {paths_to_delete} path (when system is not Windows)", + 'name': f"Delete {paths_to_delete} path for Windows systems", 'win_file': { 'state': 'absent', 'path': "{{ item }}" @@ -77,3 +115,69 @@ def remove_paths(inventory_file_path, hosts, paths_to_delete, become=False, ansi return _ansible_runner(inventory_file_path, {'tasks_list': tasks_list, 'hosts': hosts, 'gather_facts': True, 'become': become}, ansible_output) + +def launch_remote_commands(inventory_file_path, hosts, commands, become=False, ansible_output=False): + """Launch remote commands in the specified hosts. + + Args: + inventory_file_path (str): Path where is located the inventory file. + hosts (str): Inventory hosts where to run the tasks. + commands (list(str)): Commands to launch in remote hosts. + become (boolean): True if the tasks have to be launch as root user, False otherwise + ansible_output (boolean): True for showing ansible output, False otherwise. + + Returns: + AnsibleOutput: Result of the ansible run. + """ + tasks_list = [ + AnsibleTask({ + 'name': 'Running command list on UNIX systems', + 'command': "{{ item }}", + 'with_items': commands, + 'when': 'ansible_distribution is not search("Microsoft Windows")' + }), + AnsibleTask({ + 'name': 'Running command list on Windows systems', + 'win_command': "{{ item }}", + 'with_items': commands, + 'when': 'ansible_distribution is search("Microsoft Windows")' + }) + ] + + return _ansible_runner(inventory_file_path, {'tasks_list': tasks_list, 'hosts': hosts, 'gather_facts': True, + 'become': become}, ansible_output) + + +def check_windows_ansible_credentials(user, password): + """Check if the windows ansible credentials are correct. + + This method must be run in a Windows WSL. + + Args: + user (str): Windows user. + password (str): Windows user password. + + Returns: + boolean: True if credentials are correct, False otherwise. + """ + inventory = AnsibleInventory([AnsibleInstance('127.0.0.1', connection_user=user, + connection_user_password=password, + connection_method='winrm', + connection_port='5986') + ]) + inventory_file_path = inventory.inventory_file_path + tasks_list = [ + AnsibleTask({ + 'debug': { + 'msg': 'Hello world' + } + }), + + ] + + try: + _ansible_runner(inventory_file_path, {'tasks_list': tasks_list, 'hosts': '127.0.0.1', 'gather_facts': True, + 'become': False}, ansible_output=False) + return True + except AnsibleException: + return False From 0bac9118686a4f2a81c71496b94361583ca7e303 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Tue, 21 Sep 2021 13:09:54 +0200 Subject: [PATCH 08/41] add: Add `vagrant_deployer` integration in `qa-ctl` #1900 Necessary for running instance deployments in Windows hosts. --- .../qa_ctl/deployment/qa_infraestructure.py | 61 +++++++++++++------ .../wazuh_testing/scripts/qa_ctl.py | 41 ++++++++++++- 2 files changed, 83 insertions(+), 19 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py index b99b147227..3fe661af54 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py @@ -10,7 +10,7 @@ from wazuh_testing.qa_ctl.deployment.docker_wrapper import DockerWrapper from wazuh_testing.qa_ctl.deployment.vagrant_wrapper import VagrantWrapper -from wazuh_testing.qa_ctl.provisioning.ansible.ansible import copy_files_to_remote, remove_paths +from wazuh_testing.qa_ctl.provisioning.ansible.ansible import copy_files_to_remote, launch_remote_commands from wazuh_testing.tools.thread_executor import ThreadExecutor from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging @@ -42,14 +42,13 @@ class QAInfraestructure: DOCKER_NETWORK_NAME = 'wazuh_net' LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, deployment_data, qa_ctl_configuration, windows_deployment=False): + def __init__(self, deployment_data, qa_ctl_configuration): self.deployment_data = deployment_data self.qa_ctl_configuration = qa_ctl_configuration self.instances = [] self.docker_client = None self.docker_network = None self.network_address = None - self.windows_deployment = windows_deployment QAInfraestructure.LOGGER.debug('Processing deployment configuration') for host in deployment_data: @@ -127,43 +126,69 @@ def __threads_runner(self, threads): for runner_thread in threads: runner_thread.join() - def run(self): - """Execute the run method on every configured instance.""" - self.run_unix_deployment() if not self.windows_deployment else self.run_windows_deployment() - + def run(self, local_environment_info): + """Launch instances deployment according to the local host system.""" + if local_environment_info['system'] == 'unix': + self.run_unix_deployment() + elif local_environment_info['system'] == 'windows': + self.run_windows_deployment(local_environment_info['user'], local_environment_info['password']) + else: + raise QAValueError(f"local_environment_info['system'] is not a valid system for QAInfraestructure " + "runnning method", QAInfraestructure.LOGGER.error) def run_unix_deployment(self): + """Run the instances deployment when the local host is UNIX.""" QAInfraestructure.LOGGER.info('Running Unix deployment') QAInfraestructure.LOGGER.info(f"Starting {len(self.instances)} instances deployment") + self.__threads_runner([ThreadExecutor(instance.run) for instance in self.instances]) + QAInfraestructure.LOGGER.info('The instances deployment has finished sucessfully') - def run_windows_deployment(self): + def run_windows_deployment(self, user, user_password): + """Run the instances deployment when the local host is Windows. + + This will provision the Windows host with the vagrant deployer script and deployment config file, and it will + launch that script using Ansible. + + Args: + user (str): Windows local user. + user_password(str): Windows local user password. + """ QAInfraestructure.LOGGER.info('Running Windows deployment') # Generate deployment configuration file - config_file_path = f"{gettempdir()}/qa_ctl_deployment_config_{get_current_timestamp()}.yaml" + config_file_name = f"qa_ctl_deployment_config_{get_current_timestamp()}.yaml" + config_file_path = f"{gettempdir()}/{config_file_name}" with open(config_file_path, 'w') as config_file: config_file.write(yaml.dump({'deployment': self.deployment_data}, allow_unicode=True, sort_keys=False)) - user = 'vagrant' - user_password = 'vagrant' - - inventory = AnsibleInventory([AnsibleInstance('172.16.1.13', connection_user=user, - connection_user_password=user_password, - connection_method='winrm', - connection_port='5986')]) + inventory = AnsibleInventory([ + AnsibleInstance('127.0.0.1', connection_user=user, connection_user_password=user_password, + connection_method='winrm', connection_port='5986') + ]) deployer_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'windows', 'vagrant_deployer.py') + windows_tmp_files_path = f"C:\\qa_ctl\\{get_current_timestamp()}" + windows_vagrant_deployer_path = f"{windows_tmp_files_path}\\vagrant_deployer.py" + windows_config_file_path = f"{windows_tmp_files_path}\\{config_file_path}" + vagrant_deployer_command = f"python {windows_vagrant_deployer_path} -c {windows_config_file_path}" QAInfraestructure.LOGGER.info('Provisioning deployment files to Windows host') - copy_files_to_remote(inventory.inventory_file_path, '172.16.1.13', [deployer_file_path, config_file_path], - 'C:\\qa_ctl', ansible_output=True)#self.qa_ctl_configuration.ansible_output) + # Provision vagrant deployer and deployment config file in Windows host + copy_files_to_remote(inventory.inventory_file_path, '127.0.0.1', [deployer_file_path, config_file_path], + windows_tmp_files_path, ansible_output=self.qa_ctl_configuration.ansible_output) QAInfraestructure.LOGGER.info('Provisioning has been done successfully') + QAInfraestructure.LOGGER.info(f"Starting {len(self.instances)} instances deployment on Windows host") + # Launch vagrant deployer script + launch_remote_commands(inventory.inventory_file_path, '127.0.0.1', vagrant_deployer_command, + ansible_output=self.qa_ctl_configuration.ansible_output) + + QAInfraestructure.LOGGER.info('The instances deployment has finished sucessfully') def halt(self): """Execute the 'halt' method on every configured instance.""" diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index e360414bda..38e2c5c519 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -8,6 +8,7 @@ from jsonschema import validate from tempfile import gettempdir +from getpass import getpass from wazuh_testing.qa_ctl.deployment.qa_infraestructure import QAInfraestructure from wazuh_testing.qa_ctl.provisioning.qa_provisioning import QAProvisioning @@ -20,6 +21,7 @@ from wazuh_testing.tools.github_repository import version_is_released, branch_exist, WAZUH_QA_REPO from wazuh_testing.qa_ctl.provisioning.local_actions import download_local_wazuh_qa_repository, run_local_command from wazuh_testing.tools.github_repository import get_last_wazuh_version +from wazuh_testing.qa_ctl.provisioning.ansible.ansible import check_windows_ansible_credentials DEPLOY_KEY = 'deployment' @@ -81,6 +83,26 @@ def set_qactl_logging(qactl_configuration): qactl_logger = Logging(QACTL_LOGGER, qactl_configuration.logging_level, True, qactl_configuration.logging_file) +def set_parameters(parameters): + """Update script parameters and add extra information. + + Args: + (argparse.Namespace): Object with the user parameters. + """ + def ask_user_credentials(parameters): + """Ask and set user and password credentials""" + parameters.user = input('Enter the username of Windows host: ') + parameters.user_password = getpass(f"Enter the password of {parameters.user} user: ") + + if parameters.run_test and parameters.windows: # Automatic mode + ask_user_credentials(parameters) + elif parameters.config and parameters.windows: # Manual mode + configuration_data = read_configuration_data(parameters.config) + + if DEPLOY_KEY in configuration_data: + ask_user_credentials(parameters) + + def validate_parameters(parameters): """Validate the input parameters entered by the user of qa-ctl tool. @@ -104,6 +126,12 @@ def validate_parameters(parameters): if parameters.dry_run and parameters.run_test is None: raise QAValueError('The -d, --dry-run parameter can only be used with -r, --run', qactl_script_logger.error) + # Check if Windows user credentials are correct + if parameters.user and parameters.user_password: + if not check_windows_ansible_credentials(parameters.user, parameters.user_password): + raise QAValueError('Windows credentials are not correct. Please check user and password and try again', + qactl_script_logger.error) + # Check version parameter if parameters.version is not None: version = (parameters.version).replace('v', '') @@ -161,8 +189,13 @@ def main(): parser.add_argument('--version', '-v', type=str, action='store', required=False, dest='version', help='Wazuh installation and tests version') + parser.add_argument('-w', '--windows', action='store_true', help='Flag to indicate that the deployment environment ' + 'will be deployed on native Windows.') + arguments = parser.parse_args() + set_parameters(arguments) + validate_parameters(arguments) # Generate or get the qactl configuration file @@ -202,8 +235,14 @@ def main(): try: if DEPLOY_KEY in configuration_data: deploy_dict = configuration_data[DEPLOY_KEY] + local_environment_info = { + 'system': 'windows' if arguments.windows else 'unix', + 'user': arguments.user if arguments.user else None, + 'password': arguments.user_password if arguments.user_password else None + } instance_handler = QAInfraestructure(deploy_dict, qactl_configuration) - instance_handler.run() + + instance_handler.run(local_environment_info) launched['instance_handler'] = True if PROVISION_KEY in configuration_data: From f813123cfe06cb0d5dff2ca7ed5eed290de9f89d Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Tue, 21 Sep 2021 17:22:17 +0200 Subject: [PATCH 09/41] refac: Improve QA exceptions according to the logging level #1900 --- .../wazuh_testing/tools/exceptions.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py index ae367afc25..e785bae671 100644 --- a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py +++ b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py @@ -1,10 +1,23 @@ +import sys -class AnsibleException(Exception): - def __init__(self, message): - super().__init__(message) -class QAValueError(Exception): +class QABaseException(Exception): def __init__(self, message, logger=None): self.message = f"\033[91m{message}\033[0m" - logger(message) - super().__init__(self.message) + if logger: + logger(self.message) + if logger.__name__ == 'debug': + super().__init__(self.message) + else: + sys.exit(1) + else: + super().__init__(self.message) + +class AnsibleException(QABaseException): + def __init__(self, message, logger=None): + super().__init__(message, logger) + + +class QAValueError(QABaseException): + def __init__(self, message, logger=None): + super().__init__(message, logger) From 86081c4762faa857218696f6c64ddfc86abb1909 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 22 Sep 2021 13:59:51 +0200 Subject: [PATCH 10/41] refac: Unify `qa-ctl` logging #1916 In addition, it improves the Logging class --- .../configuration/qa_ctl_configuration.py | 16 +++-- .../wazuh_testing/scripts/qa_ctl.py | 57 +++++++++------- .../wazuh_testing/tools/logging.py | 66 ++++++++++++------- 3 files changed, 87 insertions(+), 52 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py index 06cd9ceb00..5a0398f929 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py @@ -4,6 +4,7 @@ class QACTLConfiguration: Args: configuration_data (dict) : Dict with all the info needed for this module coming from config file. + debug_level (int): Debug level. It is indicated in the qa-ctl script parameters. Attributes: configuration_data (dict) : Dict with all the info needed for this module coming from config file. @@ -18,7 +19,7 @@ class QACTLConfiguration: logging_file (string): This field defines a path for a file where the outputs will be logged as well """ - def __init__(self, configuration_data): + def __init__(self, configuration_data, debug_level=0): self.configuration_data = configuration_data self.vagrant_output = False self.ansible_output = False @@ -28,10 +29,17 @@ def __init__(self, configuration_data): self.__read_configuration_data() + # Check debug level parameter set in qa-ctl script parameters. It has a higher priority than indicated in + # the configuration file. + if debug_level == 1: + self.logging_level = 'DEBUG' + if debug_level > 1: + self.vagrant_output = True + self.ansible_output = True + def __read_configuration_data(self): - """Read the given configuration data of the object and sets the values of the - parameters of the class. - """ + """Read the given configuration data of the object and sets the values of the parameters of the class.""" + if 'config' in self.configuration_data: if 'vagrant_output' in self.configuration_data['config']: self.vagrant_output = self.configuration_data['config']['vagrant_output'] diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 38e2c5c519..9d7622e884 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -29,7 +29,7 @@ TEST_KEY = 'tests' WAZUH_QA_FILES = os.path.join(gettempdir(), 'wazuh-qa') -qactl_script_logger = Logging('QACTL_SCRIPT', 'INFO', True) +qactl_logger = Logging(QACTL_LOGGER) _data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'data') launched = { 'instance_handler': False, @@ -44,10 +44,10 @@ def read_configuration_data(configuration_file_path): Args: configuration_file_path (string): Local path where is localted the qa-ctl configuration file. """ - qactl_script_logger.debug('Reading configuration_data') + qactl_logger.debug('Reading configuration_data') with open(configuration_file_path) as config_file_fd: configuration_data = yaml.safe_load(config_file_fd) - qactl_script_logger.debug('The configuration data has been read successfully') + qactl_logger.debug('The configuration data has been read successfully') return configuration_data @@ -58,7 +58,7 @@ def validate_configuration_data(configuration_data): Args: configuration_data (dict): Configuration data info. """ - qactl_script_logger.debug('Validating configuration schema') + qactl_logger.debug('Validating configuration schema') data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'data') schema_file = os.path.join(data_path, 'qactl_conf_validator_schema.json') @@ -67,7 +67,7 @@ def validate_configuration_data(configuration_data): validate(instance=configuration_data, schema=schema) - qactl_script_logger.debug('Schema validation has passed successfully') + qactl_logger.debug('Schema validation has passed successfully') def set_qactl_logging(qactl_configuration): @@ -77,11 +77,12 @@ def set_qactl_logging(qactl_configuration): qactl_configuration (dict): Configuration data info. """ if not qactl_configuration.logging_enable: - qactl_logger = Logging(QACTL_LOGGER) qactl_logger.disable() else: - qactl_logger = Logging(QACTL_LOGGER, qactl_configuration.logging_level, True, qactl_configuration.logging_file) - + qactl_logger.set_level(qactl_configuration.logging_level) + qactl_logger.stdout = True + qactl_logger.logging_file = qactl_configuration.logging_file + qactl_logger.update_configuration() def set_parameters(parameters): """Update script parameters and add extra information. @@ -94,6 +95,10 @@ def ask_user_credentials(parameters): parameters.user = input('Enter the username of Windows host: ') parameters.user_password = getpass(f"Enter the password of {parameters.user} user: ") + # Set logging level + level = 'DEBUG' if parameters.debug >= 1 else 'INFO' + qactl_logger.set_level(level) + if parameters.run_test and parameters.windows: # Automatic mode ask_user_credentials(parameters) elif parameters.config and parameters.windows: # Manual mode @@ -113,24 +118,24 @@ def validate_parameters(parameters): QAValueError: If parameters are incompatible, or version has not a valid format, or the specified wazuh version has not been released, or wazuh QA branch does not exist (calculated from wazuh_version). """ - qactl_script_logger.info('Validating input parameters') + qactl_logger.info('Validating input parameters') # Check incompatible parameters if parameters.config and parameters.run_test: raise QAValueError('The --run parameter is incompatible with --config. --run will autogenerate the ' - 'configuration', qactl_script_logger.error) + 'configuration', qactl_logger.error) if parameters.version and parameters.run_test is None: - raise QAValueError('The -v, --version parameter can only be used with -r, --run', qactl_script_logger.error) + raise QAValueError('The -v, --version parameter can only be used with -r, --run', qactl_logger.error) if parameters.dry_run and parameters.run_test is None: - raise QAValueError('The -d, --dry-run parameter can only be used with -r, --run', qactl_script_logger.error) + raise QAValueError('The --dry-run parameter can only be used with -r, --run', qactl_logger.error) # Check if Windows user credentials are correct - if parameters.user and parameters.user_password: + if 'user' in parameters and 'user_password' in parameters: if not check_windows_ansible_credentials(parameters.user, parameters.user_password): raise QAValueError('Windows credentials are not correct. Please check user and password and try again', - qactl_script_logger.error) + qactl_logger.error) # Check version parameter if parameters.version is not None: @@ -138,17 +143,17 @@ def validate_parameters(parameters): if len((parameters.version).split('.')) != 3: raise QAValueError(f"Version parameter has to be in format x.y.z. You entered {version}", - qactl_script_logger.error) + qactl_logger.error) if not version_is_released(parameters.version): raise QAValueError(f"The wazuh {parameters.version} version has not been released. Enter a right version.", - qactl_script_logger.error) + qactl_logger.error) short_version = f"{version.split('.')[0]}.{version.split('.')[1]}" if not branch_exist(short_version, WAZUH_QA_REPO): raise QAValueError(f"{short_version} branch does not exist in Wazuh QA repository.", - qactl_script_logger.error) + qactl_logger.error) # Check if tests exist if parameters.run_test: @@ -162,9 +167,9 @@ def validate_parameters(parameters): for test in parameters.run_test: if 'test exists' not in run_local_command(f"qa-docs -e {test} -I {WAZUH_QA_FILES}/tests/"): - raise QAValueError(f"{test} does not exist in {WAZUH_QA_FILES}/tests/", qactl_script_logger.error) + raise QAValueError(f"{test} does not exist in {WAZUH_QA_FILES}/tests/", qactl_logger.error) - qactl_script_logger.info('Input parameters validation has passed successfully') + qactl_logger.info('Input parameters validation has passed successfully') def main(): @@ -179,7 +184,7 @@ def main(): parser.add_argument('-p', '--persistent', action='store_true', help='Persistent instance mode. Do not destroy the instances once the process has finished.') - parser.add_argument('-d', '--dry-run', action='store_true', + parser.add_argument('--dry-run', action='store_true', help='Config generation mode. The test data will be processed and the configuration will be ' 'generated without running anything.') @@ -189,6 +194,8 @@ def main(): parser.add_argument('--version', '-v', type=str, action='store', required=False, dest='version', help='Wazuh installation and tests version') + parser.add_argument('-d', '--debug', action='count', default=0, help='Run in debug mode. You can increase the debug' + ' level with more [-d+]') parser.add_argument('-w', '--windows', action='store_true', help='Flag to indicate that the deployment environment ' 'will be deployed on native Windows.') @@ -200,15 +207,15 @@ def main(): # Generate or get the qactl configuration file if arguments.run_test: - qactl_script_logger.debug('Generating configuration file') + qactl_logger.debug('Generating configuration file') config_generator = QACTLConfigGenerator(arguments.run_test, arguments.version, WAZUH_QA_FILES) config_generator.run() configuration_file = config_generator.config_file_path - qactl_script_logger.debug(f"Configuration file has been created sucessfully in {configuration_file}") + qactl_logger.debug(f"Configuration file has been created sucessfully in {configuration_file}") # If dry-run mode, then exit after generating the configuration file if arguments.dry_run: - qactl_script_logger.info(f"Run as dry-run mode. Configuration file saved in " + qactl_logger.info(f"Run as dry-run mode. Configuration file saved in " f"{config_generator.config_file_path}") return 0 else: @@ -217,7 +224,7 @@ def main(): # Check configuration file path exists if not os.path.exists(configuration_file): raise QAValueError(f"{configuration_file} file doesn't exists or could not be generated correctly", - qactl_script_logger.error) + qactl_logger.error) # Read configuration data configuration_data = read_configuration_data(configuration_file) @@ -226,7 +233,7 @@ def main(): validate_configuration_data(configuration_data) # Set QACTL configuration - qactl_configuration = QACTLConfiguration(configuration_data) + qactl_configuration = QACTLConfiguration(configuration_data, arguments.debug) # Set QACTL logging set_qactl_logging(qactl_configuration) diff --git a/deps/wazuh_testing/wazuh_testing/tools/logging.py b/deps/wazuh_testing/wazuh_testing/tools/logging.py index 5aae14b26f..954d5c80d9 100644 --- a/deps/wazuh_testing/wazuh_testing/tools/logging.py +++ b/deps/wazuh_testing/wazuh_testing/tools/logging.py @@ -1,6 +1,7 @@ import logging import os + class Logging: """Class to handle modules logging. It is a wrapper class from logging standard python module. @@ -22,34 +23,19 @@ def __init__(self, logger_name, level='INFO', stdout=True, log_file=None): self.stdout = stdout self.log_file = log_file - self.__validate_parameters() - self.__initialize_parameters() - self.__default_config() - - def __validate_parameters(self): - """Verify class parameters value""" - if self.level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: - raise ValueError('LOGGER level must be one of the following values: DEBUG, INFO, WARNING, ERROR, CRITICAL') + self.update_configuration() - def __initialize_parameters(self): - """Set logger level, mapping the string into enum constant""" - level_mapping = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL - } - - self.level = level_mapping[self.level] - - def __default_config(self): + def update_configuration(self): """Set default handler configuration""" - if self.level == logging.DEBUG: + if self.level == 'DEBUG': formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s') else: formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s') - self.logger.setLevel(self.level) + + self.logger.setLevel(Logging.parse_level(self.level)) + + # Remove old hadlers if exist + self.logger.handlers = [] if self.stdout: handler = logging.StreamHandler() @@ -87,6 +73,40 @@ def get_logger(logger_name): """ return logging.getLogger(logger_name) + @staticmethod + def parse_level(level): + """Get logger level, mapping the string into enum constant + + Returns: + int: Logger level (10 DEBUG - 20 INFO - 30 WARNING - 40 ERROR - 50 CRITICAL) + + Raises: + ValueError if level is not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] + """ + if level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: + raise ValueError('LOGGER level must be one of the following values: DEBUG, INFO, WARNING, ERROR, CRITICAL') + + level_mapping = { + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL + } + + return level_mapping[level] + + def update_default_handlers(self, level='INFO', stdout=True, log_file=None): + self.stdout = stdout + self.log_file = log_file + self.level = level + + self.update_configuration() + + def set_level(self, level): + self.level = level + self.logger.setLevel(Logging.parse_level(level)) + def enable(self): """Enable logger""" self.logger.disabled = False From a6c6ef72d04b7b11f3620346fdebbf0ef72adf8a Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 22 Sep 2021 15:13:41 +0200 Subject: [PATCH 11/41] add: Show only traceback exceptions when `qa-ctl` is in debug mode #1916 --- .../qa_ctl/configuration/config_generator.py | 8 ++++---- .../qa_ctl/deployment/docker_wrapper.py | 2 +- .../qa_ctl/deployment/qa_infraestructure.py | 5 +++-- .../provisioning/ansible/ansible_runner.py | 5 +++-- .../wazuh_deployment/wazuh_s3_package.py | 3 ++- .../wazuh_testing/scripts/qa_ctl.py | 17 +++++++++-------- .../wazuh_testing/tools/exceptions.py | 14 ++++++++------ 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py index 3b6a606ef1..277ecbf6cd 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py @@ -102,7 +102,7 @@ def __get_test_info(self, test_name): except FileNotFoundError: raise QAValueError(f"Could not find {gettempdir()}/{test_name}.json file. Perhaps qa-docs has not " f"generated it correctly. Try manually with command: {qa_docs_command}", - QACTLConfigGenerator.LOGGER.error) + QACTLConfigGenerator.LOGGER.error, QACTL_LOGGER) # Add test name extra info info['test_name'] = test_name @@ -153,7 +153,7 @@ def _check_validate(check, test_info, allowed_values): if len(list(set(test_info[check]) & set(allowed_values))) == 0: error_message = f"{test_info['test_name']} cannot be launched. Reason: Currently we do not "\ f"support {test_info[check]}. Allowed values: {allowed_values}" - raise QAValueError(error_message, QACTLConfigGenerator.LOGGER.error) + raise QAValueError(error_message, QACTLConfigGenerator.LOGGER.error, QACTL_LOGGER) return True @@ -170,7 +170,7 @@ def _check_validate(check, test_info, allowed_values): if parse(str(test_info['wazuh_min_version'])) > parse(str(self.wazuh_version)): error_message = f"The minimal version of wazuh to launch the {test_info['test_name']} is " \ f"{test_info['wazuh_min_version']} and you are using {self.wazuh_version}" - raise QAValueError(error_message, QACTLConfigGenerator.LOGGER.error) + raise QAValueError(error_message, QACTLConfigGenerator.LOGGER.error, QACTL_LOGGER) return True @@ -202,7 +202,7 @@ def ip_is_already_used(ip, qactl_host_used_ips): break if _ip == 255: raise QAValueError(f"Could not find an IP available in {HOST_NETWORK}", - QACTLConfigGenerator.LOGGER.error) + QACTLConfigGenerator.LOGGER.error, QACTL_LOGGER) # Write new used IP in used IPs file with open(self.qactl_used_ips_file, 'a') as used_ips_file: diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py index 9bdbe170f9..4360da07c2 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py @@ -87,7 +87,7 @@ def run(self): 'subnets. Please check if you have already set this docker network ' \ '(run `docker network ls`) and then remove it if it is created with ' \ 'docker network rm ``' - raise QAValueError(exception_message, DockerWrapper.LOGGER.error) + raise QAValueError(exception_message, DockerWrapper.LOGGER.error, QACTL_LOGGER) def restart(self): """Restart the container. diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py index 3fe661af54..6765355d67 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py @@ -88,7 +88,8 @@ def __init__(self, deployment_data, qa_ctl_configuration): if network != self.network_address: exception_message = 'Two different networks where found for docker containers when only ' \ f"one network is allowed: {network} != {self.network_address}" - raise QAValueError(exception_message, QAInfraestructure.LOGGER.error) + raise QAValueError(exception_message, QAInfraestructure.LOGGER.error, QACTL_LOGGER, + QACTL_LOGGER) if not self.docker_network: # Try to get the DOCKER_NETWORK_NAME network, if it fails, try to create it. @@ -134,7 +135,7 @@ def run(self, local_environment_info): self.run_windows_deployment(local_environment_info['user'], local_environment_info['password']) else: raise QAValueError(f"local_environment_info['system'] is not a valid system for QAInfraestructure " - "runnning method", QAInfraestructure.LOGGER.error) + "runnning method", QAInfraestructure.LOGGER.error, QACTL_LOGGER) def run_unix_deployment(self): """Run the instances deployment when the local host is UNIX.""" diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py index 5d40693712..a9f064edba 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py @@ -47,7 +47,8 @@ def run(self): ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0: - raise AnsibleException(f"The playbook execution has failed. RC = {ansible_output.rc}") + raise AnsibleException(f"The playbook execution has failed. RC = {ansible_output.rc}", + AnsibleRunner.LOGGER.error, QACTL_LOGGER) return ansible_output @@ -77,7 +78,7 @@ def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_er ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0 and raise_on_error: - raise AnsibleException(f'Failed: {ansible_output}') + raise AnsibleException(f'Failed: {ansible_output}', AnsibleRunner.LOGGER.error, QACTL_LOGGER) return ansible_output diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py index 9ac7653692..b00cd10faf 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py @@ -60,7 +60,8 @@ def __get_package_url(self): self.revision, self.system, architecture) else: raise QAValueError('Could not get Wazuh Package S3 URL. s3_package_url or ' - '(version, repository, system, revision) has None value', WazuhS3Package.LOGGER.error) + '(version, repository, system, revision) has None value', WazuhS3Package.LOGGER.error, + QACTL_LOGGER) @staticmethod def get_architecture(system): diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 9d7622e884..1cebdf0eb7 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -123,19 +123,19 @@ def validate_parameters(parameters): # Check incompatible parameters if parameters.config and parameters.run_test: raise QAValueError('The --run parameter is incompatible with --config. --run will autogenerate the ' - 'configuration', qactl_logger.error) + 'configuration', qactl_logger.error, QACTL_LOGGER) if parameters.version and parameters.run_test is None: raise QAValueError('The -v, --version parameter can only be used with -r, --run', qactl_logger.error) if parameters.dry_run and parameters.run_test is None: - raise QAValueError('The --dry-run parameter can only be used with -r, --run', qactl_logger.error) + raise QAValueError('The --dry-run parameter can only be used with -r, --run', qactl_logger.error, QACTL_LOGGER) # Check if Windows user credentials are correct if 'user' in parameters and 'user_password' in parameters: if not check_windows_ansible_credentials(parameters.user, parameters.user_password): raise QAValueError('Windows credentials are not correct. Please check user and password and try again', - qactl_logger.error) + qactl_logger.error, QACTL_LOGGER) # Check version parameter if parameters.version is not None: @@ -143,17 +143,17 @@ def validate_parameters(parameters): if len((parameters.version).split('.')) != 3: raise QAValueError(f"Version parameter has to be in format x.y.z. You entered {version}", - qactl_logger.error) + qactl_logger.error, QACTL_LOGGER) if not version_is_released(parameters.version): raise QAValueError(f"The wazuh {parameters.version} version has not been released. Enter a right version.", - qactl_logger.error) + qactl_logger.error, QACTL_LOGGER) short_version = f"{version.split('.')[0]}.{version.split('.')[1]}" if not branch_exist(short_version, WAZUH_QA_REPO): raise QAValueError(f"{short_version} branch does not exist in Wazuh QA repository.", - qactl_logger.error) + qactl_logger.error, QACTL_LOGGER) # Check if tests exist if parameters.run_test: @@ -167,7 +167,8 @@ def validate_parameters(parameters): for test in parameters.run_test: if 'test exists' not in run_local_command(f"qa-docs -e {test} -I {WAZUH_QA_FILES}/tests/"): - raise QAValueError(f"{test} does not exist in {WAZUH_QA_FILES}/tests/", qactl_logger.error) + raise QAValueError(f"{test} does not exist in {WAZUH_QA_FILES}/tests/", qactl_logger.error, + QACTL_LOGGER) qactl_logger.info('Input parameters validation has passed successfully') @@ -224,7 +225,7 @@ def main(): # Check configuration file path exists if not os.path.exists(configuration_file): raise QAValueError(f"{configuration_file} file doesn't exists or could not be generated correctly", - qactl_logger.error) + qactl_logger.error, QACTL_LOGGER) # Read configuration data configuration_data = read_configuration_data(configuration_file) diff --git a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py index e785bae671..462d77eabf 100644 --- a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py +++ b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py @@ -1,12 +1,14 @@ import sys +from wazuh_testing.tools.logging import Logging class QABaseException(Exception): - def __init__(self, message, logger=None): + def __init__(self, message, logger=None, logger_name=None): self.message = f"\033[91m{message}\033[0m" if logger: logger(self.message) - if logger.__name__ == 'debug': + logger_level = Logging.get_logger(logger_name).level + if logger_level == 10: # DEBUG level super().__init__(self.message) else: sys.exit(1) @@ -14,10 +16,10 @@ def __init__(self, message, logger=None): super().__init__(self.message) class AnsibleException(QABaseException): - def __init__(self, message, logger=None): - super().__init__(message, logger) + def __init__(self, message, logger=None, logger_name=None): + super().__init__(message, logger, logger_name) class QAValueError(QABaseException): - def __init__(self, message, logger=None): - super().__init__(message, logger) + def __init__(self, message, logger=None, logger_name=None): + super().__init__(message, logger, logger_name) From bd607b2e82d5aacc12e1e77cfe5e8b839f4b574e Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 23 Sep 2021 13:40:22 +0200 Subject: [PATCH 12/41] add: Add the possibility to log ansible runner exceptions #1916 --- .../qa_ctl/provisioning/ansible/ansible.py | 13 ++++++++----- .../provisioning/ansible/ansible_runner.py | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py index f2ececf63d..2dde6955f2 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible.py @@ -5,19 +5,20 @@ from wazuh_testing.tools.exceptions import AnsibleException -def _ansible_runner(inventory_file_path, playbook_parameters, ansible_output=False): +def _ansible_runner(inventory_file_path, playbook_parameters, ansible_output=False, log_ansible_error=True): """Ansible runner method. Launch the playbook tasks with the indicated host. Args: inventory_file_path (str): Path where is located the inventory file. playbook_parameters (dict): Playbook parameters to create and launch. ansible_output (boolean): True for showing ansible output, False otherwise. + log_ansible_error (boolean): True for logging the error exception message if any. Returns: AnsibleOutput: Result of the ansible run. """ - tasks_result = AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, output=ansible_output) - + tasks_result = AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, output=ansible_output, + log_ansible_error=log_ansible_error) return tasks_result @@ -148,7 +149,7 @@ def launch_remote_commands(inventory_file_path, hosts, commands, become=False, a 'become': become}, ansible_output) -def check_windows_ansible_credentials(user, password): +def check_windows_ansible_credentials(user, password, log_ansible_error=False): """Check if the windows ansible credentials are correct. This method must be run in a Windows WSL. @@ -156,6 +157,7 @@ def check_windows_ansible_credentials(user, password): Args: user (str): Windows user. password (str): Windows user password. + log_ansible_error (boolean): True for logging the error exception message if any. Returns: boolean: True if credentials are correct, False otherwise. @@ -177,7 +179,8 @@ def check_windows_ansible_credentials(user, password): try: _ansible_runner(inventory_file_path, {'tasks_list': tasks_list, 'hosts': '127.0.0.1', 'gather_facts': True, - 'become': False}, ansible_output=False) + 'become': False}, ansible_output=False, + log_ansible_error=log_ansible_error) return True except AnsibleException: return False diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py index a9f064edba..fef2088a1c 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py @@ -32,9 +32,12 @@ def __init__(self, ansible_inventory_path, ansible_playbook_path, private_data_d self.private_data_dir = private_data_dir self.output = output - def run(self): + def run(self, log_ansible_error=True): """Run the ansible playbook in the indicated hosts. + Args: + log_ansible_error (boolean): True for logging the error exception message if any. + Returns: AnsibleOutput: Result of the ansible playbook run. """ @@ -47,13 +50,14 @@ def run(self): ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0: - raise AnsibleException(f"The playbook execution has failed. RC = {ansible_output.rc}", - AnsibleRunner.LOGGER.error, QACTL_LOGGER) + raise AnsibleException(f'Failed: {ansible_output}', AnsibleRunner.LOGGER.error, QACTL_LOGGER) if \ + log_ansible_error else AnsibleException(f'Failed: {ansible_output}') return ansible_output @staticmethod - def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_error=True, output=False): + def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_error=True, output=False, + log_ansible_error=True): """Run the ansible tasks given from playbook parameters Args: @@ -61,7 +65,8 @@ def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_er playbook_parameters : Parameters for the ansible playbook. raise_on_error (boolean): Set if errors or unexpected behaviour are goint to raise errors, Set to 'True' by default. - output (boolena): Set if there are going to be outputs. Set to 'False' by default. + output (boolean): Set if there are going to be outputs. Set to 'False' by default. + log_ansible_error (boolean): True for logging the error exception message if any. Returns: AnsibleOutput: Result of the ansible playbook run. @@ -78,7 +83,8 @@ def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_er ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0 and raise_on_error: - raise AnsibleException(f'Failed: {ansible_output}', AnsibleRunner.LOGGER.error, QACTL_LOGGER) + raise AnsibleException(f'Failed: {ansible_output}', AnsibleRunner.LOGGER.error, QACTL_LOGGER) if \ + log_ansible_error else AnsibleException(f'Failed: {ansible_output}') return ansible_output From 9bd3e4547649ac824b9f29942443321de5bbadd8 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 23 Sep 2021 13:41:34 +0200 Subject: [PATCH 13/41] add: Add custom AnsibleException management #1916 --- .../wazuh_testing/tools/exceptions.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py index 462d77eabf..2e9ef58cf9 100644 --- a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py +++ b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py @@ -2,24 +2,25 @@ from wazuh_testing.tools.logging import Logging + class QABaseException(Exception): - def __init__(self, message, logger=None, logger_name=None): + def __init__(self, message, exception_name=None, logger=None, logger_name=None): self.message = f"\033[91m{message}\033[0m" if logger: - logger(self.message) + logger(self.message) if exception_name == 'AnsibleException' else logger(message) logger_level = Logging.get_logger(logger_name).level - if logger_level == 10: # DEBUG level - super().__init__(self.message) - else: - sys.exit(1) + + # Disable exception traceback if logging level is not DEBUG + sys.tracebacklimit = 0 if logger_level > 10 else 1000 + + super().__init__() if exception_name == 'AnsibleException' else super().__init__(self.message) else: super().__init__(self.message) class AnsibleException(QABaseException): def __init__(self, message, logger=None, logger_name=None): - super().__init__(message, logger, logger_name) - + super().__init__(message, self.__class__.__name__, logger, logger_name) class QAValueError(QABaseException): def __init__(self, message, logger=None, logger_name=None): - super().__init__(message, logger, logger_name) + super().__init__(message, self.__class__.__name__, logger, logger_name) From b2dfb8a5749a4a9cf2c6785296fab6d6cc29ab25 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 23 Sep 2021 14:03:55 +0200 Subject: [PATCH 14/41] fix: Fix error parameters in `qa-ctl` script #1916 --- deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 1cebdf0eb7..1cecbd4397 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -245,8 +245,8 @@ def main(): deploy_dict = configuration_data[DEPLOY_KEY] local_environment_info = { 'system': 'windows' if arguments.windows else 'unix', - 'user': arguments.user if arguments.user else None, - 'password': arguments.user_password if arguments.user_password else None + 'user': arguments.user if'user' in arguments else None, + 'password': arguments.user_password if 'user_password' in arguments else None } instance_handler = QAInfraestructure(deploy_dict, qactl_configuration) From a198cd31630408d385fc20040378d0ce544b5c3b Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Mon, 27 Sep 2021 11:41:10 +0200 Subject: [PATCH 15/41] refac: Update python dependencies for Windows compatibility #1900 --- requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7992f483dc..f89749d212 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ numpy>=1.19.5 pandas>=1.1.5 paramiko==2.7.1 pillow>=6.2.0; platform_system == "Linux" or platform_system == "MacOS" or platform_system=='Windows' -psutil==5.6.6 +psutil py~=1.10.0 pycryptodome==3.9.8 pyOpenSSL==19.1.0 @@ -32,9 +32,9 @@ testinfra==5.0.0 jq==1.1.2; platform_system == "Linux" or platform_system == "MacOS" cryptography==3.3.2; platform_system == "Linux" or platform_system == "MacOS" or platform_system=='Windows' urllib3>=1.26.5 -elasticsearch>=7.14.1 ; platform_system == "Linux" +elasticsearch>=7.14.1 ; platform_system == "Linux" or platform_system=='Windows' numpydoc>=1.1.0 -ansible-runner>=2.0.1 ; platform_system == "Linux" -docker>=5.0.0 ; platform_system == "Linux" -python-vagrant>=0.5.15 ; platform_system == "Linux" +ansible-runner>=2.0.1 ; platform_system == "Linux" or platform_system=='Windows' +docker>=5.0.0 ; platform_system == "Linux" or platform_system=='Windows' +python-vagrant>=0.5.15 ; platform_system == "Linux" or platform_system=='Windows' ansible>=3.1.0 ; platform_system == "Linux" From f81e99c495cebeb28bf8e2e87742e32b5b94f1d2 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Mon, 27 Sep 2021 12:30:27 +0200 Subject: [PATCH 16/41] refac: Update qa-ctl instances deployment for Windows hosts #1900 --- .../qa_ctl/deployment/qa_infraestructure.py | 65 +------------------ .../provisioning/ansible/ansible_runner.py | 4 +- .../wazuh_testing/scripts/qa_ctl.py | 42 ++---------- 3 files changed, 11 insertions(+), 100 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py index 6765355d67..4f7ceeedef 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py @@ -3,21 +3,13 @@ # This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 import ipaddress import docker -import os -import yaml - -from tempfile import gettempdir from wazuh_testing.qa_ctl.deployment.docker_wrapper import DockerWrapper from wazuh_testing.qa_ctl.deployment.vagrant_wrapper import VagrantWrapper -from wazuh_testing.qa_ctl.provisioning.ansible.ansible import copy_files_to_remote, launch_remote_commands from wazuh_testing.tools.thread_executor import ThreadExecutor from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging from wazuh_testing.tools.exceptions import QAValueError -from wazuh_testing.tools.time import get_current_timestamp -from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance -from wazuh_testing.qa_ctl.provisioning.ansible.ansible_inventory import AnsibleInventory class QAInfraestructure: @@ -127,17 +119,7 @@ def __threads_runner(self, threads): for runner_thread in threads: runner_thread.join() - def run(self, local_environment_info): - """Launch instances deployment according to the local host system.""" - if local_environment_info['system'] == 'unix': - self.run_unix_deployment() - elif local_environment_info['system'] == 'windows': - self.run_windows_deployment(local_environment_info['user'], local_environment_info['password']) - else: - raise QAValueError(f"local_environment_info['system'] is not a valid system for QAInfraestructure " - "runnning method", QAInfraestructure.LOGGER.error, QACTL_LOGGER) - - def run_unix_deployment(self): + def run(self): """Run the instances deployment when the local host is UNIX.""" QAInfraestructure.LOGGER.info('Running Unix deployment') QAInfraestructure.LOGGER.info(f"Starting {len(self.instances)} instances deployment") @@ -146,51 +128,6 @@ def run_unix_deployment(self): QAInfraestructure.LOGGER.info('The instances deployment has finished sucessfully') - def run_windows_deployment(self, user, user_password): - """Run the instances deployment when the local host is Windows. - - This will provision the Windows host with the vagrant deployer script and deployment config file, and it will - launch that script using Ansible. - - Args: - user (str): Windows local user. - user_password(str): Windows local user password. - """ - QAInfraestructure.LOGGER.info('Running Windows deployment') - - # Generate deployment configuration file - config_file_name = f"qa_ctl_deployment_config_{get_current_timestamp()}.yaml" - config_file_path = f"{gettempdir()}/{config_file_name}" - - with open(config_file_path, 'w') as config_file: - config_file.write(yaml.dump({'deployment': self.deployment_data}, allow_unicode=True, sort_keys=False)) - - inventory = AnsibleInventory([ - AnsibleInstance('127.0.0.1', connection_user=user, connection_user_password=user_password, - connection_method='winrm', connection_port='5986') - ]) - - deployer_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'windows', 'vagrant_deployer.py') - windows_tmp_files_path = f"C:\\qa_ctl\\{get_current_timestamp()}" - windows_vagrant_deployer_path = f"{windows_tmp_files_path}\\vagrant_deployer.py" - windows_config_file_path = f"{windows_tmp_files_path}\\{config_file_path}" - vagrant_deployer_command = f"python {windows_vagrant_deployer_path} -c {windows_config_file_path}" - - QAInfraestructure.LOGGER.info('Provisioning deployment files to Windows host') - - # Provision vagrant deployer and deployment config file in Windows host - copy_files_to_remote(inventory.inventory_file_path, '127.0.0.1', [deployer_file_path, config_file_path], - windows_tmp_files_path, ansible_output=self.qa_ctl_configuration.ansible_output) - - QAInfraestructure.LOGGER.info('Provisioning has been done successfully') - QAInfraestructure.LOGGER.info(f"Starting {len(self.instances)} instances deployment on Windows host") - - # Launch vagrant deployer script - launch_remote_commands(inventory.inventory_file_path, '127.0.0.1', vagrant_deployer_command, - ansible_output=self.qa_ctl_configuration.ansible_output) - - QAInfraestructure.LOGGER.info('The instances deployment has finished sucessfully') - def halt(self): """Execute the 'halt' method on every configured instance.""" QAInfraestructure.LOGGER.info(f"Stopping {len(self.instances)} instances") diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py index fef2088a1c..807fedd135 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py @@ -1,4 +1,4 @@ -import ansible_runner +import sys import shutil from tempfile import gettempdir @@ -8,6 +8,8 @@ from wazuh_testing.tools.logging import Logging from wazuh_testing.tools.exceptions import AnsibleException +if sys.platform != 'win32': + import ansible_runner class AnsibleRunner: """Allow to run ansible playbooks in the indicated hosts. diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 1cecbd4397..6df9e6d520 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -8,7 +8,6 @@ from jsonschema import validate from tempfile import gettempdir -from getpass import getpass from wazuh_testing.qa_ctl.deployment.qa_infraestructure import QAInfraestructure from wazuh_testing.qa_ctl.provisioning.qa_provisioning import QAProvisioning @@ -21,7 +20,6 @@ from wazuh_testing.tools.github_repository import version_is_released, branch_exist, WAZUH_QA_REPO from wazuh_testing.qa_ctl.provisioning.local_actions import download_local_wazuh_qa_repository, run_local_command from wazuh_testing.tools.github_repository import get_last_wazuh_version -from wazuh_testing.qa_ctl.provisioning.ansible.ansible import check_windows_ansible_credentials DEPLOY_KEY = 'deployment' @@ -84,29 +82,17 @@ def set_qactl_logging(qactl_configuration): qactl_logger.logging_file = qactl_configuration.logging_file qactl_logger.update_configuration() + def set_parameters(parameters): """Update script parameters and add extra information. Args: (argparse.Namespace): Object with the user parameters. """ - def ask_user_credentials(parameters): - """Ask and set user and password credentials""" - parameters.user = input('Enter the username of Windows host: ') - parameters.user_password = getpass(f"Enter the password of {parameters.user} user: ") - # Set logging level level = 'DEBUG' if parameters.debug >= 1 else 'INFO' qactl_logger.set_level(level) - if parameters.run_test and parameters.windows: # Automatic mode - ask_user_credentials(parameters) - elif parameters.config and parameters.windows: # Manual mode - configuration_data = read_configuration_data(parameters.config) - - if DEPLOY_KEY in configuration_data: - ask_user_credentials(parameters) - def validate_parameters(parameters): """Validate the input parameters entered by the user of qa-ctl tool. @@ -131,12 +117,6 @@ def validate_parameters(parameters): if parameters.dry_run and parameters.run_test is None: raise QAValueError('The --dry-run parameter can only be used with -r, --run', qactl_logger.error, QACTL_LOGGER) - # Check if Windows user credentials are correct - if 'user' in parameters and 'user_password' in parameters: - if not check_windows_ansible_credentials(parameters.user, parameters.user_password): - raise QAValueError('Windows credentials are not correct. Please check user and password and try again', - qactl_logger.error, QACTL_LOGGER) - # Check version parameter if parameters.version is not None: version = (parameters.version).replace('v', '') @@ -163,12 +143,13 @@ def validate_parameters(parameters): version = get_last_wazuh_version() qa_branch = f"{version.split('.')[0]}.{version.split('.')[1]}".replace('v', '') - download_local_wazuh_qa_repository(branch=qa_branch, path=gettempdir()) + qa_branch = '1900-qa-ctl-windows' + download_local_wazuh_qa_repository(branch=qa_branch, path=os.path.join(gettempdir(), 'wazuh-qa')) for test in parameters.run_test: - if 'test exists' not in run_local_command(f"qa-docs -e {test} -I {WAZUH_QA_FILES}/tests/"): - raise QAValueError(f"{test} does not exist in {WAZUH_QA_FILES}/tests/", qactl_logger.error, - QACTL_LOGGER) + tests_path = os.path.join(WAZUH_QA_FILES, 'tests') + if 'test exists' not in run_local_command(f"qa-docs -e {test} -I {tests_path}"): + raise QAValueError(f"{test} does not exist in {tests_path}", qactl_logger.error, QACTL_LOGGER) qactl_logger.info('Input parameters validation has passed successfully') @@ -197,9 +178,6 @@ def main(): parser.add_argument('-d', '--debug', action='count', default=0, help='Run in debug mode. You can increase the debug' ' level with more [-d+]') - parser.add_argument('-w', '--windows', action='store_true', help='Flag to indicate that the deployment environment ' - 'will be deployed on native Windows.') - arguments = parser.parse_args() set_parameters(arguments) @@ -243,14 +221,8 @@ def main(): try: if DEPLOY_KEY in configuration_data: deploy_dict = configuration_data[DEPLOY_KEY] - local_environment_info = { - 'system': 'windows' if arguments.windows else 'unix', - 'user': arguments.user if'user' in arguments else None, - 'password': arguments.user_password if 'user_password' in arguments else None - } instance_handler = QAInfraestructure(deploy_dict, qactl_configuration) - - instance_handler.run(local_environment_info) + instance_handler.run() launched['instance_handler'] = True if PROVISION_KEY in configuration_data: From 8a9f4a12f62c3d77aaaddca5836afccb65c13160 Mon Sep 17 00:00:00 2001 From: mdengra Date: Mon, 27 Sep 2021 13:44:02 +0200 Subject: [PATCH 17/41] fix: Replace backslash in paths used as arguments Replace backslash with the slash in the paths where the input or output directory is specified. It seems that from Python version 3.7 onwards backslashes are interpreted as escape characters by the re module. This causes problems in Windows paths. Closes: #1930 --- deps/wazuh_testing/wazuh_testing/qa_docs/lib/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_docs/lib/config.py b/deps/wazuh_testing/wazuh_testing/qa_docs/lib/config.py index 386e0c3528..4121c1af84 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_docs/lib/config.py +++ b/deps/wazuh_testing/wazuh_testing/qa_docs/lib/config.py @@ -21,7 +21,7 @@ class Config(): def __init__(self, *args): # If it is called using the config file self.mode = mode.DEFAULT - self.project_path = args[1] + self.project_path = args[1].replace('\\','/') self.include_paths = [] self.include_regex = [] self.group_files = "" @@ -49,7 +49,7 @@ def __init__(self, *args): self._read_ignore_paths() if len(args) >= 3: - self.documentation_path = args[2] + self.documentation_path = args[2].replace('\\','/') if len(args) == 4: # It is called with a single test to parse self.mode = mode.SINGLE_TEST @@ -195,4 +195,4 @@ class mode(Enum): brief: Enumeration for classificate differents behaviours for DocGenerator ''' DEFAULT = 1 - SINGLE_TEST = 2 \ No newline at end of file + SINGLE_TEST = 2 From 454378055909369fc94f0273008580b075d9f31c Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Mon, 27 Sep 2021 15:23:21 +0200 Subject: [PATCH 18/41] refac: Change group name in qa-ctl Vagrantfile template #1900 --- .../wazuh_testing/qa_ctl/deployment/vagrantfile_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile_template.txt b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile_template.txt index 0ea5be7b27..70b42ee244 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile_template.txt +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile_template.txt @@ -20,7 +20,7 @@ Vagrant.configure("2") do |config| vb.cpus = "#{vm_parameters['cpus']}" vb.name = "#{vm_name}" vb.customize ["setextradata", :id, "VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled", 1] - vb.customize ["modifyvm", "#{vm_name}", "--groups", "#{vm_parameters['system']}"] + vb.customize ["modifyvm", "#{vm_name}", "--groups", "/qac-tl"] end end end From bc610a325dd4c9ffd5cf8459b870a0ebc5b7c275 Mon Sep 17 00:00:00 2001 From: mdengra Date: Mon, 27 Sep 2021 16:26:20 +0200 Subject: [PATCH 19/41] fix: Replace backslash in regex used as ignore and include paths It seems that from Python version 3.7 onwards backslashes are interpreted as escape characters by the re module. This causes problems in Windows paths. Closes: #1930 --- deps/wazuh_testing/wazuh_testing/qa_docs/doc_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_docs/doc_generator.py b/deps/wazuh_testing/wazuh_testing/qa_docs/doc_generator.py index ea5b1f9b7a..83faa2c07c 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_docs/doc_generator.py +++ b/deps/wazuh_testing/wazuh_testing/qa_docs/doc_generator.py @@ -29,11 +29,11 @@ def __init__(self, config): self.__id_counter = 0 self.ignore_regex = [] for ignore_regex in self.conf.ignore_paths: - self.ignore_regex.append(re.compile(ignore_regex)) + self.ignore_regex.append(re.compile(ignore_regex.replace('\\','/'))) self.include_regex = [] if self.conf.mode == mode.DEFAULT: for include_regex in self.conf.include_regex: - self.include_regex.append(re.compile(include_regex)) + self.include_regex.append(re.compile(include_regex.replace('\\','/'))) def is_valid_folder(self, path): """ From 7587555b350bc1faa3207c07d400fef78f230815 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Tue, 28 Sep 2021 12:06:15 +0200 Subject: [PATCH 20/41] fix: Fix QA provisioning health check --- .../qa_ctl/deployment/qa_infraestructure.py | 6 +-- .../qa_ctl/provisioning/qa_provisioning.py | 46 +++++++++++-------- .../qa_ctl/run_tests/qa_test_runner.py | 23 ++++++---- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py index 4f7ceeedef..8bb204cde8 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py @@ -80,8 +80,7 @@ def __init__(self, deployment_data, qa_ctl_configuration): if network != self.network_address: exception_message = 'Two different networks where found for docker containers when only ' \ f"one network is allowed: {network} != {self.network_address}" - raise QAValueError(exception_message, QAInfraestructure.LOGGER.error, QACTL_LOGGER, - QACTL_LOGGER) + raise QAValueError(exception_message, QAInfraestructure.LOGGER.error, QACTL_LOGGER) if not self.docker_network: # Try to get the DOCKER_NETWORK_NAME network, if it fails, try to create it. @@ -121,11 +120,8 @@ def __threads_runner(self, threads): def run(self): """Run the instances deployment when the local host is UNIX.""" - QAInfraestructure.LOGGER.info('Running Unix deployment') QAInfraestructure.LOGGER.info(f"Starting {len(self.instances)} instances deployment") - self.__threads_runner([ThreadExecutor(instance.run) for instance in self.instances]) - QAInfraestructure.LOGGER.info('The instances deployment has finished sucessfully') def halt(self): diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py index f9c8303453..63ab8c153e 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py @@ -1,4 +1,5 @@ import os +import sys from time import sleep from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance @@ -96,7 +97,7 @@ def __process_config_data(self, host_provision_info): if 'wazuh_deployment' in host_provision_info: deploy_info = host_provision_info['wazuh_deployment'] - health_check = True if 'health_check' not in host_provision_info['wazuh_deployment'] \ + health_check = False if 'health_check' not in host_provision_info['wazuh_deployment'] \ else host_provision_info['wazuh_deployment']['health_check'] install_target = None if 'target' not in deploy_info else deploy_info['target'] install_type = None if 'type' not in deploy_info else deploy_info['type'] @@ -140,23 +141,23 @@ def __process_config_data(self, host_provision_info): installation_files_parameters['repository'] = repository installation_instance = WazuhS3Package(**installation_files_parameters) remote_files_path = installation_instance.download_installation_files(self.inventory_file_path, - hosts=current_host) + hosts=current_host) elif s3_package_url is None and local_package_path is not None: installation_files_parameters['local_package_path'] = local_package_path installation_instance = WazuhLocalPackage(**installation_files_parameters) remote_files_path = installation_instance.download_installation_files(self.inventory_file_path, - hosts=current_host) + hosts=current_host) else: installation_files_parameters['s3_package_url'] = s3_package_url installation_instance = WazuhS3Package(**installation_files_parameters) remote_files_path = installation_instance.download_installation_files(self.inventory_file_path, - hosts=current_host) + hosts=current_host) if install_target == 'agent': deployment_instance = AgentDeployment(remote_files_path, - inventory_file_path=self.inventory_file_path, - install_mode=install_type, hosts=current_host, - server_ip=manager_ip, - qa_ctl_configuration=self.qa_ctl_configuration) + inventory_file_path=self.inventory_file_path, + install_mode=install_type, hosts=current_host, + server_ip=manager_ip, + qa_ctl_configuration=self.qa_ctl_configuration) if install_target == 'manager': deployment_instance = ManagerDeployment(remote_files_path, inventory_file_path=self.inventory_file_path, @@ -168,7 +169,7 @@ def __process_config_data(self, host_provision_info): # Wait for Wazuh initialization before health_check health_check_sleep_time = 60 QAProvisioning.LOGGER.info(f"Waiting {health_check_sleep_time} seconds before performing the " - f"healthcheck in {current_host} host") + f"healthcheck in {current_host} host") sleep(health_check_sleep_time) deployment_instance.health_check() @@ -180,7 +181,7 @@ def __process_config_data(self, host_provision_info): else qa_framework_info['wazuh_qa_branch'] qa_instance = QAFramework(qa_branch=wazuh_qa_branch, - ansible_output=self.qa_ctl_configuration.ansible_output) + ansible_output=self.qa_ctl_configuration.ansible_output) qa_instance.download_qa_repository(inventory_file_path=self.inventory_file_path, hosts=current_host) qa_instance.install_dependencies(inventory_file_path=self.inventory_file_path, hosts=current_host) qa_instance.install_framework(inventory_file_path=self.inventory_file_path, hosts=current_host) @@ -203,20 +204,25 @@ def __check_hosts_connection(self, hosts='all'): def run(self): """Provision all hosts in a parallel way""" - self.__check_hosts_connection() - provision_threads = [ThreadExecutor(self.__process_config_data, parameters={'host_provision_info': host_value}) - for _, host_value in self.provision_info['hosts'].items()] - QAProvisioning.LOGGER.info(f"Provisioning {len(provision_threads)} instances") - for runner_thread in provision_threads: - runner_thread.start() + if sys.platform == 'win32': + print("DOCKER RUN WINDOWS PROVISIONING") + else: + self.__check_hosts_connection() + provision_threads = [ThreadExecutor(self.__process_config_data, + parameters={'host_provision_info': host_value}) + for _, host_value in self.provision_info['hosts'].items()] + QAProvisioning.LOGGER.info(f"Provisioning {len(provision_threads)} instances") - for runner_thread in provision_threads: - runner_thread.join() + for runner_thread in provision_threads: + runner_thread.start() - QAProvisioning.LOGGER.info(f"The instances have been provisioned sucessfully") + for runner_thread in provision_threads: + runner_thread.join() + + QAProvisioning.LOGGER.info(f"The instances have been provisioned sucessfully") def destroy(self): """Destroy all the temporary files created by an instance of this object""" - if os.path.exists(self.inventory_file_path): + if os.path.exists(self.inventory_file_path) and sys.platform != 'win32': os.remove(self.inventory_file_path) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py index 03c8a0220a..72593d689e 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py @@ -1,4 +1,5 @@ import os +import sys from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance from wazuh_testing.qa_ctl.provisioning.ansible.ansible_inventory import AnsibleInventory @@ -130,21 +131,25 @@ def __build_test(self, test_params, host=['all']): def run(self): """Run testing threads. One thread per TestLauncher object""" - runner_threads = [ThreadExecutor(test_launcher.run) for test_launcher in self.test_launchers] - QATestRunner.LOGGER.info(f"Launching {len(runner_threads)} tests") + if sys.platform == 'win32': + print("DOCKER RUN WINDOWS TESTING") + else: + runner_threads = [ThreadExecutor(test_launcher.run) for test_launcher in self.test_launchers] + + QATestRunner.LOGGER.info(f"Launching {len(runner_threads)} tests") - for runner_thread in runner_threads: - runner_thread.start() + for runner_thread in runner_threads: + runner_thread.start() - QATestRunner.LOGGER.info('Waiting for tests to finish') + QATestRunner.LOGGER.info('Waiting for tests to finish') - for runner_thread in runner_threads: - runner_thread.join() + for runner_thread in runner_threads: + runner_thread.join() - QATestRunner.LOGGER.info('The test run is finished') + QATestRunner.LOGGER.info('The test run is finished') def destroy(self): """"Destroy all the temporary files created during a running QAtestRunner instance""" - if os.path.exists(self.inventory_file_path): + if os.path.exists(self.inventory_file_path) and sys.platform != 'win32': os.remove(self.inventory_file_path) From ade8d233643058fcd3e03afba733883b8f21be22 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Tue, 28 Sep 2021 12:11:27 +0200 Subject: [PATCH 21/41] refac: Delete wazuh_deployment and qa_framework schema contraints #1900 That labels are not required --- .../wazuh_testing/data/qactl_conf_validator_schema.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json b/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json index 53f372609b..803af7d393 100644 --- a/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json +++ b/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json @@ -120,9 +120,7 @@ "^host[0-9]*$": { "type": "object", "required": [ - "host_info", - "wazuh_deployment", - "qa_framework" + "host_info" ], "properties": { "host_info": { @@ -219,8 +217,8 @@ {"required": ["s3_package_url"]}, {"required": [ "system", - "version", - "revision", + "version", + "revision", "repository"]} ] } From 2f77124f10470c7b5946b3c5c1329beaba11c2f4 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Tue, 28 Sep 2021 12:59:33 +0200 Subject: [PATCH 22/41] add: Add `qa-ctl` Dockerfile to launch provisioning and testing modules on Windows #1900 --- .../qa_ctl/deployment/windows/Dockerfile | 22 +++++++++++++++++++ .../qa_ctl/deployment/windows/entrypoint.sh | 18 +++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/Dockerfile create mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/entrypoint.sh diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/Dockerfile b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/Dockerfile new file mode 100644 index 0000000000..b1ac325ada --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/Dockerfile @@ -0,0 +1,22 @@ +From ubuntu:focal + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get -q update && \ + apt-get install -y \ + git \ + python \ + python3 \ + sshpass \ + python3-pip \ + python3-setuptools + +ADD https://raw.githubusercontent.com/wazuh/wazuh-qa/1900-qa-ctl-windows/requirements.txt /tmp/requirements.txt +RUN python3 -m pip install --upgrade pip && python3 -m pip install -r /tmp/requirements.txt --ignore-installed + +RUN mkdir /qa_ctl + +COPY ./entrypoint.sh /usr/bin/entrypoint.sh +RUN chmod 755 /usr/bin/entrypoint.sh + +ENTRYPOINT ["/usr/bin/entrypoint.sh"] diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/entrypoint.sh b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/entrypoint.sh new file mode 100644 index 0000000000..bf341692ee --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +BRANCH="$1" +CONFIG_FILE_PATH="$2" +EXTRA_ARGS="${@:3}" + +# Clone custom wazuh-qa repository branch +git clone https://github.com/wazuh/wazuh-qa --depth=1 -b ${BRANCH} + +# Install python dependencies not installed from +python3 -m pip install -r wazuh-qa/requirements.txt + +# Install Wazuh QA framework +cd wazuh-qa/deps/wazuh_testing +python3 setup.py install + +# Run qa-ctl tool +/usr/local/bin/qa-ctl -c /qa_ctl/${CONFIG_FILE_PATH} ${EXTRA_ARGS[@]} From 11a085ed3dc701be2b668d8d7febf559fc7e5fcf Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 29 Sep 2021 09:29:17 +0200 Subject: [PATCH 23/41] fix: Fix qa repository download path in qa-ctl #1900 --- deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 6df9e6d520..926179ce1d 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -144,7 +144,7 @@ def validate_parameters(parameters): qa_branch = f"{version.split('.')[0]}.{version.split('.')[1]}".replace('v', '') qa_branch = '1900-qa-ctl-windows' - download_local_wazuh_qa_repository(branch=qa_branch, path=os.path.join(gettempdir(), 'wazuh-qa')) + download_local_wazuh_qa_repository(branch=qa_branch, path=gettempdir()) for test in parameters.run_test: tests_path = os.path.join(WAZUH_QA_FILES, 'tests') From 16af414e1c7de9400eaecc6f842d6ad16c492483 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 29 Sep 2021 12:30:51 +0200 Subject: [PATCH 24/41] add: Add new function for running local commands without output #1900 --- .../qa_ctl/configuration/config_generator.py | 4 +- .../qa_ctl/deployment/vagrant_wrapper.py | 4 +- .../qa_ctl/provisioning/local_actions.py | 49 ++++++++++++------- .../wazuh_testing/scripts/qa_ctl.py | 6 +-- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py index 277ecbf6cd..284e604b2d 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py @@ -12,7 +12,7 @@ from wazuh_testing.tools.s3_package import get_s3_package_url from wazuh_testing.qa_ctl.provisioning.wazuh_deployment.wazuh_s3_package import WazuhS3Package from wazuh_testing.tools.github_repository import get_last_wazuh_version -from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command +from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command_with_output class QACTLConfigGenerator: @@ -94,7 +94,7 @@ def __get_test_info(self, test_name): """ qa_docs_command = f"qa-docs -T {test_name} -o {gettempdir()} -I {self.qa_files_path}/tests" - run_local_command(qa_docs_command) + run_local_command_with_output(qa_docs_command) # Read test data file try: diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py index 18da38f4ac..47d56a171f 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py @@ -11,7 +11,7 @@ from wazuh_testing.qa_ctl.deployment.instance import Instance from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging -from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command +from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command_with_output class VagrantWrapper(Instance): @@ -54,7 +54,7 @@ def run(self): """Write the vagrantfile and starts the VM specified in the vagrantfile.""" VagrantWrapper.LOGGER.debug(f"Running {self.vm_name} vagrant up") - if len(run_local_command(f"vagrant box list | grep {self.vm_box}")) == 0: + if len(run_local_command_with_output(f"vagrant box list | grep {self.vm_box}")) == 0: VagrantWrapper.LOGGER.info(f"{self.vm_box} vagrant box not found in local repository. Downloading and " 'running') self.vagrant.up() diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py index 1d6d25ec87..b465f15e86 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py @@ -1,37 +1,50 @@ import subprocess -from wazuh_testing.tools import file -from wazuh_testing.qa_ctl.provisioning.ansible.ansible_inventory import AnsibleInventory -from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance -from wazuh_testing.qa_ctl.provisioning.qa_framework.qa_framework import QAFramework +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging +from wazuh_testing.tools.exceptions import QAValueError -LOCALHOST = '127.0.0.1' -LOCAL_ANSIBLE_INSTANCE = [AnsibleInstance('127.0.0.1', 'local_user', connection_method='local')] +LOGGER = Logging.get_logger(QACTL_LOGGER) -def download_local_wazuh_qa_repository(branch, path): - """Download wazuh QA repository in local machine. +def run_local_command(command): + """Run local commands without getting the output, but validating the result code. Args: - branch (string): Wazuh QA repository branch. - path (string): Local path where save the repository files. + command (string): Command to run. """ - qa_instance = QAFramework(ansible_output=False, qa_branch=branch, workdir=path) + run = subprocess.Popen(command, shell=True) - inventory = AnsibleInventory(LOCAL_ANSIBLE_INSTANCE) - try: - qa_instance.download_qa_repository(inventory_file_path=inventory.inventory_file_path, hosts=LOCALHOST) - finally: - file.delete_file(inventory.inventory_file_path) + # Wait for the process to finish + run.communicate() + result_code = run.returncode -def run_local_command(command): - """Run local commands in local machine. + if result_code != 0: + raise QAValueError(f"The command {command} returned {result_code} as result code.", LOGGER.error, + QACTL_LOGGER) + +def run_local_command_with_output(command): + """Run local commands getting the command output. Args: command (string): Command to run. + + Returns: + str: Command output """ run = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) return run.stdout.read().decode() + + +def download_local_wazuh_qa_repository(branch, path): + """Download wazuh QA repository in local machine. + + Args: + branch (string): Wazuh QA repository branch. + path (string): Local path where save the repository files. + """ + command = f"git clone https://github.com/wazuh/wazuh-qa --branch {branch} --single-branch {path}" + run_local_command_with_output(command) diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 926179ce1d..3607f3465a 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -18,7 +18,7 @@ from wazuh_testing.tools.exceptions import QAValueError from wazuh_testing.qa_ctl.configuration.config_generator import QACTLConfigGenerator from wazuh_testing.tools.github_repository import version_is_released, branch_exist, WAZUH_QA_REPO -from wazuh_testing.qa_ctl.provisioning.local_actions import download_local_wazuh_qa_repository, run_local_command +from wazuh_testing.qa_ctl.provisioning import local_actions from wazuh_testing.tools.github_repository import get_last_wazuh_version @@ -144,11 +144,11 @@ def validate_parameters(parameters): qa_branch = f"{version.split('.')[0]}.{version.split('.')[1]}".replace('v', '') qa_branch = '1900-qa-ctl-windows' - download_local_wazuh_qa_repository(branch=qa_branch, path=gettempdir()) + local_actions.download_local_wazuh_qa_repository(branch=qa_branch, path=gettempdir()) for test in parameters.run_test: tests_path = os.path.join(WAZUH_QA_FILES, 'tests') - if 'test exists' not in run_local_command(f"qa-docs -e {test} -I {tests_path}"): + if 'test exists' not in run_local_command_with_output(f"qa-docs -e {test} -I {tests_path}"): raise QAValueError(f"{test} does not exist in {tests_path}", qactl_logger.error, QACTL_LOGGER) qactl_logger.info('Input parameters validation has passed successfully') From 7a33280023daa63b0dfef8c60a4e872ffaf57323 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 29 Sep 2021 12:33:06 +0200 Subject: [PATCH 25/41] add: Add no-validation-logging parameter to qa-ctl #1900 --- deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 3607f3465a..8757df555c 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -77,6 +77,7 @@ def set_qactl_logging(qactl_configuration): if not qactl_configuration.logging_enable: qactl_logger.disable() else: + qactl_logger.enable() qactl_logger.set_level(qactl_configuration.logging_level) qactl_logger.stdout = True qactl_logger.logging_file = qactl_configuration.logging_file @@ -89,9 +90,11 @@ def set_parameters(parameters): Args: (argparse.Namespace): Object with the user parameters. """ - # Set logging level - level = 'DEBUG' if parameters.debug >= 1 else 'INFO' - qactl_logger.set_level(level) + if parameters.no_validation_logging: + qactl_logger.disable() + else: + level = 'DEBUG' if parameters.debug >= 1 else 'INFO' + qactl_logger.set_level(level) def validate_parameters(parameters): @@ -148,7 +151,7 @@ def validate_parameters(parameters): for test in parameters.run_test: tests_path = os.path.join(WAZUH_QA_FILES, 'tests') - if 'test exists' not in run_local_command_with_output(f"qa-docs -e {test} -I {tests_path}"): + if 'test exists' not in local_actions.run_local_command_with_output(f"qa-docs -e {test} -I {tests_path}"): raise QAValueError(f"{test} does not exist in {tests_path}", qactl_logger.error, QACTL_LOGGER) qactl_logger.info('Input parameters validation has passed successfully') @@ -178,6 +181,8 @@ def main(): parser.add_argument('-d', '--debug', action='count', default=0, help='Run in debug mode. You can increase the debug' ' level with more [-d+]') + parser.add_argument('--no-validation-logging', action='store_true', help='Disable initial logging of parameter ' + 'validations') arguments = parser.parse_args() set_parameters(arguments) From 4357d88492a0614bd8a258769f4a554207b377df Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 29 Sep 2021 13:10:11 +0200 Subject: [PATCH 26/41] add: Integrate docker container provision and testing tasks in Windows qa-ctl #1900 --- .../configuration/qa_ctl_configuration.py | 1 + .../qa_ctl/provisioning/qa_provisioning.py | 27 +++++++++++++++++-- .../qa_ctl/run_tests/qa_test_runner.py | 26 ++++++++++++++++-- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py index 5a0398f929..3f5bb401f2 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py @@ -26,6 +26,7 @@ def __init__(self, configuration_data, debug_level=0): self.logging_enable = True self.logging_level = 'INFO' self.logging_file = None + self.debug_level = debug_level self.__read_configuration_data() diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py index 63ab8c153e..55b2bc9538 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py @@ -1,5 +1,7 @@ import os import sys +from tempfile import gettempdir + from time import sleep from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance @@ -15,6 +17,9 @@ from wazuh_testing.tools.thread_executor import ThreadExecutor from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging +from wazuh_testing.tools.time import get_current_timestamp +from wazuh_testing.tools import file +from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command class QAProvisioning(): @@ -132,8 +137,8 @@ def __process_config_data(self, host_provision_info): if install_type == 'sources': installation_files_parameters['wazuh_branch'] = wazuh_branch installation_instance = WazuhSources(**installation_files_parameters) - if install_type == 'package': + if install_type == 'package': if s3_package_url is None and local_package_path is None: installation_files_parameters['system'] = system installation_files_parameters['version'] = version @@ -206,7 +211,25 @@ def run(self): """Provision all hosts in a parallel way""" if sys.platform == 'win32': - print("DOCKER RUN WINDOWS PROVISIONING") + tmp_config_file_name = f"config_{get_current_timestamp()}.yaml" + tmp_config_file = os.path.join(gettempdir(), tmp_config_file_name) + + file.write_yaml_file(tmp_config_file, {'provision': self.provision_info}) + + try: + debug_arg = '' if self.qa_ctl_configuration.debug_level == 0 else \ + ('-d' if self.qa_ctl_configuration.debug_level == 1 else '-dd') + + docker_args = f"1900-qa-ctl-windows {tmp_config_file_name} --no-validation-logging {debug_arg}" + + QAProvisioning.LOGGER.debug('Creating a Linux container for provisioning the instances') + + run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") + + QAProvisioning.LOGGER.debug('The container for provisioning the instances was run sucessfully') + finally: + file.remove_file(tmp_config_file) + else: self.__check_hosts_connection() provision_threads = [ThreadExecutor(self.__process_config_data, diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py index 72593d689e..b39376428c 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py @@ -1,5 +1,6 @@ import os import sys +from tempfile import gettempdir from wazuh_testing.qa_ctl.provisioning.ansible.ansible_instance import AnsibleInstance from wazuh_testing.qa_ctl.provisioning.ansible.ansible_inventory import AnsibleInventory @@ -8,7 +9,9 @@ from wazuh_testing.tools.thread_executor import ThreadExecutor from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging - +from wazuh_testing.tools.time import get_current_timestamp +from wazuh_testing.tools import file +from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command class QATestRunner(): """The class encapsulates the build of the tests from the test parameters read from the configuration file @@ -21,13 +24,16 @@ class QATestRunner(): inventory_file_path (string): Path of the inventory file generated. test_launchers (list(TestLauncher)): Test launchers objects (one for each host). qa_ctl_configuration (QACTLConfiguration): QACTL configuration. + test_parameters (dict): a dictionary containing all the required data to build the tests """ LOGGER = Logging.get_logger(QACTL_LOGGER) def __init__(self, tests_parameters, qa_ctl_configuration): self.inventory_file_path = None self.test_launchers = [] + self.test_parameters = tests_parameters self.qa_ctl_configuration = qa_ctl_configuration + self.__process_inventory_data(tests_parameters) self.__process_test_data(tests_parameters) @@ -133,7 +139,23 @@ def run(self): """Run testing threads. One thread per TestLauncher object""" if sys.platform == 'win32': - print("DOCKER RUN WINDOWS TESTING") + tmp_config_file_name = f"config_{get_current_timestamp()}.yaml" + tmp_config_file = os.path.join(gettempdir(), tmp_config_file_name) + + file.write_yaml_file(tmp_config_file, {'tests': self.test_parameters}) + + try: + debug_arg = '' if self.qa_ctl_configuration.debug_level == 0 else \ + ('-d' if self.qa_ctl_configuration.debug_level == 1 else '-dd') + docker_args = f"1900-qa-ctl-windows {tmp_config_file_name} --no-validation-logging {debug_arg}" + QATestRunner.LOGGER.debug('Creating a Linux container for launching the tests') + + run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") + + QATestRunner.LOGGER.debug('The container for launching the tests was run sucessfully') + finally: + file.remove_file(tmp_config_file) + else: runner_threads = [ThreadExecutor(test_launcher.run) for test_launcher in self.test_launchers] From 6fc569284fcb75128915fe06bc248319fd9eaea8 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 29 Sep 2021 16:16:55 +0200 Subject: [PATCH 27/41] refac: Move qa-ctl Dockerfile #1900 --- deps/wazuh_testing/setup.py | 2 +- .../qa_ctl}/Dockerfile | 0 .../qa_ctl}/entrypoint.sh | 10 +- .../qa_ctl/deployment/windows/config.yaml | 25 -- .../deployment/windows/requirements.txt | 5 - .../deployment/windows/vagrant_deployer.py | 237 ------------------ 6 files changed, 6 insertions(+), 273 deletions(-) rename deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/{windows => dockerfiles/qa_ctl}/Dockerfile (100%) rename deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/{windows => dockerfiles/qa_ctl}/entrypoint.sh (66%) delete mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml delete mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt delete mode 100644 deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py diff --git a/deps/wazuh_testing/setup.py b/deps/wazuh_testing/setup.py index 9fb034c48f..b2b03de50a 100644 --- a/deps/wazuh_testing/setup.py +++ b/deps/wazuh_testing/setup.py @@ -22,10 +22,10 @@ 'tools/macos_log/log_generator.m', 'qa_docs/config.yaml', 'qa_ctl/deployment/dockerfiles/*', + 'qa_ctl/deployment/dockerfiles/qa_ctl/*', 'qa_ctl/deployment/vagrantfile_template.txt', 'qa_ctl/provisioning/wazuh_deployment/templates/preloaded_vars.conf.j2', 'data/qactl_conf_validator_schema.json', - 'qa_ctl/deployment/windows/vagrant_deployer.py' ] scripts_list = [ diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/Dockerfile b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/Dockerfile similarity index 100% rename from deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/Dockerfile rename to deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/Dockerfile diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/entrypoint.sh b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/entrypoint.sh similarity index 66% rename from deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/entrypoint.sh rename to deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/entrypoint.sh index bf341692ee..bab4e21241 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/entrypoint.sh +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/entrypoint.sh @@ -5,14 +5,14 @@ CONFIG_FILE_PATH="$2" EXTRA_ARGS="${@:3}" # Clone custom wazuh-qa repository branch -git clone https://github.com/wazuh/wazuh-qa --depth=1 -b ${BRANCH} +git clone https://github.com/wazuh/wazuh-qa --depth=1 -b ${BRANCH} &> /dev/null # Install python dependencies not installed from -python3 -m pip install -r wazuh-qa/requirements.txt +python3 -m pip install -r wazuh-qa/requirements.txt &> /dev/null # Install Wazuh QA framework -cd wazuh-qa/deps/wazuh_testing -python3 setup.py install +cd wazuh-qa/deps/wazuh_testing &> /dev/null +python3 setup.py install &> /dev/null # Run qa-ctl tool -/usr/local/bin/qa-ctl -c /qa_ctl/${CONFIG_FILE_PATH} ${EXTRA_ARGS[@]} +/usr/local/bin/qa-ctl -c /qa_ctl/${CONFIG_FILE_PATH} ${EXTRA_ARGS} diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml deleted file mode 100644 index c783f9a359..0000000000 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/config.yaml +++ /dev/null @@ -1,25 +0,0 @@ -deployment: - host_1: - provider: - vagrant: - enabled: true - vagrantfile_path: /tmp - vagrant_box: qactl/centos_8 - vm_memory: 1024 - vm_cpu: 1 - vm_name: manager_test_general_settings_enabled_1631885562.550129 - vm_system: linux - label: manager_test_general_settings_enabled_1631885562.550129 - vm_ip: 10.150.50.4 - host_2: - provider: - vagrant: - enabled: true - vagrantfile_path: /tmp - vagrant_box: qactl/ubuntu_20_04 - vm_memory: 1024 - vm_cpu: 1 - vm_name: manager_test_general_settings_enabled_1631885562.550129 - vm_system: linux - label: manager_test_general_settings_enabled_1631885562.550129 - vm_ip: 10.150.50.5 \ No newline at end of file diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt deleted file mode 100644 index a1df77e970..0000000000 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -requests -lockfile -setuptools -python-vagrant -pyyaml diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py deleted file mode 100644 index 2521b3c990..0000000000 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/windows/vagrant_deployer.py +++ /dev/null @@ -1,237 +0,0 @@ -import yaml -import vagrant -import argparse -import os -import json -import logging -import threading - -import requests - -from tempfile import gettempdir -from datetime import datetime - - -LOGGER = logging.getLogger('vagrant_deployer') -TMP_FILES_PATH = os.path.join(gettempdir(), 'vagrant_deployer') -VAGRANTFILE_TEMPLATE_URL = 'https://raw.githubusercontent.com/wazuh/wazuh-qa/1900-qa-ctl-windows/deps/wazuh_testing/' \ - 'wazuh_testing/qa_ctl/deployment/vagrantfile_template.txt' - - -class ThreadExecutor(threading.Thread): - """Class which allows us to upload the thread exception to the parent process. - - This is useful to cause the pytest test to fail in the event of an exception or failure in any of the threads. - - Args: - function (callable): Function to run in the thread. - parameters (dict): Function parameters. Used as kwargs in the callable function. - - Attributes: - function (callable): Function to run in the thread. - parameters (dict): Function parameters. Used as kwargs in the callable function. - exception (Exception): Thread exception in case it has occurred. - """ - def __init__(self, function, parameters={}): - super().__init__() - self.function = function - self.exception = None - self.parameters = parameters - self._return = None - - def _run(self): - """Run the target function with its parameters in the thread""" - self._return = self.function(**self.parameters) - - def run(self): - """Overwrite run function of threading Thread module. - - Launch the target function and catch the exception in case it occurs. - """ - self.exc = None - try: - self._run() - except Exception as e: - self.exception = e - - def join(self): - """Overwrite join function of threading Thread module. - - Raises the exception to the parent in case it was raised when executing the target function. - - Raises: - Exception: Target function exception if ocurrs - """ - super(ThreadExecutor, self).join() - if self.exception: - raise self.exception - - return self._return - - -def read_parameters(): - parser = argparse.ArgumentParser() - - parser.add_argument('-c', '--config', type=str, action='store', required=True, dest='config', - help='Path to the configuration file.') - - parser.add_argument('-d', '--debug', action='store_true', - help='Persistent instance mode. Do not destroy the instances once the process has finished.') - - parameters = parser.parse_args() - - return parameters - - -def set_logging(debug_mode=False): - logging_level = logging.DEBUG if debug_mode else logging.INFO - - LOGGER.setLevel(logging_level) - - handler = logging.StreamHandler() - handler.setLevel(logging_level) - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - handler.setFormatter(formatter) - - LOGGER.addHandler(handler) - - -def process_config_file(configuration_file_path, debug_mode=False): - def _read_configuration_data(configuration_file_path): - with open(configuration_file_path) as config_file_fd: - configuration_data = yaml.safe_load(config_file_fd) - - return configuration_data - - LOGGER.debug(f"Processing {configuration_file_path} configuration file") - - instance_list = _read_configuration_data(configuration_file_path)['deployment'] - instances_data = [] - - for host in instance_list: - for provider in instance_list[host]['provider']: - data = instance_list[host]['provider'][provider] - if not data['enabled']: - continue - - if provider == 'vagrant': - vm_name = data['vm_name'].replace('_', '-') - instances_data.append({'vagrantfile_path': data['vagrantfile_path'], 'box': data['vagrant_box'], - 'label': data['label'], 'name': vm_name, 'cpus': data['vm_cpu'], - 'memory': data['vm_memory'], 'system': data['vm_system'], - 'ip': data['vm_ip'], 'quiet_out': not debug_mode}) - else: - raise ValueError("This tool can only deploy vagrant machines") - - LOGGER.debug(f"The {configuration_file_path} configuration file has been processed successfully") - - return instances_data - - -def download_vagrantfile_template(vagrantfile_template_file_path): - if not os.path.exists(vagrantfile_template_file_path): - LOGGER.debug(f"Downloading Vagrantfile template file from {vagrantfile_template_file_path}") - - with open(vagrantfile_template_file_path, 'w') as f: - f.write((requests.get(VAGRANTFILE_TEMPLATE_URL)).text) - - LOGGER.debug(f"The Vagrantfile template has been downloaded successfully") - - -def create_vagrantfile(instance_data, vagrantfile_template_file_path): - def _get_box_url(box_name): - box_mapping = { - 'qactl/ubuntu_20_04': 'https://s3.amazonaws.com/ci.wazuh.com/qa/boxes/QACTL_ubuntu20_04.box', - 'qactl/centos_8': 'https://s3.amazonaws.com/ci.wazuh.com/qa/boxes/QACTL_centos_8.box' - } - - return box_mapping[box_name] - - def _read_vagrantfile_template(vagrantfile_template_file_path): - with open(vagrantfile_template_file_path, 'r') as template_fd: - return template_fd.readlines() - - def _parse_instance_data(instance_data): - return json.dumps({ - instance_data['name']: { - 'box_image': instance_data['box'], - 'box_url': _get_box_url(instance_data['box']), - 'vm_label': instance_data['label'], - 'cpus': instance_data['cpus'], - 'memory': instance_data['memory'], - 'system': instance_data['system'], - 'private_ip': instance_data['ip'] - } - }) - - def _write_vagrantfile(instance_data, vagrantfile_file_path, vagrantfile_template_file_path): - LOGGER.debug(f"Writing Vagrantfile for {instance_data['name']} instance in {vagrantfile_file_path} path") - - REPLACE_PATTERN = 'json_box = {}\n' - read_lines = _read_vagrantfile_template(vagrantfile_template_file_path) - replace_line = read_lines.index(REPLACE_PATTERN) - read_lines[replace_line] = REPLACE_PATTERN.format(f"'{_parse_instance_data(instance_data)}'") - - with open(vagrantfile_file_path, 'w') as vagrantfile_fd: - vagrantfile_fd.writelines(read_lines) - - LOGGER.debug(f"Vagrantfile for {instance_data['name']} instance has been written sucessfully") - - vagrantfile_path = os.path.join(TMP_FILES_PATH, instance_data['name']) - - if not os.path.exists(vagrantfile_path): - os.makedirs(vagrantfile_path) - LOGGER.debug(f"{vagrantfile_path} path has been created") - - _write_vagrantfile(instance_data, os.path.join(vagrantfile_path, 'Vagrantfile'), vagrantfile_template_file_path) - - -def deploy(instances_data, vagrantfile_template_file_path): - def __threads_runner(threads): - for runner_thread in threads: - runner_thread.start() - - for runner_thread in threads: - runner_thread.join() - - def _deploy_instance(instance_data, vagrantfile_template_file_path): - LOGGER.debug(f"Deploying {instance_data['name']} instance") - - create_vagrantfile(instance_data, vagrantfile_template_file_path) - vagrantfile_path = os.path.join(TMP_FILES_PATH, instance_data['name']) - vagrant_instance = vagrant.Vagrant(root=vagrantfile_path, quiet_stdout=instance_data['quiet_out'], - quiet_stderr=False) - vagrant_instance.up() - - LOGGER.debug(f"The {instance_data['name']} instance has been deployed successfully") - - LOGGER.info(f"Deploying {len(instances_data)} instances") - - __threads_runner( - [ThreadExecutor(_deploy_instance, {'instance_data': instance, - 'vagrantfile_template_file_path': vagrantfile_template_file_path}) - for instance in instances_data]) - - LOGGER.info(f"The {len(instances_data)} instances has been deployed sucessfully") - - -def main(): - parameters = read_parameters() - - set_logging(True if parameters.debug else False) - - instances_data = process_config_file(parameters.config) - - if not os.path.exists(TMP_FILES_PATH): - os.makedirs(TMP_FILES_PATH) - LOGGER.debug(f"{TMP_FILES_PATH} path has been created") - - vagrantfile_template_file_path = os.path.join(TMP_FILES_PATH, 'vagrantfile_template.txt') - - download_vagrantfile_template(vagrantfile_template_file_path) - - deploy(instances_data, vagrantfile_template_file_path) - - -if __name__ == '__main__': - main() From f5c9a1b0b4b61746d51bc68cb6c7f63f52d43946 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 29 Sep 2021 16:35:43 +0200 Subject: [PATCH 28/41] refac: Improve docker integration in Windows qa-ctl #1900 --- .../qa_ctl/provisioning/local_actions.py | 23 +++++++++++++++++++ .../qa_ctl/provisioning/qa_provisioning.py | 17 ++++---------- .../qa_ctl/run_tests/qa_test_runner.py | 15 ++++-------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py index b465f15e86..ee3cde0b04 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py @@ -1,4 +1,6 @@ import subprocess +import os +from tempfile import gettempdir from wazuh_testing.qa_ctl import QACTL_LOGGER from wazuh_testing.tools.logging import Logging @@ -13,6 +15,9 @@ def run_local_command(command): Args: command (string): Command to run. + + Raises: + QAValueError: If the run command has failed (rc != 0). """ run = subprocess.Popen(command, shell=True) @@ -48,3 +53,21 @@ def download_local_wazuh_qa_repository(branch, path): """ command = f"git clone https://github.com/wazuh/wazuh-qa --branch {branch} --single-branch {path}" run_local_command_with_output(command) + + +def qa_ctl_docker_run(config_file, debug_level): + """Run qa-ctl in a Linux docker container. Useful when running qa-ctl in native Windows host. + + Args: + config_file (str): qa-ctl configuration file name to run. + debug_level (int): qa-ctl debug level. + """ + debug_args = '' if debug_level == 0 else ('-d' if debug_level == 1 else '-dd') + docker_args = f"1900-qa-ctl-windows {config_file} --no-validation-logging {debug_args}" + docker_image_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'deployment', + 'dockerfiles', 'qa_ctl') + + LOGGER.info('Building docker image') + run_local_command_with_output(f"cd {docker_image_path} && docker build -q -t wazuh/qa-ctl .") + + run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py index 55b2bc9538..c4c6822cd2 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py @@ -19,7 +19,7 @@ from wazuh_testing.tools.logging import Logging from wazuh_testing.tools.time import get_current_timestamp from wazuh_testing.tools import file -from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command +from wazuh_testing.qa_ctl.provisioning.local_actions import qa_ctl_docker_run class QAProvisioning(): @@ -209,7 +209,7 @@ def __check_hosts_connection(self, hosts='all'): def run(self): """Provision all hosts in a parallel way""" - + # If Windows, then run a Linux docker container to run provisioning stage with qa-ctl provision if sys.platform == 'win32': tmp_config_file_name = f"config_{get_current_timestamp()}.yaml" tmp_config_file = os.path.join(gettempdir(), tmp_config_file_name) @@ -217,19 +217,10 @@ def run(self): file.write_yaml_file(tmp_config_file, {'provision': self.provision_info}) try: - debug_arg = '' if self.qa_ctl_configuration.debug_level == 0 else \ - ('-d' if self.qa_ctl_configuration.debug_level == 1 else '-dd') - - docker_args = f"1900-qa-ctl-windows {tmp_config_file_name} --no-validation-logging {debug_arg}" - - QAProvisioning.LOGGER.debug('Creating a Linux container for provisioning the instances') - - run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") - - QAProvisioning.LOGGER.debug('The container for provisioning the instances was run sucessfully') + QAProvisioning.LOGGER.info('Creating a Linux container for provisioning the instances') + qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level) finally: file.remove_file(tmp_config_file) - else: self.__check_hosts_connection() provision_threads = [ThreadExecutor(self.__process_config_data, diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py index b39376428c..a1b550a96e 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py @@ -11,7 +11,7 @@ from wazuh_testing.tools.logging import Logging from wazuh_testing.tools.time import get_current_timestamp from wazuh_testing.tools import file -from wazuh_testing.qa_ctl.provisioning.local_actions import run_local_command +from wazuh_testing.qa_ctl.provisioning.local_actions import qa_ctl_docker_run class QATestRunner(): """The class encapsulates the build of the tests from the test parameters read from the configuration file @@ -137,7 +137,7 @@ def __build_test(self, test_params, host=['all']): def run(self): """Run testing threads. One thread per TestLauncher object""" - + # If Windows, then run a Linux docker container to run testing stage with qa-ctl testing if sys.platform == 'win32': tmp_config_file_name = f"config_{get_current_timestamp()}.yaml" tmp_config_file = os.path.join(gettempdir(), tmp_config_file_name) @@ -145,17 +145,10 @@ def run(self): file.write_yaml_file(tmp_config_file, {'tests': self.test_parameters}) try: - debug_arg = '' if self.qa_ctl_configuration.debug_level == 0 else \ - ('-d' if self.qa_ctl_configuration.debug_level == 1 else '-dd') - docker_args = f"1900-qa-ctl-windows {tmp_config_file_name} --no-validation-logging {debug_arg}" - QATestRunner.LOGGER.debug('Creating a Linux container for launching the tests') - - run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") - - QATestRunner.LOGGER.debug('The container for launching the tests was run sucessfully') + QATestRunner.LOGGER.info('Creating a Linux container for launching the tests') + qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level) finally: file.remove_file(tmp_config_file) - else: runner_threads = [ThreadExecutor(test_launcher.run) for test_launcher in self.test_launchers] From 19ec5f2f32e09e46b6b32461e65bf7d84b886294 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Wed, 29 Sep 2021 16:49:06 +0200 Subject: [PATCH 29/41] refac: Rename ubuntu focal vagrant box URL --- .../wazuh_testing/qa_ctl/deployment/vagrantfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py index 7fea64240f..33624f7637 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py @@ -63,7 +63,7 @@ def __get_box_url(self): it will return a 'None' value. """ box_mapping = { - 'qactl/ubuntu_20_04': 'https://s3.amazonaws.com/ci.wazuh.com/qa/boxes/QACTL_ubuntu20_04.box', + 'qactl/ubuntu_20_04': 'https://s3.amazonaws.com/ci.wazuh.com/qa/boxes/QACTL_ubuntu_20_04.box', 'qactl/centos_8': 'https://s3.amazonaws.com/ci.wazuh.com/qa/boxes/QACTL_centos_8.box' } From 1cbb1bf0f47a5ae2e14f02d11cb3a8b7806b9726 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 30 Sep 2021 12:41:27 +0200 Subject: [PATCH 30/41] refac: Rename assets directory from qa-ctl results --- deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py index bd3a76332d..ba4a1ebd6f 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py @@ -82,10 +82,10 @@ def run(self, ansible_inventory_path): Args: ansible_inventory_path (str): Path to ansible inventory file """ + date_time = datetime.now().strftime('%Y_%m_%d_%H_%M_%S_%f') assets_folder = 'assets/' reports_folder = 'reports' - assets_zip = "assets.zip" - date_time = datetime.now().strftime('%Y-%m-%d_%H:%M:%S.%f') + assets_zip = f"assets_{date_time}.zip" html_report_file_name = f"test_report_{date_time}.html" plain_report_file_name = f"test_report_{date_time}.txt" playbook_file_path = os.path.join(gettempdir(), f"{get_current_timestamp()}.yaml") From 3e465887d69e9222514134e9ae0189964af031e4 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 30 Sep 2021 13:55:38 +0200 Subject: [PATCH 31/41] add: Add new util function to move all files from one directory to another #1900 --- .../wazuh_testing/wazuh_testing/tools/file.py | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/tools/file.py b/deps/wazuh_testing/wazuh_testing/tools/file.py index 571847ef76..894a7a4ff7 100644 --- a/deps/wazuh_testing/wazuh_testing/tools/file.py +++ b/deps/wazuh_testing/wazuh_testing/tools/file.py @@ -13,7 +13,6 @@ import string import xml.etree.ElementTree as ET import zipfile -from os.path import exists import filetype import requests @@ -146,7 +145,7 @@ def download_file(source_url, dest_path): def remove_file(file_path): - if exists(file_path): + if os.path.exists(file_path): os.remove(file_path) @@ -168,7 +167,7 @@ def validate_xml_file(file_path): def get_file_info(file_path, info_type="extension"): - if exists(file_path) and filetype.guess(file_path) is not None: + if os.path.exists(file_path) and filetype.guess(file_path) is not None: file = filetype.guess(file_path) return file.extension if info_type == "extension" else file.mime @@ -297,7 +296,8 @@ def set_file_owner_and_group(file_path, owner, group): def recursive_directory_creation(path): """Recursive function to create folders. - Args: + + Args: path (str): Path to create. If a folder doesn't exists, it will create it. """ parent, _ = os.path.split(path) @@ -308,3 +308,26 @@ def recursive_directory_creation(path): if not os.path.exists(path): os.mkdir(path) + + +def move_everything_from_one_directory_to_another(source_directory, destination_directory): + """Move all files and directories from one directory to another. + + Important note: Copied files must not exist on destination directory. + + Args: + source_directory (str): Source_directory path. + destination_directory (str): Destination_directory path. + Raises: + ValueError: If source_directory or destination_directory does not exist. + """ + if not os.path.exists(source_directory): + raise ValueError(f"{source_directory} does not exist") + + if not os.path.exists(destination_directory): + raise ValueError(f"{destination_directory} does not exist") + + file_names = os.listdir(source_directory) + + for file_name in file_names: + shutil.move(os.path.join(source_directory, file_name), destination_directory) From da19098cae55fbf2527ced55fae2d8ba6e21bfa6 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 30 Sep 2021 13:59:00 +0200 Subject: [PATCH 32/41] add: Add test results collection for Windows qa-ctl #1900 --- .../qa_ctl/configuration/config_generator.py | 16 ++++----- .../qa_ctl/provisioning/local_actions.py | 6 ++-- .../qa_ctl/provisioning/qa_provisioning.py | 5 +-- .../wazuh_testing/qa_ctl/run_tests/pytest.py | 2 -- .../qa_ctl/run_tests/qa_test_runner.py | 33 +++++++++++++++++-- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py index 284e604b2d..83fb39dc13 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py @@ -1,7 +1,6 @@ import os from tempfile import gettempdir -from sys import exit from packaging.version import parse from wazuh_testing.tools import file @@ -38,6 +37,9 @@ class QACTLConfigGenerator: generated. """ + LOGGER = Logging.get_logger(QACTL_LOGGER) + LINUX_TMP = '/tmp' + BOX_MAPPING = { 'Ubuntu Focal': 'qactl/ubuntu_20_04', 'CentOS 8': 'qactl/centos_8' @@ -51,7 +53,7 @@ class QACTLConfigGenerator: 'connection_port': 22, 'ansible_python_interpreter': '/usr/bin/python3', 'system': 'deb', - 'installation_files_path': '/tmp' + 'installation_files_path': LINUX_TMP }, 'qactl/centos_8': { 'connection_method': 'ssh', @@ -60,12 +62,10 @@ class QACTLConfigGenerator: 'connection_port': 22, 'ansible_python_interpreter': '/usr/bin/python3', 'system': 'rpm', - 'installation_files_path': '/tmp' + 'installation_files_path': LINUX_TMP } } - LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, tests, wazuh_version, qa_files_path=f"{gettempdir()}/wazuh-qa"): self.tests = tests self.wazuh_version = get_last_wazuh_version() if wazuh_version is None else wazuh_version @@ -345,7 +345,7 @@ def __process_provision_data(self): wazuh_qa_branch = self.__get_qa_branch() self.config['provision']['hosts'][instance]['qa_framework'] = { 'wazuh_qa_branch': wazuh_qa_branch, - 'qa_workdir': gettempdir() + 'qa_workdir': self.LINUX_TMP } def __process_test_data(self, tests_info): @@ -365,8 +365,8 @@ def __process_test_data(self, tests_info): self.config['tests'][instance]['test'] = { 'type': 'pytest', 'path': { - 'test_files_path': f"{gettempdir()}/wazuh-qa/{test['path']}", - 'run_tests_dir_path': f"{gettempdir()}/wazuh-qa/test/integration", + 'test_files_path': f"{self.LINUX_TMP}/wazuh-qa/{test['path']}", + 'run_tests_dir_path': f"{self.LINUX_TMP}/wazuh-qa/test/integration", 'test_results_path': f"{gettempdir()}/{test['test_name']}_{get_current_timestamp()}/" } } diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py index ee3cde0b04..7c647279b4 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py @@ -55,19 +55,21 @@ def download_local_wazuh_qa_repository(branch, path): run_local_command_with_output(command) -def qa_ctl_docker_run(config_file, debug_level): +def qa_ctl_docker_run(config_file, debug_level, topic): """Run qa-ctl in a Linux docker container. Useful when running qa-ctl in native Windows host. Args: config_file (str): qa-ctl configuration file name to run. debug_level (int): qa-ctl debug level. + topic (str): Reason for running the qa-ctl docker. """ debug_args = '' if debug_level == 0 else ('-d' if debug_level == 1 else '-dd') docker_args = f"1900-qa-ctl-windows {config_file} --no-validation-logging {debug_args}" docker_image_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'deployment', 'dockerfiles', 'qa_ctl') - LOGGER.info('Building docker image') + LOGGER.info(f"Building docker image for {topic}") run_local_command_with_output(f"cd {docker_image_path} && docker build -q -t wazuh/qa-ctl .") + LOGGER.info(f"Running the Linux container {topic}") run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py index c4c6822cd2..a9f1c79214 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py @@ -214,11 +214,12 @@ def run(self): tmp_config_file_name = f"config_{get_current_timestamp()}.yaml" tmp_config_file = os.path.join(gettempdir(), tmp_config_file_name) + # Write a custom configuration file with only provision section file.write_yaml_file(tmp_config_file, {'provision': self.provision_info}) try: - QAProvisioning.LOGGER.info('Creating a Linux container for provisioning the instances') - qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level) + qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level, + topic='for provisioning the instances') finally: file.remove_file(tmp_config_file) else: diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py index ba4a1ebd6f..95ce2bf95e 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py @@ -189,8 +189,6 @@ def run(self, ansible_inventory_path): plain_report_file_path=os.path.join(self.tests_result_path, plain_report_file_name), test_name=self.tests_path) - Pytest.LOGGER.info(f"{self.tests_path} tests results were saved in {self.tests_result_path}") - # Print test result in stdout if self.qa_ctl_configuration.logging_enable: Pytest.LOGGER.info(self.result) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py index a1b550a96e..79a32959e2 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py @@ -142,11 +142,36 @@ def run(self): tmp_config_file_name = f"config_{get_current_timestamp()}.yaml" tmp_config_file = os.path.join(gettempdir(), tmp_config_file_name) + # Save original directory where to store the results in Windows host + original_result_paths = [ self.test_parameters[host_key]['test']['path']['test_results_path'] \ + for host_key, _ in self.test_parameters.items()] + + # Change the destination directory, as the results will initially be stored in the shared volume between + # the Windows host and the docker container (Windows tmp as /qa_ctl). + test_results_folder = f"test_results_{get_current_timestamp()}" + temp_test_results_files_path = f"/qa_ctl/{test_results_folder}" + + index = 0 + for host_key, _ in self.test_parameters.items(): + self.test_parameters[host_key]['test']['path']['test_results_path'] = \ + f"{temp_test_results_files_path}_{index}" + index += 1 + + # Write a custom configuration file with only running test section file.write_yaml_file(tmp_config_file, {'tests': self.test_parameters}) try: - QATestRunner.LOGGER.info('Creating a Linux container for launching the tests') - qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level) + qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level, + topic='for launching the tests') + # Move all test results to their original paths specified in Windows qa-ctl configuration + index = 0 + for _, host_data in self.test_parameters.items(): + source_directory = os.path.join(gettempdir(), f"{test_results_folder}_{index}") + file.move_everything_from_one_directory_to_another(source_directory, original_result_paths[index]) + file.delete_path_recursively(source_directory) + QATestRunner.LOGGER.info(f"The results of {host_data['test']['path']['test_files_path']} tests " + f"have been saved in {original_result_paths[index]}") + index += 1 finally: file.remove_file(tmp_config_file) else: @@ -164,6 +189,10 @@ def run(self): QATestRunner.LOGGER.info('The test run is finished') + for _, host_data in self.test_parameters.items(): + QATestRunner.LOGGER.info(f"The results of {host_data['test']['path']['test_files_path']} tests " + f"have been saved in {host_data['test']['path']['test_results_path']}") + def destroy(self): """"Destroy all the temporary files created during a running QAtestRunner instance""" if os.path.exists(self.inventory_file_path) and sys.platform != 'win32': From 580b9963c5d1ad9f4ce0a26b05594caac900cd7f Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 30 Sep 2021 15:54:49 +0200 Subject: [PATCH 33/41] refac: Improve the download process of wazuh-qa repository for qa-ctl #1900 --- .../qa_ctl/provisioning/local_actions.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py index 7c647279b4..305623cfde 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py @@ -1,5 +1,6 @@ import subprocess import os +import sys from tempfile import gettempdir from wazuh_testing.qa_ctl import QACTL_LOGGER @@ -47,12 +48,23 @@ def run_local_command_with_output(command): def download_local_wazuh_qa_repository(branch, path): """Download wazuh QA repository in local machine. + Important note: Path must not include the wazuh-qa folder + Args: branch (string): Wazuh QA repository branch. path (string): Local path where save the repository files. """ - command = f"git clone https://github.com/wazuh/wazuh-qa --branch {branch} --single-branch {path}" - run_local_command_with_output(command) + wazuh_qa_path = os.path.join(path, 'wazuh-qa') + + mute_output = '&> /dev/null' if sys.platform != 'win32' else '>nul 2>&1' + + if os.path.exists(wazuh_qa_path): + LOGGER.info(f"Pulling remote repository changes in {wazuh_qa_path} local repository") + run_local_command(f"cd {wazuh_qa_path} && git pull {mute_output} && git checkout {branch} {mute_output}") + else: + LOGGER.info(f"Downloading wazuh-qa repository in {wazuh_qa_path}") + run_local_command_with_output(f"git clone https://github.com/wazuh/wazuh-qa --branch {branch} " + f"--single-branch {wazuh_qa_path} {mute_output}") def qa_ctl_docker_run(config_file, debug_level, topic): @@ -71,5 +83,5 @@ def qa_ctl_docker_run(config_file, debug_level, topic): LOGGER.info(f"Building docker image for {topic}") run_local_command_with_output(f"cd {docker_image_path} && docker build -q -t wazuh/qa-ctl .") - LOGGER.info(f"Running the Linux container {topic}") + LOGGER.info(f"Running the Linux container for {topic}") run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") From 8ddcfbb4e2d9f97d8cba43865936c139de441522 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 30 Sep 2021 16:10:28 +0200 Subject: [PATCH 34/41] style: Improve some log messages in qa-ctl #1900 --- .../wazuh_testing/qa_ctl/provisioning/qa_provisioning.py | 7 +++---- .../wazuh_testing/qa_ctl/run_tests/qa_test_runner.py | 2 +- deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py index a9f1c79214..1edc874783 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py @@ -172,9 +172,8 @@ def __process_config_data(self, host_provision_info): if health_check: # Wait for Wazuh initialization before health_check - health_check_sleep_time = 60 - QAProvisioning.LOGGER.info(f"Waiting {health_check_sleep_time} seconds before performing the " - f"healthcheck in {current_host} host") + health_check_sleep_time = 30 + QAProvisioning.LOGGER.info(f"Performing a Wazuh installation healthcheck in {current_host} host") sleep(health_check_sleep_time) deployment_instance.health_check() @@ -219,7 +218,7 @@ def run(self): try: qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level, - topic='for provisioning the instances') + topic='provisioning the instances') finally: file.remove_file(tmp_config_file) else: diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py index 79a32959e2..8a659f403d 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py @@ -162,7 +162,7 @@ def run(self): try: qa_ctl_docker_run(tmp_config_file_name, self.qa_ctl_configuration.debug_level, - topic='for launching the tests') + topic='launching the tests') # Move all test results to their original paths specified in Windows qa-ctl configuration index = 0 for _, host_data in self.test_parameters.items(): diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 8757df555c..83f895b787 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -199,8 +199,7 @@ def main(): # If dry-run mode, then exit after generating the configuration file if arguments.dry_run: - qactl_logger.info(f"Run as dry-run mode. Configuration file saved in " - f"{config_generator.config_file_path}") + qactl_logger.info(f"Run as dry-run mode. Configuration file saved in {config_generator.config_file_path}") return 0 else: configuration_file = arguments.config From 263e9c7d4f8e8887605cebcc538709e91e2a1d3d Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Thu, 30 Sep 2021 16:48:07 +0200 Subject: [PATCH 35/41] refac: Update qa-ctl deployment range IPs #1900 Removed x.x.x.1 address due to it causes some problems. --- .../wazuh_testing/qa_ctl/configuration/config_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py index 83fb39dc13..cb9f28f384 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/config_generator.py @@ -196,7 +196,7 @@ def ip_is_already_used(ip, qactl_host_used_ips): open(self.qactl_used_ips_file, 'a').close() # Get a free IP in HOST_NETWORK range - for _ip in range(1, 256): + for _ip in range(2, 256): host_ip = HOST_NETWORK.replace('x', str(_ip)) if not ip_is_already_used(host_ip, self.qactl_used_ips_file): break From 602902a5424dbb18589e37b68bbe7a95f09f0f61 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Fri, 1 Oct 2021 08:55:36 +0200 Subject: [PATCH 36/41] add: Add env var to know if qa-ctl is running in a docker container #1900 Needed to avoid duplicated logs --- .../qa_ctl/deployment/dockerfiles/qa_ctl/Dockerfile | 1 + .../wazuh_testing/qa_ctl/run_tests/qa_test_runner.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/Dockerfile b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/Dockerfile index b1ac325ada..eba3526c5f 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/Dockerfile +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/dockerfiles/qa_ctl/Dockerfile @@ -1,6 +1,7 @@ From ubuntu:focal ENV DEBIAN_FRONTEND=noninteractive +ENV RUNNING_ON_DOCKER_CONTAINER=true RUN apt-get -q update && \ apt-get install -y \ diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py index 8a659f403d..8619c71869 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py @@ -190,8 +190,9 @@ def run(self): QATestRunner.LOGGER.info('The test run is finished') for _, host_data in self.test_parameters.items(): - QATestRunner.LOGGER.info(f"The results of {host_data['test']['path']['test_files_path']} tests " - f"have been saved in {host_data['test']['path']['test_results_path']}") + if 'RUNNING_ON_DOCKER_CONTAINER' not in os.environ: + QATestRunner.LOGGER.info(f"The results of {host_data['test']['path']['test_files_path']} tests " + f"have been saved in {host_data['test']['path']['test_results_path']}") def destroy(self): """"Destroy all the temporary files created during a running QAtestRunner instance""" From 8970a6156b9cec36597fbd92a3db67f3dbb48063 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Fri, 1 Oct 2021 10:36:25 +0200 Subject: [PATCH 37/41] refac: Improve how to run Linux commands with subprocess module #1900 Needed to run Linux commands with BASH shell. Some commands were not compatible with SH --- .../qa_ctl/provisioning/local_actions.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py index 305623cfde..d6010cdff3 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py @@ -20,7 +20,10 @@ def run_local_command(command): Raises: QAValueError: If the run command has failed (rc != 0). """ - run = subprocess.Popen(command, shell=True) + if sys.platform == 'win32': + run = subprocess.Popen(command, shell=True) + else: + run = subprocess.Popen(['/bin/bash', '-c', command]) # Wait for the process to finish run.communicate() @@ -40,7 +43,10 @@ def run_local_command_with_output(command): Returns: str: Command output """ - run = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + if sys.platform == 'win32': + run = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + else: + run = subprocess.Popen(['/bin/bash', '-c', command], stdout=subprocess.PIPE) return run.stdout.read().decode() @@ -60,7 +66,8 @@ def download_local_wazuh_qa_repository(branch, path): if os.path.exists(wazuh_qa_path): LOGGER.info(f"Pulling remote repository changes in {wazuh_qa_path} local repository") - run_local_command(f"cd {wazuh_qa_path} && git pull {mute_output} && git checkout {branch} {mute_output}") + run_local_command_with_output(f"cd {wazuh_qa_path} && git pull {mute_output} && git checkout {branch} " + f"{mute_output}") else: LOGGER.info(f"Downloading wazuh-qa repository in {wazuh_qa_path}") run_local_command_with_output(f"git clone https://github.com/wazuh/wazuh-qa --branch {branch} " From 554c631a2fac07c9ea002c288676fd66222b8bfb Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Fri, 1 Oct 2021 11:04:55 +0200 Subject: [PATCH 38/41] add: Clean configuration info after finishing the qa-ctl run #1900 --- deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 83f895b787..be57fa2bb8 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -30,6 +30,7 @@ qactl_logger = Logging(QACTL_LOGGER) _data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'data') launched = { + 'config_generator': False, 'instance_handler': False, 'qa_provisioning': False, 'test_runner': False @@ -146,7 +147,6 @@ def validate_parameters(parameters): version = get_last_wazuh_version() qa_branch = f"{version.split('.')[0]}.{version.split('.')[1]}".replace('v', '') - qa_branch = '1900-qa-ctl-windows' local_actions.download_local_wazuh_qa_repository(branch=qa_branch, path=gettempdir()) for test in parameters.run_test: @@ -194,6 +194,7 @@ def main(): qactl_logger.debug('Generating configuration file') config_generator = QACTLConfigGenerator(arguments.run_test, arguments.version, WAZUH_QA_FILES) config_generator.run() + launched['config_generator'] = True configuration_file = config_generator.config_file_path qactl_logger.debug(f"Configuration file has been created sucessfully in {configuration_file}") @@ -251,6 +252,8 @@ def main(): if TEST_KEY in configuration_data and launched['test_runner']: tests_runner.destroy() + if arguments.run_test and launched['config_generator']: + config_generator.destroy() if __name__ == '__main__': main() From afd93c648039360c008145a8cb374ff2755e4ba2 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Fri, 1 Oct 2021 11:26:28 +0200 Subject: [PATCH 39/41] refac: Update Windows requirements dependencies for qa-ctl #1900 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f89749d212..08d41c52f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,9 +32,9 @@ testinfra==5.0.0 jq==1.1.2; platform_system == "Linux" or platform_system == "MacOS" cryptography==3.3.2; platform_system == "Linux" or platform_system == "MacOS" or platform_system=='Windows' urllib3>=1.26.5 -elasticsearch>=7.14.1 ; platform_system == "Linux" or platform_system=='Windows' numpydoc>=1.1.0 -ansible-runner>=2.0.1 ; platform_system == "Linux" or platform_system=='Windows' +ansible-runner>=2.0.1 ; platform_system == "Linux" docker>=5.0.0 ; platform_system == "Linux" or platform_system=='Windows' python-vagrant>=0.5.15 ; platform_system == "Linux" or platform_system=='Windows' ansible>=3.1.0 ; platform_system == "Linux" +elasticsearch>=7.14.1 ; platform_system == "Linux" or platform_system=='Windows' From b7784e4b371e5fcef729dbafc8aebd2bf9018835 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Fri, 1 Oct 2021 11:58:56 +0200 Subject: [PATCH 40/41] fix: Fix qa-ctl docker image name #1900 --- .../wazuh_testing/qa_ctl/provisioning/local_actions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py index d6010cdff3..efa32d5ce9 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/local_actions.py @@ -84,11 +84,12 @@ def qa_ctl_docker_run(config_file, debug_level, topic): """ debug_args = '' if debug_level == 0 else ('-d' if debug_level == 1 else '-dd') docker_args = f"1900-qa-ctl-windows {config_file} --no-validation-logging {debug_args}" + docker_image_name = 'wazuh/qa-ctl' docker_image_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'deployment', 'dockerfiles', 'qa_ctl') LOGGER.info(f"Building docker image for {topic}") - run_local_command_with_output(f"cd {docker_image_path} && docker build -q -t wazuh/qa-ctl .") + run_local_command_with_output(f"cd {docker_image_path} && docker build -q -t {docker_image_name} .") LOGGER.info(f"Running the Linux container for {topic}") - run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl qa-ctl {docker_args}") + run_local_command(f"docker run --rm -v {gettempdir()}:/qa_ctl {docker_image_name} {docker_args}") From 65650fb042c99f87b9dce4e1933afa7f7bfc3fb9 Mon Sep 17 00:00:00 2001 From: jmv74211 Date: Fri, 1 Oct 2021 11:59:46 +0200 Subject: [PATCH 41/41] add: Add new log message to the qa-ctl provision stage #1900 --- .../wazuh_testing/qa_ctl/provisioning/qa_provisioning.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py index 1edc874783..1991cdc454 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py @@ -184,8 +184,10 @@ def __process_config_data(self, host_provision_info): wazuh_qa_branch = None if 'wazuh_qa_branch' not in qa_framework_info \ else qa_framework_info['wazuh_qa_branch'] + QAProvisioning.LOGGER.info(f"Provisioning the {current_host} host with the Wazuh QA framework using " + f"{wazuh_qa_branch} branch.") qa_instance = QAFramework(qa_branch=wazuh_qa_branch, - ansible_output=self.qa_ctl_configuration.ansible_output) + ansible_output=self.qa_ctl_configuration.ansible_output) qa_instance.download_qa_repository(inventory_file_path=self.inventory_file_path, hosts=current_host) qa_instance.install_dependencies(inventory_file_path=self.inventory_file_path, hosts=current_host) qa_instance.install_framework(inventory_file_path=self.inventory_file_path, hosts=current_host)