-
Notifications
You must be signed in to change notification settings - Fork 77
Add aux_tag module #892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add aux_tag module #892
Changes from all commits
c0b0909
c509261
c3072de
5615c02
0c3ff53
6109b44
44de1c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # README: | ||
| # - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! | ||
| # | ||
| # Resources: | ||
| # - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml | ||
| # - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html | ||
|
|
||
| env: | ||
| NAMESPACE: checkmk | ||
| COLLECTION_NAME: general | ||
| MODULE_NAME: aux_tag | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| name: Ansible Integration Tests for Aux Tag Module | ||
| on: | ||
| workflow_dispatch: | ||
| schedule: | ||
| - cron: '0 0 * * 0' | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| - devel | ||
| paths: | ||
| - 'plugins/modules/aux_tag.py' | ||
| push: | ||
| paths: | ||
| - '.github/workflows/ans-int-test-aux_tag.yaml' | ||
| - 'plugins/modules/aux_tag.py' | ||
| - 'tests/integration/files/includes/' | ||
| - 'tests/integration/targets/aux_tag/' | ||
|
|
||
| jobs: | ||
|
|
||
| integration: | ||
| runs-on: ubuntu-24.04 | ||
| name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| ansible: | ||
| - stable-2.17 | ||
| - stable-2.18 | ||
| - stable-2.19 | ||
| - devel | ||
| python: | ||
| - '3.11' | ||
| - '3.12' | ||
| exclude: | ||
| # Exclude unsupported sets. | ||
| - ansible: devel | ||
| python: '3.11' | ||
|
|
||
| services: | ||
| ancient_cre: | ||
| image: checkmk/check-mk-raw:2.2.0p45 | ||
| ports: | ||
| - 5022:5000 | ||
| env: | ||
| CMK_SITE_ID: "ancient_cre" | ||
| CMK_PASSWORD: "Sup3rSec4et!" | ||
| old_cre: | ||
| image: checkmk/check-mk-raw:2.3.0p36 | ||
| ports: | ||
| - 5023:5000 | ||
| env: | ||
| CMK_SITE_ID: "old_cre" | ||
| CMK_PASSWORD: "Sup3rSec4et!" | ||
| old_cme: | ||
| image: checkmk/check-mk-managed:2.3.0p36 | ||
| ports: | ||
| - 5323:5000 | ||
| env: | ||
| CMK_SITE_ID: "old_cme" | ||
| CMK_PASSWORD: "Sup3rSec4et!" | ||
| stable_cre: | ||
| image: checkmk/check-mk-raw:2.4.0p10 | ||
| ports: | ||
| - 5024:5000 | ||
| env: | ||
| CMK_SITE_ID: "stable_cre" | ||
| CMK_PASSWORD: "Sup3rSec4et!" | ||
| stable_cme: | ||
| image: checkmk/check-mk-managed:2.4.0p10 | ||
| ports: | ||
| - 5324:5000 | ||
| env: | ||
| CMK_SITE_ID: "stable_cme" | ||
| CMK_PASSWORD: "Sup3rSec4et!" | ||
|
|
||
| steps: | ||
| - name: Check out code | ||
| uses: actions/checkout@v5 | ||
| with: | ||
| path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} | ||
|
|
||
| - name: "Install uv and set the python version." | ||
| uses: astral-sh/setup-uv@v6 | ||
| with: | ||
| python-version: ${{ matrix.python }} | ||
| enable-cache: true | ||
| working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}}/ | ||
|
|
||
| - name: "Setup uv venv." | ||
| run: uv venv | ||
| working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} | ||
|
|
||
| - name: Install ansible-base (${{ matrix.ansible }}) | ||
| run: uv pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz | ||
| working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} | ||
|
|
||
| - name: Run integration test | ||
| run: uv run ansible-test integration ${{env.MODULE_NAME}} -v --color --continue-on-error --diff --python ${{ matrix.python }} | ||
| working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| minor_changes: | ||
| - aux_tag module - Add new module to manage auxiliary tags in Checkmk. | ||
| Auxiliary tags can be created, updated, and deleted independently, making it easier to manage tag hierarchies and dependencies. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| #!/usr/bin/python | ||
| # -*- encoding: utf-8; py-indent-offset: 4 -*- | ||
|
|
||
| # Copyright: (c) 2025, Nicolas Brainez <[email protected]> | ||
| # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
| from __future__ import absolute_import, division, print_function | ||
|
|
||
| __metaclass__ = type | ||
|
|
||
| DOCUMENTATION = r""" | ||
| --- | ||
| module: aux_tag | ||
|
|
||
| short_description: Manage auxiliary tags in Checkmk. | ||
|
|
||
| version_added: "6.3.0" | ||
|
|
||
| description: | ||
| - Manage auxiliary tags in Checkmk. | ||
|
|
||
| extends_documentation_fragment: [checkmk.general.common] | ||
|
|
||
| options: | ||
| name: | ||
| description: The ID of the auxiliary tag. | ||
| required: true | ||
| type: str | ||
| aliases: ["id"] | ||
|
|
||
| title: | ||
| description: The title of the auxiliary tag. | ||
| required: false | ||
| type: str | ||
|
|
||
| topic: | ||
| description: The topic or category of the auxiliary tag. | ||
| required: false | ||
| type: str | ||
|
|
||
| help: | ||
| description: Help text describing the auxiliary tag. | ||
| required: false | ||
| type: str | ||
|
|
||
| state: | ||
| description: The desired state. | ||
| required: true | ||
| choices: ["present", "absent"] | ||
| type: str | ||
|
|
||
| author: | ||
| - Nicolas Brainez (@nicoske) | ||
| """ | ||
|
|
||
| EXAMPLES = r""" | ||
| # Create an auxiliary tag | ||
| - name: "Create auxiliary tag for HTTPS" | ||
| checkmk.general.aux_tag: | ||
| server_url: "http://myserver/" | ||
| site: "mysite" | ||
| automation_user: "myuser" | ||
| automation_secret: "mysecret" | ||
| name: https | ||
| title: Web Server HTTPS | ||
| topic: Services | ||
| help: "Host provides HTTPS services" | ||
| state: "present" | ||
|
|
||
| # Update an auxiliary tag | ||
| - name: "Update auxiliary tag" | ||
| checkmk.general.aux_tag: | ||
| server_url: "http://myserver/" | ||
| site: "mysite" | ||
| automation_user: "myuser" | ||
| automation_secret: "mysecret" | ||
| name: https | ||
| title: Web Server HTTPS/TLS | ||
| topic: Services | ||
| state: "present" | ||
|
|
||
| # Delete an auxiliary tag | ||
| - name: "Delete auxiliary tag" | ||
| checkmk.general.aux_tag: | ||
| server_url: "http://myserver/" | ||
| site: "mysite" | ||
| automation_user: "myuser" | ||
| automation_secret: "mysecret" | ||
| name: https | ||
| state: "absent" | ||
| """ | ||
|
|
||
| RETURN = r""" | ||
| http_code: | ||
| description: The HTTP code the Checkmk API returns. | ||
| type: int | ||
| returned: always | ||
| sample: '200' | ||
| message: | ||
| description: The output message that the module generates. | ||
| type: str | ||
| returned: always | ||
| sample: 'Done.' | ||
| """ | ||
|
|
||
| import time | ||
|
|
||
| from ansible.module_utils.basic import AnsibleModule | ||
| from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI | ||
| from ansible_collections.checkmk.general.plugins.module_utils.types import RESULT | ||
| from ansible_collections.checkmk.general.plugins.module_utils.utils import ( | ||
| base_argument_spec, | ||
| result_as_dict, | ||
| ) | ||
|
|
||
| # We count 404 not as failed, because we want to know if the aux tag exists or not. | ||
| HTTP_CODES_GET = { | ||
| # http_code: (changed, failed, "Message") | ||
| 404: (False, False, "Not Found: The requested object has not been found."), | ||
| } | ||
|
|
||
| HTTP_CODES_DELETE = { | ||
| # http_code: (changed, failed, "Message") | ||
| 404: (False, False, "Not Found: The requested object has not been found."), | ||
| } | ||
|
|
||
|
|
||
| class AuxTagCreateAPI(CheckmkAPI): | ||
| def post(self): # Create aux tag | ||
| data = { | ||
| "aux_tag_id": self.params.get("name", ""), | ||
| "title": self.params.get("title", ""), | ||
| "topic": self.params.get("topic", ""), | ||
| "help": self.params.get("help", ""), | ||
| } | ||
|
|
||
| # Remove all keys without value, as otherwise they would be None. | ||
| data = {key: val for key, val in data.items() if val} | ||
|
|
||
| return self._fetch( | ||
| endpoint="/domain-types/aux_tag/collections/all", | ||
| data=data, | ||
| method="POST", | ||
| ) | ||
|
|
||
|
|
||
| class AuxTagUpdateAPI(CheckmkAPI): | ||
| def put(self): # Update aux tag | ||
| data = { | ||
| "title": self.params.get("title", ""), | ||
| "topic": self.params.get("topic", ""), | ||
| "help": self.params.get("help", ""), | ||
| } | ||
|
|
||
| # Remove all keys without value, as they would be emptied. | ||
| data = {key: val for key, val in data.items() if val} | ||
|
|
||
| return self._fetch( | ||
| endpoint="/objects/aux_tag/%s" % self.params.get("name"), | ||
| data=data, | ||
| method="PUT", | ||
| ) | ||
|
|
||
|
|
||
| class AuxTagDeleteAPI(CheckmkAPI): | ||
| def delete(self): # Remove aux tag | ||
| return self._fetch( | ||
| code_mapping=HTTP_CODES_DELETE, | ||
| endpoint="/objects/aux_tag/%s" % self.params.get("name"), | ||
| method="DELETE", | ||
| ) | ||
|
|
||
|
|
||
| class AuxTagGetAPI(CheckmkAPI): | ||
| def get(self): | ||
| return self._fetch( | ||
| code_mapping=HTTP_CODES_GET, | ||
| endpoint="/objects/aux_tag/%s" % self.params.get("name"), | ||
| method="GET", | ||
| ) | ||
|
|
||
|
|
||
|
Comment on lines
+164
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I don't get the idea, but why have you created four ChecckmkAPI classes? I usually created only one, which has a put, get and delete method. |
||
| def run_module(): | ||
| argument_spec = base_argument_spec() | ||
| argument_spec.update( | ||
| name=dict(type="str", required=True, aliases=["id"]), | ||
| title=dict(type="str", required=False), | ||
| topic=dict(type="str", required=False), | ||
| help=dict(type="str", required=False), | ||
| state=dict( | ||
| type="str", | ||
| choices=["present", "absent"], | ||
| required=True, | ||
| ), | ||
| ) | ||
|
|
||
| module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) | ||
|
|
||
| result = RESULT( | ||
| http_code=0, | ||
| msg="Nothing to be done", | ||
| content="", | ||
| etag="", | ||
| failed=False, | ||
| changed=False, | ||
| ) | ||
|
|
||
| if module.params.get("state") == "present": | ||
| auxtagget = AuxTagGetAPI(module) | ||
| result = auxtagget.get() | ||
|
|
||
| if result.http_code == 200: | ||
| auxtagupdate = AuxTagUpdateAPI(module) | ||
| auxtagupdate.headers["If-Match"] = result.etag | ||
| result = auxtagupdate.put() | ||
|
Comment on lines
+212
to
+214
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be cool if you only update the aux tag if something has changed. (-> Idempotency). |
||
|
|
||
| time.sleep(3) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the sleep needed to avoid race conditions? |
||
|
|
||
| elif result.http_code == 404: | ||
| auxtagcreate = AuxTagCreateAPI(module) | ||
| result = auxtagcreate.post() | ||
|
|
||
| time.sleep(3) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
|
|
||
| if module.params.get("state") == "absent": | ||
| # Only delete if the aux tag exists | ||
| auxtagget = AuxTagGetAPI(module) | ||
| result = auxtagget.get() | ||
|
|
||
| if result.http_code == 200: | ||
| auxtagdelete = AuxTagDeleteAPI(module) | ||
| auxtagdelete.headers["If-Match"] = result.etag | ||
| result = auxtagdelete.delete() | ||
|
|
||
| time.sleep(3) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
|
|
||
| module.exit_json(**result_as_dict(result)) | ||
|
|
||
|
|
||
| def main(): | ||
| run_module() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails in my 2.4.0p14 environment, as deleting is done with a POST on the endpoint /objects/aux_tag/<aux_tag_name>/actions/delete/invoke.