diff --git a/.gitignore b/.gitignore index bedbb9a72e..b81f4dcadf 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,8 @@ ansible.log key #Python -__pycache__/ \ No newline at end of file +__pycache__/ + +#Python setup +*.egg-info/ +build/ \ No newline at end of file diff --git a/deployability/Jenkinsfiles/Launcher.groovy b/deployability/Jenkinsfiles/Launcher.groovy index fa5ada17ec..75e88108e8 100755 --- a/deployability/Jenkinsfiles/Launcher.groovy +++ b/deployability/Jenkinsfiles/Launcher.groovy @@ -1,7 +1,6 @@ - String jenkins_reference = params.getOrDefault('JENKINS_REFERENCE', 'enhancement/4751-dtt1-iteration-2-poc') -String launcher_path = "launchers" -String task_flow_launcher = "provision.py" +String launcher_path = "modules/provision" +String task_flow_launcher = "main.py" String workflow = "modules/workflow_engine/examples/dtt1-managers.yaml" String schema = "modules/workflow_engine/schema.json" diff --git a/deployability/Jenkinsfiles/Provision.groovy b/deployability/Jenkinsfiles/Provision.groovy index bb3aa4623b..eb50ddc6dc 100755 --- a/deployability/Jenkinsfiles/Provision.groovy +++ b/deployability/Jenkinsfiles/Provision.groovy @@ -1,7 +1,5 @@ - - -String provision_path = "${WORKSPACE}/scripts/provision" -String provision_script = "provision.py" +String provision_path = "${WORKSPACE}/modules/provision" +String provision_script = "main.py" String inventory = "inventory.yaml" String jenkins_reference = params.getOrDefault('JENKINS_REFERENCE', 'enhancement/4665-dtt1-poc') diff --git a/deployability/modules/allocation/allocation.py b/deployability/modules/allocation/allocation.py index c076ed821f..aa70cc5987 100755 --- a/deployability/modules/allocation/allocation.py +++ b/deployability/modules/allocation/allocation.py @@ -64,7 +64,7 @@ def __delete(cls, payload: models.DeletionPayload) -> None: Args: payload (DeletionPayload): The payload containing the parameters - for instance deletion. + for instance deletion. """ payload = models.DeletionPayload(**dict(payload)) # Read the data from the track file. diff --git a/deployability/modules/allocation/aws/instance.py b/deployability/modules/allocation/aws/instance.py index 1537c80e05..794baa91f7 100644 --- a/deployability/modules/allocation/aws/instance.py +++ b/deployability/modules/allocation/aws/instance.py @@ -68,9 +68,9 @@ def ssh_connection_info(self) -> ConnectionInfo: ConnectionInfo: SSH connection information. """ return ConnectionInfo(hostname=self._instance.public_dns_name, - user=self._user, - port=22, - private_key=str(self.credentials.key_path)) + user=self._user, + port=22, + private_key=str(self.credentials.key_path)) def __get_credentials(self) -> AWSCredentials: """ diff --git a/deployability/launchers/allocation.py b/deployability/modules/allocation/main.py similarity index 98% rename from deployability/launchers/allocation.py rename to deployability/modules/allocation/main.py index f4700d2e36..c6a31be81b 100755 --- a/deployability/launchers/allocation.py +++ b/deployability/modules/allocation/main.py @@ -2,7 +2,7 @@ import os import sys -project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) sys.path.append(project_root) from modules.allocation import Allocator diff --git a/deployability/modules/generic/ansible.py b/deployability/modules/generic/ansible.py index 590cfc5773..0d75fb1837 100755 --- a/deployability/modules/generic/ansible.py +++ b/deployability/modules/generic/ansible.py @@ -19,7 +19,8 @@ class Inventory(BaseModel): class Ansible: def __init__(self, ansible_data: dict | Inventory, path: str | Path = None): self.path = path - self.playbooks_path = Path(__file__).parents[2] / 'playbooks' + self.modules_path = Path(__file__).parents[1] + self.provision_playbook_path = self.modules_path / 'provision/playbooks' self.ansible_data = Inventory(**dict(ansible_data)) self.inventory = self.generate_inventory() self.logger = Logger(Path(__file__).stem).get_logger() @@ -32,7 +33,7 @@ def render_playbooks(self, rendering_variables: dict) -> list[str]: rendering_variables (dict): Extra variables to render the playbooks. """ tasks = [] - path_to_render_playbooks = self.playbooks_path / rendering_variables.get("templates_path") + path_to_render_playbooks = self.provision_playbook_path / rendering_variables.get("templates_path") template_loader = jinja2.FileSystemLoader(searchpath=path_to_render_playbooks) template_env = jinja2.Environment(loader=template_loader) diff --git a/deployability/modules/provision/componentType.py b/deployability/modules/provision/componentType.py index 75f3bf4ae8..5295ba4fad 100644 --- a/deployability/modules/provision/componentType.py +++ b/deployability/modules/provision/componentType.py @@ -41,7 +41,7 @@ class Package(ComponentType): """ Class to define the type of package to be provisioned """ - TEMPLATE_BASE_PATH = 'provision/wazuh' + TEMPLATE_BASE_PATH = 'wazuh' def __init__(self, component_info, action): super().__init__(component_info) @@ -58,7 +58,7 @@ class AIO(ComponentType): """ Class to define the type of AIO to be provisioned """ - TEMPLATE_BASE_PATH = 'provision/wazuh' + TEMPLATE_BASE_PATH = 'wazuh' def __init__(self, component_info, action): super().__init__(component_info) @@ -73,7 +73,7 @@ class Generic(ComponentType): """ Class to define the type of generic component to be provisioned """ - TEMPLATE_BASE_PATH = 'provision/generic' + TEMPLATE_BASE_PATH = 'generic' def __init__(self, component_info, action): super().__init__(component_info) @@ -88,7 +88,7 @@ class Dependencies(ComponentType): """ Class to define the type of dependencies to be provisioned """ - TEMPLATE_BASE_PATH = 'provision/deps' + TEMPLATE_BASE_PATH = 'deps' def __init__(self, component_info, action): super().__init__(component_info) diff --git a/deployability/launchers/provision.py b/deployability/modules/provision/main.py similarity index 98% rename from deployability/launchers/provision.py rename to deployability/modules/provision/main.py index 161d8ce179..a222680f78 100755 --- a/deployability/launchers/provision.py +++ b/deployability/modules/provision/main.py @@ -6,7 +6,7 @@ # ---------------- Vars ------------------------ -project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) sys.path.append(project_root) from modules.provision import Provision, models diff --git a/deployability/playbooks/provision/deps/dependencies.j2 b/deployability/modules/provision/playbooks/deps/dependencies.j2 similarity index 100% rename from deployability/playbooks/provision/deps/dependencies.j2 rename to deployability/modules/provision/playbooks/deps/dependencies.j2 diff --git a/deployability/playbooks/provision/generic/install/install.j2 b/deployability/modules/provision/playbooks/generic/install/install.j2 similarity index 100% rename from deployability/playbooks/provision/generic/install/install.j2 rename to deployability/modules/provision/playbooks/generic/install/install.j2 diff --git a/deployability/playbooks/provision/generic/uninstall/uninstall.j2 b/deployability/modules/provision/playbooks/generic/uninstall/uninstall.j2 similarity index 100% rename from deployability/playbooks/provision/generic/uninstall/uninstall.j2 rename to deployability/modules/provision/playbooks/generic/uninstall/uninstall.j2 diff --git a/deployability/playbooks/provision/information/host.yaml b/deployability/modules/provision/playbooks/information/host.yaml similarity index 100% rename from deployability/playbooks/provision/information/host.yaml rename to deployability/modules/provision/playbooks/information/host.yaml diff --git a/deployability/playbooks/provision/wazuh/aio/install/download.j2 b/deployability/modules/provision/playbooks/wazuh/aio/install/download.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/aio/install/download.j2 rename to deployability/modules/provision/playbooks/wazuh/aio/install/download.j2 diff --git a/deployability/playbooks/provision/wazuh/aio/install/install.j2 b/deployability/modules/provision/playbooks/wazuh/aio/install/install.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/aio/install/install.j2 rename to deployability/modules/provision/playbooks/wazuh/aio/install/install.j2 diff --git a/deployability/playbooks/provision/wazuh/aio/uninstall/download.j2 b/deployability/modules/provision/playbooks/wazuh/aio/uninstall/download.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/aio/uninstall/download.j2 rename to deployability/modules/provision/playbooks/wazuh/aio/uninstall/download.j2 diff --git a/deployability/playbooks/provision/wazuh/aio/uninstall/uninstall.j2 b/deployability/modules/provision/playbooks/wazuh/aio/uninstall/uninstall.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/aio/uninstall/uninstall.j2 rename to deployability/modules/provision/playbooks/wazuh/aio/uninstall/uninstall.j2 diff --git a/deployability/playbooks/provision/wazuh/package/install/install.j2 b/deployability/modules/provision/playbooks/wazuh/package/install/install.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/package/install/install.j2 rename to deployability/modules/provision/playbooks/wazuh/package/install/install.j2 diff --git a/deployability/playbooks/provision/wazuh/package/install/register.j2 b/deployability/modules/provision/playbooks/wazuh/package/install/register.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/package/install/register.j2 rename to deployability/modules/provision/playbooks/wazuh/package/install/register.j2 diff --git a/deployability/playbooks/provision/wazuh/package/install/service.j2 b/deployability/modules/provision/playbooks/wazuh/package/install/service.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/package/install/service.j2 rename to deployability/modules/provision/playbooks/wazuh/package/install/service.j2 diff --git a/deployability/playbooks/provision/wazuh/package/install/set_repo.j2 b/deployability/modules/provision/playbooks/wazuh/package/install/set_repo.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/package/install/set_repo.j2 rename to deployability/modules/provision/playbooks/wazuh/package/install/set_repo.j2 diff --git a/deployability/playbooks/provision/wazuh/package/uninstall/uninstall.j2 b/deployability/modules/provision/playbooks/wazuh/package/uninstall/uninstall.j2 similarity index 100% rename from deployability/playbooks/provision/wazuh/package/uninstall/uninstall.j2 rename to deployability/modules/provision/playbooks/wazuh/package/uninstall/uninstall.j2 diff --git a/deployability/modules/setup.py b/deployability/modules/setup.py new file mode 100644 index 0000000000..fea1915f39 --- /dev/null +++ b/deployability/modules/setup.py @@ -0,0 +1,42 @@ + +# Copyright (C) 2015-2024, Wazuh Inc. +# Created by Wazuh, Inc. . +# This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 +import json +from setuptools import setup, find_packages +import os + +def get_files_from_directory(directory): + paths = [] + for (path, directories, filenames) in os.walk(directory): + for filename in filenames: + paths.append(os.path.join('..', path, filename)) + return paths + +def get_version(): + script_path = os.path.dirname(__file__) + rel_path = "../version.json" + abs_file_path = os.path.join(script_path, rel_path) + f = open(abs_file_path) + data = json.load(f) + version = data['version'] + return version + +package_data_list = get_files_from_directory("workflow_engine") +scripts_list = ['engine=workflow_engine.__main__:main'] + +setup( + name='workflow_engine', + version=get_version(), + description='Wazuh testing utilities to help programmers automate deployment tests', + url='https://github.com/wazuh', + author='Wazuh', + author_email='hello@wazuh.com', + license='GPLv2', + packages=['workflow_engine'], + package_dir={'workflow_engine': 'workflow_engine'}, + package_data={'workflow_engine': package_data_list}, + entry_points={'console_scripts': scripts_list}, + include_package_data=True, + zip_safe=False +) \ No newline at end of file diff --git a/deployability/launchers/test.py b/deployability/modules/testing/main.py similarity index 98% rename from deployability/launchers/test.py rename to deployability/modules/testing/main.py index 3c2255c532..d7fef49089 100755 --- a/deployability/launchers/test.py +++ b/deployability/modules/testing/main.py @@ -2,7 +2,7 @@ import sys import os -project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) sys.path.append(project_root) from modules.testing import Tester, InputPayload diff --git a/deployability/playbooks/tests/cleanup.yml b/deployability/modules/testing/playbooks/cleanup.yml similarity index 100% rename from deployability/playbooks/tests/cleanup.yml rename to deployability/modules/testing/playbooks/cleanup.yml diff --git a/deployability/playbooks/tests/setup.yml b/deployability/modules/testing/playbooks/setup.yml similarity index 100% rename from deployability/playbooks/tests/setup.yml rename to deployability/modules/testing/playbooks/setup.yml diff --git a/deployability/playbooks/tests/test.yml b/deployability/modules/testing/playbooks/test.yml similarity index 100% rename from deployability/playbooks/tests/test.yml rename to deployability/modules/testing/playbooks/test.yml diff --git a/deployability/modules/testing/testing.py b/deployability/modules/testing/testing.py index 204616d7ae..04ddb29064 100644 --- a/deployability/modules/testing/testing.py +++ b/deployability/modules/testing/testing.py @@ -7,9 +7,8 @@ from .models import InputPayload, ExtraVars from .utils import logger - class Tester: - _playbooks_dir = Path('tests') + _playbooks_dir = Path(__file__).parent / 'playbooks' _setup_playbook = _playbooks_dir / 'setup.yml' _cleanup_playbook = _playbooks_dir / 'cleanup.yml' _test_template = _playbooks_dir / 'test.yml' @@ -77,7 +76,7 @@ def _run_tests(cls, test_list: list[str], ansible: Ansible, extra_vars: ExtraVar """ for test in test_list: rendering_var = {**extra_vars, 'test': test} - template = str(ansible.playbooks_path / cls._test_template) + template = str(cls._test_template) playbook = ansible.render_playbook(template, rendering_var) if not playbook: logger.warning(f"Test {test} not found. Skipped.") @@ -95,7 +94,7 @@ def _setup(cls, ansible: Ansible, remote_working_dir: str = '/tmp') -> None: """ extra_vars = {'local_path': str(Path(__file__).parent / 'tests'), 'working_dir': remote_working_dir} - playbook = str(ansible.playbooks_path / cls._setup_playbook) + playbook = str(cls._setup_playbook) ansible.run_playbook(playbook, extra_vars) @classmethod @@ -108,5 +107,5 @@ def _cleanup(cls, ansible: Ansible, remote_working_dir: str = '/tmp') -> None: remote_working_dir (str): The remote working directory. """ extra_vars = {'working_dir': remote_working_dir} - playbook = str(ansible.playbooks_path / cls._cleanup_playbook) - ansible.run_playbook(playbook, extra_vars) + playbook = str(cls._cleanup_playbook) + ansible.run_playbook(playbook, extra_vars) \ No newline at end of file diff --git a/deployability/modules/workflow_engine/README.md b/deployability/modules/workflow_engine/README.md index 74793771b3..51eaad4ad6 100755 --- a/deployability/modules/workflow_engine/README.md +++ b/deployability/modules/workflow_engine/README.md @@ -1,98 +1,321 @@ -# Workflow Processor - -The Workflow Processor is a tool for executing tasks defined in a YAML-based workflow file. It supports parallel execution of tasks with dependency management. - -## Table of Contents - -- [Workflow Processor](#workflow-processor) - - [Table of Contents](#table-of-contents) - - [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Usage](#usage) - - [Command Line Arguments](#command-line-arguments) - - [Workflow File](#workflow-file) - - [Logging](#logging) - - [Examples](#examples) - - [Basic Execution](#basic-execution) - - [Parallel Execution](#parallel-execution) - - [Dry Run](#dry-run) - - [License](#license) - -## Getting Started - -### Prerequisites - -Before using the Workflow Processor, make sure you have the following prerequisites installed: - -- Python 3.9 - -### Installation - -1. Clone the repository: - - ```bash - git clone https://github.com/wazuh/wazuh-qa.git - ``` - -2. Navigate to the project directory: - - ```bash - cd wazuh-qa/poc-tests/scripts/qa-workflow-engine - ``` - -3. Install the required dependencies: - - ```bash - pip install -r requirements.txt - ``` - -Now, you're ready to use the QA Workflow Engine. - -## Usage - -### Command Line Arguments - -Run the workflow processor using the following command: +## Workflow engine + +### User documentation + +The execution of the Workflow is done through the installation of its library. + +Initially, Python libraries must be installed. It is recommended to use virtual environments. Follow the technical documentation at https://docs.python.org/3/library/venv.html. + +1. Activate the environment: + + ```bash + source {venv directory}/bin/activate + ``` + +2. Clone the `wazuh-qa` repository: + + Navigate to the project directory and switch to the project branch: + + ```bash + cd wazuh-qa + git checkout {project-branch} + ``` + +3. Install requirements: + + ```bash + pip3 install -r deployability/deps/requirements.txt + ``` + +4. Install the Workflow engine library and its launcher: + + While in wazuh-qa: + + ```bash + cd modules + pip3 uninstall -y workflow_engine && pip3 install . + ``` + +5. Test Fixture to Execute: + + It will be necessary to create a fixture (yaml file) where the infrastructure, provisioning, and tests to be executed will be declared. + + >Note: It is possible to find some fixture examples in deployability/modules/workflow_engine/examples/ + + Example: + + ```bash + version: 0.1 + description: This workflow is used to test agents deployment por DDT1 PoC + variables: + agents-os: + - linux-ubuntu-22.04-amd64 + manager-os: linux-ubuntu-22.04-amd64 + infra-provider: vagrant + working-dir: /tmp/dtt1-poc + + tasks: + # Generic agent test task + - task: "run-agent-tests-{agent}" + description: "Run tests uninstall for the {agent} agent." + do: + this: process + with: + path: python3 + args: + - modules/testing/main.py + - inventory: "{working-dir}/agent-{agent}/inventory.yaml" + - dependencies: + - manager: "{working-dir}/manager-{manager-os}/inventory.yaml" + - agent: "{working-dir}/agent-{agent}/inventory.yaml" + - tests: "install,register,stop" + - component: "agent" + - wazuh-version: "4.7.1" + - wazuh-revision: "40709" + depends-on: + - "provision-install-{agent}" + - "provision-manager" + foreach: + - variable: agents-os + as: agent + + # Generic agent test task + - task: "run-agent-tests-uninstall-{agent}" + description: "Run tests uninstall for the {agent} agent." + do: + this: process + with: + path: python3 + args: + - modules/testing/main.py + - inventory: "{working-dir}/agent-{agent}/inventory.yaml" + - dependencies: + - manager: "{working-dir}/manager-{manager-os}/inventory.yaml" + - tests: "uninstall" + - component: "agent" + - wazuh-version: "4.7.1" + - wazuh-revision: "40709" + depends-on: + - "run-agent-tests-{agent}" + - "provision-uninstall-{agent}" + foreach: + - variable: agents-os + as: agent + + # Unique manager provision task + - task: "provision-manager" + description: "Provision the manager." + do: + this: process + with: + path: python3 + args: + - modules/provision/main.py + - inventory-manager: "{working-dir}/manager-{manager-os}/inventory.yaml" + - install: + - component: wazuh-manager + type: package + depends-on: + - "allocate-manager" + + # Unique manager allocate task + - task: "allocate-manager" + description: "Allocate resources for the manager." + do: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: create + - provider: "{infra-provider}" + - size: large + - composite-name: "{manager-os}" + - inventory-output: "{working-dir}/manager-{manager-os}/inventory.yaml" + - track-output: "{working-dir}/manager-{manager-os}/track.yaml" + cleanup: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: delete + - track-output: "{working-dir}/manager-{manager-os}/track.yaml" + + # Generic agent provision task + - task: "provision-install-{agent}" + description: "Provision resources for the {agent} agent." + do: + this: process + with: + path: python3 + args: + - modules/provision/main.py + - inventory-agent: "{working-dir}/agent-{agent}/inventory.yaml" + - inventory-manager: "{working-dir}/manager-{manager-os}/inventory.yaml" + - install: + - component: wazuh-agent + type: package + - component: curl + depends-on: + - "allocate-{agent}" + - "provision-manager" + foreach: + - variable: agents-os + as: agent + + # Generic agent provision task + - task: "provision-uninstall-{agent}" + description: "Provision resources for the {agent} agent." + do: + this: process + with: + path: python3 + args: + - modules/provision/main.py + - inventory-agent: "{working-dir}/agent-{agent}/inventory.yaml" + - inventory-manager: "{working-dir}/manager-{manager-os}/inventory.yaml" + - uninstall: + - component: wazuh-agent + type: package + depends-on: + - "provision-install-{agent}" + foreach: + - variable: agents-os + as: agent + + # Generic agent allocate task + - task: "allocate-{agent}" + description: "Allocate resources for the {agent} agent." + do: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: create + - provider: "{infra-provider}" + - size: small + - composite-name: "{agent}" + - inventory-output: "{working-dir}/agent-{agent}/inventory.yaml" + - track-output: "{working-dir}/agent-{agent}/track.yaml" + cleanup: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: delete + - track-output: "{working-dir}/agent-{agent}/track.yaml" + foreach: + - variable: agents-os + as: agent + ``` + + Following the schema of the example: + + Configure the following parameters depending on your test case: + + ```yaml + variables/agent-os + variables/manager-os + infra-provider + working-dir + tasks + ``` + + Pay attention to the tasks: + + ```yaml + args + depends-on + ``` + + >Note: In args, configure the launcher's path correctly (main.py files in each module), and to fill `depends-on`, consider the steps of your test (allocation, provision, and test) + +7. Execution of Command (local): + + Execute the command by referencing the parameters required by the library (launcher). + + ```bash + python3 -m workflow_engine {.yaml fixture path} + ``` + + Example + + ```bash + python3 -m workflow_engine modules/workflow_engine/examples/dtt1-agents-poc.yaml + ``` + + > Note The command execution can also be mediated through Jenkins. + +--- + +### Technical documentation + +`Workflow Engine` is the orchestrator of the deployability test architecture. + +Its function is to allow the ordered and structured execution in steps of allocation, provision, and testing. + +`The Workflow Engine` receives instructions through a `YAML document`, the structure of which can be exemplified in tests found in: +`wazuh-qa/deployability/modules/workflow_engine/examples` + +**In these tests**: + - Tasks: define the steps. + - Task: defines a step. + +**Within Task**: + - description: description of the task. + - do: instructions for the task. + - this: nature of the task. + - with: tools with which the task will be executed. + - path: executable. + - args: arguments. it receives the binary or file to execute and the parameters. + - depends-on: steps prior to the execution of that task. + - foreach: loop that executes the task on the previously declared hosts. ```bash -python main.py workflow_file.yml --threads 4 --dry-run --log-format json --log-level INFO +tasks: + # Generic agent test task + - task: "run-agent-tests-{agent}" + description: "Run tests uninstall for the {agent} agent." + do: + this: process + with: + path: python3 + args: + - modules/testing/main.py + - inventory: "{working-dir}/agent-{agent}/inventory.yaml" + - dependencies: + - manager: "{working-dir}/manager-{manager-os}/inventory.yaml" + - agent: "{working-dir}/agent-{agent}/inventory.yaml" + - tests: "install,register,stop" + - component: "agent" + - wazuh-version: "4.7.1" + - wazuh-revision: "40709" + depends-on: + - "provision-install-{agent}" + - "provision-manager" + foreach: + - variable: agents-os + as: agent ``` -- `workflow_file.yml`: Path to the YAML-based workflow file. -- `--threads`: Number of threads to use for parallel execution (default is 1). -- `--dry-run`: Display the plan without executing tasks. -- `--log-format`: Log format (`plain` or `json`, default is `plain`). -- `--log-level`: Log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`, default is `INFO`). - -### Workflow File - -The workflow file is written in YAML format. It defines tasks, dependencies, and other configurations. See the provided examples in the `examples/` directory for reference. - -### Logging +These tasks are executed by the `Workflow Engine` launcher installed as workflow_engine library in your virtual environment. -The workflow processor logs messages to the console. You can configure the log format (`plain` or `json`) and log level using command line arguments. +This launcher receives the parameters, sets up the test logs, and proceeds with the ordered execution. -## Examples +The parameters sent from the launcher are processed by deployability/modules/workflow_engine/models.py, which checks the nature of the parameters sent and filters out incorrect parameters. -### Basic Execution +![image](https://github.com/wazuh/wazuh-qa/assets/125690423/32aa77b7-f294-41ac-af93-db8a084dbad1) -```bash -python main.py examples/basic_workflow.yml -``` +These are then sent to `deployability/modules/workflow_engine/workflow_processor.py`, where using `deployability/modules/schemas`, instructions in YAML are received and the schema of the instructions is checked. -### Parallel Execution +The commands are executed in the WorkflowProcessor of the same file, which also handles parallel executions and aborts failed executions. -```bash -python main.py examples/parallel_workflow.yml --threads 4 -``` +[WF.drawio.zip](https://github.com/wazuh/wazuh-qa/files/14167559/WF.drawio.zip) -### Dry Run - -```bash -python main.py examples/dry_run_workflow.yml --dry-run -``` -## License +### License WAZUH Copyright (C) 2015 Wazuh Inc. (License GPLv2) diff --git a/deployability/modules/workflow_engine/__init__.py b/deployability/modules/workflow_engine/__init__.py index 5627726b79..338f58e9fd 100755 --- a/deployability/modules/workflow_engine/__init__.py +++ b/deployability/modules/workflow_engine/__init__.py @@ -1,2 +1 @@ from .workflow_processor import WorkflowProcessor - diff --git a/deployability/launchers/workflow_engine.py b/deployability/modules/workflow_engine/__main__.py similarity index 69% rename from deployability/launchers/workflow_engine.py rename to deployability/modules/workflow_engine/__main__.py index 3aba267f5c..5d9fe0a0c4 100755 --- a/deployability/launchers/workflow_engine.py +++ b/deployability/modules/workflow_engine/__main__.py @@ -5,20 +5,18 @@ import os import sys import argparse -import logging -import colorlog +import signal -project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) sys.path.append(project_root) -from modules.workflow_engine.workflow_processor import WorkflowProcessor -from modules.workflow_engine.models import InputPayload +from workflow_engine.workflow_processor import WorkflowProcessor +from workflow_engine.models import InputPayload def parse_arguments() -> argparse.Namespace: """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description='Execute tasks in a workflow.') + parser = argparse.ArgumentParser(description='Execute tasks in a workflow.') parser.add_argument('workflow_file', type=str,help='Path to the workflow file (YAML format).') parser.add_argument('--threads', type=int, default=1, required=False, help='Number of threads to use for parallel execution.') parser.add_argument('--dry-run', action='store_true', required=False, help='Display the plan without executing tasks.') @@ -29,11 +27,13 @@ def parse_arguments() -> argparse.Namespace: def main() -> None: """Main entry point.""" - - args = parse_arguments() - processor = WorkflowProcessor(**dict(InputPayload(**vars(args)))) - processor.run() - + try: + args = parse_arguments() + processor = WorkflowProcessor(**dict(InputPayload(**vars(args)))) + signal.signal(signal.SIGINT, processor.handle_interrupt) + processor.run() + except Exception as e: + sys.exit(f"Error while provisioning: {e}") if __name__ == "__main__": main() diff --git a/deployability/modules/workflow_engine/examples/dtt1-agents-poc.yaml b/deployability/modules/workflow_engine/examples/dtt1-agents-poc.yaml index f90f16c530..0773ccc4cd 100755 --- a/deployability/modules/workflow_engine/examples/dtt1-agents-poc.yaml +++ b/deployability/modules/workflow_engine/examples/dtt1-agents-poc.yaml @@ -5,8 +5,8 @@ version: 0.1 description: This workflow is used to test agents deployment por DDT1 PoC variables: agents-os: - - linux-ubuntu-20.04-amd64 - manager-os: linux-ubuntu-20.04-amd64 + - linux-ubuntu-22.04-amd64 + manager-os: linux-ubuntu-22.04-amd64 infra-provider: vagrant working-dir: /tmp/dtt1-poc @@ -19,9 +19,9 @@ tasks: with: path: python3 args: - - test.py + - modules/testing/main.py - inventory: "{working-dir}/agent-{agent}/inventory.yaml" - - dependencies: + - dependencies: - manager: "{working-dir}/manager-{manager-os}/inventory.yaml" - agent: "{working-dir}/agent-{agent}/inventory.yaml" - tests: "install,register,stop" @@ -43,7 +43,7 @@ tasks: with: path: python3 args: - - test.py + - modules/testing/main.py - inventory: "{working-dir}/agent-{agent}/inventory.yaml" - dependencies: - manager: "{working-dir}/manager-{manager-os}/inventory.yaml" @@ -66,7 +66,7 @@ tasks: with: path: python3 args: - - provision.py + - modules/provision/main.py - inventory-manager: "{working-dir}/manager-{manager-os}/inventory.yaml" - install: - component: wazuh-manager @@ -82,7 +82,7 @@ tasks: with: path: python3 args: - - allocation.py + - modules/allocation/main.py - action: create - provider: "{infra-provider}" - size: large @@ -90,13 +90,13 @@ tasks: - inventory-output: "{working-dir}/manager-{manager-os}/inventory.yaml" - track-output: "{working-dir}/manager-{manager-os}/track.yaml" cleanup: - this: process - with: - path: python3 - args: - - allocation.py - - action: delete - - track-output: "{working-dir}/manager-{manager-os}/track.yaml" + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: delete + - track-output: "{working-dir}/manager-{manager-os}/track.yaml" # Generic agent provision task - task: "provision-install-{agent}" @@ -106,7 +106,7 @@ tasks: with: path: python3 args: - - provision.py + - modules/provision/main.py - inventory-agent: "{working-dir}/agent-{agent}/inventory.yaml" - inventory-manager: "{working-dir}/manager-{manager-os}/inventory.yaml" - install: @@ -128,7 +128,7 @@ tasks: with: path: python3 args: - - provision.py + - modules/provision/main.py - inventory-agent: "{working-dir}/agent-{agent}/inventory.yaml" - inventory-manager: "{working-dir}/manager-{manager-os}/inventory.yaml" - uninstall: @@ -148,7 +148,7 @@ tasks: with: path: python3 args: - - allocation.py + - modules/allocation/main.py - action: create - provider: "{infra-provider}" - size: small @@ -156,13 +156,13 @@ tasks: - inventory-output: "{working-dir}/agent-{agent}/inventory.yaml" - track-output: "{working-dir}/agent-{agent}/track.yaml" cleanup: - this: process - with: - path: python3 - args: - - allocation.py - - action: delete - - track-output: "{working-dir}/agent-{agent}/track.yaml" + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: delete + - track-output: "{working-dir}/agent-{agent}/track.yaml" foreach: - variable: agents-os as: agent \ No newline at end of file diff --git a/deployability/launchers/__init__.py b/deployability/modules/workflow_engine/logger/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from deployability/launchers/__init__.py rename to deployability/modules/workflow_engine/logger/__init__.py diff --git a/deployability/modules/workflow_engine/logger/config.yaml b/deployability/modules/workflow_engine/logger/config.yaml new file mode 100644 index 0000000000..764f4cc7a3 --- /dev/null +++ b/deployability/modules/workflow_engine/logger/config.yaml @@ -0,0 +1,28 @@ +version: 1 +formatters: + simple: + format: '[%(asctime)s] [%(levelname)s] [%(process)d] [%(threadName)s] [%(name)s]: %(message)s' + colored: + (): colorlog.ColoredFormatter + format: '%(log_color)s[%(asctime)s] [%(levelname)s] [%(process)d] [%(threadName)s] [%(name)s]: %(message)s' + datefmt: '%Y-%m-%d %H:%M:%S' + log_colors: + DEBUG: cyan + INFO: green + WARNING: yellow + ERROR: red + CRITICAL: red,bg_white +handlers: + console: + class: colorlog.StreamHandler + level: DEBUG + formatter: colored + stream: ext://sys.stdout + file: + class: logging.FileHandler + level: DEBUG + formatter: simple + filename: /tmp/flowbacca.log +root: + level: DEBUG + handlers: [console, file] diff --git a/deployability/modules/workflow_engine/logger/filter.py b/deployability/modules/workflow_engine/logger/filter.py new file mode 100644 index 0000000000..099193a3ac --- /dev/null +++ b/deployability/modules/workflow_engine/logger/filter.py @@ -0,0 +1,19 @@ +import logging +import threading + +class ThreadIDFilter(logging.Filter): + """ + A filter that uppercases the name of the log record. + """ + def filter(self, record: str) -> bool: + """ + Inject thread_id to log records. + + Args: + record (LogRecord): The log record to filter. + + Returns: + bool: True if the record should be logged, False otherwise. + """ + record.thread_id = threading.get_native_id() + return record diff --git a/deployability/modules/workflow_engine/logger/logger.py b/deployability/modules/workflow_engine/logger/logger.py new file mode 100644 index 0000000000..5922e6cc9a --- /dev/null +++ b/deployability/modules/workflow_engine/logger/logger.py @@ -0,0 +1,20 @@ +import logging +import logging.config +from pathlib import Path +import threading + +import yaml + +def _load_config() -> None: + """ + Loads the logging configuration from 'config.yaml' file. + """ + config_path = Path(__file__).parent / 'config.yaml' + with open(config_path, 'r') as f: + config = yaml.safe_load(f.read()) + logging.config.dictConfig(config) + +_load_config() + +logger = logging.getLogger("workflow_engine") + diff --git a/deployability/modules/workflow_engine/schema_validator.py b/deployability/modules/workflow_engine/schema_validator.py index 6e69a6db95..7039ee76d9 100755 --- a/deployability/modules/workflow_engine/schema_validator.py +++ b/deployability/modules/workflow_engine/schema_validator.py @@ -1,12 +1,12 @@ import jsonschema import json +import os from jsonschema.exceptions import ValidationError from pathlib import Path from ruamel.yaml import YAML -from modules.generic.logger import Logger - +from workflow_engine.logger.logger import logger class SchemaValidator: """ @@ -28,11 +28,18 @@ def __init__(self, schema: Path | str, to_validate: Path | str): schema_data: str = None yaml_data: str = None - self.logger = Logger(Path(__file__).stem).get_logger() + self.logger = logger + + if not os.path.exists(schema): + raise FileNotFoundError(f'File "{schema}" not found.') + with open(schema, 'r') as schema_file: self.logger.debug(f"Loading schema file: {schema}") schema_data = json.load(schema_file) + if not os.path.exists(to_validate): + raise FileNotFoundError(f'File "{to_validate}" not found.') + with open(to_validate, 'r') as file: self.logger.debug(f"Loading yaml file: {to_validate}") yaml = YAML(typ='safe', pure=True) diff --git a/deployability/modules/workflow_engine/task.py b/deployability/modules/workflow_engine/task.py index 8903119142..b9e349a74b 100755 --- a/deployability/modules/workflow_engine/task.py +++ b/deployability/modules/workflow_engine/task.py @@ -7,8 +7,7 @@ import time from abc import ABC, abstractmethod -from .utils import logger - +from workflow_engine.logger.logger import logger class Task(ABC): """Abstract base class for tasks.""" @@ -18,7 +17,6 @@ def execute(self) -> None: """Execute the task.""" pass - class ProcessTask(Task): """Task for executing a process.""" @@ -39,18 +37,23 @@ def execute(self) -> None: """Execute the process task.""" task_args = [] + if self.task_parameters.get('args') is None: + raise ValueError(f'Not argument found in {self.task_name}') + for arg in self.task_parameters['args']: if isinstance(arg, str): task_args.append(arg) elif isinstance(arg, dict): key, value = list(arg.items())[0] if isinstance(value, list): - for argvalue in value: - print(f"argvalue {argvalue}") task_args.extend([f"--{key}={argvalue}" for argvalue in value]) else: task_args.append(f"--{key}={value}") - print(f"task_args {task_args}") + else: + logger.error(f'Could not parse arguments {arg}') + + logger.debug(f'Running task "{self.task_name}" with arguments: {task_args}') + result = None try: result = subprocess.run( @@ -59,14 +62,14 @@ def execute(self) -> None: capture_output=True, text=True, ) - - logger.info(str(result.stdout)) - logger.info("%s: %s", "Finish task: ", self.task_name, extra={'tag': self.task_name}) - + logger.debug(f'Finished task "{self.task_name}" execution with result:\n{str(result.stdout)}') if result.returncode != 0: raise subprocess.CalledProcessError(returncode=result.returncode, cmd=result.args, output=result.stdout) except subprocess.CalledProcessError as e: + error_msg = e.stderr + if "KeyboardInterrupt" in error_msg: + raise KeyboardInterrupt(f"Error executing process task with keyboard interrupt.") raise Exception(f"Error executing process task {e.stderr}") class DummyTask(Task): @@ -78,7 +81,6 @@ def execute(self): message = self.task_parameters.get('message', 'No message provided') logger.info("%s: %s", message, self.task_name, extra={'tag': self.task_name}) - class DummyRandomTask(Task): def __init__(self, task_name, task_parameters): self.task_name = task_name @@ -93,7 +95,6 @@ def execute(self): time.sleep(sleep_time) - TASKS_HANDLERS = { 'process': ProcessTask, 'dummy': DummyTask, diff --git a/deployability/modules/workflow_engine/utils.py b/deployability/modules/workflow_engine/utils.py deleted file mode 100644 index 3d64f331a8..0000000000 --- a/deployability/modules/workflow_engine/utils.py +++ /dev/null @@ -1,6 +0,0 @@ -import logging - -# The logging module will use the generic logger anyway -# that is because the logging module is a singleton. -# So, we can just use it this way here, and it will work. -logger = (lambda: logging.getLogger("workflow_engine"))() diff --git a/deployability/modules/workflow_engine/workflow_processor.py b/deployability/modules/workflow_engine/workflow_processor.py index 0a22727a06..bd4b19ef7f 100755 --- a/deployability/modules/workflow_engine/workflow_processor.py +++ b/deployability/modules/workflow_engine/workflow_processor.py @@ -7,15 +7,14 @@ import json import time import yaml +import os -from pathlib import Path +from pathlib import Path from itertools import product -from .schema_validator import SchemaValidator -from .task import * -from .utils import logger - - +from workflow_engine.logger.logger import logger +from workflow_engine.schema_validator import SchemaValidator +from workflow_engine.task import Task, TASKS_HANDLERS class WorkflowFile: """Class for loading and processing a workflow file.""" @@ -35,9 +34,14 @@ def __validate_schema(self, workflow_file: Path | str) -> None: Args: workflow_file (Path | str): Path to the workflow file. """ - validator = SchemaValidator(self.schema_path, workflow_file) - validator.preprocess_data() - validator.validateSchema() + try: + logger.debug(f"Validating input file: {workflow_file}") + validator = SchemaValidator(self.schema_path, workflow_file) + validator.preprocess_data() + validator.validateSchema() + except Exception as e: + logger.error("Error while validating schema [%s] with error: %s", self.schema_path, e) + raise def __load_workflow(self, file_path: str) -> dict: """ @@ -49,15 +53,25 @@ def __load_workflow(self, file_path: str) -> dict: Returns: dict: Workflow data. """ + + if not os.path.exists(file_path): + raise FileNotFoundError(f'File "{file_path}" not found.') + + logger.debug(f"Loading workflow file: {file_path}") + with open(file_path, 'r', encoding='utf-8') as file: return yaml.safe_load(file) def __process_workflow(self): """Process the workflow and return a list of tasks.""" + logger.debug("Process workflow.") task_collection = [] variables = self.workflow_raw_data.get('variables', {}) for task in self.workflow_raw_data.get('tasks', []): task_collection.extend(self.__expand_task(task, variables)) + + if not task_collection: + raise ValueError("No tasks found in the workflow.") return task_collection def __replace_placeholders(self, element: str, values: dict, parent_key: str = None): @@ -283,14 +297,18 @@ def execute_task(self, dag: DAG, task: dict, action) -> None: task_object.execute() logger.info("[%s] Finished task in %.2f seconds.", task_name, time.time() - start_time) dag.set_status(task_name, 'successful') + except KeyboardInterrupt as e: + logger.error("[%s] Task failed with error: %s.", task_name, e) + dag.set_status(task_name, 'failed') + dag.cancel_dependant_tasks(task_name, task.get('on-error', 'abort-related-flows')) + raise KeyboardInterrupt except Exception as e: - # Pass the tag to the tag_formatter function if it exists logger.error("[%s] Task failed with error: %s.", task_name, e) dag.set_status(task_name, 'failed') dag.cancel_dependant_tasks(task_name, task.get('on-error', 'abort-related-flows')) - # Handle the exception or re-raise if necessary raise + def create_task_object(self, task: dict, action) -> Task: """Create and return a Task object based on task type.""" task_type = task[action]['this'] @@ -302,56 +320,65 @@ def create_task_object(self, task: dict, action) -> Task: raise ValueError(f"Unknown task type '{task_type}'.") - def execute_tasks_parallel(self) -> None: + def execute_tasks_parallel(self, dag: DAG, reverse: bool = False) -> None: """Execute tasks in parallel.""" - if not self.dry_run: - logger.info("Executing tasks in parallel.") - dag = DAG(self.task_collection) - # Execute tasks based on the DAG + logger.info("Executing tasks in parallel.") + try: with concurrent.futures.ThreadPoolExecutor(max_workers=self.threads) as executor: - futures = {} - while True: - if not dag.is_active(): - break - for task_name in dag.get_available_tasks(): - task = next(t for t in self.task_collection if t['task'] == task_name) - future = executor.submit(self.execute_task, dag, task, 'do') - futures[task_name] = future + futures = self.generate_futures(dag, executor, reverse) concurrent.futures.wait(futures.values()) + except KeyboardInterrupt: + logger.error("User interrupt detected. Aborting execution...") + self.execute_tasks_parallel(dag, reverse=True) - # Now execute cleanup tasks based on the reverse DAG - reverse_dag = DAG(self.task_collection, reverse=True) + def generate_futures(self, dag, executor, reverse: bool = False): + futures = {} - logger.info("Executing cleanup tasks.") - with concurrent.futures.ThreadPoolExecutor(max_workers=self.threads) as executor: - reverse_futures = {} - - while True: - if not reverse_dag.is_active(): - break - for task_name in reverse_dag.get_available_tasks(): - task = next(t for t in self.task_collection if t['task'] == task_name) - if 'cleanup' in task: - future = executor.submit(self.execute_task, reverse_dag, task, 'cleanup') - reverse_futures[task_name] = future - else: - reverse_dag.set_status(task_name, 'successful') - concurrent.futures.wait(reverse_futures.values()) + while True: + if not dag.is_active(): + break + + for task_name in list(dag.get_available_tasks()): + task = next(t for t in self.task_collection if t['task'] == task_name) + action = 'cleanup' if reverse and 'cleanup' in task else 'do' + if reverse and 'cleanup' not in task: + dag.set_status(task_name, 'successful') + else: + future = executor.submit(self.execute_task, dag, task, action) + futures[task_name] = future + + return futures - else: - dag = DAG(self.task_collection) - logger.info("Execution plan: %s", json.dumps(dag.get_execution_plan(), indent=2)) def run(self) -> None: """Main entry point.""" - self.execute_tasks_parallel() + try: + if not self.dry_run: + logger.info("Executing DAG tasks.") + dag = DAG(self.task_collection) + self.execute_tasks_parallel(dag) + + logger.info("Executing Reverse DAG tasks.") + reversed_dag = DAG(self.task_collection, reverse=True) + self.execute_tasks_parallel(reversed_dag, reverse=True) + else: + dag = DAG(self.task_collection) + logger.info("Execution plan: %s", json.dumps(dag.get_execution_plan(), indent=2)) + + except Exception as e: + logger.error("Error in Workflow: %s", e) - def abort_execution(self, executor: concurrent.futures.ThreadPoolExecutor, futures: dict) -> None: - """Abort the execution of tasks.""" - for future in concurrent.futures.as_completed(futures.values()): - try: - _ = future.result() - except Exception as e: - logger.error("Error in aborted task: %s", e) - executor.shutdown(wait=False) + def handle_interrupt(self, signum, frame): + logger.error("User interrupt detected. End process...") + raise KeyboardInterrupt("User interrupt detected. End process...") + + def abort_execution(self, futures) -> None: + """Abort the execution of tasks.""" + with concurrent.futures.ThreadPoolExecutor(max_workers=self.threads) as executor: + for future in concurrent.futures.as_completed(futures.values()): + try: + _ = future.result() + except Exception as e: + logger.error("Error in aborted task: %s", e) + executor.shutdown(wait=False, cancel_futures=True) diff --git a/deployability/version.json b/deployability/version.json new file mode 100644 index 0000000000..c885fdde40 --- /dev/null +++ b/deployability/version.json @@ -0,0 +1,4 @@ +{ + "version": "1.0", + "revision": "1" +} \ No newline at end of file diff --git a/deployability/workflow_engine/examples/multiple_linear_independant_flow.yaml b/deployability/workflow_engine/examples/multiple_linear_independant_flow.yaml deleted file mode 100644 index 7f0ae53560..0000000000 --- a/deployability/workflow_engine/examples/multiple_linear_independant_flow.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (C) 2015, Wazuh Inc. -# Created by Wazuh, Inc. . -# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 -version: 0.1 -description: This is a basic example of two linear and independant flows. - -tasks: - - task: "A1" - description: "Task A1" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task A1" - depends-on: - - "B1" - - task: "B1" - description: "Task B1" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task B1" - depends-on: - - "C1" - - task: "C1" - description: "Task C1" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task C1" - - task: "A2" - description: "Task A2" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task A2" - depends-on: - - "B2" - - task: "B2" - description: "Task B2" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task B2" - depends-on: - - "C2" - - task: "C2" - description: "Task C2" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task C2" diff --git a/deployability/workflow_engine/examples/multiple_linear_independant_flow_templetized.yaml b/deployability/workflow_engine/examples/multiple_linear_independant_flow_templetized.yaml deleted file mode 100644 index b4c55c96d7..0000000000 --- a/deployability/workflow_engine/examples/multiple_linear_independant_flow_templetized.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (C) 2015, Wazuh Inc. -# Created by Wazuh, Inc. . -# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 -version: 0.1 -description: This is a basic example of two linear and independant flows using templates. - -variables: - index: - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 -tasks: - - task: "A{i}" - description: "Task A{i}" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task A{i}" - depends-on: - - "B{i}" - foreach: - - variable: index - as: i - - task: "B{i}" - description: "Task B{i}" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task B{i}" - depends-on: - - "C{i}" - foreach: - - variable: index - as: i - - task: "C{i}" - description: "Task C{i}" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task C{i}" - foreach: - - variable: index - as: i diff --git a/deployability/workflow_engine/examples/single_linear_flow.yaml b/deployability/workflow_engine/examples/single_linear_flow.yaml deleted file mode 100644 index 25c6965a00..0000000000 --- a/deployability/workflow_engine/examples/single_linear_flow.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (C) 2015, Wazuh Inc. -# Created by Wazuh, Inc. . -# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 -version: 0.1 -description: This is a basic example of linear flow. - -tasks: - - task: "A" - description: "Task A" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task A" - depends-on: - - "B" - - task: "B" - description: "Task B" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task B" - depends-on: - - "C" - - task: "C" - description: "Task C" - do: - this: process - with: - path: /bin/echo - args: - - -n - - "Running task C"