├── .dockerignore ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── actions │ ├── all-tests │ │ └── action.yml │ ├── common-tests │ │ └── action.yml │ ├── e2e-tests │ │ └── action.yml │ ├── image-test │ │ └── action.yml │ ├── run-flake │ │ └── action.yml │ ├── run-isort │ │ └── action.yml │ └── setup │ │ └── action.yml ├── dependabot.yml └── workflows │ ├── build-image.yml │ ├── ci.yml │ ├── e2e-tests.yml.disabled │ ├── release.yml │ └── scheduled.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── ansible_rulebook ├── __init__.py ├── __main__.py ├── action │ ├── __init__.py │ ├── control.py │ ├── debug.py │ ├── helper.py │ ├── metadata.py │ ├── noop.py │ ├── pg_notify.py │ ├── post_event.py │ ├── print_event.py │ ├── retract_fact.py │ ├── run_job_template.py │ ├── run_module.py │ ├── run_playbook.py │ ├── run_workflow_template.py │ ├── runner.py │ ├── set_fact.py │ └── shutdown.py ├── app.py ├── cli.py ├── collection.py ├── common.py ├── condition_parser.py ├── condition_types.py ├── conf.py ├── engine.py ├── event_filter │ ├── __init__.py │ └── insert_meta_info.py ├── exception.py ├── job_template_runner.py ├── json_generator.py ├── messages.py ├── rule_generator.py ├── rule_set_runner.py ├── rule_types.py ├── rules_parser.py ├── schema │ └── ruleset_schema.json ├── terminal.py ├── token.py ├── util.py ├── validators.py ├── vault.py └── websocket.py ├── commitlint.config.js ├── demos └── sensu-kafka-demo │ ├── ansible-rulebook │ ├── Dockerfile │ └── data │ │ ├── inventory.yml │ │ ├── recover-fake-app.yml │ │ └── rules.yml │ ├── diagram.jpg │ ├── docker-compose.yml │ ├── instructions.md │ └── sensu-cli │ ├── Dockerfile │ └── assets │ ├── check.yml │ └── handler.yml ├── docs ├── Makefile ├── actions.rst ├── collections.rst ├── conditions.rst ├── conf.py ├── contributing.rst ├── decision_environment.rst ├── development_environment.rst ├── drools_seq.md ├── events_and_facts.rst ├── filters.rst ├── getting_started.rst ├── host_limit.rst ├── index.rst ├── installation.rst ├── introduction.rst ├── make.bat ├── multi_events.rst ├── readme.md ├── requirements.txt ├── rulebooks.rst ├── rules.rst ├── runner.rst ├── sources.rst ├── usage.rst └── variables.rst ├── minimal-decision-environment.yml ├── performance_test ├── 100M_event_rules.yml ├── 100k_event_rules.yml ├── 10M_event_rules.yml ├── 10k_event_rules.yml ├── 1M_event_rules.yml ├── 1k_event_rules.yml ├── generate_inventory.py ├── hello_playbook1.yml ├── inventory1.yml ├── inventory10.yml ├── inventory100.yml ├── null_rules.yml ├── perf_test.py ├── rules.yml ├── run_perf.sh └── sources │ └── range.py ├── pyproject.toml ├── pytest.ini ├── requirements_dev.txt ├── requirements_lint.txt ├── requirements_test.txt ├── setup.cfg ├── sonar-project.properties ├── tests ├── __init__.py ├── asts │ ├── 01_noop.yml │ ├── 02_debug.yml │ ├── 03_print_event.yml │ ├── 04_set_fact.yml │ ├── 05_post_event.yml │ ├── 06_retract_fact.yml │ ├── 07_and.yml │ ├── 08_or.yml │ ├── 09_gt.yml │ ├── 10_lt.yml │ ├── 11_le.yml │ ├── 12_ge.yml │ ├── 13_add.yml │ ├── 14_sub.yml │ ├── 15_multiple_events_all.yml │ ├── 16_multiple_events_any.yml │ ├── 17_multiple_sources_any.yml │ ├── 18_multiple_sources_all.yml │ ├── 19_is_defined.yml │ ├── 20_is_not_defined.yml │ ├── 21_run_playbook.yml │ ├── 23_nested_data.yml │ ├── 24_max_attributes.yml │ ├── 25_max_attributes_nested.yml │ ├── 26_print_events.yml │ ├── 27_var_root.yml │ ├── 28_right_side_condition_template.yml │ ├── 29_run_module.yml │ ├── 30_run_module_missing.yml │ ├── 31_run_module_missing_args.yml │ ├── 32_run_module_fail.yml │ ├── 33_run_playbook_retry.yml │ ├── 34_run_playbook_retries.yml │ ├── 35_multiple_rulesets_1_fired.yml │ ├── 36_multiple_rulesets_both_fired.yml │ ├── 37_hosts_facts.yml │ ├── 38_shutdown.yml │ ├── rules.yml │ ├── rules_with_assignment.yml │ ├── rules_with_assignment2.yml │ ├── rules_with_multiple_conditions.yml │ ├── rules_with_multiple_conditions2.yml │ ├── rules_with_multiple_conditions3.yml │ ├── rules_with_time.yml │ ├── rules_with_timestamp.yml │ ├── rules_with_vars.yml │ ├── rules_without_assignment.yml │ ├── test_filters.yml │ ├── test_host_rules.yml │ ├── test_rules.yml │ ├── test_rules_multiple_hosts.yml │ ├── test_rules_multiple_hosts2.yml │ ├── test_rules_multiple_hosts3.yml │ ├── test_set_facts.yml │ └── test_simple.yml ├── conftest.py ├── data │ ├── awx_test_data.py │ ├── bad_source.py │ ├── not_asyncio.py │ ├── rulebook.yml │ ├── test.tar │ ├── test_cert.pem │ ├── test_env.yml │ ├── test_key.pem │ └── test_vars.yml ├── e2e │ ├── README.md │ ├── __init__.py │ ├── config │ │ └── default.yml │ ├── conftest.py │ ├── files │ │ ├── extra_vars │ │ │ ├── operator_variables.yml │ │ │ ├── test_debug.yml │ │ │ ├── test_variables_extra_vars.yml │ │ │ └── vaulted_variables.yml │ │ ├── inventories │ │ │ ├── default_inventory.ini │ │ │ ├── default_inventory.yml │ │ │ └── inventory_as_dir │ │ │ │ ├── group_vars │ │ │ │ └── customgroup.yml │ │ │ │ ├── host_vars │ │ │ │ └── localhost.yml │ │ │ │ └── hosts.yml │ │ ├── passwords │ │ │ ├── pass1.txt │ │ │ ├── pass2.txt │ │ │ └── pass3.txt │ │ ├── playbooks │ │ │ ├── long_running.yml │ │ │ ├── print_event.yml │ │ │ ├── print_group_vars.yml │ │ │ ├── print_rule_name.yml │ │ │ ├── run_playbook_test_playbook.yml │ │ │ ├── run_playbook_test_playbook_with_set_stats.yml │ │ │ └── run_playbook_test_playbook_without_cacheable.yml │ │ └── rulebooks │ │ │ ├── actions │ │ │ ├── test_actions_sanity.yml │ │ │ ├── test_run_playbook.yml │ │ │ ├── test_run_playbook_with_set_stats.yml │ │ │ ├── test_run_playbook_without_cacheable.yml │ │ │ ├── test_shutdown_graceful.yml │ │ │ └── test_shutdown_now.yml │ │ │ ├── hello_events_with_var.yml │ │ │ ├── malformed_rulebook.yml │ │ │ ├── operators │ │ │ ├── test_logical_operators.yml │ │ │ ├── test_membership_operators.yml │ │ │ ├── test_relational_operators.yml │ │ │ ├── test_select_operator.yml │ │ │ ├── test_selectattr_operator.yml │ │ │ ├── test_string_match.yml │ │ │ ├── test_string_search_regex.yml │ │ │ └── test_string_search_search.yml │ │ │ ├── test_debug_no_params.yml │ │ │ ├── test_debug_var_and_message.yml │ │ │ ├── test_disabled_rules.yml │ │ │ ├── test_hot_reload.yml │ │ │ ├── test_inventory_as_dir.yml │ │ │ ├── test_match_multiple_rules.yml │ │ │ ├── test_process_sigint.yml │ │ │ ├── test_process_source_end.yml │ │ │ ├── test_source_stacktrace.yml │ │ │ ├── test_vaulted_rulebook.yml │ │ │ ├── test_vaulted_rulebook_interpolate.yml │ │ │ ├── test_vaulted_v2.yml │ │ │ ├── variables │ │ │ └── test_variables_sanity.yml │ │ │ └── websockets │ │ │ └── test_websocket_range.yml │ ├── settings.py │ ├── test_actions.py │ ├── test_match_multiple_rules.py │ ├── test_non_alpha_keys.py │ ├── test_operators.py │ ├── test_run_module_output.py │ ├── test_runtime.py │ ├── test_skip_audit_events.py │ ├── test_source_stacktrace.py │ ├── test_variables.py │ ├── test_vault.py │ ├── test_websocket.py │ ├── utils.py │ └── utils │ │ └── awx │ │ ├── ansible.cfg │ │ ├── create-cluster.yml │ │ ├── install-awx.yml │ │ ├── kind-config.yml │ │ ├── readme.md │ │ ├── requirements.txt │ │ └── roles │ │ └── install-awx │ │ ├── defaults │ │ └── main.yaml │ │ ├── tasks │ │ └── main.yaml │ │ └── templates │ │ ├── admin-password-secret.yml.j2 │ │ ├── awx.yaml.j2 │ │ └── kustomization │ │ └── kustomization.yaml.j2 ├── event_filter │ ├── dashes_to_underscores.py │ ├── json_filter.py │ ├── noop.py │ └── test_insert_meta_info.py ├── examples │ ├── 01_noop.yml │ ├── 02_debug.yml │ ├── 03_print_event.yml │ ├── 04_set_fact.yml │ ├── 05_post_event.yml │ ├── 06_retract_fact.yml │ ├── 07_and.yml │ ├── 08_or.yml │ ├── 09_gt.yml │ ├── 10_lt.yml │ ├── 11_le.yml │ ├── 12_ge.yml │ ├── 13_add.yml │ ├── 14_sub.yml │ ├── 15_multiple_events_all.yml │ ├── 16_multiple_events_any.yml │ ├── 17_multiple_sources_any.yml │ ├── 18_multiple_sources_all.yml │ ├── 19_is_defined.yml │ ├── 20_is_not_defined.yml │ ├── 21_run_playbook.yml │ ├── 22_run_playbook.yml │ ├── 23_nested_data.yml │ ├── 24_max_attributes.yml │ ├── 25_max_attributes_nested.yml │ ├── 26_print_events.yml │ ├── 27_var_root.yml │ ├── 28_right_side_condition_template.yml │ ├── 29_run_module.yml │ ├── 30_run_module_missing.yml │ ├── 31_run_module_missing_args.yml │ ├── 32_run_module_fail.yml │ ├── 33_run_playbook_retry.yml │ ├── 34_run_playbook_retries.yml │ ├── 35_multiple_rulesets_1_fired.yml │ ├── 36_multiple_rulesets_both_fired.yml │ ├── 37_hosts_facts.yml │ ├── 38_shutdown.yml │ ├── 39_is_defined.yml │ ├── 40_in.yml │ ├── 41_not_in.yml │ ├── 42_contains.yml │ ├── 43_not_contains.yml │ ├── 44_in_and.yml │ ├── 45_in_or.yml │ ├── 46_job_template.yml │ ├── 47_generic_plugin.yml │ ├── 48_echo.yml │ ├── 49_float.yml │ ├── 50_negation.yml │ ├── 51_vars_namespace.yml │ ├── 52_once_within.yml │ ├── 53_once_within_multiple_hosts.yml │ ├── 54_time_window.yml │ ├── 55_not_all.yml │ ├── 56_once_after.yml │ ├── 57_once_after_multi.yml │ ├── 58_string_search.yml │ ├── 59_multiple_actions.yml │ ├── 60_json_filter.yml │ ├── 61_select_1.yml │ ├── 62_select_2.yml │ ├── 63_selectattr_1.yml │ ├── 64_selectattr_2.yml │ ├── 65_selectattr_3.yml │ ├── 66_sleepy_playbook.yml │ ├── 67_shutdown_now.yml │ ├── 68_disabled_rule.yml │ ├── 69_enhanced_debug.yml │ ├── 70_null.yml │ ├── 72_set_fact_with_type.yml │ ├── 73_mix_and_match_list.yml │ ├── 74_self_referential.yml │ ├── 75_all_conditions.yml │ ├── 76_all_conditions.yml │ ├── 77_default_events_ttl.yml │ ├── 78_complete_retract_fact.yml │ ├── 79_workflow_template.yml │ ├── 80_match_multiple_rules.yml │ ├── 81_match_single_rule.yml │ ├── 82_non_alpha_keys.yml │ ├── 83_boolean_true.yml │ ├── 84_job_template_exclude_events.yml │ ├── 85_workflow_template_exclude_events.yml │ ├── 86_job_template_include_events.yml │ ├── 87_workflow_template_include_events.yml │ ├── 88_job_template_no_args.yml │ ├── 89_source_error_with_msg.yml │ ├── 90_source_error_without_msg.yml │ ├── 91_mask_senstive_variables.yml │ └── replays │ │ ├── 23_nested_data │ │ ├── 00.json │ │ ├── 01.json │ │ ├── 02.json │ │ ├── 03.json │ │ ├── 04.json │ │ ├── 05.json │ │ ├── 06.json │ │ ├── 07.json │ │ ├── 08.json │ │ ├── 09.json │ │ └── generate_data.py │ │ ├── 24_max_attributes │ │ ├── 00.json │ │ └── generate_data.py │ │ ├── 25_max_attributes_nested │ │ ├── 00.json │ │ └── generate_data.py │ │ └── 39_is_defined │ │ ├── 00.json │ │ ├── 01.json │ │ ├── 02.json │ │ ├── 03.json │ │ ├── 04.json │ │ ├── 05.json │ │ ├── 06.json │ │ ├── 07.json │ │ ├── 08.json │ │ ├── 09.json │ │ └── generate_data.py ├── generate_asts.py ├── pass1.txt ├── playbooks │ ├── check_facts_playbook.yml │ ├── compare_value.yml │ ├── fail_and_succeed.yml │ ├── hello.yml │ ├── hello_events.yml │ ├── hello_world_set_fact.yml │ ├── inventory.yml │ ├── inventory1.yml │ ├── sleeper.yml │ ├── validate_args_playbook.yml │ └── vars.yml ├── rules │ ├── rule_names_with_substitution.yml │ ├── rules.yml │ ├── rules_with_assignment.yml │ ├── rules_with_assignment2.yml │ ├── rules_with_multiple_conditions.yml │ ├── rules_with_multiple_conditions2.yml │ ├── rules_with_multiple_conditions3.yml │ ├── rules_with_time.yml │ ├── rules_with_timestamp.yml │ ├── rules_with_vars.yml │ ├── rules_without_assignment.yml │ ├── test_blank_rule_name.yml │ ├── test_blank_ruleset_name.yml │ ├── test_combine_hosts.yml │ ├── test_combine_hosts_module.yml │ ├── test_duplicate_rule_names.yml │ ├── test_duplicate_ruleset_names.yml │ ├── test_empty_rule_names.yml │ ├── test_filters.yml │ ├── test_host_rules.yml │ ├── test_kafka.yml │ ├── test_missing_rule_names.yml │ ├── test_missing_ruleset_name.yml │ ├── test_multiple_sources.yml │ ├── test_rules.yml │ ├── test_rules_expanded_conditions.yml │ ├── test_rules_multiple_hosts.yml │ ├── test_rules_multiple_hosts2.yml │ ├── test_rules_multiple_hosts3.yml │ ├── test_rules_playbooks.yml │ ├── test_set_facts.yml │ ├── test_simple.yml │ ├── test_states.yml │ └── webserver_down.yml ├── run_examples.sh ├── sources │ ├── __init__.py │ ├── fail_after.py │ ├── file.py │ ├── generic.py │ ├── hosts.py │ ├── infrange.py │ ├── log_scraper.py │ ├── nested.py │ ├── ping.py │ ├── process_check.py │ ├── range.py │ ├── range2.py │ ├── replay.py │ ├── replays │ │ ├── 01.json │ │ ├── 02.json │ │ ├── 03.json │ │ ├── 04.json │ │ └── 05.json │ ├── source_with_exception.py │ ├── template.py │ ├── tick.py │ ├── timestamp.py │ └── url_check.py ├── test_ansible_events.py ├── test_app.py ├── test_ast.py ├── test_collection.py ├── test_controller.py ├── test_engine.py ├── test_examples.py ├── test_rules.py ├── test_simple.yml ├── test_token.py ├── test_vault.py ├── test_websocket.py └── unit │ ├── action │ ├── __init__.py │ ├── conftest.py │ ├── playbooks │ │ ├── fail.yml │ │ └── rule_name.yml │ ├── test_controller.py │ ├── test_debug.py │ ├── test_noop.py │ ├── test_pg_notify.py │ ├── test_post_event.py │ ├── test_print_event.py │ ├── test_retract_fact.py │ ├── test_run_job_template.py │ ├── test_run_module.py │ ├── test_run_playbook.py │ ├── test_run_workflow_template.py │ ├── test_set_fact.py │ └── test_shutdown.py │ ├── test_cli.py │ ├── test_terminal.py │ └── test_util.py ├── tools └── convert_to_ast.py └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | venv 3 | .venv/ 4 | **/*.pyc 5 | **/__pycache__ 6 | .tox 7 | .vscode/ 8 | .idea/ 9 | .ansible/ 10 | *.egg-info/ 11 | .pytest_cache/ 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.{yml,yaml}] 14 | indent_size = 2 15 | 16 | [*.bat] 17 | indent_style = tab 18 | end_of_line = crlf 19 | 20 | [LICENSE] 21 | insert_final_newline = false 22 | 23 | [Makefile] 24 | indent_style = tab 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: For debugging help or technical support 4 | url: https://ansible-rulebook.readthedocs.io/en/latest/contributing.html 5 | about: For general debugging or technical support please see the linked section of our docs. 6 | 7 | - name: 📝 Ansible Code of Conduct 8 | url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser 9 | about: EDA-Controlles uses the Ansible Code of Conduct; ❤ Be nice to other members of the community. ☮ Behave. 10 | 11 | - name: 💼 For Enterprise 12 | url: https://www.ansible.com/use-cases/event-driven-automation 13 | about: Red Hat offers support for the Ansible Automation Platform 14 | -------------------------------------------------------------------------------- /.github/actions/common-tests/action.yml: -------------------------------------------------------------------------------- 1 | name: common-tests 2 | description: run common tests for ansible-rulebook (unit and integration) 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup jUnit reporter 8 | shell: bash 9 | run: | 10 | echo "GIT_SHA=$(git rev-parse "$GITHUB_SHA")" >> "$GITHUB_ENV" 11 | 12 | - name: Run common tests 13 | shell: bash 14 | run: pytest -vv -s -m "not e2e and not long_run" -vv -n auto --cov=./ --cov-report=xml --junit-xml=common-test-results.xml 15 | 16 | - name: Upload jUnit test results (APDE CI) 17 | shell: bash 18 | if: env.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_URL != '' && github.ref == 'refs/heads/main' 19 | run: >- 20 | http --check-status --ignore-stdin 21 | --auth "${{ env.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_USER }}:${{ env.PDE_ORG_RESULTS_UPLOAD_PASSWORD }}" 22 | -f POST "${{ env.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_URL }}/api/results/upload/" 23 | xunit_xml@common-test-results.xml 24 | component_name=eda 25 | git_commit_sha=${{ env.GIT_SHA }} 26 | git_repository_url="https://github.com/${{ github.repository }}" 27 | -------------------------------------------------------------------------------- /.github/actions/e2e-tests/action.yml: -------------------------------------------------------------------------------- 1 | name: e2e-tests 2 | description: run e2e tests for ansible-rulebook 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup jUnit reporter 8 | shell: bash 9 | run: | 10 | echo "GIT_SHA=$(git rev-parse "$GITHUB_SHA")" >> "$GITHUB_ENV" 11 | 12 | - name: Run e2e tests 13 | shell: bash 14 | run: pytest -m "e2e" -n auto --cov=./ --cov-report=xml --cov-append --junit-xml=e2e-test-results.xml 15 | 16 | - name: Upload jUnit test results (APDE CI) 17 | shell: bash 18 | if: env.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_URL != '' && github.ref == 'refs/heads/main' 19 | run: >- 20 | http --check-status --ignore-stdin 21 | --auth "${{ env.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_USER }}:${{ env.PDE_ORG_RESULTS_UPLOAD_PASSWORD }}" 22 | -f POST "${{ env.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_URL }}/api/results/upload/" 23 | xunit_xml@e2e-test-results.xml 24 | component_name=eda 25 | git_commit_sha=${{ env.GIT_SHA }} 26 | git_repository_url="https://github.com/${{ github.repository }}" 27 | -------------------------------------------------------------------------------- /.github/actions/image-test/action.yml: -------------------------------------------------------------------------------- 1 | name: build-and-test-image 2 | description: Build and test the container image 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Set up Python 8 | uses: actions/setup-python@v5 9 | with: 10 | python-version: 3.11 11 | 12 | - name: Get package version 13 | shell: bash 14 | run: | 15 | python -m pip install setuptools_scm 16 | echo "SETUPTOOLS_SCM_PRETEND_VERSION=$(python -m setuptools_scm)" >> $GITHUB_ENV 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v2 20 | 21 | - name: Build container image 22 | uses: docker/build-push-action@v4 23 | with: 24 | context: . 25 | platforms: linux/amd64 26 | tags: localhost/ansible-rulebook:test 27 | load: true 28 | build-args: | 29 | SETUPTOOLS_SCM_PRETEND_VERSION=${{ env.SETUPTOOLS_SCM_PRETEND_VERSION }} 30 | 31 | - name: Run tests 32 | shell: bash 33 | run: > 34 | docker run --rm localhost/ansible-rulebook:test bash -c ' 35 | pip install -r requirements_test.txt && 36 | pytest -m "e2e" -n auto' 37 | -------------------------------------------------------------------------------- /.github/actions/run-flake/action.yml: -------------------------------------------------------------------------------- 1 | name: run-flake 2 | description: run flake8 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Set up Python 8 | uses: actions/setup-python@v5 9 | with: 10 | python-version: "3.10" 11 | 12 | - name: Install dependencies 13 | shell: bash 14 | run: python -m pip install -r requirements_lint.txt 15 | 16 | - name: Lint with flake8 17 | shell: bash 18 | run: flake8 . --count --show-source --statistics 19 | -------------------------------------------------------------------------------- /.github/actions/run-isort/action.yml: -------------------------------------------------------------------------------- 1 | name: run-isort 2 | description: run isort 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Set up Python 8 | uses: actions/setup-python@v5 9 | with: 10 | python-version: "3.10" 11 | 12 | - name: Install dependencies 13 | shell: bash 14 | run: python -m pip install isort 15 | 16 | - name: Lint with isort 17 | shell: bash 18 | run: isort . --check --diff 19 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: setup 2 | description: Common steps to install ansible-rulebook for CI workflows 3 | 4 | inputs: 5 | python-version: 6 | description: python version to use 7 | required: true 8 | 9 | runs: 10 | using: composite 11 | steps: 12 | - name: Install Java 13 | uses: actions/setup-java@v3 14 | with: 15 | distribution: "zulu" 16 | java-version: "17" 17 | 18 | - name: Set up Python ${{ inputs.python-version }} 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ inputs.python-version }} 22 | 23 | - name: Install package 24 | shell: bash 25 | run: python -m pip install .[production] 26 | 27 | - name: Install test dependencies 28 | shell: bash 29 | run: python -m pip install -r requirements_test.txt 30 | 31 | - name: Install `ansible.eda` collection 32 | shell: bash 33 | run: ansible-galaxy collection install git+https://github.com/ansible/event-driven-ansible 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: weekly 8 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml.disabled: -------------------------------------------------------------------------------- 1 | # at this stage, ci.yml run all-tests, which performs: common-tests, long_run tests, e2e-tests 2 | name: e2e tests 3 | 4 | on: 5 | push: 6 | branches: 7 | - "main" 8 | pull_request: 9 | branches: 10 | - "main" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | e2e-tests: 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | python-version: 20 | - "3.9" 21 | - "3.10" 22 | - "3.11" 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | 28 | - name: Setup 29 | uses: ./.github/actions/setup 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | 33 | - name: e2e tests 34 | uses: ./.github/actions/e2e-tests 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Workflow 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-and-publish: 9 | runs-on: ubuntu-latest 10 | # Restrict this action only to protected tags 11 | if: github.repository == 'ansible/ansible-rulebook' && startsWith(github.event.release.tag_name, 'v') 12 | permissions: 13 | contents: write 14 | steps: 15 | - name: Check out repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: "3.11" 22 | 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install -U build setuptools setuptools_scm 26 | 27 | - name: Build package 28 | run: make dist 29 | 30 | - name: Upload artifacts to GitHub Release 31 | env: 32 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | RELEASE_TAG: ${{ github.event.release.tag_name }} 34 | RELEASE_ID: ${{ github.event.release.id }} 35 | run: | 36 | gh release upload "$RELEASE_TAG" dist/* 37 | 38 | - name: Publish to PyPI 39 | uses: pypa/gh-action-pypi-publish@release/v1 40 | with: 41 | password: ${{ secrets.PYPI_API_TOKEN }} 42 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude: "^docs/" 3 | repos: 4 | - repo: https://github.com/psf/black 5 | rev: 22.6.0 6 | hooks: 7 | - id: black 8 | - repo: https://github.com/pycqa/isort 9 | rev: 5.12.0 10 | hooks: 11 | - id: isort 12 | - repo: https://github.com/pycqa/flake8 13 | rev: 4.0.1 14 | hooks: 15 | - id: flake8 16 | additional_dependencies: 17 | - flake8-bugbear 18 | - repo: local 19 | hooks: 20 | - id: ajv 21 | name: ajv 22 | description: Validate JSON schema 23 | language: node 24 | types: [json] 25 | files: ^ansible_rulebook/schema/ruleset_schema.json$ 26 | additional_dependencies: 27 | - ajv-cli 28 | entry: ajv compile -s 29 | - repo: https://github.com/pre-commit/pre-commit-hooks 30 | rev: v4.4.0 31 | hooks: 32 | - id: pretty-format-json 33 | language_version: python3 34 | args: ['--autofix', '--no-sort-keys', '--indent', '4'] 35 | - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook 36 | rev: v9.5.0 37 | hooks: 38 | - id: commitlint 39 | additional_dependencies: ['@commitlint/config-conventional'] 40 | stages: [commit-msg] 41 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | 13 | recursive-include ansible_rulebook/schema *.json 14 | -------------------------------------------------------------------------------- /ansible_rulebook/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Top-level package for Ansible Events.""" 16 | 17 | import yaml 18 | 19 | 20 | def construct_vault_encrypted_unicode(loader, node): 21 | return loader.construct_scalar(node) 22 | 23 | 24 | yaml.SafeLoader.add_constructor("!vault", construct_vault_encrypted_unicode) 25 | -------------------------------------------------------------------------------- /ansible_rulebook/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ansible_rulebook.cli import main 16 | 17 | main() 18 | -------------------------------------------------------------------------------- /ansible_rulebook/action/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-rulebook/da1bed4a19a08e37eb45394248941156692a304f/ansible_rulebook/action/__init__.py -------------------------------------------------------------------------------- /ansible_rulebook/action/noop.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from .control import Control 18 | from .helper import Helper 19 | from .metadata import Metadata 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | class Noop: 25 | """The No Op action usually used for debugging, doesn't do anything and 26 | just sends the action status 27 | """ 28 | 29 | def __init__( 30 | self, 31 | metadata: Metadata, 32 | control: Control, 33 | **action_args, 34 | ): 35 | self.helper = Helper(metadata, control, "noop") 36 | self.action_args = action_args 37 | 38 | async def __call__(self): 39 | await self.helper.send_default_status() 40 | -------------------------------------------------------------------------------- /ansible_rulebook/event_filter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-rulebook/da1bed4a19a08e37eb45394248941156692a304f/ansible_rulebook/event_filter/__init__.py -------------------------------------------------------------------------------- /ansible_rulebook/messages.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from dataclasses import dataclass 16 | 17 | DEFAULT_SHUTDOWN_DELAY = 60.0 18 | 19 | 20 | @dataclass(frozen=True) 21 | class Shutdown: 22 | message: str = "Not specified" 23 | delay: float = DEFAULT_SHUTDOWN_DELAY 24 | kind: str = "graceful" 25 | source_plugin: str = "" 26 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | // custom rules for commitlint 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'body-max-line-length': [2, 'always', Infinity], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/ansible-rulebook/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/aizquier/ansible-rulebook:latest 2 | 3 | COPY ./data/* /data/ 4 | -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/ansible-rulebook/data/inventory.yml: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | localhost: 4 | ansible_connection: local 5 | -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/ansible-rulebook/data/recover-fake-app.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | gather_facts: false 3 | vars: 4 | app_host: fake-app 5 | app_port: 5080 6 | tasks: 7 | - name: Fix fake app 8 | ansible.builtin.uri: 9 | url: "http://{{ app_host }}:{{ app_port }}/up" 10 | register: response 11 | failed_when: response.status != 200 12 | 13 | - name: check the app is running again 14 | ansible.builtin.uri: 15 | url: "http://{{ app_host }}:{{ app_port }}/health" 16 | register: response 17 | failed_when: response.status != 200 18 | -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/ansible-rulebook/data/rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: sensu-kafka demo rules 3 | hosts: localhost 4 | sources: 5 | - name: kafka 6 | ansible.eda.kafka: 7 | topic: sensu-events 8 | host: broker 9 | port: 9092 10 | rules: 11 | - name: recover fake-app outage 12 | condition: event.check.metadata.name == "check-fake-app" and event.check.status == 2 13 | action: 14 | run_playbook: 15 | name: recover-fake-app.yml 16 | -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-rulebook/da1bed4a19a08e37eb45394248941156692a304f/demos/sensu-kafka-demo/diagram.jpg -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/sensu-cli/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mirror.gcr.io/sensu/sensu:latest 2 | 3 | COPY assets/* /sensu-assets/ 4 | -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/sensu-cli/assets/check.yml: -------------------------------------------------------------------------------- 1 | --- 2 | type: CheckConfig 3 | api_version: core/v2 4 | metadata: 5 | name: check-fake-app 6 | namespace: default 7 | spec: 8 | command: http-check --url http://fake-app:5080/health 9 | subscriptions: 10 | - webserver 11 | handlers: 12 | - sensu-kafka-handler 13 | interval: 5 14 | runtime_assets: 15 | - sensu/http-checks 16 | publish: true 17 | -------------------------------------------------------------------------------- /demos/sensu-kafka-demo/sensu-cli/assets/handler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | type: Handler 3 | api_version: core/v2 4 | metadata: 5 | name: sensu-kafka-handler 6 | namespace: default 7 | spec: 8 | command: sensu-kafka-handler --host broker:9092 --topic sensu-events 9 | type: pipe 10 | runtime_assets: 11 | - sensu/sensu-kafka-handler 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = ansible_rulebook 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/drools_seq.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | sequenceDiagram 3 | Rulebook->>+Drools: Create Async Socket 4 | Rulebook->>+Rulebook:Create Asyncio Task
to wait for async responses 5 | Rulebook->>+Ruleset: Create Ruleset 6 | Ruleset->>+SourceQueue: Create Source Queue 7 | Ruleset->>+Sources: Start Sources with a Source Queue 8 | Ruleset->>+ActionPlanQueue: CreatePlan Queue 9 | Loop Add all rules to Ruleset 10 | Ruleset->>+Rule: Create a Rule 11 | end 12 | Ruleset->>+Drools: Create Ruleset Session 13 | Loop Till Shutdown Event 14 | Sources->>+SourceQueue: Send Events 15 | Ruleset->>+SourceQueue: Read Events 16 | Ruleset->>+Drools: Process Event 17 | Drools->>+Rule: Synchronous response 18 | Rule->>+ActionPlanQueue: Queue Actions
for dispatch 19 | Rulebook-->>+Rulebook: Handle Async Response 20 | Drools--X+Rulebook: Send Async response
with ruleset session and rule 21 | Rulebook--X+Rule: Send Async Response 22 | Rule->>+ActionPlanQueue: Queue Actions
for dispatch 23 | ActionPlanQueue-->>+Ruleset: post event/set fact 24 | Ruleset-->>+Drools: Process Event/Fact 25 | end 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Ansible Rulebook documentation 2 | ========================================= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | introduction 9 | getting_started 10 | installation 11 | development_environment 12 | contributing 13 | usage 14 | rulebooks 15 | rules 16 | conditions 17 | events_and_facts 18 | variables 19 | host_limit 20 | multi_events 21 | actions 22 | sources 23 | filters 24 | runner 25 | collections 26 | decision_environment 27 | 28 | Indices and tables 29 | ================== 30 | * :ref:`modindex` 31 | * :ref:`search` 32 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=ansible_rulebook 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Some tips for documentation development 2 | 3 | - Ensure your dependencies 4 | 5 | ``` 6 | pip install -r requirements_dev.txt 7 | ``` 8 | 9 | - Simulate a hot-reload web server 10 | 11 | ``` 12 | # install when-changed 13 | pip install git+https://github.com/joh/when-changed 14 | 15 | # rebuild with every change 16 | cd docs 17 | when-changed . -c "make clean && make html" 18 | 19 | # spin up a basic http server 20 | cd _build/html 21 | python -m http.server 3000 22 | 23 | # or install sphinx-autobuild 24 | pip install sphinx-autobuild 25 | 26 | # monitor it and auto-rebuild with every change 27 | sphinx-autobuild docs _build/html --port 3000 28 | ``` 29 | 30 | - nice RST extension for vscode: 31 | 32 | 33 | ## Some useful links 34 | 35 | - 36 | - 37 | - 38 | - 39 | - 40 | - 41 | - 42 | - 43 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # Docs 2 | sphinx 3 | sphinx-ansible-theme == 0.10.1 4 | pyyaml 5 | -------------------------------------------------------------------------------- /docs/runner.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Runner 3 | ====== 4 | 5 | 6 | 7 | Work in progress 8 | -------------------------------------------------------------------------------- /minimal-decision-environment.yml: -------------------------------------------------------------------------------- 1 | version: 3 2 | 3 | images: 4 | base_image: 5 | name: 'registry.access.redhat.com/ubi9/python-311:latest' 6 | 7 | dependencies: 8 | galaxy: 9 | collections: 10 | - ansible.eda 11 | python: 12 | - azure-servicebus 13 | - aiobotocore 14 | - aiohttp 15 | - aiokafka 16 | - gssapi 17 | - watchdog 18 | - systemd-python 19 | - dpath 20 | - ansible-rulebook 21 | ansible_core: 22 | package_pip: ansible-core~=2.16.0 23 | ansible_runner: 24 | package_pip: ansible-runner 25 | system: 26 | - java-17-openjdk-devel [platform:rpm] 27 | -------------------------------------------------------------------------------- /performance_test/100M_event_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "100M event rules" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 100000000 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /performance_test/100k_event_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "10k event rules" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 100000 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /performance_test/10M_event_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "10M event rules" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 10000000 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /performance_test/10k_event_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "10k event rules" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 10000 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /performance_test/1M_event_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "1M event rules" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 1000000 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /performance_test/1k_event_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "1k event rules" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 1000 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /performance_test/hello_playbook1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: A playbook 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: 7 | debug: 8 | msg: 'Hello event {{ansible_eda.event}}' 9 | ... 10 | -------------------------------------------------------------------------------- /performance_test/inventory1.yml: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | localhost0: 4 | ansible_host: localhost 5 | 6 | -------------------------------------------------------------------------------- /performance_test/inventory10.yml: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | localhost0: 4 | ansible_host: localhost 5 | localhost1: 6 | ansible_host: localhost 7 | localhost2: 8 | ansible_host: localhost 9 | localhost3: 10 | ansible_host: localhost 11 | localhost4: 12 | ansible_host: localhost 13 | localhost5: 14 | ansible_host: localhost 15 | localhost6: 16 | ansible_host: localhost 17 | localhost7: 18 | ansible_host: localhost 19 | localhost8: 20 | ansible_host: localhost 21 | localhost9: 22 | ansible_host: localhost 23 | 24 | -------------------------------------------------------------------------------- /performance_test/null_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Null rules" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /performance_test/rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Host Rules Perf 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | fact: 14 | j: 1 15 | - name: r2 16 | condition: event.i == 2 17 | action: 18 | run_playbook: 19 | name: ./hello_playbook1.yml 20 | - name: r3 21 | condition: event.i == 3 22 | action: 23 | retract_fact: 24 | fact: 25 | j: 3 26 | - name: r4 27 | condition: event.i == 4 28 | action: 29 | post_event: 30 | event: 31 | j: 4 32 | - name: r5 33 | condition: event.j is defined 34 | action: 35 | none: 36 | -------------------------------------------------------------------------------- /performance_test/run_perf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ulimit -Sn 10000 3 | #Argtest 4 | # 5 | 6 | now=$(date +"%Y%m%d%H%M%S") 7 | 8 | ./perf_test.py "perf${now}.csv" x x x x --only-header 9 | for t in rules.yml null_rules.yml 1k_event_rules.yml 10k_event_rules.yml 100k_event_rules.yml 1M_event_rules.yml 10M_event_rules.yml 100M_event_rules.yml 10 | do 11 | for n in 1 10 100 12 | do 13 | ./perf_test.py "perf${now}.csv" "ansible-rulebook -i inventory${n}.yml -S sources --rulebook ${t}" ansible-rulebook ${t} ${n} --no-header 14 | done 15 | done 16 | -------------------------------------------------------------------------------- /performance_test/sources/range.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import asyncio 16 | from typing import Any, Dict 17 | 18 | 19 | async def main(queue: asyncio.Queue, args: Dict[str, Any]): 20 | delay = args.get("delay", 0) 21 | 22 | for i in range(int(args["limit"])): 23 | await queue.put(dict(i=i)) 24 | await asyncio.sleep(delay) 25 | 26 | 27 | if __name__ == "__main__": 28 | 29 | class MockQueue: 30 | async def put(self, event): 31 | print(event) 32 | 33 | asyncio.run(main(MockQueue(), dict(limit=5))) 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64", "setuptools_scm"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.setuptools_scm] 6 | version_scheme = "release-branch-semver" 7 | 8 | 9 | [tool.black] 10 | line-length = 79 11 | target-version = ["py39", "py310"] 12 | extend-exclude = "docs" 13 | 14 | [tool.isort] 15 | profile = "black" 16 | combine_as_imports = true 17 | line_length = 79 18 | extend_skip = ["docs"] 19 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | asyncio_mode = strict 3 | log_file_level = INFO 4 | log_cli = true 5 | log_format = %(asctime)s %(levelname)s %(message)s 6 | log_date_format = %Y-%m-%d %H:%M:%S 7 | markers = 8 | e2e: mark for end to end tests 9 | temporal: mark for time based tests 10 | long_run: mark for long running tests 11 | vcr: required by tests that use pytest-vcr 12 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements_test.txt 2 | -r requirements_lint.txt 3 | -r docs/requirements.txt 4 | 5 | setuptools_scm 6 | 7 | # Packaging / distribution 8 | twine 9 | build 10 | 11 | # Testing 12 | tox 13 | coverage 14 | 15 | # For building DEs 16 | ansible-builder 17 | 18 | pre-commit 19 | -------------------------------------------------------------------------------- /requirements_lint.txt: -------------------------------------------------------------------------------- 1 | flake8==5.0.4 2 | flake8-bugbear 3 | isort 4 | black==22.12.0 5 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | watchdog 2 | docopt 3 | psutil 4 | requests 5 | ansible 6 | ansible-core~=2.16.0; python_version >= "3.11" 7 | ansible-core~=2.15.0; python_version < "3.11" 8 | ansible-runner 9 | jmespath 10 | 11 | pytest 12 | pytest-asyncio 13 | pytest-timeout 14 | pytest-xdist 15 | pytest-cov 16 | pytest-check 17 | marshmallow==3.26.1 18 | pytest-jira 19 | dynaconf==3.1.11 20 | freezegun 21 | oauthlib>=3.2.0 22 | kubernetes 23 | urllib3<2 24 | aioresponses 25 | httpie 26 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # Complete documentation with many more options at: 2 | # https://docs.sonarqube.org/latest/analysis/analysis-parameters/ 3 | 4 | ## The unique project identifier. This is mandatory. 5 | # Do not duplicate or reuse! 6 | # Available characters: [a-zA-Z0-9_:\.\-] 7 | # Must have least one non-digit. 8 | # Recommended format: : 9 | sonar.projectKey=ansible_ansible-rulebook 10 | 11 | sonar.organization=ansible 12 | 13 | # Customize what paths to scan. Default is . 14 | sonar.sources=. 15 | 16 | # Verbose name of project displayed in WUI. Default is set to the projectKey. This field is optional. 17 | sonar.projectName=ansible-rulebook 18 | 19 | sonar.issue.ignore.multicriteria=e1 20 | # Ignore "should be a variable" 21 | #sonar.issue.ignore.multicriteria.e1.ruleKey=python:S1192 22 | sonar.issue.ignore.multicriteria.e1.resourceKey=**/action/* 23 | 24 | # Only scan with python3 25 | sonar.python.version=3.9,3.10,3.11,3.12 26 | 27 | # Ignore code dupe for premature code reuse recommened in PR 28 | # https://github.com/ansible/ansible-rulebook/pull/537#issuecomment-1598883529 29 | sonar.cpd.exclusions=**/action/* 30 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unit test package for ansible_rulebook.""" 16 | -------------------------------------------------------------------------------- /tests/asts/01_noop.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 01 No operation 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: none 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/02_debug.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 02 Debug 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/03_print_event.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 03 Print event 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: print_event 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/04_set_fact.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 04 Assert Fact 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: set_fact 10 | action_args: 11 | fact: 12 | msg: hello world 13 | condition: 14 | AllCondition: 15 | - EqualsExpression: 16 | lhs: 17 | Event: i 18 | rhs: 19 | Integer: 1 20 | enabled: true 21 | name: r1 22 | - Rule: 23 | action: 24 | Action: 25 | action: print_event 26 | action_args: {} 27 | condition: 28 | AllCondition: 29 | - EqualsExpression: 30 | lhs: 31 | Event: msg 32 | rhs: 33 | String: hello world 34 | enabled: true 35 | name: r2 36 | sources: 37 | - EventSource: 38 | name: range 39 | source_args: 40 | limit: 5 41 | source_filters: [] 42 | source_name: range 43 | -------------------------------------------------------------------------------- /tests/asts/05_post_event.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 05 Post event 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: post_event 10 | action_args: 11 | event: 12 | msg: hello world 13 | condition: 14 | AllCondition: 15 | - EqualsExpression: 16 | lhs: 17 | Event: i 18 | rhs: 19 | Integer: 1 20 | enabled: true 21 | name: r1 22 | - Rule: 23 | action: 24 | Action: 25 | action: print_event 26 | action_args: {} 27 | condition: 28 | AllCondition: 29 | - EqualsExpression: 30 | lhs: 31 | Event: msg 32 | rhs: 33 | String: hello world 34 | enabled: true 35 | name: r2 36 | sources: 37 | - EventSource: 38 | name: range 39 | source_args: 40 | limit: 5 41 | source_filters: [] 42 | source_name: range 43 | -------------------------------------------------------------------------------- /tests/asts/07_and.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 07 And 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - AndExpression: 14 | lhs: 15 | EqualsExpression: 16 | lhs: 17 | Event: nested.i 18 | rhs: 19 | Integer: 1 20 | rhs: 21 | EqualsExpression: 22 | lhs: 23 | Event: nested.j 24 | rhs: 25 | Integer: 1 26 | enabled: true 27 | name: r1 28 | sources: 29 | - EventSource: 30 | name: nested 31 | source_args: 32 | i_limit: 5 33 | j_limit: 5 34 | source_filters: [] 35 | source_name: nested 36 | -------------------------------------------------------------------------------- /tests/asts/08_or.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 08 Or 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - OrExpression: 14 | lhs: 15 | EqualsExpression: 16 | lhs: 17 | Event: nested.i 18 | rhs: 19 | Integer: 1 20 | rhs: 21 | EqualsExpression: 22 | lhs: 23 | Event: nested.j 24 | rhs: 25 | Integer: 1 26 | enabled: true 27 | name: r1 28 | sources: 29 | - EventSource: 30 | name: nested 31 | source_args: 32 | i_limit: 5 33 | j_limit: 5 34 | source_filters: [] 35 | source_name: nested 36 | -------------------------------------------------------------------------------- /tests/asts/09_gt.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 09 Greater Than 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - GreaterThanExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 2 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/10_lt.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 10 Less Than 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - LessThanExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 2 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/11_le.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 11 Less Than Or Equal To 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - LessThanOrEqualToExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 2 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/12_ge.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 09 Greater Than or Equal To 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - GreaterThanOrEqualToExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 2 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/13_add.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 09 Add 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: nested.i 16 | rhs: 17 | AdditionExpression: 18 | lhs: 19 | Event: nested.j 20 | rhs: 21 | Integer: 1 22 | enabled: true 23 | name: r1 24 | sources: 25 | - EventSource: 26 | name: nested 27 | source_args: 28 | i_limit: 5 29 | j_limit: 5 30 | source_filters: [] 31 | source_name: nested 32 | -------------------------------------------------------------------------------- /tests/asts/14_sub.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 14 Subtract 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: nested.i 16 | rhs: 17 | SubtractionExpression: 18 | lhs: 19 | Event: nested.j 20 | rhs: 21 | Integer: 1 22 | enabled: true 23 | name: r1 24 | sources: 25 | - EventSource: 26 | name: nested 27 | source_args: 28 | i_limit: 5 29 | j_limit: 5 30 | source_filters: [] 31 | source_name: nested 32 | -------------------------------------------------------------------------------- /tests/asts/15_multiple_events_all.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 15 multiple events all 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: nested.i 16 | rhs: 17 | Integer: 1 18 | - EqualsExpression: 19 | lhs: 20 | Event: nested.j 21 | rhs: 22 | Integer: 1 23 | enabled: true 24 | name: r1 25 | sources: 26 | - EventSource: 27 | name: nested 28 | source_args: 29 | i_limit: 5 30 | j_limit: 5 31 | source_filters: [] 32 | source_name: nested 33 | -------------------------------------------------------------------------------- /tests/asts/16_multiple_events_any.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 16 multiple events any 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AnyCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: nested.i 16 | rhs: 17 | Integer: 1 18 | - EqualsExpression: 19 | lhs: 20 | Event: nested.j 21 | rhs: 22 | Integer: 1 23 | enabled: true 24 | name: r1 25 | sources: 26 | - EventSource: 27 | name: nested 28 | source_args: 29 | i_limit: 5 30 | j_limit: 5 31 | source_filters: [] 32 | source_name: nested 33 | -------------------------------------------------------------------------------- /tests/asts/17_multiple_sources_any.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 17 multiple sources 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AnyCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | - EqualsExpression: 19 | lhs: 20 | Event: range2.i 21 | rhs: 22 | Integer: 1 23 | enabled: true 24 | name: r1 25 | sources: 26 | - EventSource: 27 | name: range 28 | source_args: 29 | limit: 5 30 | source_filters: [] 31 | source_name: range 32 | - EventSource: 33 | name: range2 34 | source_args: 35 | limit: 5 36 | source_filters: [] 37 | source_name: range2 38 | -------------------------------------------------------------------------------- /tests/asts/18_multiple_sources_all.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 18 multiple sources all 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | - EqualsExpression: 19 | lhs: 20 | Event: range2.i 21 | rhs: 22 | Integer: 1 23 | enabled: true 24 | name: r1 25 | sources: 26 | - EventSource: 27 | name: range 28 | source_args: 29 | limit: 5 30 | source_filters: [] 31 | source_name: range 32 | - EventSource: 33 | name: range2 34 | source_args: 35 | limit: 5 36 | source_filters: [] 37 | source_name: range2 38 | -------------------------------------------------------------------------------- /tests/asts/19_is_defined.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 19 is defined 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: set_fact 10 | action_args: 11 | fact: 12 | msg: hello 13 | condition: 14 | AllCondition: 15 | - EqualsExpression: 16 | lhs: 17 | Event: i 18 | rhs: 19 | Integer: 1 20 | enabled: true 21 | name: r1 22 | - Rule: 23 | action: 24 | Action: 25 | action: debug 26 | action_args: {} 27 | condition: 28 | AllCondition: 29 | - IsDefinedExpression: 30 | Event: msg 31 | enabled: true 32 | name: r2 33 | - Rule: 34 | action: 35 | Action: 36 | action: print_event 37 | action_args: 38 | pretty: true 39 | condition: 40 | AllCondition: 41 | - IsDefinedExpression: 42 | Event: payload 43 | enabled: true 44 | name: r3 45 | sources: 46 | - EventSource: 47 | name: range 48 | source_args: 49 | limit: 5 50 | source_filters: [] 51 | source_name: range 52 | -------------------------------------------------------------------------------- /tests/asts/20_is_not_defined.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 20 is not defined 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: set_fact 10 | action_args: 11 | fact: 12 | msg: hello 13 | condition: 14 | AllCondition: 15 | - EqualsExpression: 16 | lhs: 17 | Event: i 18 | rhs: 19 | Integer: 1 20 | enabled: true 21 | name: r1 22 | - Rule: 23 | action: 24 | Action: 25 | action: retract_fact 26 | action_args: 27 | fact: 28 | msg: hello 29 | condition: 30 | AllCondition: 31 | - IsDefinedExpression: 32 | Event: msg 33 | enabled: true 34 | name: r2 35 | - Rule: 36 | action: 37 | Action: 38 | action: debug 39 | action_args: {} 40 | condition: 41 | AllCondition: 42 | - IsNotDefinedExpression: 43 | Event: msg 44 | enabled: true 45 | name: r3 46 | sources: 47 | - EventSource: 48 | name: range 49 | source_args: 50 | limit: 5 51 | source_filters: [] 52 | source_name: range 53 | -------------------------------------------------------------------------------- /tests/asts/21_run_playbook.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 21 run playbook 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: run_playbook 10 | action_args: 11 | name: playbooks/hello.yml 12 | condition: 13 | AllCondition: 14 | - EqualsExpression: 15 | lhs: 16 | Event: i 17 | rhs: 18 | Integer: 1 19 | enabled: true 20 | name: r1 21 | sources: 22 | - EventSource: 23 | name: range 24 | source_args: 25 | limit: 5 26 | source_filters: [] 27 | source_name: range 28 | -------------------------------------------------------------------------------- /tests/asts/23_nested_data.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 23 run playbook 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: root.nested.i 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: replay 23 | source_args: 24 | directory: examples/replays/23_nested_data 25 | source_filters: [] 26 | source_name: replay 27 | -------------------------------------------------------------------------------- /tests/asts/24_max_attributes.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 24 max attributes 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: attr_1 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: replay 23 | source_args: 24 | directory: examples/replays/24_max_attributes 25 | source_filters: [] 26 | source_name: replay 27 | -------------------------------------------------------------------------------- /tests/asts/25_max_attributes_nested.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 25 max attributes nested 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: branch_0.branch_0.branch_0.leaf_1 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: replay 23 | source_args: 24 | directory: examples/replays/25_max_attributes_nested 25 | source_filters: [] 26 | source_name: replay 27 | -------------------------------------------------------------------------------- /tests/asts/26_print_events.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 26 Print events 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: print_event 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | - EqualsExpression: 19 | lhs: 20 | Event: i 21 | rhs: 22 | Integer: 2 23 | enabled: true 24 | name: r1 25 | sources: 26 | - EventSource: 27 | name: range 28 | source_args: 29 | limit: 5 30 | source_filters: [] 31 | source_name: range 32 | -------------------------------------------------------------------------------- /tests/asts/27_var_root.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 27 multiple events all with var_root 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: print_event 10 | action_args: 11 | var_root: 12 | kafka.message: kafka 13 | webhook.payload: webhook 14 | condition: 15 | AllCondition: 16 | - AssignmentExpression: 17 | lhs: 18 | Events: webhook 19 | rhs: 20 | EqualsExpression: 21 | lhs: 22 | Event: webhook.payload.url 23 | rhs: 24 | String: http://www.example.com 25 | - AssignmentExpression: 26 | lhs: 27 | Events: kafka 28 | rhs: 29 | EqualsExpression: 30 | lhs: 31 | Event: kafka.message.channel 32 | rhs: 33 | String: red 34 | enabled: true 35 | name: r1 36 | sources: 37 | - EventSource: 38 | name: non_existent 39 | source_args: {} 40 | source_filters: [] 41 | source_name: non_existent 42 | -------------------------------------------------------------------------------- /tests/asts/28_right_side_condition_template.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 28 test jinja templating on the right side of the condition 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - AssignmentExpression: 14 | lhs: 15 | Facts: first 16 | rhs: 17 | IsDefinedExpression: 18 | Fact: custom.expected_index 19 | - EqualsExpression: 20 | lhs: 21 | Event: i 22 | rhs: 23 | Facts: first.custom.expected_index 24 | enabled: true 25 | name: r1 26 | sources: 27 | - EventSource: 28 | name: range 29 | source_args: 30 | limit: 5 31 | source_filters: [] 32 | source_name: range 33 | -------------------------------------------------------------------------------- /tests/asts/29_run_module.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 29 run module 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: run_module 10 | action_args: 11 | module_args: 12 | name: Fred Flintstone 13 | name: ansible.eda.upcase 14 | condition: 15 | AllCondition: 16 | - EqualsExpression: 17 | lhs: 18 | Event: i 19 | rhs: 20 | Integer: 1 21 | enabled: true 22 | name: r1 23 | sources: 24 | - EventSource: 25 | name: range 26 | source_args: 27 | limit: 5 28 | source_filters: [] 29 | source_name: range 30 | -------------------------------------------------------------------------------- /tests/asts/30_run_module_missing.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 30 run module missing 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: run_module 10 | action_args: 11 | module_args: 12 | name: fred 13 | name: ansible.eda.upcase2 14 | condition: 15 | AllCondition: 16 | - EqualsExpression: 17 | lhs: 18 | Event: i 19 | rhs: 20 | Integer: 1 21 | enabled: true 22 | name: r1 23 | sources: 24 | - EventSource: 25 | name: range 26 | source_args: 27 | limit: 5 28 | source_filters: [] 29 | source_name: range 30 | -------------------------------------------------------------------------------- /tests/asts/31_run_module_missing_args.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 31 run module missing args 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: run_module 10 | action_args: 11 | module_args: 12 | un_name: fred 13 | name: ansible.eda.upcase 14 | condition: 15 | AllCondition: 16 | - EqualsExpression: 17 | lhs: 18 | Event: i 19 | rhs: 20 | Integer: 1 21 | enabled: true 22 | name: r1 23 | sources: 24 | - EventSource: 25 | name: range 26 | source_args: 27 | limit: 5 28 | source_filters: [] 29 | source_name: range 30 | -------------------------------------------------------------------------------- /tests/asts/32_run_module_fail.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 32 run module fail 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: run_module 10 | action_args: 11 | module_args: 12 | name: fail 13 | name: ansible.eda.upcase 14 | retry: true 15 | condition: 16 | AllCondition: 17 | - EqualsExpression: 18 | lhs: 19 | Event: i 20 | rhs: 21 | Integer: 1 22 | enabled: true 23 | name: r1 24 | sources: 25 | - EventSource: 26 | name: range 27 | source_args: 28 | limit: 5 29 | source_filters: [] 30 | source_name: range 31 | -------------------------------------------------------------------------------- /tests/asts/33_run_playbook_retry.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 33 run playbook and retry after an interval 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: run_playbook 10 | action_args: 11 | delay: 1 12 | name: playbooks/fail_and_succeed.yml 13 | retry: true 14 | condition: 15 | AllCondition: 16 | - EqualsExpression: 17 | lhs: 18 | Event: i 19 | rhs: 20 | Integer: 1 21 | enabled: true 22 | name: r1 23 | sources: 24 | - EventSource: 25 | name: range 26 | source_args: 27 | limit: 5 28 | source_filters: [] 29 | source_name: range 30 | -------------------------------------------------------------------------------- /tests/asts/34_run_playbook_retries.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: 34 run playbook and retry a number of times 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: run_playbook 10 | action_args: 11 | name: playbooks/fail_and_succeed.yml 12 | retries: 1 13 | condition: 14 | AllCondition: 15 | - EqualsExpression: 16 | lhs: 17 | Event: i 18 | rhs: 19 | Integer: 1 20 | enabled: true 21 | name: r1 22 | sources: 23 | - EventSource: 24 | name: range 25 | source_args: 26 | limit: 5 27 | source_filters: [] 28 | source_name: range 29 | -------------------------------------------------------------------------------- /tests/asts/37_hosts_facts.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: Host facts 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Fact: meta.hosts 16 | rhs: 17 | String: localhost 18 | - EqualsExpression: 19 | lhs: 20 | Event: i 21 | rhs: 22 | Integer: 1 23 | enabled: true 24 | name: Host 1 rule 25 | - Rule: 26 | action: 27 | Action: 28 | action: debug 29 | action_args: {} 30 | condition: 31 | AllCondition: 32 | - EqualsExpression: 33 | lhs: 34 | Fact: os 35 | rhs: 36 | String: linux 37 | - EqualsExpression: 38 | lhs: 39 | Event: i 40 | rhs: 41 | Integer: 4 42 | enabled: true 43 | name: Host 2 rule 44 | sources: 45 | - EventSource: 46 | name: range 47 | source_args: 48 | limit: 5 49 | source_filters: [] 50 | source_name: range 51 | -------------------------------------------------------------------------------- /tests/asts/38_shutdown.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: Test shutdown action 5 | rules: 6 | - Rule: 7 | action: 8 | Action: 9 | action: shutdown 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: Host 1 rule 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/rules_with_assignment.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - localhost 4 | name: Demo rules with assignment 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: 11 | var: events.first 12 | condition: 13 | AllCondition: 14 | - AssignmentExpression: 15 | lhs: 16 | Events: first 17 | rhs: 18 | EqualsExpression: 19 | lhs: 20 | Event: i 21 | rhs: 22 | Integer: 0 23 | enabled: true 24 | name: assignment 25 | sources: 26 | - EventSource: 27 | name: range 28 | source_args: 29 | limit: 5 30 | source_filters: [] 31 | source_name: range 32 | -------------------------------------------------------------------------------- /tests/asts/rules_with_assignment2.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - localhost 4 | name: Demo rules with assignment2 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: 11 | var: events.first 12 | condition: 13 | AllCondition: 14 | - AssignmentExpression: 15 | lhs: 16 | Facts: first 17 | rhs: 18 | EqualsExpression: 19 | lhs: 20 | Fact: i 21 | rhs: 22 | Integer: 0 23 | enabled: true 24 | name: assignment 25 | sources: 26 | - EventSource: 27 | name: range 28 | source_args: 29 | limit: 5 30 | source_filters: [] 31 | source_name: range 32 | -------------------------------------------------------------------------------- /tests/asts/rules_with_multiple_conditions.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - localhost 4 | name: Demo rules multiple conditions any 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AnyCondition: 13 | - AssignmentExpression: 14 | lhs: 15 | Events: event 16 | rhs: 17 | EqualsExpression: 18 | lhs: 19 | Event: i 20 | rhs: 21 | Integer: 0 22 | - AssignmentExpression: 23 | lhs: 24 | Events: event 25 | rhs: 26 | EqualsExpression: 27 | lhs: 28 | Event: i 29 | rhs: 30 | Integer: 1 31 | enabled: true 32 | name: multiple conditions 33 | sources: 34 | - EventSource: 35 | name: range 36 | source_args: 37 | limit: 5 38 | source_filters: [] 39 | source_name: range 40 | -------------------------------------------------------------------------------- /tests/asts/rules_with_multiple_conditions2.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - localhost 4 | name: Demo rules multiple conditions all 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - AssignmentExpression: 14 | lhs: 15 | Events: first 16 | rhs: 17 | EqualsExpression: 18 | lhs: 19 | Event: i 20 | rhs: 21 | Integer: 0 22 | - AssignmentExpression: 23 | lhs: 24 | Events: second 25 | rhs: 26 | EqualsExpression: 27 | lhs: 28 | Event: i 29 | rhs: 30 | Integer: 1 31 | enabled: true 32 | name: multiple conditions 33 | sources: 34 | - EventSource: 35 | name: range 36 | source_args: 37 | limit: 5 38 | source_filters: [] 39 | source_name: range 40 | -------------------------------------------------------------------------------- /tests/asts/rules_with_vars.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - localhost 4 | name: Rules with vars 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - AssignmentExpression: 14 | lhs: 15 | Events: first 16 | rhs: 17 | EqualsExpression: 18 | lhs: 19 | Event: i 20 | rhs: 21 | Integer: 0 22 | - AssignmentExpression: 23 | lhs: 24 | Events: second 25 | rhs: 26 | EqualsExpression: 27 | lhs: 28 | Event: i 29 | rhs: 30 | Integer: 1 31 | enabled: true 32 | name: multiple conditions 33 | sources: 34 | - EventSource: 35 | name: range 36 | source_args: 37 | limit: '{{limit}}' 38 | source_filters: [] 39 | source_name: range 40 | -------------------------------------------------------------------------------- /tests/asts/rules_without_assignment.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - localhost 4 | name: Demo rules multiple conditions all 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: 11 | event: '{{event}}' 12 | condition: 13 | AllCondition: 14 | - EqualsExpression: 15 | lhs: 16 | Fact: i 17 | rhs: 18 | Integer: 0 19 | enabled: true 20 | name: assignment 21 | sources: 22 | - EventSource: 23 | name: range 24 | source_args: 25 | limit: 5 26 | source_filters: [] 27 | source_name: range 28 | -------------------------------------------------------------------------------- /tests/asts/test_rules_multiple_hosts2.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: Test rules multiple hosts 2 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/asts/test_rules_multiple_hosts3.yml: -------------------------------------------------------------------------------- 1 | - RuleSet: 2 | hosts: 3 | - all 4 | name: Test rules multiple hosts 3 5 | rules: 6 | - Rule: 7 | actions: 8 | - Action: 9 | action: debug 10 | action_args: {} 11 | condition: 12 | AllCondition: 13 | - EqualsExpression: 14 | lhs: 15 | Event: i 16 | rhs: 17 | Integer: 1 18 | enabled: true 19 | name: r1 20 | sources: 21 | - EventSource: 22 | name: range 23 | source_args: 24 | limit: 5 25 | source_filters: [] 26 | source_name: range 27 | -------------------------------------------------------------------------------- /tests/data/bad_source.py: -------------------------------------------------------------------------------- 1 | print("This is a bad source plugin") 2 | -------------------------------------------------------------------------------- /tests/data/not_asyncio.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Any, Dict 3 | 4 | 5 | def main(queue: asyncio.Queue, args: Dict[str, Any]): 6 | print("Not asyncio should fail") 7 | -------------------------------------------------------------------------------- /tests/data/rulebook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Sample Rulebook 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 0 11 | action: 12 | debug: 13 | - name: r2 14 | condition: event.i == 1 15 | action: 16 | debug: 17 | msg: "This is a sample message with {{ event.i }}" 18 | - name: r3 19 | condition: event.i == 2 20 | action: 21 | debug: 22 | msg: 23 | - "Hello World {{ event }}" 24 | - "Hello Java" 25 | - "Hello Java again {{ event }}" 26 | - name: r4 27 | condition: event.i == 3 28 | action: 29 | debug: 30 | var: event.does_not_exist 31 | - name: r5 32 | condition: event.i == 4 33 | action: 34 | debug: 35 | var: event.i 36 | -------------------------------------------------------------------------------- /tests/data/test_cert.pem: -------------------------------------------------------------------------------- 1 | This is a bogus certfile 2 | -------------------------------------------------------------------------------- /tests/data/test_env.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ENV1: abc 3 | ENV2: xyz 4 | -------------------------------------------------------------------------------- /tests/data/test_key.pem: -------------------------------------------------------------------------------- 1 | This is a bogus keyfile 2 | -------------------------------------------------------------------------------- /tests/data/test_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | person: 3 | name: Fred 4 | age: 42 5 | town: Bedrock 6 | employed: True 7 | -------------------------------------------------------------------------------- /tests/e2e/README.md: -------------------------------------------------------------------------------- 1 | # E2E test suite for ansible-rulebook 2 | 3 | ## Install 4 | 5 | *Requirements*: A working installation of ansible-rulebook see [official documentation for more details](https://ansible-rulebook.readthedocs.io/en/latest/installation.html). 6 | 7 | ```sh 8 | git clone git@github.com:ansible/ansible-rulebook.git 9 | pip install -r requirements_test.txt 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```sh 15 | pytest -m e2e -n auto 16 | ``` 17 | 18 | ## Configuration 19 | 20 | Configuration is managed by [dynaconf library](https://www.dynaconf.com/) 21 | 22 | Default configuration is located in `tests/e2e/config/default.yml` 23 | You can use your custom configuration file by setting `EDA_E2E_SETTINGS` environment variable: 24 | 25 | ```sh 26 | export EDA_E2E_SETTINGS=/path/to/your/config.yml 27 | ``` 28 | 29 | You can also override configuration by setting environment variables with the name of the configuration key in uppercase and prefixed by `EDA_E2E_`: 30 | 31 | ```sh 32 | export EDA_E2E_CMD_TIMEOUT=60 33 | ``` 34 | -------------------------------------------------------------------------------- /tests/e2e/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-rulebook/da1bed4a19a08e37eb45394248941156692a304f/tests/e2e/__init__.py -------------------------------------------------------------------------------- /tests/e2e/config/default.yml: -------------------------------------------------------------------------------- 1 | default_event_delay: 1 2 | default_shutdown_after: 5 3 | default_startup_delay: 30 4 | operators_shutdown_after: 1 5 | shutdown_now_startup_delay: 0.5 6 | disabled_rules_event_delay: 0.5 7 | cmd_timeout: 25 8 | controller_url: http://localhost:8080 9 | controller_token: secret 10 | -------------------------------------------------------------------------------- /tests/e2e/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Dict, Optional 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture(scope="function") 8 | def update_environment(): 9 | """Fixture factory to update environment variables 10 | Returns the updated environment variables, 11 | and restores the original environment variables after the test 12 | """ 13 | env_backup = os.environ.copy() 14 | 15 | def _update_environment(env: Optional[Dict] = None) -> Dict: 16 | if env: 17 | os.environ.update(env) 18 | return os.environ 19 | 20 | yield _update_environment 21 | os.environ.clear() 22 | os.environ.update(env_backup) 23 | -------------------------------------------------------------------------------- /tests/e2e/files/extra_vars/operator_variables.yml: -------------------------------------------------------------------------------- 1 | # logical, membership & relational vars 2 | universe_meaning: 42 3 | in_operator_int_array: 4 | - 42 5 | - 2.7182 6 | - 3.1415 7 | - 3 8 | - 2 9 | - 173.035 10 | - 20000000000000000426408380189087937446025157425359298935486676996 # > uint64 11 | - -6000 12 | 13 | in_operator_int_array_b: 14 | - 42 15 | - 2.7182 16 | - 3.1415 17 | - 173.035 18 | - 20000000000000000426408380189087937446025157425359298935486676996 # > uint64 19 | - -6000 20 | 21 | houses: 22 | - Lannister 23 | - Stark 24 | - Targaryen 25 | - Baratheon 26 | 27 | targaryen_motto: fire and blood 28 | 29 | mixed_types: 30 | - 4 31 | - 2.7182 32 | - "Vhagar" 33 | - true 34 | - Stark 35 | 36 | # is select vars 37 | type_asteroid: "asteroid" 38 | has_moons: true 39 | halleys_radius: 11 40 | halleys_min_op: 27000.123 41 | 42 | # is selectattr vars 43 | not_planet: false 44 | pluto_radius: 1188.30 45 | pluto_moons: 5 46 | eris_moon: "dysnomia" 47 | total_moons: 48 | - 50 49 | - 6.2e+1 50 | - 79.99 51 | best_moons: 52 | - "deimos" 53 | - "phobos" 54 | -------------------------------------------------------------------------------- /tests/e2e/files/extra_vars/test_debug.yml: -------------------------------------------------------------------------------- 1 | controller_password: dummy 2 | tower_password: dummy 3 | aap_password: dummy 4 | postgres_db_password: dummy 5 | postgres_db_username: admin 6 | controller_username: admin 7 | private_key: dummy 8 | aap_passphrase: dummy 9 | service_nested_list: 10 | - service1_username: admin 11 | service1_password: dummy 12 | service_dict: 13 | service2_username: admin 14 | service2_token: dummy -------------------------------------------------------------------------------- /tests/e2e/files/extra_vars/test_variables_extra_vars.yml: -------------------------------------------------------------------------------- 1 | alarm_location: hobart 2 | alarm_monitoring: enabled 3 | notify_police: true 4 | lockdown_enabled: true 5 | lockdown_threshold: 7.5 6 | zones: [1, 3, 5, 7, 11] 7 | intruder_status: null 8 | -------------------------------------------------------------------------------- /tests/e2e/files/extra_vars/vaulted_variables.yml: -------------------------------------------------------------------------------- 1 | go_var: !vault | 2 | $ANSIBLE_VAULT;1.1;AES256;label2 3 | 32353335346335396166356435383034666639356435323933353136633637306331396234343361 4 | 3631353062306433646264653165393238653464373765610a633336643266383538356263346365 5 | 32306265623832326231613433366532643636636133663639363437633166303261386534663030 6 | 6434323034623338610a373736333365623861646430636562333763633536383562373231663939 7 | 3233 -------------------------------------------------------------------------------- /tests/e2e/files/inventories/default_inventory.ini: -------------------------------------------------------------------------------- 1 | localhost ansible_connection=local 2 | -------------------------------------------------------------------------------- /tests/e2e/files/inventories/default_inventory.yml: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | localhost: 4 | ansible_connection: local 5 | -------------------------------------------------------------------------------- /tests/e2e/files/inventories/inventory_as_dir/group_vars/customgroup.yml: -------------------------------------------------------------------------------- 1 | groupvar: groupvar_value 2 | -------------------------------------------------------------------------------- /tests/e2e/files/inventories/inventory_as_dir/host_vars/localhost.yml: -------------------------------------------------------------------------------- 1 | hostvar: hostvar_value 2 | -------------------------------------------------------------------------------- /tests/e2e/files/inventories/inventory_as_dir/hosts.yml: -------------------------------------------------------------------------------- 1 | customgroup: 2 | hosts: 3 | localhost: 4 | ansible_connection: local 5 | -------------------------------------------------------------------------------- /tests/e2e/files/passwords/pass1.txt: -------------------------------------------------------------------------------- 1 | secret1 -------------------------------------------------------------------------------- /tests/e2e/files/passwords/pass2.txt: -------------------------------------------------------------------------------- 1 | secret2 -------------------------------------------------------------------------------- /tests/e2e/files/passwords/pass3.txt: -------------------------------------------------------------------------------- 1 | secret3 -------------------------------------------------------------------------------- /tests/e2e/files/playbooks/long_running.yml: -------------------------------------------------------------------------------- 1 | - name: A long running playbook 2 | hosts: all 3 | gather_facts: false 4 | vars: 5 | pause_time: "{{ sleep_seconds | default(5) }}" 6 | tasks: 7 | - name: Print sleep message 8 | ansible.builtin.debug: 9 | msg: "Sleeping..." 10 | 11 | - name: Sleep 12 | ansible.builtin.pause: 13 | seconds: "{{ pause_time }}" 14 | 15 | - name: Print wake-up message 16 | ansible.builtin.debug: 17 | msg: "Rise and shine..." 18 | -------------------------------------------------------------------------------- /tests/e2e/files/playbooks/print_event.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | gather_facts: false 3 | tasks: 4 | - name: Print event 5 | when: ansible_eda.event is defined 6 | ansible.builtin.debug: 7 | msg: "Event matched: {{ ansible_eda.event }}" 8 | 9 | - name: Print events 10 | when: ansible_eda.events is defined 11 | ansible.builtin.debug: 12 | msg: "Event matched: {{ ansible_eda.events }}" 13 | -------------------------------------------------------------------------------- /tests/e2e/files/playbooks/print_group_vars.yml: -------------------------------------------------------------------------------- 1 | - hosts: customgroup 2 | gather_facts: false 3 | tasks: 4 | - name: Show vars 5 | ansible.builtin.debug: 6 | msg: 7 | - "groupvar: {{ groupvar }}" 8 | - "hostvar: {{ hostvar }}" 9 | -------------------------------------------------------------------------------- /tests/e2e/files/playbooks/print_rule_name.yml: -------------------------------------------------------------------------------- 1 | - name: Print rule name that called this playbook 2 | hosts: all 3 | gather_facts: false 4 | tasks: 5 | - name: Print rule name 6 | when: ansible_eda.rule is defined 7 | ansible.builtin.debug: 8 | msg: "Rule name: {{ ansible_eda.rule }}" 9 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/actions/test_shutdown_now.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ruleset 1 3 | hosts: all 4 | sources: 5 | - generic: 6 | event_delay: "{{ DEFAULT_EVENT_DELAY }}" 7 | payload: 8 | - action: "long_running_playbook" 9 | - action: "send_msg" 10 | 11 | rules: 12 | - name: Start long-running playbook 13 | condition: event.action == "long_running_playbook" 14 | actions: 15 | - debug: 16 | msg: "Sequential action triggered successfully" 17 | - run_playbook: 18 | name: ./playbooks/long_running.yml 19 | extra_vars: 20 | pause_time: 30 21 | 22 | - name: Send message after shutdown 23 | condition: event.action == "send_msg" 24 | action: 25 | debug: 26 | msg: "This condition should not fire" 27 | 28 | 29 | - name: Ruleset 2 30 | hosts: all 31 | sources: 32 | - generic: 33 | startup_delay: "{{ SHUTDOWN_NOW_STARTUP_DELAY }}" 34 | payload: 35 | - action: "trigger_shutdown" 36 | 37 | rules: 38 | - name: Shutdown 39 | condition: event.action == "trigger_shutdown" 40 | action: 41 | shutdown: 42 | kind: now 43 | message: "Shutdown triggered from Ruleset 2" 44 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/hello_events_with_var.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Hello Events with variable 3 | hosts: all 4 | sources: 5 | - generic: 6 | event_delay: "{{ EVENT_DELAY }}" 7 | payload: 8 | - action: "go" 9 | 10 | rules: 11 | - name: Say Hello 12 | condition: event.action == "go" 13 | action: 14 | debug: 15 | msg: Hello there! 16 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/malformed_rulebook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Malformed rulebook 3 | hosts: all 4 | sources: 5 | - range: 6 | rules: 7 | - name: This is poorly formatted 8 | condition: event.i == 1 9 | action: 10 | run_module: 11 | name: ansible.builtin.debug 12 | module_args: 13 | msg: I am invalid 14 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_debug_no_params.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Validate debug action does not output secrets with no params 3 | hosts: all 4 | sources: 5 | - generic: 6 | payload: 7 | - action: "all" 8 | rules: 9 | - name: Validate all variables are masked in debug 10 | condition: event.action == "all" 11 | actions: 12 | - debug: -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_debug_var_and_message.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Validate debug action does not output secrets with no params 3 | hosts: all 4 | sources: 5 | - generic: 6 | payload: 7 | - action: "postgres" 8 | - action: "controller" 9 | - action: "tower" 10 | - action: "msg" 11 | - action: "username" 12 | rules: 13 | - name: Validate variable "postgres" is masked in debug 14 | condition: event.action == "postgres" 15 | actions: 16 | - debug: 17 | var: postgres_db_password 18 | - name: Validate variable "controller" is masked directly in debug 19 | condition: event.action == "controller" 20 | actions: 21 | - debug: 22 | var: controller_password 23 | - name: Validate variable "tower" is masked directly in debug 24 | condition: event.action == "tower" 25 | actions: 26 | - debug: 27 | var: tower_password 28 | - name: Validate variable is masked when specified in msg 29 | condition: event.action == "msg" 30 | actions: 31 | - debug: 32 | msg: "Test message override {{ postgres_db_password }}" 33 | - name: Validate variable is unmasked 34 | condition: event.action == "username" 35 | actions: 36 | - debug: 37 | var: controller_username -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_hot_reload.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ruleset 1 3 | hosts: all 4 | sources: 5 | - generic: 6 | payload: 7 | - action: "value_a" 8 | shutdown_after: 2 9 | rules: 10 | - name: Matching for value_a 11 | condition: event.action == "value_a" 12 | action: 13 | debug: 14 | msg: "Rule 1: I matched for value_a" 15 | - name: Matching for value_b 16 | condition: event.action == "value_b" 17 | action: 18 | debug: 19 | msg: "Rule 2: I have now matched for value_b" 20 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_inventory_as_dir.yml: -------------------------------------------------------------------------------- 1 | - name: Test inventory as dir 2 | hosts: all 3 | sources: 4 | - ansible.eda.generic: 5 | shutdown_after: 2 6 | payload: 7 | motto: winter is coming 8 | rules: 9 | - name: Test rule 10 | condition: event.motto == "winter is coming" 11 | action: 12 | run_playbook: 13 | name: ./playbooks/print_group_vars.yml 14 | copy_files: true 15 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_match_multiple_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test match multiple rules 3 | hosts: all 4 | match_multiple_rules: true 5 | sources: 6 | - name: range 7 | range: 8 | limit: 5 9 | rules: 10 | - name: r1 11 | condition: event.i == 1 12 | action: 13 | debug: 14 | - name: r11 15 | condition: event.i == 1 16 | action: 17 | print_event: 18 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_process_sigint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ruleset 1 3 | hosts: all 4 | sources: 5 | - generic: 6 | loop_count: 10 7 | loop_delay: 1 8 | payload: 9 | - action: "long_loop" 10 | 11 | rules: 12 | - name: Trigger loop 13 | condition: event.action == "long_loop" 14 | action: 15 | print_event: 16 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_process_source_end.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ruleset 1 3 | hosts: all 4 | sources: 5 | - generic: 6 | event_delay: 0.05 7 | payload: 8 | - action: "long_running_playbook" 9 | - action: "send_msg" 10 | - action: "send_msg" 11 | - action: "send_msg" 12 | 13 | rules: 14 | - name: Start long-running playbook 15 | condition: event.action == "long_running_playbook" 16 | actions: 17 | - run_playbook: 18 | name: ./playbooks/long_running.yml 19 | extra_vars: 20 | pause_time: 3 21 | 22 | - name: Send message in parallel 23 | condition: event.action == "send_msg" 24 | action: 25 | debug: 26 | msg: "Parallel action triggered successfully" 27 | 28 | 29 | - name: Ruleset 2 30 | hosts: all 31 | sources: 32 | - generic: 33 | loop_count: -1 # source is in an infinite loop 34 | loop_delay: 1 35 | payload: 36 | - action: "infinite_loop" 37 | 38 | rules: 39 | - name: Trigger loop 40 | condition: event.action == "infinite_loop" 41 | action: 42 | none: 43 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_source_stacktrace.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Raise exception midst processing 3 | hosts: localhost 4 | sources: 5 | - name: Raise Exception 6 | fail_after: 7 | limit: "{{ LIMIT | default(10) }}" 8 | after: "{{ FAIL_AFTER | default(4) }}" 9 | rules: 10 | - name: Simple Rule 11 | condition: true 12 | action: 13 | debug: 14 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_vaulted_rulebook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Hello Events with variable 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - action: "{{ go_var }}" 8 | - alert: 9 | level: warning 10 | message: !vault | 11 | $ANSIBLE_VAULT;1.1;AES256 12 | 30366463666430383031623536623666326633636437393337383731366262313231653830393865 13 | 3463376431303861383933303331323432346333656231330a623031383537623930316162346336 14 | 36363539303762306262363938653765323031316361313834336335326662363131303866643037 15 | 3239336434323230310a393163373636393163653764386632653965363537353939366631326133 16 | 3534 17 | 18 | rules: 19 | - name: Say Hello 20 | condition: event.action == "go" 21 | action: 22 | debug: 23 | msg: !vault | 24 | $ANSIBLE_VAULT;1.1;AES256 25 | 63306363613865633762616535386364653738363839623239373463343164633230343165643730 26 | 6266323465326638653964313932303164623639393736310a643666643532613262353735616532 27 | 39666662386364313739623732616333316536343762306463353036353335653537656537373763 28 | 3032623464633265610a333531343830373134366466366262313637623139373135383161363366 29 | 3732 30 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_vaulted_rulebook_interpolate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Hello Events with string interpolation 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - action: "pay and {{ go_var }}" 8 | - alert: 9 | level: warning 10 | message: !vault | 11 | $ANSIBLE_VAULT;1.1;AES256 12 | 30366463666430383031623536623666326633636437393337383731366262313231653830393865 13 | 3463376431303861383933303331323432346333656231330a623031383537623930316162346336 14 | 36363539303762306262363938653765323031316361313834336335326662363131303866643037 15 | 3239336434323230310a393163373636393163653764386632653965363537353939366631326133 16 | 3534 17 | 18 | rules: 19 | - name: Say Hello 20 | condition: event.action == "pay and go" 21 | action: 22 | debug: 23 | msg: !vault | 24 | $ANSIBLE_VAULT;1.1;AES256 25 | 63306363613865633762616535386364653738363839623239373463343164633230343165643730 26 | 6266323465326638653964313932303164623639393736310a643666643532613262353735616532 27 | 39666662386364313739623732616333316536343762306463353036353335653537656537373763 28 | 3032623464633265610a333531343830373134366466366262313637623139373135383161363366 29 | 3732 30 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/test_vaulted_v2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Hello Events with variable 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - action: go 8 | rules: 9 | - name: Say Hello 10 | condition: event.action == "go" 11 | action: 12 | debug: 13 | msg: !vault | 14 | $ANSIBLE_VAULT;1.1;AES256 15 | 63306363613865633762616535386364653738363839623239373463343164633230343165643730 16 | 6266323465326638653964313932303164623639393736310a643666643532613262353735616532 17 | 39666662386364313739623732616333316536343762306463353036353335653537656537373763 18 | 3032623464633265610a333531343830373134366466366262313637623139373135383161363366 19 | 3732 20 | -------------------------------------------------------------------------------- /tests/e2e/files/rulebooks/websockets/test_websocket_range.yml: -------------------------------------------------------------------------------- 1 | - name: Test websocket range events 2 | hosts: all 3 | sources: 4 | - name: Generate a range 5 | ansible.eda.range: 6 | limit: 2000 7 | rules: 8 | - name: match the event 9 | condition: event.i == 700 10 | action: 11 | run_playbook: 12 | name: ./playbooks/print_event.yml 13 | -------------------------------------------------------------------------------- /tests/e2e/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import dynaconf 4 | 5 | HERE = os.path.dirname(os.path.abspath(__file__)) 6 | DEFAULT_CONFIG_PATH = os.path.join(HERE, "config") 7 | DEFAULT_CONFIG_FILE = "default.yml" 8 | 9 | 10 | def get_settings() -> dynaconf.LazySettings: 11 | """ 12 | Get the settings. 13 | """ 14 | return dynaconf.LazySettings( 15 | environments=False, 16 | load_dotenv=True, 17 | envvar="EDA_E2E_SETTINGS", 18 | envvar_prefix="EDA_E2E", 19 | root_path=DEFAULT_CONFIG_PATH, 20 | settings_file=DEFAULT_CONFIG_FILE, 21 | ) 22 | 23 | 24 | SETTINGS = get_settings() 25 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | # Disable warnings about localhost 3 | localhost_warning = False 4 | 5 | # Disable warnings about unparsed inventory 6 | [inventory] 7 | inventory_unparsed_warning = False 8 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/create-cluster.yml: -------------------------------------------------------------------------------- 1 | - name: Create kind cluster 2 | hosts: localhost 3 | connection: local 4 | tasks: 5 | - name: Create kind cluster 6 | ansible.builtin.command: 7 | cmd: kind create cluster --config kind-config.yml 8 | 9 | - name: Deploy ingress controller 10 | kubernetes.core.k8s: 11 | apply: true 12 | src: https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml 13 | 14 | - name: Wait for controller 15 | kubernetes.core.k8s_info: 16 | namespace: ingress-nginx 17 | kind: pod 18 | label_selectors: 19 | - app.kubernetes.io/component=controller 20 | wait: yes 21 | wait_sleep: 1 22 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/install-awx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install AWX to Kubernetes Cluster 3 | hosts: localhost 4 | connection: local 5 | roles: 6 | - install-awx 7 | tasks: 8 | - name: Wait for controller 9 | kubernetes.core.k8s_info: 10 | namespace: ingress-nginx 11 | kind: pod 12 | label_selectors: 13 | - app.kubernetes.io/component=controller 14 | wait: yes 15 | wait_sleep: 3 16 | 17 | - name: Wait for http response 18 | uri: 19 | url: https://localhost:9443/api/v2/ping/ 20 | return_content: no 21 | validate_certs: no 22 | status_code: 23 | - 200 24 | until: uri_output.status == 200 25 | retries: 60 26 | delay: 1 27 | register: uri_output 28 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/kind-config.yml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | name: awx-kind-cluster 4 | nodes: 5 | - role: control-plane 6 | kubeadmConfigPatches: 7 | - | 8 | kind: InitConfiguration 9 | nodeRegistration: 10 | kubeletExtraArgs: 11 | node-labels: "ingress-ready=true" 12 | extraPortMappings: 13 | - containerPort: 80 14 | hostPort: 9080 15 | protocol: TCP 16 | - containerPort: 443 17 | hostPort: 9443 18 | protocol: TCP 19 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/readme.md: -------------------------------------------------------------------------------- 1 | # E2E test for AWX integration 2 | 3 | This subproject allows to run a k8s cluster for testing using kind and install AWX in it. 4 | Kind is a tool for running local k8s clusters using docker/podman containers as nodes. 5 | 6 | # Create a k8s cluster with kind 7 | 8 | Requirements: 9 | 10 | * docker/podman 11 | * ansible 12 | * kind 13 | * kubernetes ansible collection `ansible-galaxy collection install kubernetes.core` 14 | 15 | You may need to install the requirements with `pip install -r requirements.txt` 16 | if you are not using the ansible-rulebook dev environment. 17 | 18 | # Steps 19 | 20 | 0. When using Podman 21 | 22 | ``` 23 | export KIND_EXPERIMENTAL_PROVIDER=podman 24 | ``` 25 | 26 | 1. Create the cluster 27 | 28 | ``` 29 | ansible-playbook create-cluster.yml 30 | ``` 31 | 32 | 2. Install AWX 33 | 34 | ``` 35 | ansible-playbook install-awx.yml 36 | ``` 37 | 38 | You should be able to reach awx in with admin/password 39 | 40 | # Cleanup 41 | 42 | ``` 43 | kind delete cluster -n awx-kind-cluster 44 | ``` 45 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/requirements.txt: -------------------------------------------------------------------------------- 1 | oauthlib>=3.2.0 2 | kubernetes 3 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/roles/install-awx/defaults/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | awx_name: awx 3 | awx_image: quay.io/ansible/awx 4 | awx_image_tag: latest 5 | awx_operator_image: quay.io/ansible/awx-operator 6 | awx_operator_image_tag: "2.15.0" 7 | awx_namespace: awx 8 | awx_hostname: "localhost" 9 | awx_admin_password: password 10 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/roles/install-awx/templates/admin-password-secret.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ awx_name }}-admin-password 6 | namespace: {{ awx_namespace }} 7 | stringData: 8 | password: {{ awx_admin_password }} 9 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/roles/install-awx/templates/awx.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: {{ awx_name }} 6 | namespace: {{ awx_namespace }} 7 | spec: 8 | hostname: {{ awx_hostname }} 9 | image: {{ awx_image }} 10 | image_version: {{ awx_image_tag }} 11 | service_type: nodeport 12 | ingress_type: ingress 13 | -------------------------------------------------------------------------------- /tests/e2e/utils/awx/roles/install-awx/templates/kustomization/kustomization.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | # Find the latest tag here: https://github.com/ansible/awx-operator/releases 5 | - github.com/ansible/awx-operator/config/default?ref={{ awx_operator_image_tag }} 6 | 7 | # Set the image tags to match the git version from above 8 | images: 9 | - name: {{ awx_operator_image }} 10 | newTag: {{ awx_operator_image_tag }} 11 | 12 | # Specify a custom namespace in which to install AWX 13 | namespace: {{ awx_namespace }} -------------------------------------------------------------------------------- /tests/event_filter/noop.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | noop.py: An event filter that does nothing to the input. 17 | """ 18 | 19 | 20 | def main(event): 21 | return event 22 | -------------------------------------------------------------------------------- /tests/examples/01_noop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 01 No operation 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | none: 13 | -------------------------------------------------------------------------------- /tests/examples/02_debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 02 Debug 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/examples/03_print_event.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 03 Print event 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | print_event: 13 | -------------------------------------------------------------------------------- /tests/examples/04_set_fact.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 04 Assert Fact 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | fact: 14 | msg: hello world 15 | - name: r2 16 | condition: event.msg == "hello world" 17 | action: 18 | print_event: 19 | -------------------------------------------------------------------------------- /tests/examples/05_post_event.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 05 Post event 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | post_event: 13 | event: 14 | msg: hello world 15 | - name: r2 16 | condition: event.msg == "hello world" 17 | action: 18 | print_event: 19 | -------------------------------------------------------------------------------- /tests/examples/06_retract_fact.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 06 Retract Fact 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | fact: 14 | msg: hello world 15 | - name: r2 16 | condition: event.msg == "hello world" 17 | action: 18 | retract_fact: 19 | fact: 20 | msg: hello world 21 | - name: r3 22 | condition: event.msg is not defined 23 | action: 24 | debug: 25 | 26 | -------------------------------------------------------------------------------- /tests/examples/07_and.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 07 And 3 | hosts: all 4 | sources: 5 | - nested: 6 | i_limit: 5 7 | j_limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.nested.i == 1 and event.nested.j == 1 11 | action: 12 | debug: 13 | 14 | -------------------------------------------------------------------------------- /tests/examples/08_or.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 08 Or 3 | hosts: all 4 | sources: 5 | - nested: 6 | i_limit: 5 7 | j_limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.nested.i == 1 or event.nested.j == 1 11 | action: 12 | debug: 13 | 14 | -------------------------------------------------------------------------------- /tests/examples/09_gt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 09 Greater Than 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i > 2 10 | action: 11 | debug: 12 | 13 | -------------------------------------------------------------------------------- /tests/examples/10_lt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 10 Less Than 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i < 2 10 | action: 11 | debug: 12 | 13 | -------------------------------------------------------------------------------- /tests/examples/11_le.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 11 Less Than Or Equal To 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i <= 2 10 | action: 11 | debug: 12 | 13 | -------------------------------------------------------------------------------- /tests/examples/12_ge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 09 Greater Than or Equal To 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i >= 2 10 | action: 11 | debug: 12 | 13 | -------------------------------------------------------------------------------- /tests/examples/13_add.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 09 Add 3 | hosts: all 4 | sources: 5 | - nested: 6 | i_limit: 5 7 | j_limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.nested.i == event.nested.j + 1 11 | action: 12 | debug: 13 | 14 | -------------------------------------------------------------------------------- /tests/examples/14_sub.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 14 Subtract 3 | hosts: all 4 | sources: 5 | - nested: 6 | i_limit: 5 7 | j_limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.nested.i == event.nested.j - 1 11 | action: 12 | debug: 13 | 14 | -------------------------------------------------------------------------------- /tests/examples/15_multiple_events_all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 15 multiple events all 3 | hosts: all 4 | sources: 5 | - nested: 6 | i_limit: 5 7 | j_limit: 5 8 | rules: 9 | - name: r1 10 | condition: 11 | all: 12 | - event.nested.i == 1 13 | - event.nested.j == 1 14 | action: 15 | debug: 16 | 17 | -------------------------------------------------------------------------------- /tests/examples/16_multiple_events_any.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 16 multiple events any 3 | hosts: all 4 | sources: 5 | - nested: 6 | i_limit: 5 7 | j_limit: 5 8 | rules: 9 | - name: r1 10 | condition: 11 | any: 12 | - event.nested.i == 1 13 | - event.nested.j == 1 14 | action: 15 | debug: 16 | 17 | -------------------------------------------------------------------------------- /tests/examples/17_multiple_sources_any.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 17 multiple sources 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | - range2: 8 | limit: 5 9 | rules: 10 | - name: r1 11 | condition: 12 | any: 13 | - event.i == 1 14 | - event.range2.i == 1 15 | action: 16 | debug: 17 | 18 | -------------------------------------------------------------------------------- /tests/examples/18_multiple_sources_all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 18 multiple sources all 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | - range2: 8 | limit: 5 9 | rules: 10 | - name: r1 11 | condition: 12 | all: 13 | - event.i == 1 14 | - event.range2.i == 1 15 | action: 16 | debug: 17 | 18 | -------------------------------------------------------------------------------- /tests/examples/19_is_defined.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 19 is defined 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | set_fact: 12 | fact: 13 | msg: hello 14 | - name: r2 15 | condition: event.msg is defined 16 | action: 17 | debug: 18 | - name: r3 19 | condition: event.payload is defined 20 | action: 21 | print_event: 22 | pretty: true 23 | -------------------------------------------------------------------------------- /tests/examples/20_is_not_defined.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 20 is not defined 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | set_fact: 12 | fact: 13 | msg: hello 14 | - name: r2 15 | condition: event.msg is defined 16 | action: 17 | retract_fact: 18 | fact: 19 | msg: hello 20 | - name: r3 21 | condition: event.msg is not defined 22 | action: 23 | debug: 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/examples/21_run_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 21 run playbook 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | run_playbook: 12 | name: playbooks/hello.yml 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/examples/22_run_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 22 run playbook 3 | hosts: all 4 | sources: 5 | - range2: 6 | limit: 5 7 | rules: 8 | - name: 9 | condition: event.range2.i == 1 10 | action: 11 | run_playbook: 12 | name: playbooks/hello.yml 13 | var_root: range2 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/examples/23_nested_data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 23 run playbook 3 | hosts: all 4 | sources: 5 | - replay: 6 | directory: examples/replays/23_nested_data 7 | rules: 8 | - name: r1 9 | condition: event.root.nested.i == 1 10 | action: 11 | debug: 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/examples/24_max_attributes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 24 max attributes 3 | hosts: all 4 | sources: 5 | - replay: 6 | directory: examples/replays/24_max_attributes 7 | rules: 8 | - name: r1 9 | condition: event.attr_1 == 1 10 | action: 11 | debug: 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/examples/25_max_attributes_nested.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 25 max attributes nested 3 | hosts: all 4 | sources: 5 | - replay: 6 | directory: examples/replays/25_max_attributes_nested 7 | rules: 8 | - name: r1 9 | condition: event.branch_0.branch_0.branch_0.leaf_1 == 1 10 | action: 11 | debug: 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/examples/26_print_events.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 26 Print events 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: 11 | all: 12 | - event.i == 1 13 | - event.i == 2 14 | action: 15 | print_event: 16 | -------------------------------------------------------------------------------- /tests/examples/27_var_root.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 27 multiple events all with var_root 3 | hosts: all 4 | sources: 5 | - name: generic 6 | ansible.eda.generic: 7 | payload: 8 | - webhook: 9 | payload: 10 | url: http://www.example.com 11 | action: merge 12 | - kafka: 13 | message: 14 | topic: testing 15 | channel: red 16 | - webhook: 17 | payload: 18 | url: http://www.example.com 19 | action: merge 20 | - kafka: 21 | message: 22 | topic: testing 23 | channel: red 24 | rules: 25 | - name: r1 26 | condition: 27 | all: 28 | - events.webhook << event.webhook.payload.url == "http://www.example.com" 29 | - events.kafka << event.kafka.message.channel == "red" 30 | action: 31 | print_event: 32 | var_root: 33 | webhook.payload: webhook 34 | kafka.message: kafka 35 | -------------------------------------------------------------------------------- /tests/examples/28_right_side_condition_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 28 test vars 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == vars.custom.expected_index 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/examples/29_run_module.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 29 run module 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - i: 1 8 | - i: 2 9 | - i: 3 10 | rules: 11 | - name: r1 12 | condition: event.i == 1 13 | action: 14 | run_module: 15 | post_events: True 16 | name: ansible.builtin.debug 17 | module_args: 18 | msg: "I am Malenia, blade of Miquella" 19 | - name: r2 20 | condition: event.msg == "I am Malenia, blade of Miquella" 21 | action: 22 | print_event: 23 | -------------------------------------------------------------------------------- /tests/examples/30_run_module_missing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 30 run module missing 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | run_module: 12 | name: ansible.eda.upcase2 13 | module_args: 14 | name: fred 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/examples/31_run_module_missing_args.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 31 run module missing args 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - name: fred 8 | i: 1 9 | - name: fred 10 | i: 2 11 | rules: 12 | - name: r1 13 | condition: event.i == 1 14 | action: 15 | run_module: 16 | name: ansible.builtin.debug 17 | module_args: 18 | idonotexist: fred 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/examples/32_run_module_fail.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 32 run module fail 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | run_module: 12 | name: ansible.builtin.fail 13 | module_args: 14 | msg: expected failure 15 | retry: True 16 | -------------------------------------------------------------------------------- /tests/examples/33_run_playbook_retry.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 33 run playbook and retry after an interval 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | run_playbook: 12 | name: playbooks/fail_and_succeed.yml 13 | retry: True 14 | delay: 1 15 | extra_vars: 16 | rulebook_file_path: /tmp/33_demo.txt 17 | -------------------------------------------------------------------------------- /tests/examples/34_run_playbook_retries.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 34 run playbook and retry a number of times 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | run_playbook: 12 | name: playbooks/fail_and_succeed.yml 13 | retries: 1 14 | extra_vars: 15 | rulebook_file_path: /tmp/34_demo.txt 16 | -------------------------------------------------------------------------------- /tests/examples/35_multiple_rulesets_1_fired.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 35 multiple rulesets 1 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | set_fact: 12 | fact: 13 | do_not_fire_rule: True 14 | ruleset: 35 multiple rulesets 1 15 | - name: r2 16 | condition: event.do_not_fire_rule == True 17 | action: 18 | none: 19 | - name: 35 multiple rulesets 2 20 | hosts: all 21 | sources: 22 | - range: 23 | limit: 5 24 | rules: 25 | - name: r1 26 | condition: event.do_not_fire_rule == True 27 | action: 28 | debug: 29 | msg: Should not run 30 | -------------------------------------------------------------------------------- /tests/examples/36_multiple_rulesets_both_fired.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 36 multiple rulesets 1 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | loop_count: 5 7 | startup_delay: 1 8 | create_index: i 9 | payload: 10 | - x: 1 11 | rules: 12 | - name: r1 13 | condition: event.i == 1 14 | action: 15 | set_fact: 16 | fact: 17 | fire_rule: True 18 | ruleset: 36 multiple rulesets 2 19 | - name: Will not fire, fact in other ruleset 20 | condition: event.fire_rule == True 21 | action: 22 | print_event: 23 | - name: 36 multiple rulesets 2 24 | hosts: all 25 | sources: 26 | - ansible.eda.generic: 27 | loop_count: 5 28 | shutdown_after: 10 29 | payload: 30 | - y: 1 31 | - y: 2 32 | rules: 33 | - name: r1 34 | condition: event.fire_rule == True 35 | action: 36 | debug: 37 | msg: Should run 38 | -------------------------------------------------------------------------------- /tests/examples/37_hosts_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Host facts 3 | hosts: all 4 | gather_facts: true 5 | sources: 6 | - range: 7 | limit: 5 8 | rules: 9 | - name: "Host 1 rule" 10 | condition: 11 | all: 12 | - fact.meta.hosts == "localhost" 13 | - event.i == 1 14 | action: 15 | debug: 16 | - name: "Host 2 rule" 17 | condition: 18 | all: 19 | - fact.os == "linux" 20 | - event.i == 4 21 | action: 22 | debug: 23 | -------------------------------------------------------------------------------- /tests/examples/38_shutdown.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test shutdown action 3 | hosts: all 4 | gather_facts: false 5 | sources: 6 | - range: 7 | limit: 2 8 | delay: 3 9 | rules: 10 | - name: "Host 1 rule" 11 | condition: event.i == 1 12 | action: 13 | shutdown: 14 | delay: 1.1845 15 | message: My rule has triggered a shutdown 16 | -------------------------------------------------------------------------------- /tests/examples/39_is_defined.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 39 is defined 3 | hosts: all 4 | sources: 5 | - replay: 6 | directory: examples/replays/39_is_defined 7 | rules: 8 | - name: r1 9 | condition: event.root is defined 10 | action: 11 | debug: 12 | -------------------------------------------------------------------------------- /tests/examples/40_in.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 40 in 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 1 8 | rules: 9 | - name: r1 10 | condition: event.i in [0,1,2,3,4] 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/examples/41_not_in.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 41 not in 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 1 8 | rules: 9 | - name: r1 10 | condition: event.i not in [11,21,31,41] 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/examples/42_contains.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 42 contains 3 | hosts: all 4 | sources: 5 | - name: generic 6 | ansible.eda.generic: 7 | payload: 8 | - id_list: 9 | - 1 10 | - 2 11 | - 3 12 | rules: 13 | - name: r1 14 | condition: event.id_list contains 1 15 | action: 16 | debug: 17 | -------------------------------------------------------------------------------- /tests/examples/43_not_contains.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 43 not contains 3 | hosts: all 4 | sources: 5 | - name: generic 6 | ansible.eda.generic: 7 | payload: 8 | - id_list: 9 | - 1 10 | - 2 11 | - 3 12 | rules: 13 | - name: r1 14 | condition: event.id_list not contains 10 15 | action: 16 | debug: 17 | -------------------------------------------------------------------------------- /tests/examples/44_in_and.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 44 in and 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i > 3 and event.i in [1,2,3,4] 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/examples/45_in_or.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 45 in or 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i > 5 or event.i in [1,2,3,4] 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/examples/46_job_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test run job templates 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: "Run job template" 9 | condition: event.i == 1 10 | action: 11 | run_job_template: 12 | name: Demo Job Template 13 | organization: Default 14 | job_args: 15 | extra_vars: 16 | hello: Fred 17 | retries: 1 18 | delay: 10 19 | set_facts: True 20 | -------------------------------------------------------------------------------- /tests/examples/47_generic_plugin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 47 Generic Plugin 3 | hosts: all 4 | sources: 5 | - name: generic 6 | ansible.eda.generic: 7 | payload: 8 | - b: true 9 | - i: 42 10 | rules: 11 | - name: r1 12 | condition: event.b 13 | action: 14 | print_event: 15 | - name: r2 16 | condition: event.i == 42 17 | action: 18 | print_event: 19 | -------------------------------------------------------------------------------- /tests/examples/48_echo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 48 echo 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | msg: Hurray it works 14 | -------------------------------------------------------------------------------- /tests/examples/49_float.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 49 float 3 | hosts: all 4 | sources: 5 | - name: generic 6 | ansible.eda.generic: 7 | payload: 8 | - pi: 3.14159 9 | - mass: 5.97219 10 | - radius: 300.42 11 | rules: 12 | - name: r1 13 | condition: event.pi == 3.14159 14 | action: 15 | debug: 16 | - name: r2 17 | condition: event.mass in [3.14159, 5.97219] 18 | action: 19 | debug: 20 | - name: r3 21 | condition: event.radius > 2.12345e+2 22 | action: 23 | debug: 24 | -------------------------------------------------------------------------------- /tests/examples/50_negation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 50 Negation on booleans 3 | hosts: all 4 | sources: 5 | - name: generic 6 | ansible.eda.generic: 7 | payload: 8 | - b: false 9 | - bt: true 10 | - i: 10 11 | - msg: Fred 12 | - j: 9 13 | rules: 14 | - name: r1 15 | condition: not event.b 16 | action: 17 | print_event: 18 | - name: r2 19 | condition: event.bt 20 | action: 21 | print_event: 22 | - name: r3 23 | condition: not (event.i > 50 or event.i < 10) 24 | action: 25 | print_event: 26 | - name: r4 27 | condition: not event.msg == "Barney" 28 | action: 29 | print_event: 30 | - name: r5 31 | condition: not event.j >= 10 32 | action: 33 | print_event: 34 | -------------------------------------------------------------------------------- /tests/examples/52_once_within.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 52 once within 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | loop_count: 20 7 | payload: 8 | - alert: 9 | level: warning 10 | message: Low disk space 11 | meta: 12 | hosts: HostA 13 | - alert: 14 | level: error 15 | message: Disk failure 16 | meta: 17 | hosts: HostA 18 | rules: 19 | - name: r1 20 | condition: event.alert.level == "warning" or event.alert.level == "error" 21 | action: 22 | debug: 23 | throttle: 24 | once_within: 2 seconds 25 | group_by_attributes: 26 | - event.meta.hosts 27 | - event.alert.level 28 | -------------------------------------------------------------------------------- /tests/examples/53_once_within_multiple_hosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 53 once within multiple hosts 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | loop_count: 20 7 | payload: 8 | - alert: 9 | level: warning 10 | message: Low disk space 11 | meta: 12 | hosts: HostA 13 | - alert: 14 | level: warning 15 | message: Low disk space 16 | meta: 17 | hosts: HostB 18 | rules: 19 | - name: r1 20 | condition: event.alert.level == "warning" or event.alert.level == "error" 21 | action: 22 | debug: 23 | throttle: 24 | once_within: 2 seconds 25 | group_by_attributes: 26 | - event.meta.hosts 27 | - event.alert.level 28 | -------------------------------------------------------------------------------- /tests/examples/54_time_window.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 54 time window 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | create_index: event_index 7 | shutdown_after: 15 8 | event_delay: 5 9 | payload: 10 | - alert: 11 | code: 1001 12 | message: Applying maintenance 13 | - alert: 14 | code: 1002 15 | message: Restarted 16 | rules: 17 | - name: maint cycle 18 | condition: 19 | all: 20 | - event.alert.code == 1001 21 | - event.alert.code == 1002 22 | timeout: 10 seconds 23 | action: 24 | print_event: 25 | -------------------------------------------------------------------------------- /tests/examples/55_not_all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 55 not all 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | create_index: event_index 7 | timestamp: true 8 | event_delay: 15 9 | display: false 10 | shutdown_after: 5 11 | payload: 12 | - alert: 13 | code: 1001 14 | message: Applying maintenance 15 | - alert: 16 | code: 1002 17 | message: Restarted 18 | rules: 19 | - name: maint failed 20 | condition: 21 | not_all: 22 | - event.alert.code == 1001 23 | - event.alert.code == 1002 24 | timeout: 10 seconds 25 | action: 26 | debug: 27 | msg: "Not all conditions met" 28 | -------------------------------------------------------------------------------- /tests/examples/56_once_after.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 56 once after 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | loop_count: 2 7 | loop_delay: 1 8 | timestamp: true 9 | shutdown_after: 15 10 | create_index: event_index 11 | payload: 12 | - alert: 13 | level: warning 14 | message: Low disk space 15 | meta: 16 | hosts: localhost0 17 | - alert: 18 | level: warning 19 | message: Low disk space 20 | meta: 21 | hosts: localhost1 22 | rules: 23 | - name: r1 24 | condition: event.alert.level == "warning" or event.alert.level == "error" 25 | action: 26 | debug: 27 | msg: Once after 10 seconds 28 | throttle: 29 | once_after: 10 seconds 30 | group_by_attributes: 31 | - event.meta.hosts 32 | - event.alert.level 33 | -------------------------------------------------------------------------------- /tests/examples/59_multiple_actions.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | name: 59 Multiple Actions 3 | sources: 4 | - name: range 5 | range: 6 | limit: 5 7 | delay: 0.05 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | actions: 12 | - debug: 13 | - print_event: 14 | pretty: true 15 | - debug: 16 | msg: "Multiple Action Message1" 17 | - debug: 18 | msg: "Multiple Action Message2" 19 | - name: r2 20 | condition: event.i == 2 21 | action: 22 | debug: 23 | msg: Single Action 24 | -------------------------------------------------------------------------------- /tests/examples/60_json_filter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 60 json filter 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | key1: 8 | key2: 9 | f_ignore_1: 1 10 | f_ignore_2: 2 11 | key3: 12 | key4: 13 | f_use_1: 42 14 | f_use_2: 45 15 | filters: 16 | - ansible.eda.json_filter: 17 | include_keys: 18 | - key3 19 | - key4 20 | - f_use* 21 | exclude_keys: 22 | - "*" 23 | 24 | rules: 25 | - name: r1 26 | condition: event.key3.key4.f_use_1 == 42 27 | action: 28 | debug: 29 | msg: Hurray filtering works 30 | - name: r2 31 | condition: event.key1.key2.f_ignore_1 == 1 32 | action: 33 | debug: 34 | msg: Should never fire 35 | -------------------------------------------------------------------------------- /tests/examples/61_select_1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 61 select 1 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - name: Fred 8 | age: 54 9 | levels: 10 | - 10 11 | - 20 12 | - 30 13 | - name: Barney 14 | age: 53 15 | levels: 16 | - 11 17 | - 15 18 | - 16 19 | - name: Wilma 20 | age: 53 21 | levels: 22 | - 1 23 | - 5 24 | - 6 25 | rules: 26 | - name: r1 27 | condition: event.levels is select('>', 25) 28 | action: 29 | debug: 30 | msg: Found a player with level greater than 25 31 | -------------------------------------------------------------------------------- /tests/examples/62_select_2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 62 select 2 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - name: Fred 8 | age: 54 9 | addresses: 10 | - 123 Main St, Bedrock, MI 11 | - 545 Spring St, Cresskill, NJ 12 | - 435 Wall Street, New York, NY 13 | - name: Barney 14 | age: 53 15 | addresses: 16 | - 345 Bleeker St, Bedrock, MI 17 | - 145 Wall St, Dumont, NJ 18 | - name: Wilma 19 | age: 47 20 | addresses: 21 | - 123 Main St, Bedrock, MI 22 | - 432 Raymond Blvd, Newark, NJ 23 | rules: 24 | - name: r1 25 | condition: event.addresses is select('regex', 'Main St') 26 | action: 27 | debug: 28 | msg: Some one lives on Main Street 29 | - name: r2 30 | condition: event.addresses is not select('regex', 'Major St') 31 | action: 32 | debug: 33 | msg: No one lives on Major St 34 | -------------------------------------------------------------------------------- /tests/examples/63_selectattr_1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 63 selectattr 1 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - people: 8 | - person: 9 | name: Fred 10 | age: 54 11 | - person: 12 | name: Barney 13 | age: 45 14 | - person: 15 | name: Wilma 16 | age: 23 17 | - person: 18 | name: Betty 19 | age: 25 20 | - friends: 21 | - person: 22 | name: Barney 23 | hobby: golf 24 | - person: 25 | name: Fred 26 | hobby: driving 27 | 28 | 29 | rules: 30 | - name: r1 31 | condition: event.people is selectattr('person.age', '>', 30) 32 | action: 33 | debug: 34 | msg: Has a person greater than 30 35 | - name: r2 36 | condition: event.friends is selectattr('person.name', 'regex', 'Barney|Fred') 37 | action: 38 | debug: 39 | msg: Barney or Fred in friends list 40 | -------------------------------------------------------------------------------- /tests/examples/64_selectattr_2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 64 selectattr 2 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - people: 8 | - person: 9 | name: Fred 10 | age: 54 11 | - person: 12 | name: Barney 13 | age: 45 14 | - person: 15 | name: Wilma 16 | age: 23 17 | - person: 18 | name: Betty 19 | age: 25 20 | rules: 21 | - name: r1 22 | condition: event.people is selectattr('person.age', 'in', [55,25]) 23 | action: 24 | debug: 25 | msg: Found person who is either 55 or 25 26 | -------------------------------------------------------------------------------- /tests/examples/65_selectattr_3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 65 selectattr 3 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - person: 8 | name: Fred 9 | age: 54 10 | rules: 11 | - name: r1 12 | # event.person is not an array here its an object 13 | # we convert it to an array of 1 14 | condition: event.person is selectattr('age', '>', 30) 15 | action: 16 | debug: 17 | msg: Has a person greater than 30 18 | -------------------------------------------------------------------------------- /tests/examples/66_sleepy_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 66 sleepy playbook 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | create_index: i 7 | loop_count: 5 8 | shutdown_after: 45 9 | payload: 10 | - name: fred 11 | rules: 12 | - name: r1 13 | condition: event.i == 0 14 | action: 15 | print_event: 16 | - name: r2 17 | condition: event.i == 1 18 | action: 19 | run_playbook: 20 | name: playbooks/sleeper.yml 21 | - name: terminate gracefully 22 | hosts: all 23 | sources: 24 | - ansible.eda.generic: 25 | create_index: j 26 | loop_count: 5 27 | shutdown_after: 45 28 | payload: 29 | - name: barney 30 | rules: 31 | - name: r11 32 | condition: event.j == 0 33 | action: 34 | debug: 35 | msg: Next issuing shutdown 36 | - name: r12 37 | condition: event.j == 1 38 | action: 39 | shutdown: 40 | message: Issuing graceful shutdown after 5 seconds 41 | delay: 5.0 42 | kind: graceful 43 | -------------------------------------------------------------------------------- /tests/examples/67_shutdown_now.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 67 shutdown now 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | create_index: i 7 | loop_count: 5 8 | shutdown_after: 45 9 | payload: 10 | - name: fred 11 | rules: 12 | - name: r1 13 | condition: event.i == 0 14 | action: 15 | print_event: 16 | - name: r2 17 | condition: event.i == 1 18 | action: 19 | run_playbook: 20 | name: playbooks/sleeper.yml 21 | - name: terminate now 22 | hosts: all 23 | sources: 24 | - ansible.eda.generic: 25 | create_index: j 26 | loop_count: 5 27 | shutdown_after: 45 28 | payload: 29 | - name: barney 30 | rules: 31 | - name: r11 32 | condition: event.j == 0 33 | action: 34 | debug: 35 | msg: Next issuing ungraceful shutdown 36 | - name: r12 37 | condition: event.j == 1 38 | action: 39 | shutdown: 40 | message: Issuing shutdown now 41 | kind: now 42 | -------------------------------------------------------------------------------- /tests/examples/68_disabled_rule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 68 disabled rule 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | msg: Should get fired 14 | - name: r2 15 | enabled: false 16 | condition: event.i == 2 17 | action: 18 | debug: 19 | msg: Should not get fired 20 | -------------------------------------------------------------------------------- /tests/examples/69_enhanced_debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 69 enhanced debug 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 0 11 | action: 12 | debug: 13 | - name: r2 14 | condition: event.i == 1 15 | action: 16 | debug: 17 | msg: "This is a sample message with {{ event.i }}" 18 | - name: r3 19 | condition: event.i == 2 20 | action: 21 | debug: 22 | msg: 23 | - "Hello World {{ event }}" 24 | - "Hello Java" 25 | - "Hello Java again {{ event }}" 26 | - name: r5 27 | condition: event.i == 4 28 | action: 29 | debug: 30 | var: event.i 31 | -------------------------------------------------------------------------------- /tests/examples/70_null.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 70 null 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - x: 1 8 | y: null 9 | - persons: 10 | - age: 45 11 | name: Fred 12 | occupation: Dino Driver 13 | - age: 46 14 | name: Barney 15 | occupation: null 16 | - z : null 17 | 18 | rules: 19 | - name: r1 20 | condition: event.x == 1 and event.y == null 21 | action: 22 | print_event: 23 | pretty: true 24 | - name: r2 25 | condition: event.persons is selectattr('occupation', '==', null) 26 | action: 27 | print_event: 28 | pretty: true 29 | - name: r3 30 | condition: event.z in [5,6,7, null] 31 | action: 32 | print_event: 33 | pretty: true 34 | -------------------------------------------------------------------------------- /tests/examples/72_set_fact_with_type.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 72 set fact with type 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | shutdown_after: 1 7 | payload: 8 | - action: "go" 9 | rules: 10 | - name: r1 11 | condition: event.action == "go" 12 | action: 13 | set_fact: 14 | fact: 15 | var_bool: "{{ my_bool }}" 16 | var_int: "{{ my_int }}" 17 | var_float: "{{ my_float }}" 18 | literal_int: 5 19 | string_int: "5" 20 | 21 | - name: Match the bool 22 | condition: event.var_bool 23 | action: 24 | debug: 25 | msg: "The var bool matches" 26 | 27 | - name: Match the int 28 | condition: event.var_int == 2 29 | action: 30 | debug: 31 | msg: "The var int matches" 32 | 33 | - name: Match the float 34 | condition: event.var_float == 3.123 35 | action: 36 | debug: 37 | msg: "The var int matches" 38 | 39 | - name: Match the literal int 40 | condition: event.literal_int == 5 41 | action: 42 | debug: 43 | msg: "The literal int matches" 44 | - name: Match the string int 45 | condition: event.string_int == "5" 46 | action: 47 | debug: 48 | msg: "The string int matches" 49 | -------------------------------------------------------------------------------- /tests/examples/73_mix_and_match_list.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 73 mix and match list 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - my_bool: false 8 | - my_str: fred 9 | - my_null: null 10 | - my_int: 42 11 | - my_float: 3.1415 12 | 13 | rules: 14 | - name: Match bool in list 15 | condition: events.my_bool in [null, "fred", false, 42] 16 | action: 17 | print_event: 18 | - name: Match str in list 19 | condition: events.my_str in [null, "fred", false, 42] 20 | action: 21 | print_event: 22 | - name: "Match null in list" 23 | condition: events.my_null in [null, "fred", false, 42] 24 | action: 25 | print_event: 26 | - name: Match int in list 27 | condition: events.my_int in [null, "fred", false, 42] 28 | action: 29 | print_event: 30 | - name: Match float in list 31 | condition: events.my_float in [null, "fred", false, 42, 3.1415] 32 | action: 33 | print_event: 34 | -------------------------------------------------------------------------------- /tests/examples/74_self_referential.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 74 Self referential 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - x: Fred 8 | y: Fred 9 | 10 | rules: 11 | - name: rule1 12 | condition: event.x == event.y 13 | action: 14 | print_event: 15 | -------------------------------------------------------------------------------- /tests/examples/75_all_conditions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 75 all conditions 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - friend_list: 8 | names: 9 | - fred 10 | - barney 11 | - request: 12 | type: Delete 13 | friend_name: fred 14 | rules: 15 | - name: r1 16 | condition: 17 | all: 18 | - event.request.type == "Delete" 19 | - event.friend_list.names is select("search", events.m_0.request.friend_name) 20 | action: 21 | print_event: 22 | pretty: true 23 | -------------------------------------------------------------------------------- /tests/examples/76_all_conditions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 76 all conditions 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - request: 8 | type: Delete 9 | friend_name: fred 10 | - request: 11 | type: Delete 12 | friend_name: wilma 13 | - friend_list: 14 | names: 15 | - fred 16 | - barney 17 | - friend_list: 18 | names: 19 | - wilma 20 | - betty 21 | rules: 22 | - name: r1 23 | condition: 24 | all: 25 | - event.request.type == "Delete" 26 | - event.friend_list.names is select("search", events.m_0.request.friend_name) 27 | action: 28 | print_event: 29 | pretty: true 30 | -------------------------------------------------------------------------------- /tests/examples/77_default_events_ttl.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 77 default events ttl 3 | hosts: all 4 | default_events_ttl: 2 seconds 5 | sources: 6 | - ansible.eda.generic: 7 | event_delay: 4 8 | payload: 9 | - i: 1 10 | - j: 2 11 | - k: 3 12 | rules: 13 | - name: r1 14 | condition: 15 | all: 16 | - event.i == 1 17 | - event.j == 2 18 | action: 19 | print_event: 20 | pretty: true 21 | - name: r2 22 | condition: event.k == 3 23 | action: 24 | print_event: 25 | pretty: true 26 | -------------------------------------------------------------------------------- /tests/examples/78_complete_retract_fact.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 78 complete retract fact 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | fact: 14 | msg: hello world 15 | creator: Kernighan 16 | - name: r2 17 | condition: event.msg == "hello world" 18 | action: 19 | retract_fact: 20 | fact: 21 | msg: hello world 22 | creator: Kernighan 23 | partial: false 24 | - name: r3 25 | condition: event.msg is not defined 26 | action: 27 | debug: 28 | msg: Complete retract works 29 | 30 | -------------------------------------------------------------------------------- /tests/examples/79_workflow_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test run workflow templates 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: "Run workflow template" 9 | condition: event.i == 1 10 | action: 11 | run_workflow_template: 12 | name: Demo Workflow Template 13 | job_args: 14 | extra_vars: 15 | hello: Fred 16 | retries: 1 17 | delay: 10 18 | set_facts: True 19 | organization: Default 20 | -------------------------------------------------------------------------------- /tests/examples/80_match_multiple_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 80 match multiple rules 3 | hosts: all 4 | match_multiple_rules: true 5 | sources: 6 | - name: range 7 | range: 8 | limit: 5 9 | rules: 10 | - name: r1 11 | condition: event.i == 1 12 | action: 13 | debug: 14 | - name: r11 15 | condition: event.i == 1 16 | action: 17 | print_event: 18 | -------------------------------------------------------------------------------- /tests/examples/81_match_single_rule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 81 match single rule 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | - name: r11 14 | condition: event.i == 1 15 | action: 16 | print_event: 17 | -------------------------------------------------------------------------------- /tests/examples/82_non_alpha_keys.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: 82 non alpha keys 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - "http://www.example.com": "down" 8 | - urls: 9 | "http://www.example.com": "up" 10 | - नाम: മധു 11 | 12 | rules: 13 | - name: r1 14 | condition: event["http://www.example.com"] == "down" 15 | action: 16 | debug: 17 | msg: "First check worked" 18 | - name: r2 19 | condition: event.urls["http://www.example.com"] == "up" 20 | action: 21 | debug: 22 | msg: "Second check worked" 23 | - name: r3 24 | condition: event["नाम"] is search("മധു", ignorecase=true) 25 | action: 26 | print_event: 27 | -------------------------------------------------------------------------------- /tests/examples/83_boolean_true.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "83 boolean true" 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 3 8 | rules: 9 | - name: r1 10 | condition: true 11 | action: 12 | print_event: 13 | -------------------------------------------------------------------------------- /tests/examples/84_job_template_exclude_events.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test run job templates without event payload 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - age: 55 8 | name: Fred 9 | zip: 12345 10 | rules: 11 | - name: "Run job template" 12 | condition: event.name == "Fred" 13 | action: 14 | run_job_template: 15 | name: Demo Job Template 16 | organization: Default 17 | include_events: false 18 | job_args: 19 | extra_vars: 20 | name: "{{ event.name }}" 21 | -------------------------------------------------------------------------------- /tests/examples/85_workflow_template_exclude_events.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test run workflow templates without event payload 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - age: 55 8 | name: Fred 9 | zip: "12345" 10 | rules: 11 | - name: "Run workflow template" 12 | condition: event.name == "Fred" 13 | action: 14 | run_workflow_template: 15 | name: Demo Workflow Template 16 | organization: Default 17 | include_events: false 18 | job_args: 19 | extra_vars: 20 | name: "{{ event.name }}" 21 | -------------------------------------------------------------------------------- /tests/examples/86_job_template_include_events.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test run job templates with event payload 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - age: 55 8 | name: Fred 9 | zip: 12345 10 | rules: 11 | - name: "Run job template" 12 | condition: event.name == "Fred" 13 | action: 14 | run_job_template: 15 | name: Demo Job Template 16 | organization: Default 17 | job_args: 18 | extra_vars: 19 | name: "{{ event.name }}" 20 | -------------------------------------------------------------------------------- /tests/examples/87_workflow_template_include_events.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test run workflow templates with event payload 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - age: 55 8 | name: Fred 9 | zip: "12345" 10 | rules: 11 | - name: "Run workflow template" 12 | condition: event.name == "Fred" 13 | action: 14 | run_workflow_template: 15 | name: Demo Workflow Template 16 | organization: Default 17 | job_args: 18 | extra_vars: 19 | name: "{{ event.name }}" 20 | -------------------------------------------------------------------------------- /tests/examples/88_job_template_no_args.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test run job templates with no args 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - age: 55 8 | name: Fred 9 | zip: 12345 10 | rules: 11 | - name: "Run job template" 12 | condition: event.name == "Fred" 13 | action: 14 | run_job_template: 15 | name: Demo Job Template 16 | organization: Default 17 | -------------------------------------------------------------------------------- /tests/examples/89_source_error_with_msg.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "89 source error with msg" 3 | hosts: all 4 | sources: 5 | - name: range_fail_with_msg 6 | source_with_exception: 7 | error_msg: "range fail with msg" 8 | rules: 9 | - name: r1 10 | condition: true 11 | action: 12 | print_event: 13 | -------------------------------------------------------------------------------- /tests/examples/90_source_error_without_msg.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "90 source error without msg" 3 | hosts: all 4 | sources: 5 | - name: range_fail_without_msg 6 | source_with_exception: 7 | error_msg: "" 8 | rules: 9 | - name: r1 10 | condition: true 11 | action: 12 | print_event: 13 | -------------------------------------------------------------------------------- /tests/examples/91_mask_senstive_variables.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test sensitive variable is masked with missing key 3 | hosts: all 4 | sources: 5 | - ansible.eda.generic: 6 | payload: 7 | - name: Fred 8 | rules: 9 | - name: "Run job template" 10 | condition: event.name == 'Fred' 11 | actions: 12 | - debug: 13 | var: organization 14 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/00.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 0 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/01.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 1 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/02.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 2 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/03.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 3 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/04.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 4 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/05.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 5 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/06.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 6 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/07.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 7 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/08.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 8 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/09.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 9 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/23_nested_data/generate_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2022 Red Hat, Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import json 19 | 20 | for i in range(10): 21 | data = {} 22 | 23 | with open(f"{i:02}.json", "w") as f: 24 | data = dict(root=dict(nested=dict(i=i))) 25 | f.write(json.dumps(data, sort_keys=True, indent=4)) 26 | -------------------------------------------------------------------------------- /tests/examples/replays/24_max_attributes/generate_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2022 Red Hat, Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import json 19 | 20 | MAX_ATTRIBUTES = 250 21 | 22 | 23 | for i in range(1): 24 | data = {} 25 | 26 | with open(f"{i:02}.json", "w") as f: 27 | data = {} 28 | for j in range(MAX_ATTRIBUTES): 29 | data[f"attr_{j}"] = j 30 | f.write(json.dumps(data, sort_keys=True, indent=4)) 31 | -------------------------------------------------------------------------------- /tests/examples/replays/25_max_attributes_nested/generate_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2022 Red Hat, Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import json 19 | 20 | DEPTH = 3 21 | WIDTH = 3 22 | 23 | 24 | def recursive(o, depth): 25 | if depth == 0: 26 | for i in range(WIDTH): 27 | o[f"leaf_{i}"] = i 28 | else: 29 | for i in range(WIDTH): 30 | new_o = dict() 31 | o[f"branch_{i}"] = new_o 32 | recursive(new_o, depth - 1) 33 | 34 | 35 | for i in range(1): 36 | data = {} 37 | 38 | with open(f"{i:02}.json", "w") as f: 39 | data = {} 40 | recursive(data, DEPTH) 41 | f.write(json.dumps(data, sort_keys=True, indent=4)) 42 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/00.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 0 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/01.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 1 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/02.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 2 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/03.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 3 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/04.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 4 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/05.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 5 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/06.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 6 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/07.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 7 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/08.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 8 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/09.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "nested": { 4 | "i": 9 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/examples/replays/39_is_defined/generate_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2022 Red Hat, Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import json 19 | 20 | for i in range(10): 21 | data = {} 22 | 23 | with open(f"{i:02}.json", "w") as f: 24 | data = dict(root=dict(nested=dict(i=i))) 25 | f.write(json.dumps(data, sort_keys=True, indent=4)) 26 | -------------------------------------------------------------------------------- /tests/pass1.txt: -------------------------------------------------------------------------------- 1 | pass1 2 | -------------------------------------------------------------------------------- /tests/playbooks/check_facts_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Playbook to check if the facts are correct 3 | hosts: localhost 4 | gather_facts: false 5 | tasks: 6 | - name: Fail if the value is missing 7 | ansible.builtin.fail: 8 | msg: "Failing because hello world fact does not match" 9 | when: fact.msg != "hello world" 10 | 11 | - name: Fail if the alpha is missing 12 | ansible.builtin.fail: 13 | msg: "Failing because alpha fact does not match" 14 | when: fact.alpha != 1 15 | 16 | - name: Fail if the beta.location doesn't match 17 | ansible.builtin.fail: 18 | msg: "Failing because beta.location fact does not match" 19 | when: fact.beta.location != "Naboo" 20 | -------------------------------------------------------------------------------- /tests/playbooks/compare_value.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Retrieve expected value 3 | ansible.builtin.set_fact: 4 | expected_value: "{{ vars_json | from_json | community.general.json_query(item.key) }}" 5 | 6 | - name: Fail the task if mismatch 7 | ansible.builtin.fail: 8 | msg: "{{ item.key }} mismatch" 9 | # since jinja2 doesn't preserve the type we have to do a string comparison 10 | when: expected_value != item.value|string 11 | -------------------------------------------------------------------------------- /tests/playbooks/fail_and_succeed.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Playbook to fail the first run and succeed the second run 3 | hosts: localhost 4 | gather_facts: false 5 | tasks: 6 | - name: Define a temp file 7 | ansible.builtin.set_fact: 8 | my_path: "{{ rulebook_file_path }}" 9 | 10 | - name: Check that the temp file exists 11 | ansible.builtin.stat: 12 | path: "{{ my_path }}" 13 | register: my_file 14 | 15 | - name: Create the file 16 | ansible.builtin.file: 17 | path: "{{ my_path }}" 18 | state: touch 19 | mode: "0644" 20 | when: not my_file.stat.exists 21 | 22 | - name: Fail if the file does not exist 23 | ansible.builtin.fail: 24 | msg: "Failing because the test file does not exist" 25 | when: not my_file.stat.exists 26 | 27 | - name: Delete the file 28 | ansible.builtin.file: 29 | path: "{{ my_path }}" 30 | state: absent 31 | when: my_file.stat.exists 32 | -------------------------------------------------------------------------------- /tests/playbooks/hello.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Print Hello World and set as fact 3 | hosts: localhost 4 | gather_facts: false 5 | tasks: 6 | - name: Print Hello World 7 | ansible.builtin.debug: 8 | msg: 'Hello world' 9 | 10 | - name: Print event information 11 | ansible.builtin.debug: 12 | msg: '{{ ansible_eda.event }}' 13 | 14 | - name: Set msg fact 15 | ansible.builtin.set_fact: 16 | msg: 'hello world' 17 | cacheable: true 18 | ... 19 | -------------------------------------------------------------------------------- /tests/playbooks/hello_events.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Debug Hello event and facts 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Debug Hello event 7 | ansible.builtin.debug: 8 | msg: 'Hello event {{ ansible_eda.event }}' 9 | 10 | - name: Debug Hello facts 11 | ansible.builtin.debug: 12 | msg: 'Hello facts {{ ansible_eda.facts }}' 13 | ... 14 | -------------------------------------------------------------------------------- /tests/playbooks/hello_world_set_fact.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set Hello World fact 3 | hosts: localhost 4 | gather_facts: false 5 | tasks: 6 | - name: Debug Hello World 7 | ansible.builtin.debug: 8 | msg: 'Hello world' 9 | 10 | - name: Set Hello World fact 11 | ansible.builtin.set_fact: 12 | msg: 'hello world' 13 | cacheable: true 14 | ... 15 | -------------------------------------------------------------------------------- /tests/playbooks/inventory.yml: -------------------------------------------------------------------------------- 1 | --- 2 | all: 3 | hosts: 4 | localhost: 5 | ansible_connection: local 6 | ansible_python_interpreter: "{{ansible_playbook_python}}" 7 | -------------------------------------------------------------------------------- /tests/playbooks/inventory1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | all: 3 | hosts: 4 | localhost0: 5 | ansible_connection: local 6 | localhost1: 7 | ansible_connection: local 8 | -------------------------------------------------------------------------------- /tests/playbooks/sleeper.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Sleeps for 5 minutes 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Sleepy 7 | ansible.builtin.debug: 8 | msg: 'Hello from Sleepy, will sleep for next 5 minutes' 9 | 10 | - name: snore 11 | ansible.builtin.pause: 12 | minutes: 5 13 | ... 14 | -------------------------------------------------------------------------------- /tests/playbooks/validate_args_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Playbook to check if the passed in values in my_vars are correct 3 | hosts: localhost 4 | gather_facts: false 5 | tasks: 6 | - name: Validate rule name 7 | ansible.builtin.fail: 8 | msg: "Rule name did not match" 9 | when: ansible_eda.rule != "r2" 10 | - name: Validate ruleset name 11 | ansible.builtin.fail: 12 | msg: "Rule name did not match" 13 | when: ansible_eda.ruleset != "Test Assert Fact of different data types" 14 | - name: Convert vars to json so we can use nested keys 15 | ansible.builtin.set_fact: 16 | vars_json: "{{ ansible_eda | to_json }}" 17 | 18 | - name: Compare value 19 | ansible.builtin.include_tasks: compare_value.yml 20 | loop: "{{ my_vars | dict2items }}" 21 | -------------------------------------------------------------------------------- /tests/playbooks/vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | limit: 2 3 | -------------------------------------------------------------------------------- /tests/rules/rule_names_with_substitution.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Rules with names being substituted 3 | hosts: localhost 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: "{{ custom.name1 }}" 9 | condition: event.i == 1 10 | action: 11 | debug: 12 | - name: "{{ custom.name2 }}" 13 | condition: event.i == 2 14 | action: 15 | debug: 16 | -------------------------------------------------------------------------------- /tests/rules/rules_with_assignment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules with assignment 3 | hosts: localhost 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: assignment 10 | condition: events.first << event.i == 0 11 | action: 12 | debug: 13 | var: events.first 14 | -------------------------------------------------------------------------------- /tests/rules/rules_with_assignment2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules with assignment2 3 | hosts: localhost 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: assignment 10 | condition: facts.first << fact.i == 0 11 | action: 12 | debug: 13 | var: events.first 14 | -------------------------------------------------------------------------------- /tests/rules/rules_with_multiple_conditions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules multiple conditions any 3 | hosts: localhost 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: multiple conditions 10 | condition: 11 | any: 12 | - events.event << event.i == 0 13 | - events.event << event.i == 1 14 | action: 15 | debug: 16 | -------------------------------------------------------------------------------- /tests/rules/rules_with_multiple_conditions2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules multiple conditions all 3 | hosts: localhost 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: multiple conditions 10 | condition: 11 | all: 12 | - events.first << event.i == 0 13 | - events.second << event.i == 1 14 | action: 15 | debug: 16 | -------------------------------------------------------------------------------- /tests/rules/rules_with_multiple_conditions3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules multiple conditions reference assignment 3 | hosts: localhost 4 | sources: 5 | - range: 6 | limit: 5 7 | rules: 8 | - name: multiple conditions 9 | condition: 10 | all: 11 | - events.first << event.i == 0 12 | - events.second << event.i == 1 13 | - events.third << event.i == events.first.i + 2 14 | action: 15 | debug: 16 | first: "{{events.first}}" 17 | second: "{{events.second}}" 18 | third: "{{events.third}}" 19 | -------------------------------------------------------------------------------- /tests/rules/rules_with_time.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules with time 3 | hosts: localhost 4 | sources: 5 | - tick: 6 | rules: 7 | - name: promote time.tick to current_time fact 8 | condition: event.time.tick is defined 9 | action: 10 | set_fact: 11 | fact: 12 | current_time: "{{event.time.tick}}" 13 | - name: retract current_time fact 14 | condition: fact.current_time is defined 15 | action: 16 | retract_fact: 17 | fact: 18 | current_time: "{{event.current_time}}" 19 | - name: matches current_time fact before it is retracted since facts fire all matching rules 20 | condition: fact.current_time is defined 21 | action: 22 | debug: 23 | - name: shutdown after 5 ticks 24 | condition: fact.current_time >= 5 25 | action: 26 | shutdown: 27 | -------------------------------------------------------------------------------- /tests/rules/rules_with_timestamp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules with timestamp 3 | hosts: localhost 4 | sources: 5 | - timestamp: 6 | rules: 7 | - name: promote timestamp.unix_timestamp to current_time fact 8 | condition: event.timestamp.unix_timestamp is defined 9 | action: 10 | set_fact: 11 | fact: 12 | current_time: "{{event.timestamp.unix_timestamp}}" 13 | - name: set the initial time 14 | condition: 15 | all: 16 | - facts.time << fact.current_time is defined 17 | - event.initial_time is not defined 18 | action: 19 | set_fact: 20 | fact: 21 | initial_time: "{{facts.time.current_time}}" 22 | - name: retract current_time fact 23 | condition: fact.current_time is defined 24 | action: 25 | retract_fact: 26 | fact: 27 | current_time: "{{fact.current_time}}" 28 | - name: debug 29 | condition: fact.initial_time is defined 30 | action: 31 | debug: 32 | - name: shutdown 33 | condition: 34 | all: 35 | - facts.a << fact.initial_time is defined 36 | - fact.current_time >= facts.a.initial_time + 5 37 | action: 38 | shutdown: 39 | -------------------------------------------------------------------------------- /tests/rules/rules_with_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Rules with vars 3 | hosts: localhost 4 | sources: 5 | - range: 6 | limit: "{{limit}}" 7 | rules: 8 | - name: multiple conditions 9 | condition: 10 | all: 11 | - events.first << event.i == 0 12 | - events.second << event.i == 1 13 | action: 14 | debug: 15 | -------------------------------------------------------------------------------- /tests/rules/rules_without_assignment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Demo rules multiple conditions all 3 | hosts: localhost 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: assignment 10 | condition: fact.i == 0 11 | action: 12 | debug: 13 | event: "{{event}}" 14 | -------------------------------------------------------------------------------- /tests/rules/test_blank_rule_name.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test ruleset with blank rule names 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: ' ' 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/rules/test_blank_ruleset_name.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ' ' 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/rules/test_combine_hosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Combine hosts when run the same playbook 3 | hosts: localhost0, localhost1 4 | sources: 5 | - name: range 6 | range: 7 | limit: 2 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | run_playbook: 13 | name: playbooks/hello_events.yml -------------------------------------------------------------------------------- /tests/rules/test_combine_hosts_module.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Combine hosts when run the same module 3 | hosts: localhost0, localhost1 4 | sources: 5 | - name: range 6 | range: 7 | limit: 2 8 | rules: 9 | - name: r1 10 | condition: event.i is defined 11 | action: 12 | run_module: 13 | name: ansible.eda.upcase 14 | module_args: 15 | name: Fred Flintstone -------------------------------------------------------------------------------- /tests/rules/test_duplicate_rule_names.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test ruleset with duplicate rule names 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: rule1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | - name: rule1 14 | condition: event.i == 2 15 | action: 16 | debug: 17 | -------------------------------------------------------------------------------- /tests/rules/test_duplicate_ruleset_names.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ruleset1 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | - name: ruleset1 14 | hosts: all 15 | sources: 16 | - name: range 17 | range: 18 | limit: 5 19 | rules: 20 | - name: r1 21 | condition: event.i == 1 22 | action: 23 | debug: 24 | -------------------------------------------------------------------------------- /tests/rules/test_empty_rule_names.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test ruleset with empty rule names 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/rules/test_filters.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rule filters 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | filters: 9 | - noop: 10 | rules: 11 | - name: r1 12 | condition: event.i == 0 13 | action: 14 | print_event: 15 | pretty: true 16 | var_root: i 17 | - name: r2 18 | condition: event.i == 1 19 | action: 20 | print_event: 21 | - name: r3 22 | condition: event.i == 2 23 | action: 24 | run_playbook: 25 | name: playbooks/hello_world_set_fact.yml 26 | var_root: i 27 | post_events: true 28 | - name: r4 29 | condition: event.msg is defined 30 | action: 31 | print_event: 32 | 33 | -------------------------------------------------------------------------------- /tests/rules/test_host_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test host rules 3 | hosts: all 4 | sources: 5 | - name: hosts 6 | hosts: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | ruleset: Test host rules 14 | fact: 15 | j: 1 16 | - name: r2 17 | condition: event.i == 2 18 | action: 19 | run_playbook: 20 | name: playbooks/hello_events.yml 21 | - name: r3 22 | condition: event.i == 3 23 | action: 24 | retract_fact: 25 | ruleset: Test host rules 26 | fact: 27 | j: 3 28 | - name: r4 29 | condition: event.i == 4 30 | action: 31 | post_event: 32 | ruleset: Test host rules 33 | event: 34 | j: 4 35 | - name: r5 36 | condition: event.j is defined 37 | action: 38 | none: 39 | -------------------------------------------------------------------------------- /tests/rules/test_missing_rule_names.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test ruleset with missing rule names 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - condition: event.i == 1 10 | action: 11 | debug: 12 | -------------------------------------------------------------------------------- /tests/rules/test_missing_ruleset_name.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | sources: 4 | - name: range 5 | range: 6 | limit: 5 7 | rules: 8 | - name: r1 9 | condition: event.i == 1 10 | action: 11 | debug: 12 | -------------------------------------------------------------------------------- /tests/rules/test_multiple_sources.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test multiple sources 3 | hosts: all 4 | sources: 5 | - range: 6 | limit: 6 7 | - range2: 8 | limit: 5 9 | rules: 10 | - name: 11 | condition: event.range2.i == 2 12 | action: 13 | run_playbook: 14 | name: playbooks/hello_world_set_fact.yml 15 | - name: 16 | condition: +event.range2.i 17 | action: 18 | none: 19 | -------------------------------------------------------------------------------- /tests/rules/test_rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rules4 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | ruleset: Test rules4 14 | fact: 15 | j: 1 16 | - name: r2 17 | condition: event.i == 2 18 | action: 19 | run_playbook: 20 | name: playbooks/hello_world_set_fact.yml 21 | - name: r3 22 | condition: event.i == 3 23 | action: 24 | retract_fact: 25 | ruleset: Test rules4 26 | fact: 27 | j: 3 28 | - name: r4 29 | condition: event.i == 4 30 | action: 31 | post_event: 32 | ruleset: Test rules4 33 | event: 34 | j: 4 35 | - name: r5 36 | condition: event.j is defined 37 | action: 38 | none: 39 | -------------------------------------------------------------------------------- /tests/rules/test_rules_expanded_conditions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: run playbooks 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: hello1 10 | condition: 11 | any: 12 | - i == 2 13 | - j == 2 14 | action: 15 | run_playbooks: 16 | - name: playbooks/hello_world_set_fact.yml 17 | - name: hello2 18 | condition: 19 | all: 20 | - i == 2 21 | - j == 2 22 | action: 23 | run_playbooks: 24 | - name: playbooks/hello_world_set_fact.yml 25 | -------------------------------------------------------------------------------- /tests/rules/test_rules_multiple_hosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rules multiple hosts 3 | hosts: localhost0, localhost1 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | ruleset: Test rules multiple hosts 14 | fact: 15 | j: 1 16 | - name: r2 17 | condition: event.i == 2 18 | action: 19 | run_playbook: 20 | name: playbooks/hello_events.yml 21 | - name: r3 22 | condition: event.i == 3 23 | action: 24 | retract_fact: 25 | ruleset: Test rules multiple hosts 26 | fact: 27 | j: 3 28 | - name: r4 29 | condition: event.i == 4 30 | action: 31 | post_event: 32 | ruleset: Test rules multiple hosts 33 | event: 34 | j: 4 35 | - name: r5 36 | condition: event.j is defined 37 | action: 38 | none: 39 | -------------------------------------------------------------------------------- /tests/rules/test_rules_multiple_hosts2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rules multiple hosts 2 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/rules/test_rules_multiple_hosts3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rules multiple hosts 3 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | debug: 13 | -------------------------------------------------------------------------------- /tests/rules/test_rules_playbooks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: run playbooks 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: 10 | condition: i == 2 and j == 2 11 | action: 12 | run_playbooks: 13 | - name: playbooks/hello_world_set_fact.yml 14 | -------------------------------------------------------------------------------- /tests/rules/test_set_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test Assert Fact of different data types 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 1 11 | action: 12 | set_fact: 13 | fact: 14 | msg: hello world 15 | alpha: 1 16 | beta: 17 | name: R2D2 18 | location: "{{ Naboo }}" 19 | - name: r2 20 | condition: event.msg == "hello world" 21 | action: 22 | run_playbook: 23 | name: playbooks/validate_args_playbook.yml 24 | extra_vars: 25 | my_vars: 26 | event.msg: hello world 27 | event.alpha: 1 28 | event.beta.name: R2D2 29 | event.beta.location: "{{ Naboo }}" 30 | copy_files: True 31 | -------------------------------------------------------------------------------- /tests/rules/test_simple.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rules simple 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r1 10 | condition: event.i == 0 11 | action: 12 | print_event: 13 | pretty: true 14 | var_root: i 15 | - name: r2 16 | condition: event.i == 1 17 | action: 18 | print_event: 19 | - name: r3 20 | condition: event.i == 2 21 | action: 22 | run_playbook: 23 | name: playbooks/hello_world_set_fact.yml 24 | var_root: i 25 | post_events: true 26 | - name: r4 27 | condition: event.msg is defined 28 | action: 29 | print_event: 30 | 31 | -------------------------------------------------------------------------------- /tests/rules/test_states.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rules4 Prod 3 | hosts: all 4 | sources: 5 | - name: sensu 6 | topic: sensuprod 7 | url: prod 8 | schema: sensu/v1 9 | - name: datadog 10 | topic: datadogprod 11 | schema: datadog/v1 12 | url: prod 13 | states: 14 | - name: Initial 15 | state: dev 16 | rules: 17 | - name: 18 | condition: sensu.data.i == 1 and datadog.header.k == 2 19 | action: 20 | set_fact: 21 | ruleset: Test rules4 Prod 22 | fact: 23 | j: 1 24 | - name: 25 | condition: i == 2 26 | action: 27 | change_state: 28 | name: production 29 | - name: Production 30 | state: production 31 | rules: 32 | - name: 33 | condition: sensu.data.i == 1 and datadog.header.k == 2 34 | action: 35 | set_fact: 36 | ruleset: Test rules4 Prod 37 | fact: 38 | j: 1 39 | - name: 40 | condition: i == 2 41 | action: 42 | change_state: 43 | name: shutdown 44 | - name: Shutdown 45 | state: shutdown 46 | rules: [] 47 | 48 | -------------------------------------------------------------------------------- /tests/rules/webserver_down.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Automatic Remediation of a webserver 3 | hosts: webservers 4 | sources: 5 | - name: ping web server 6 | ping: 7 | ip: "{{gateway_ip}}" 8 | timeout: 10s 9 | repeats_every: 1s 10 | - name: sensu 11 | sensu: 12 | url: "{{sensu_url}}" 13 | token: "{{sensu_token}}" 14 | host_selector: event.sensu.host 15 | rules: 16 | - name: If webserver is down remediate the problem 17 | condition: 18 | all: 19 | - events.ping << event.ping.timeout == true # no host info 20 | - events.process << event.sensu.process.status == "stopped" # web server 21 | - events.database << event.sensu.storage.percent > 95 # database server 22 | timeout: 5 minute 23 | action: 24 | run_playbook: 25 | name: expand_storage.yml 26 | post_events: true 27 | - name: Validate remediation 28 | condition: event.fixed_storage == true 29 | action: 30 | run_playbook: 31 | name: validate_webserver.yml 32 | ... 33 | -------------------------------------------------------------------------------- /tests/run_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | function run { 5 | echo "RUNNING $1" 6 | ansible-rulebook --rulebook "./${1}" --inventory ./playbooks/inventory.yml -S ./sources/ &>/dev/null 7 | if [[ $? -ne 0 ]]; then 8 | echo "PROBLEM with $1" 9 | fi 10 | } 11 | 12 | for file in examples/*.yml; do 13 | run $file 14 | done 15 | -------------------------------------------------------------------------------- /tests/sources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-rulebook/da1bed4a19a08e37eb45394248941156692a304f/tests/sources/__init__.py -------------------------------------------------------------------------------- /tests/sources/hosts.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import asyncio 16 | from typing import Any, Dict 17 | 18 | 19 | async def main(queue: asyncio.Queue, args: Dict[str, Any]): 20 | 21 | for i in range(int(args["limit"])): 22 | await queue.put(dict(i=i, meta=dict(hosts="localhost"))) 23 | 24 | 25 | if __name__ == "__main__": 26 | 27 | class MockQueue: 28 | async def put(self, event): 29 | print(event) 30 | 31 | asyncio.run(main(MockQueue(), dict(limit=5))) 32 | -------------------------------------------------------------------------------- /tests/sources/nested.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import asyncio 16 | from typing import Any, Dict 17 | 18 | 19 | async def main(queue: asyncio.Queue, args: Dict[str, Any]): 20 | delay = args.get("delay", 0) 21 | str_enable = args.get("str_enable", False) 22 | 23 | for i in range(int(args["i_limit"])): 24 | for j in range(int(args["j_limit"])): 25 | payload = {"nested": {"i": i, "j": j}} 26 | if str_enable: 27 | payload = {"nested": {"i": f"{i}", "j": f"{j}"}} 28 | await queue.put(payload) 29 | await asyncio.sleep(delay) 30 | 31 | 32 | if __name__ == "__main__": 33 | 34 | class MockQueue: 35 | async def put(self, event): 36 | print(event) 37 | 38 | asyncio.run(main(MockQueue(), dict(i_limit=5, j_limit=5))) 39 | -------------------------------------------------------------------------------- /tests/sources/ping.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import subprocess 16 | import time 17 | 18 | 19 | def main(queue, args): 20 | 21 | ips = args.get("ips", []) 22 | delay = args.get("delay", 1) 23 | timeout = str(args.get("timeout", 10)) 24 | 25 | if not ips: 26 | return 27 | 28 | while True: 29 | 30 | for ip in ips: 31 | 32 | result = subprocess.call(["ping", "-c", "1", "-t", timeout, ip]) 33 | queue.put( 34 | dict(ping=dict(ip=ip, timeout=result != 0, exit_code=result)) 35 | ) 36 | 37 | time.sleep(delay) 38 | 39 | 40 | if __name__ == "__main__": 41 | 42 | class MockQueue: 43 | def put(self, event): 44 | print(event) 45 | 46 | main(MockQueue(), {"ips": ["127.0.0.1"], "timeout": 1}) 47 | -------------------------------------------------------------------------------- /tests/sources/range.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import asyncio 16 | from typing import Any, Dict 17 | 18 | 19 | async def main(queue: asyncio.Queue, args: Dict[str, Any]): 20 | delay = args.get("delay", 0) 21 | 22 | for i in range(int(args["limit"])): 23 | payload = {"i": i} 24 | await queue.put(payload) 25 | await asyncio.sleep(delay) 26 | 27 | 28 | if __name__ == "__main__": 29 | 30 | class MockQueue: 31 | async def put(self, event): 32 | print(event) 33 | 34 | asyncio.run(main(MockQueue(), dict(limit=5))) 35 | -------------------------------------------------------------------------------- /tests/sources/range2.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import asyncio 16 | from typing import Any, Dict 17 | 18 | 19 | async def main(queue: asyncio.Queue, args: Dict[str, Any]): 20 | delay = args.get("delay", 0) 21 | 22 | for i in range(int(args["limit"])): 23 | await queue.put(dict(range2=dict(i=i))) 24 | await asyncio.sleep(delay) 25 | 26 | 27 | if __name__ == "__main__": 28 | 29 | class MockQueue: 30 | async def put(self, event): 31 | print(event) 32 | 33 | asyncio.run(main(MockQueue(), dict(limit=5))) 34 | -------------------------------------------------------------------------------- /tests/sources/replays/01.json: -------------------------------------------------------------------------------- 1 | { 2 | "i": 0 3 | } 4 | -------------------------------------------------------------------------------- /tests/sources/replays/02.json: -------------------------------------------------------------------------------- 1 | { 2 | "i": 1 3 | } 4 | -------------------------------------------------------------------------------- /tests/sources/replays/03.json: -------------------------------------------------------------------------------- 1 | { 2 | "i": 2 3 | } 4 | -------------------------------------------------------------------------------- /tests/sources/replays/04.json: -------------------------------------------------------------------------------- 1 | { 2 | "i": 3 3 | } 4 | -------------------------------------------------------------------------------- /tests/sources/replays/05.json: -------------------------------------------------------------------------------- 1 | { 2 | "i": 4 3 | } 4 | -------------------------------------------------------------------------------- /tests/sources/source_with_exception.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import asyncio 16 | from typing import Any, Dict 17 | 18 | 19 | class TestException(Exception): 20 | pass 21 | 22 | 23 | async def main(queue: asyncio.Queue, args: Dict[str, Any]): 24 | error_msg = str(args.get("error_msg", "")) 25 | raise TestException(error_msg) 26 | 27 | 28 | if __name__ == "__main__": 29 | 30 | class MockQueue: 31 | async def put(self, event): 32 | print(event) 33 | 34 | asyncio.run(main(MockQueue(), dict(limit=5))) 35 | -------------------------------------------------------------------------------- /tests/sources/tick.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import itertools 16 | import time 17 | 18 | 19 | def main(queue, args): 20 | 21 | for i in itertools.count(start=1): 22 | queue.put(dict(time=dict(tick=i))) 23 | time.sleep(1) 24 | 25 | 26 | if __name__ == "__main__": 27 | 28 | class MockQueue: 29 | def put(self, event): 30 | print(event) 31 | 32 | main(MockQueue(), dict(limit=5)) 33 | -------------------------------------------------------------------------------- /tests/sources/timestamp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import time 16 | from datetime import datetime 17 | 18 | 19 | def main(queue, args): 20 | 21 | while True: 22 | queue.put( 23 | dict( 24 | timestamp=dict( 25 | unix_timestamp=time.mktime(datetime.now().timetuple()) 26 | ) 27 | ) 28 | ) 29 | time.sleep(1) 30 | 31 | 32 | if __name__ == "__main__": 33 | 34 | class MockQueue: 35 | def put(self, event): 36 | print(event) 37 | 38 | main(MockQueue(), dict(limit=5)) 39 | -------------------------------------------------------------------------------- /tests/test_ansible_events.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2022 Red Hat, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Tests for `ansible_rulebook` package.""" 18 | 19 | import pytest 20 | 21 | 22 | @pytest.fixture 23 | def response(): 24 | """Sample pytest fixture. 25 | 26 | See more at: http://doc.pytest.org/en/latest/fixture.html 27 | """ 28 | # import requests 29 | # return requests.get('https://github.com/audreyr/cookiecutter-pypackage') 30 | 31 | 32 | def test_content(response): 33 | """Sample pytest test function with the pytest fixture as an argument.""" 34 | # from bs4 import BeautifulSoup 35 | # assert 'GitHub' in BeautifulSoup(response.content).title.string 36 | -------------------------------------------------------------------------------- /tests/test_simple.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test rules simple 3 | hosts: all 4 | sources: 5 | - name: range 6 | range: 7 | limit: 5 8 | rules: 9 | - name: r0 10 | condition: event.i == 0 11 | action: 12 | print_event: 13 | pretty: true 14 | var_root: i 15 | - name: r1 16 | condition: event.i == 1 17 | action: 18 | print_event: 19 | - name: r2 20 | condition: event.i == 2 21 | action: 22 | run_playbook: 23 | name: playbooks/hello_world_set_fact.yml 24 | var_root: i 25 | post_events: true 26 | - name: r3 27 | condition: event.msg is defined 28 | action: 29 | print_event: 30 | 31 | -------------------------------------------------------------------------------- /tests/unit/action/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-rulebook/da1bed4a19a08e37eb45394248941156692a304f/tests/unit/action/__init__.py -------------------------------------------------------------------------------- /tests/unit/action/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | from ansible_rulebook.action.control import Control 6 | from ansible_rulebook.action.metadata import Metadata 7 | 8 | 9 | @pytest.fixture 10 | def base_metadata(): 11 | return Metadata( 12 | rule="r1", 13 | rule_set="rs1", 14 | rule_uuid="u1", 15 | rule_set_uuid="u2", 16 | rule_run_at="abc", 17 | ) 18 | 19 | 20 | # TODO : Seems like asyncio.Queue() in a fixture is causing test failure 21 | @pytest.fixture 22 | def base_control(): 23 | return Control( 24 | queue=asyncio.Queue(), 25 | inventory="abc", 26 | hosts=["all"], 27 | variables={"a": 1}, 28 | project_data_file="", 29 | ) 30 | -------------------------------------------------------------------------------- /tests/unit/action/playbooks/fail.yml: -------------------------------------------------------------------------------- 1 | - name: Fail the rule 2 | hosts: all 3 | gather_facts: false 4 | tasks: 5 | - name: Fail if we have a rule name defined 6 | when: ansible_eda.rule is defined 7 | ansible.builtin.fail: 8 | msg: "Failed because of Rule name: {{ ansible_eda.rule }}" 9 | -------------------------------------------------------------------------------- /tests/unit/action/playbooks/rule_name.yml: -------------------------------------------------------------------------------- 1 | - name: Print rule name that called this playbook 2 | hosts: all 3 | gather_facts: false 4 | tasks: 5 | - name: Print rule name 6 | when: ansible_eda.rule is defined 7 | ansible.builtin.debug: 8 | msg: "Rule name: {{ ansible_eda.rule }}" 9 | - name: Set the RuleName as a fact 10 | ansible.builtin.set_fact: 11 | results: 12 | my_rule_name: "{{ ansible_eda.rule }}" 13 | my_rule_set_name: "{{ ansible_eda.ruleset }}" 14 | cacheable: true 15 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = flake8,py39,py310,py311,py312 3 | isolated_build = True 4 | 5 | [travis] 6 | python = 7 | 3.9: py39 8 | 3.10: py310 9 | 3.11: py311 10 | 3.12: py312 11 | 12 | [testenv:flake8] 13 | basepython = python 14 | deps = flake8==5.0.4 15 | commands = flake8 ansible_rulebook tests 16 | 17 | [testenv] 18 | passenv = JAVA_HOME 19 | setenv = 20 | PYTHONPATH = {toxinidir} 21 | deps = 22 | -r{toxinidir}/requirements_dev.txt 23 | ; If you want to make tox run the tests with the same versions, create a 24 | ; requirements.txt with the pinned versions and uncomment the following line: 25 | ; -r{toxinidir}/requirements.txt 26 | commands = 27 | pip install -U pip 28 | pytest --basetemp={envtmpdir} 29 | 30 | --------------------------------------------------------------------------------