From 1fdb83e9ab91495e897c111b6183878bf4636152 Mon Sep 17 00:00:00 2001 From: Manuel Warum Date: Mon, 22 Apr 2024 09:28:27 +0200 Subject: [PATCH] CA-9156: Add ansible test and linting workflows (#35) * CA-9156: add workflow for Ansible tests * CA-9156: drop duplicate empty line * CA-9156: add linting workflow * CA-9156: add newlines * CA-9156: add linebreak * CA-9156: address more linter violations * CA-9156: address more linter violations * CA-9156: fix linter issues in tests * CA-9156: fix ansible sanity tests * CA-9156: fix one more sanity test * CA-9156: add requirements for python * CA-9156: add working directory * CA-9156: change install requirements command * CA-9156: add integration tests * CA-9156: change port in integration test * CA-9156: add debug log level * CA-9156: fix log-cli-level * CA-9156: remove integration tests * CA-9156: add ansibl lint config with production profile * CA-9156: initialize changelog * CA-9156: add changelog fragment * CA-9156: fix fragment * CA-9156: adapt README * CA-9156: fix typo * CA-9156: add code of conduct * CA-9156: change description and add docs link * CA-9156: fix linter problems --------- Co-authored-by: Dietmar --- .ansible-lint.yml | 7 + .github/workflows/ah_token_refresh.yml | 2 +- .github/workflows/ansible_lint.yml | 15 ++ .github/workflows/ansible_test.yml | 45 ++++++ .github/workflows/release.yml | 2 +- .github/workflows/tox.yml | 4 +- .gitignore | 5 +- CHANGELOG.md | 1 + CODE_OF_CONDUCT.md | 132 ++++++++++++++++++ README.md | 38 ++++- changelogs/config.yaml | 34 +++++ ...9156-add-ansible-lint-and-ansible-test.yml | 3 + docs/dt_webhook.md | 3 +- .../eda/plugins/event_source/dt_esa_api.py | 1 + .../eda/plugins/event_source/dt_webhook.py | 2 +- galaxy.yml | 11 +- inventory.yml | 2 +- meta/runtime.yml | 2 +- playbooks/run-task.yml | 7 +- requirements.txt | 1 + rulebooks/dt_webhook_event_example_rule.yml | 10 +- test_requirements.txt | 6 + tests/integration/test_dt_webhook.py | 9 +- tests/unit/requirements.txt | 4 + tests/unit/test_dt_webhook.py | 6 +- 25 files changed, 323 insertions(+), 29 deletions(-) create mode 100644 .ansible-lint.yml create mode 100644 .github/workflows/ansible_lint.yml create mode 100644 .github/workflows/ansible_test.yml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 changelogs/config.yaml create mode 100644 changelogs/fragments/CA-9156-add-ansible-lint-and-ansible-test.yml create mode 100644 requirements.txt create mode 100644 test_requirements.txt create mode 100644 tests/unit/requirements.txt diff --git a/.ansible-lint.yml b/.ansible-lint.yml new file mode 100644 index 0000000..83a9af8 --- /dev/null +++ b/.ansible-lint.yml @@ -0,0 +1,7 @@ +--- +# .ansible-lint configuration + +profile: production # null, min, basic, moderate,safety, shared, production + +warn_list: + - experimental diff --git a/.github/workflows/ah_token_refresh.yml b/.github/workflows/ah_token_refresh.yml index b16df69..e324bb0 100644 --- a/.github/workflows/ah_token_refresh.yml +++ b/.github/workflows/ah_token_refresh.yml @@ -12,4 +12,4 @@ jobs: with: environment: release secrets: - ah_token: ${{ secrets.AH_TOKEN }} \ No newline at end of file + ah_token: ${{ secrets.AH_TOKEN }} diff --git a/.github/workflows/ansible_lint.yml b/.github/workflows/ansible_lint.yml new file mode 100644 index 0000000..c45a143 --- /dev/null +++ b/.github/workflows/ansible_lint.yml @@ -0,0 +1,15 @@ +name: Run Ansible linter +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + build: + name: Ansible Lint # Naming the build is important to use it as a status check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run ansible-lint + uses: ansible/ansible-lint@main # or version tag instead of 'main' diff --git a/.github/workflows/ansible_test.yml b/.github/workflows/ansible_test.yml new file mode 100644 index 0000000..113a048 --- /dev/null +++ b/.github/workflows/ansible_test.yml @@ -0,0 +1,45 @@ +name: Run Ansible tests +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: + - "3.10" + - "3.9" + + defaults: + run: + working-directory: ansible_collections/ + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: ansible_collections/ansible/eda + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install test requirements + run: | + pip install -U pip + pip install -r test_requirements.txt + working-directory: ansible_collections/ansible/eda + + - name: Run sanity tests + run: ansible-test sanity + working-directory: ansible_collections/ansible/eda + + - name: Run unit tests + run: ansible-test units --venv -v --num-workers 1 + working-directory: ansible_collections/ansible/eda diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1fce893..7cb7b9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,4 +11,4 @@ jobs: environment: release secrets: ah_token: ${{ secrets.AH_TOKEN }} - ansible_galaxy_api_key: ${{ secrets.ANSIBLE_GALAXY_API_KEY }} \ No newline at end of file + ansible_galaxy_api_key: ${{ secrets.ANSIBLE_GALAXY_API_KEY }} diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 15d6bb2..a70087e 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -9,6 +9,6 @@ jobs: - name: Install deps run: python -m pip install tox - name: Move to tox conf file and run tox - run: | + run: | cd .github/workflows - python -m tox -- ../.. \ No newline at end of file + python -m tox -- ../.. diff --git a/.gitignore b/.gitignore index f77b6b4..56533ff 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ tests/output .tox releases/* vars.yml -*__pycac \ No newline at end of file +*__pycac + +# Antsibull-changelog +changelogs/.plugin-cache.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a9acf09 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Dynatrace\.Event\_Driven\_Ansible Release Notes diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..de9a90a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +opensource@dynatrace.com. All complaints will be reviewed and investigated +promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/README.md b/README.md index c901e47..aef0934 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,21 @@ -# Dynatrace + Red Hat Event Driven Ansible: Auto-Remediation +# Dynatrace + Red Hat Event Driven Ansible + +This collection contains the following Event-Driven Ansible source plugins: + + * dt_webhook + * dt_esa_api + +## Auto-Remediation (dt_esa_api) This Event source plugin from Dynatrace captures all problems from your Dynatrace tenant and in conjunction with Ansible EDA rulebooks helps to enable auto-remediation in your environment. -## Requirements +### Requirements * Dynatrace SaaS or Managed environment * Dynatrace API Token with the following scopes: `Read problems` and `Write problems` * Ansible Automation Platform with EDA Controller instance -## Example rulebook +### Example rulebook ```yaml --- @@ -43,6 +50,31 @@ This Event source plugin from Dynatrace captures all problems from your Dynatrac name: dt-update-comments.yml ``` +## dt_webhook + +The dt_webhook event-source plugin is capable of receiving events from Dynatrace via the "Send event to Event-Driven Ansible" workflow action of the [Red Hat Ansible for Workflows integration](https://docs.dynatrace.com/docs/platform-modules/automations/workflows/actions/redhat). + +### Example rulebook + + ```yaml + --- + - name: Listen for events on dt_webhook + hosts: all + sources: + - dynatrace.event_driven_ansible.dt_webhook: + host: 0.0.0.0 + port: 5000 + token: '{{ }}' + + rules: + - name: API Endpoint not available + condition: event.payload.eventData["event.name"] is match ("Monitoring not available") + action: + run_job_template: + name: "Trigger test playbook" + organization: "Default" + ``` + ## Licensing We are using Apache License 2.0 as our default. diff --git a/changelogs/config.yaml b/changelogs/config.yaml new file mode 100644 index 0000000..f01b2ac --- /dev/null +++ b/changelogs/config.yaml @@ -0,0 +1,34 @@ +changelog_filename_template: ../CHANGELOG.md +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +ignore_other_fragment_extensions: true +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +output_formats: + - md +prelude_section_name: release_summary +prelude_section_title: Release Summary +sanitize_changelog: true +sections: + - - major_changes + - Major Changes + - - minor_changes + - Minor Changes + - - breaking_changes + - Breaking Changes / Porting Guide + - - deprecated_features + - Deprecated Features + - - removed_features + - Removed Features (previously deprecated) + - - security_fixes + - Security Fixes + - - bugfixes + - Bugfixes + - - known_issues + - Known Issues +title: Dynatrace.Event_Driven_Ansible +trivial_section_name: trivial +use_fqcn: true diff --git a/changelogs/fragments/CA-9156-add-ansible-lint-and-ansible-test.yml b/changelogs/fragments/CA-9156-add-ansible-lint-and-ansible-test.yml new file mode 100644 index 0000000..fb4d6f8 --- /dev/null +++ b/changelogs/fragments/CA-9156-add-ansible-lint-and-ansible-test.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Set up ansible-lint and ansible-test for collection. diff --git a/docs/dt_webhook.md b/docs/dt_webhook.md index 4d1e18a..8f9f9a2 100644 --- a/docs/dt_webhook.md +++ b/docs/dt_webhook.md @@ -90,7 +90,8 @@ Steps to do: * Checkout the GitHub repository -* Create a vars.yml file in the rulebooks directory and configure the variable var_eda_token. This “token variable” will then be used when running the rulebook and passed to the event-source plugin. Hint: Currently the token configuration is done by setting a variable. This will be changed in the future. +* Create a vars.yml file in the rulebooks directory and configure the variable var_eda_token. This "token variable" will then be used when running the + rulebook and passed to the event-source plugin. Hint: Currently the token configuration is done by setting a variable. This will be changed in the future. ``` var_eda_token: diff --git a/extensions/eda/plugins/event_source/dt_esa_api.py b/extensions/eda/plugins/event_source/dt_esa_api.py index b8304d8..fd836c3 100644 --- a/extensions/eda/plugins/event_source/dt_esa_api.py +++ b/extensions/eda/plugins/event_source/dt_esa_api.py @@ -83,6 +83,7 @@ async def updatedtproblem(prob_id: str, dtapihost: str, dtapitoken: str, except aiohttp.ClientError: logging.exception("aiohttp client Exception") + async def main(queue: asyncio.Queue, args: dict[str, Any]) -> None: """Process the problem information. diff --git a/extensions/eda/plugins/event_source/dt_webhook.py b/extensions/eda/plugins/event_source/dt_webhook.py index 07bbaea..9276e6d 100644 --- a/extensions/eda/plugins/event_source/dt_webhook.py +++ b/extensions/eda/plugins/event_source/dt_webhook.py @@ -3,7 +3,7 @@ Description: This is an event source plugin for receiving events via a webhook -from the "send-event-to-eda" action of Dynatrace Workflows. +from the "send-event-to-eda" action of the Red Hat Ansible for Workflows integration. The payload must be a valid JSON object. Usage in a rulebook: diff --git a/galaxy.yml b/galaxy.yml index 46bd8f6..82f4b21 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -16,21 +16,24 @@ readme: README.md # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) # @nicks:irc/im.site#channel' authors: -- Dynatrace + - Dynatrace ### OPTIONAL but strongly recommended # A short summary description of the collection -description: This Event source plugin from Dynatrace captures all problems from your Dynatrace tenant and in conjunction with Ansible EDA rulebooks helps to enable auto-remediation in your environment. +description: | + This collection contains two event source plugins from Dynatrace. The dt_esa_api plugin captures all problems from your Dynatrace tenant + and in conjunction with Ansible EDA rulebooks helps to enable auto-remediation in your environment. The dt_webhook plugin listens for + events sent by the Red Hat Ansible for workflows integration from Dynatrace. # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' license: -- Apache-2.0 + - Apache-2.0 # The path to the license file for the collection. This path is relative to the root of the collection. This key is # mutually exclusive with 'license' -#license_file: '' +# license_file: '' # Must also include one of the following: application, cloud, database, infrastructure, linux, monitoring, networking, security, storage, tools, windows tags: [dynatrace, eda, monitoring, tools, cloud] diff --git a/inventory.yml b/inventory.yml index 412a660..5c924bf 100644 --- a/inventory.yml +++ b/inventory.yml @@ -3,4 +3,4 @@ all: hosts: localhost: ansible_connection: local - ansible_python_interpreter: /usr/bin/python3 \ No newline at end of file + ansible_python_interpreter: /usr/bin/python3 diff --git a/meta/runtime.yml b/meta/runtime.yml index cb11260..cbf39f3 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,4 +1,4 @@ --- # Collections must specify a minimum required ansible version to upload # to galaxy -requires_ansible: '>=2.14.0' \ No newline at end of file +requires_ansible: '>=2.14.0' diff --git a/playbooks/run-task.yml b/playbooks/run-task.yml index 6659765..b39ae09 100644 --- a/playbooks/run-task.yml +++ b/playbooks/run-task.yml @@ -1,11 +1,10 @@ --- -- name: local event-driven-ansible test +- name: Local event-driven-ansible test hosts: localhost tasks: - name: Print Dynatrace event - debug: + ansible.builtin.debug: msg: "{{ ansible_eda.event.payload }}" - name: "Here could be your ansible task for resolving the problem" - debug: + ansible.builtin.debug: msg: "Resolve problem for the following affected entities {{ ansible_eda.event.payload.eventData.affected_entity_ids }}" - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ee4ba4f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +aiohttp diff --git a/rulebooks/dt_webhook_event_example_rule.yml b/rulebooks/dt_webhook_event_example_rule.yml index e17faec..f102469 100644 --- a/rulebooks/dt_webhook_event_example_rule.yml +++ b/rulebooks/dt_webhook_event_example_rule.yml @@ -2,10 +2,10 @@ - name: Listen for events on dt_webhook hosts: all sources: - - dynatrace.event_driven_ansible.dt_webhook: #use dt_webhook as source for tests with local ansible-rulebook + - dynatrace.event_driven_ansible.dt_webhook: # use dt_webhook as source for tests with local ansible-rulebook host: 0.0.0.0 port: 6009 - token: '{{ var_eda_token }}' + token: '{{ var_eda_token }}' rules: - name: API Endpoint not available @@ -13,6 +13,6 @@ action: run_job_template: name: "Trigger test playbook" - organization: "Default" - #run_playbook: #use run_playbook for local tests with ansible-rulebook - #name: playbooks/run-task.yml + organization: "Default" + # run_playbook: # use run_playbook for local tests with ansible-rulebook + # name: playbooks/run-task.yml diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 0000000..d31b727 --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,6 @@ +-r requirements.txt +pytest +pytest-asyncio +ansible +ansible-rulebook +tox \ No newline at end of file diff --git a/tests/integration/test_dt_webhook.py b/tests/integration/test_dt_webhook.py index cf3de62..356495f 100644 --- a/tests/integration/test_dt_webhook.py +++ b/tests/integration/test_dt_webhook.py @@ -12,33 +12,35 @@ args = { "host": "127.0.0.1", - "port": 1234, + "port": 5000, "token": "thisisnotanactualtoken", } url = f'http://{args["host"]}:{args["port"]}/event' payload = json.dumps({"eventId": "1A2B3C"}) headers = {"Authorization": "Bearer " + args["token"]} + async def run_webhook() -> None: # noqa: FA102, D103 """Start webhook.""" await dt_webhook(asyncio.Queue(), args) + @pytest.mark.asyncio async def test_with_incorrect_path(): """When posting data to an incorrect path, HTTP 404 Not Found should be returned""" async def do_request(): async with aiohttp.ClientSession(headers=headers) as session: async with session.post(f'http://{args["host"]}:{args["port"]}/something', data=payload) as resp: - # task_cancel() stops the plugin coroutine which is wrapped into the task will never + # task_cancel() stops the plugin coroutine which is wrapped into the task will never # stop and also the test will not be terminated. plugin_task.cancel() assert resp.status == HTTPStatus.NOT_FOUND - plugin_task = asyncio.create_task(run_webhook()) request_task = asyncio.create_task(do_request()) await asyncio.gather(plugin_task, request_task) + @pytest.mark.asyncio async def test_event_body_valid_json(): """When posting valid JSON data, HTTP 200 OK with an empty JSON should be returned""" @@ -54,6 +56,7 @@ async def do_request(): request_task = asyncio.create_task(do_request()) await asyncio.gather(plugin_task, request_task) + @pytest.mark.asyncio async def test_event_body_with_invalid_json(): """When posting invalid JSON data, HTTP 400 BAD REQUEST should be returned""" diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt new file mode 100644 index 0000000..b61d959 --- /dev/null +++ b/tests/unit/requirements.txt @@ -0,0 +1,4 @@ +aiohttp +asyncio +pytest +pytest-asyncio diff --git a/tests/unit/test_dt_webhook.py b/tests/unit/test_dt_webhook.py index 3e73b1f..36770a9 100644 --- a/tests/unit/test_dt_webhook.py +++ b/tests/unit/test_dt_webhook.py @@ -14,10 +14,12 @@ "token": "thisisnotanactualtoken", } + def test_parse_token_with_incorrect_token(): with pytest.raises(web.HTTPUnauthorized, match="Invalid authorization token"): _parse_auth_header("Bearer", "thisisnotanactualtoken!", args["token"]) + def test_parse_token_invalid_auth_type(): with pytest.raises(web.HTTPUnauthorized, match="Authorization type Token is not allowed"): _parse_auth_header("Token", "thisisnotanactualtoken!", args["token"]) @@ -37,6 +39,7 @@ def test_set_app_attributes_without_port(): "token": "thisisnotanactualtoken", }) + def test_set_app_attributes_without_host(): with pytest.raises(ValueError, match="Host is missing as an argument"): _set_app_attributes({ @@ -44,9 +47,10 @@ def test_set_app_attributes_without_host(): "token": "thisisnotanactualtoken", }) + def test_set_app_attributes_without_token(): with pytest.raises(ValueError, match="Token is missing as an argument"): _set_app_attributes({ "host": "127.0.0.1", "port": "1234", - }) \ No newline at end of file + })