From ccd5d8adff3c2a94c49c7fb8d79d670831bff1e6 Mon Sep 17 00:00:00 2001 From: lucasheld Date: Fri, 30 Dec 2022 21:27:59 +0100 Subject: [PATCH] feat: add support for uptime kuma 1.19.2 --- README.md | 4 +- plugins/module_utils/common.py | 7 + plugins/modules/maintenance.py | 319 ++++++++++++++++++ plugins/modules/maintenance_info.py | 201 +++++++++++ plugins/modules/monitor.py | 40 ++- plugins/modules/monitor_info.py | 40 +++ plugins/modules/notification_info.py | 32 ++ plugins/modules/proxy.py | 3 +- plugins/modules/settings.py | 48 +++ plugins/modules/settings_info.py | 10 + plugins/modules/status_page_info.py | 7 +- run_tests.sh | 16 +- .../plugins/module_utils/module_test_case.py | 28 +- .../plugins/module_utils/test_docker_host.py | 5 +- tests/unit/plugins/module_utils/test_login.py | 4 +- .../plugins/module_utils/test_maintenance.py | 153 +++++++++ .../module_utils/test_maintenance_info.py | 48 +++ .../unit/plugins/module_utils/test_monitor.py | 11 +- .../plugins/module_utils/test_settings.py | 2 + .../module_utils/test_settings_info.py | 2 +- .../plugins/module_utils/test_status_page.py | 6 +- 21 files changed, 956 insertions(+), 30 deletions(-) create mode 100644 plugins/modules/maintenance.py create mode 100644 plugins/modules/maintenance_info.py create mode 100644 tests/unit/plugins/module_utils/test_maintenance.py create mode 100644 tests/unit/plugins/module_utils/test_maintenance_info.py diff --git a/README.md b/README.md index 2a0ead4..8cef58e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This collection contains modules that allow to configure [Uptime Kuma](https://g Python version 3.6+ is required. -Supported Uptime Kuma versions: 1.17.0 - 1.18.5 +Supported Uptime Kuma versions: 1.17.0 - 1.19.2 ## Installation @@ -39,6 +39,8 @@ The following modules are available: - [docker_host_info](https://github.com/lucasheld/ansible-uptime-kuma/wiki/docker_host_info) - [settings](https://github.com/lucasheld/ansible-uptime-kuma/wiki/settings) - [settings_info](https://github.com/lucasheld/ansible-uptime-kuma/wiki/settings_info) +- [maintenance](https://github.com/lucasheld/ansible-uptime-kuma/wiki/maintenance) +- [maintenance_info](https://github.com/lucasheld/ansible-uptime-kuma/wiki/maintenance_info) ## Getting started diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 0006a6e..d65def8 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -86,6 +86,13 @@ def get_docker_host_by_name(api, name): return docker_host +def get_maintenance_by_title(api, title): + maintenances = api.get_maintenances() + for maintenance in maintenances: + if maintenance["title"] == title: + return maintenance + + common_module_args = dict( api_url=dict(type="str", default="http://127.0.0.1:3001"), api_username=dict(type="str"), diff --git a/plugins/modules/maintenance.py b/plugins/modules/maintenance.py new file mode 100644 index 0000000..0b248f2 --- /dev/null +++ b/plugins/modules/maintenance.py @@ -0,0 +1,319 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Lucas Held +# 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 + +import datetime + +DOCUMENTATION = r''' +--- +extends_documentation_fragment: + - lucasheld.uptime_kuma.uptime_kuma + +module: maintenance +author: Lucas Held (@lucasheld) +short_description: Manages maintenances. +description: Manages maintenances. + +options: + id: + description: + - The id of the maintenance. + - Only required if no I(title) specified. + type: int + title: + description: + - The title of the maintenance. + - Only required if no I(id) specified. + type: str + strategy: + description: The strategy of the maintenance. + type: str + choices: ["manual", "single", "recurring-interval", "recurring-weekday", "recurring-day-of-month"] + active: + description: True if the maintenance is active. + type: bool + description: + description: The description of the maintenance. + type: str + dateRange: + description: The date range of the maintenance. + type: list + elements: str + intervalDay: + description: The interval day of the maintenance. + type: int + weekdays: + description: The weekdays of the maintenance. + type: list + elements: int + daysOfMonth: + description: The weekdays of the maintenance. + type: list + timeRange: + description: The time range of the maintenance. + type: list + monitors: + description: The monitors of the maintenance. + type: list + status_pages: + description: The status pages of the maintenance. + type: list +''' + +EXAMPLES = r''' +- name: Add a maintenance + lucasheld.uptime_kuma.maintenance: + api_url: http://127.0.0.1:3001 + api_username: admin + api_password: secret123 + title: maintenance 1 + description: test + strategy: single + active: true + intervalDay: 1 + dateRange: + - "2022-12-27 22:36:00" + - "2022-12-29 22:36:00" + timeRange: + - hours: 2 + minutes: 0 + - hours: 3 + minutes: 0 + monitors: + - name: monitor 1 + - name: monitor 2 + status_pages: + - name: status page 1 + - name: status page 2 + state: present + +- name: Edit a maintenance + lucasheld.uptime_kuma.maintenance: + api_url: http://127.0.0.1:3001 + api_username: admin + api_password: secret123 + title: maintenance 1 + description: test + strategy: recurring-interval + active: true + intervalDay: 1 + dateRange: + - "2022-12-27 22:37:00" + - "2022-12-29 22:37:00" + timeRange: + - hours: 2 + minutes: 0 + - hours: 3 + minutes: 0 + monitors: + - id: 1 + status_pages: + - id: 1 + state: present + +- name: Remove a maintenance + lucasheld.uptime_kuma.maintenance: + api_url: http://127.0.0.1:3001 + api_username: admin + api_password: secret123 + title: maintenance 1 + state: absent + +- name: Pause a maintenance + lucasheld.uptime_kuma.maintenance: + api_url: http://127.0.0.1:3001 + api_username: admin + api_password: secret123 + title: maintenance 1 + state: paused + +- name: Resume a maintenance + lucasheld.uptime_kuma.maintenance: + api_url: http://127.0.0.1:3001 + api_username: admin + api_password: secret123 + title: maintenance 1 + state: resumed +''' + +RETURN = r''' +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible_collections.lucasheld.uptime_kuma.plugins.module_utils.common import object_changed, clear_params, \ + common_module_args, get_proxy_by_host_port, get_notification_by_name, get_maintenance_by_title, clear_unset_params, \ + get_docker_host_by_name, get_monitor_by_name + +try: + from uptime_kuma_api import UptimeKumaApi, MaintenanceType + HAS_UPTIME_KUMA_API = True +except ImportError: + HAS_UPTIME_KUMA_API = False + + +def get_status_page_by(api, key, value): + status_pages = api.get_status_pages() + for status_page in status_pages: + if status_page[key] == value: + return status_page + + +def run(api, params, result): + if not params["dateRange"]: + params["dateRange"] = [ + datetime.date.today().strftime("%Y-%m-%d 00:00") + ] + + if not params["timeRange"]: + params["timeRange"] = [ + { + "hours": 2, + "minutes": 0, + }, + { + "hours": 3, + "minutes": 0, + } + ] + + if not params["weekdays"]: + params["weekdays"] = [] + + if not params["daysOfMonth"]: + params["daysOfMonth"] = [] + + state = params["state"] + options = clear_params(params) + options = clear_unset_params(options) + if "monitors" in options: + del options["monitors"] + if "status_pages" in options: + del options["status_pages"] + + if params["id"]: + maintenance = api.get_maintenance(params["id"]) + else: + maintenance = get_maintenance_by_title(api, params["title"]) + + if state == "present": + if not maintenance: + r = api.add_maintenance(**options) + maintenance_id = r["maintenanceID"] + maintenance = api.get_maintenance(maintenance_id) + result["changed"] = True + else: + maintenance_id = maintenance["id"] + changed_keys = object_changed(maintenance, options) + if changed_keys: + api.edit_maintenance(maintenance["id"], **options) + result["changed"] = True + if maintenance: + monitors = params["monitors"] + status_pages = params["status_pages"] + + # add id or name to monitor + for monitor in monitors: + if "id" not in monitor: + r = get_monitor_by_name(api, monitor["name"]) + monitor["id"] = r["id"] + if "name" not in monitor: + r = api.get_monitor(monitor["id"]) + monitor["name"] = r["name"] + + # add id or name to status page + for status_page in status_pages: + if "id" not in status_page: + r = get_status_page_by(api, "name", status_page["name"]) + status_page["id"] = r["id"] + if "name" not in status_page: + r = get_status_page_by(api, "id", status_page["id"]) + status_page["name"] = r["name"] + + # add monitors to maintenance if changed + monitors_old = api.get_monitor_maintenance(maintenance_id) + monitors_old.sort() + monitors.sort() + if monitors_old != monitors: + api.add_monitor_maintenance(maintenance_id, monitors) + + # add status pages to maintenance if changed + status_pages_old = api.get_status_page_maintenance(maintenance_id) + status_pages_old.sort() + status_pages.sort() + if status_pages_old != status_pages: + api.add_status_page_maintenance(maintenance_id, status_pages) + elif state == "absent": + if maintenance: + api.delete_maintenance(maintenance["id"]) + result["changed"] = True + elif state == "paused": + if maintenance and maintenance["active"]: + api.pause_maintenance(maintenance["id"]) + result["changed"] = True + elif state == "resumed": + if maintenance and not maintenance["active"]: + api.resume_maintenance(maintenance["id"]) + result["changed"] = True + + +def main(): + module_args = dict( + id=dict(type="int"), + title=dict(type="str"), + strategy=dict(type="str", choices=["manual", "single", "recurring-interval", "recurring-weekday", "recurring-day-of-month"]), + active=dict(type="bool"), + description=dict(type="str"), + dateRange=dict(type="list"), + intervalDay=dict(type="int"), + weekdays=dict(type="list"), + daysOfMonth=dict(type="list"), + timeRange=dict(type="list"), + monitors=dict(type="list"), + status_pages=dict(type="list"), + state=dict(type="str", default="present", choices=["present", "absent", "paused", "resumed"]) + ) + module_args.update(common_module_args) + + module = AnsibleModule(module_args) + params = module.params + + if not HAS_UPTIME_KUMA_API: + module.fail_json(msg=missing_required_lib("uptime_kuma_api")) + + api = UptimeKumaApi(params["api_url"]) + api_token = params.get("api_token") + api_username = params.get("api_username") + api_password = params.get("api_password") + if api_token: + api.login_by_token(api_token) + elif api_username and api_password: + api.login(api_username, api_password) + else: + # autoLogin for enabled disableAuth + api.login() + + result = { + "changed": False + } + + try: + run(api, params, result) + + api.disconnect() + module.exit_json(**result) + except Exception: + api.disconnect() + error = traceback.format_exc() + module.fail_json(msg=error, **result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/maintenance_info.py b/plugins/modules/maintenance_info.py new file mode 100644 index 0000000..61b38dc --- /dev/null +++ b/plugins/modules/maintenance_info.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Lucas Held +# 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''' +--- +extends_documentation_fragment: + - lucasheld.uptime_kuma.uptime_kuma + +module: maintenance_info +author: Lucas Held (@lucasheld) +short_description: Retrieves facts about maintenances. +description: Retrieves facts about maintenances. + +options: + id: + description: + - The id of the maintenance to inspect. + - Only required if no I(title) specified. + type: int + title: + description: + - The name of the maintenance to inspect. + - Only required if no I(id) specified. + type: str +''' + +EXAMPLES = r''' +- name: get all maintenances + lucasheld.uptime_kuma.maintenance_info: + api_url: http://127.0.0.1:3001 + api_username: admin + api_password: secret123 + register: result +''' + +RETURN = r''' +maintenances: + description: The maintenances as list + returned: always + type: complex + contains: + id: + description: The id of the maintenance. + returned: always + type: int + sample: 1 + title: + description: The title of the maintenance. + returned: always + type: str + sample: 'maintenance 1' + description: + description: The description of the maintenance. + returned: always + type: str + sample: 'description' + strategy: + description: The strategy of the maintenance. + returned: always + type: str + sample: 'single' + intervalDay: + description: The interval day of the maintenance. + returned: always + type: int + sample: 1 + active: + description: True if the maintenance is active. + returned: always + type: bool + sample: true + dateRange: + description: The date range of the maintenance. + returned: always + type: list + sample: ["2022-12-27 15:39:00","2022-12-30 15:39:00"] + timeRange: + description: The time range of the maintenance. + returned: always + type: list + sample: [{"hours": 2,"minutes": 0,"seconds": 0},{"hours": 3,"minutes": 0,"seconds": 0}] + weekdays: + description: The time range of the maintenance. + returned: always + type: list + sample: [] + daysOfMonth: + description: The days of month of the maintenance. + returned: always + type: list + sample: [] + timeslotList: + description: The timeslot list of the maintenance. + returned: always + type: list + sample: [{"id": 1,"startDate": "2022-12-27 14:39:00","endDate": "2022-12-30 14:39:00","startDateServerTimezone": "2022-12-27 15:39","endDateServerTimezone": "2022-12-30 15:39","serverTimezoneOffset": "+01:00"}] + status: + description: The status of the maintenance. + returned: always + type: str + sample: "under-maintenance" + monitors: + description: The monitors of the maintenance. + returned: If I(id) or I(title) specified. + type: list + sample: [{"id": 1,"name": "monitor 1"}] + status_pages: + description: The status pages of the maintenance. + returned: If I(id) or I(title) specified. + type: list + sample: [{"id": 1,"title": "status page 1"}] +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.lucasheld.uptime_kuma.plugins.module_utils.common import common_module_args, get_maintenance_by_title +from ansible.module_utils.basic import missing_required_lib + +try: + from uptime_kuma_api import UptimeKumaApi + HAS_UPTIME_KUMA_API = True +except ImportError: + HAS_UPTIME_KUMA_API = False + + +def add_maintenance_monitors_status_pages(api, maintenance): + maintenance_id = maintenance["id"] + maintenance_monitors = api.get_monitor_maintenance(maintenance_id) + maintenance_status_pages = api.get_status_page_maintenance(maintenance_id) + maintenance.update({ + "monitors": maintenance_monitors, + "status_pages": maintenance_status_pages + }) + + +def run(api, params, result): + if params["id"]: + maintenance = api.get_maintenance(params["id"]) + add_maintenance_monitors_status_pages(api, maintenance) + maintenances = [maintenance] + elif params["title"]: + maintenance = get_maintenance_by_title(api, params["title"]) + add_maintenance_monitors_status_pages(api, maintenance) + maintenances = [maintenance] + else: + maintenances = api.get_maintenances() + + result["maintenances"] = maintenances + + +def main(): + module_args = dict( + id=dict(type="int"), + title=dict(type="str"), + ) + module_args.update(common_module_args) + + module = AnsibleModule(module_args, supports_check_mode=True) + params = module.params + + if not HAS_UPTIME_KUMA_API: + module.fail_json(msg=missing_required_lib("uptime_kuma_api")) + + api = UptimeKumaApi(params["api_url"]) + api_token = params.get("api_token") + api_username = params.get("api_username") + api_password = params.get("api_password") + if api_token: + api.login_by_token(api_token) + elif api_username and api_password: + api.login(api_username, api_password) + else: + # autoLogin for enabled disableAuth + api.login() + + result = { + "changed": False + } + + try: + run(api, params, result) + + api.disconnect() + module.exit_json(**result) + except Exception: + api.disconnect() + error = traceback.format_exc() + module.fail_json(msg=error, **result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/monitor.py b/plugins/modules/monitor.py index a3dda09..a7b29c5 100644 --- a/plugins/modules/monitor.py +++ b/plugins/modules/monitor.py @@ -33,7 +33,7 @@ type: description: The type of the monitor. type: str - choices: ["http", "port", "ping", "keyword", "dns", "docker", "push", "steam", "mqtt", "sqlserver", "postgres", "radius"] + choices: ["http", "port", "ping", "keyword", "grpc-keyword", "dns", "docker", "push", "steam", "mqtt", "sqlserver", "postgres", "mysql", "radius"] interval: description: The heartbeat interval of the monitor. type: int @@ -124,6 +124,27 @@ keyword: description: The keyword of the monitor. type: str + grpcUrl: + description: The grpc url of the monitor. + type: str + grpcEnableTls: + description: True to enable grpc tls. + type: bool + grpcServiceName: + description: The grpc service name of the monitor. + type: str + grpcMethod: + description: The grpc method of the monitor. + type: str + grpcProtobuf: + description: The grpc protobuf of the monitor. + type: str + grpcBody: + description: The grpc body of the monitor. + type: str + grpcMetadata: + description: The grpc metadata of the monitor. + type: str hostname: description: The hostname of the monitor. type: str @@ -267,6 +288,12 @@ def run(api, params, result): elif params["type"] == MonitorType.POSTGRES: params["databaseConnectionString"] = "postgres://username:password@host:port/database" + if not params["port"]: + if type == MonitorType.DNS: + params["port"] = 53 + elif type == MonitorType.RADIUS: + params["port"] = 1812 + # notification_names -> notificationIDList if params["notification_names"]: notification_ids = [] @@ -356,10 +383,19 @@ def main(): # KEYWORD keyword=dict(type="str"), + # GRPC_KEYWORD + grpcUrl=dict(type="str"), + grpcEnableTls=dict(type="bool"), + grpcServiceName=dict(type="str"), + grpcMethod=dict(type="str"), + grpcProtobuf=dict(type="str"), + grpcBody=dict(type="str"), + grpcMetadata=dict(type="str"), + # PORT, PING, DNS, STEAM, MQTT hostname=dict(type="str"), - # PORT, DNS, STEAM, MQTT + # PORT, DNS, STEAM, MQTT, RADIUS port=dict(type="int"), # DNS diff --git a/plugins/modules/monitor_info.py b/plugins/modules/monitor_info.py index 4995a24..9d9d68b 100644 --- a/plugins/modules/monitor_info.py +++ b/plugins/modules/monitor_info.py @@ -117,6 +117,41 @@ returned: always type: bool sample: False + grpcBody: + description: The grpc body of the monitor. + returned: always + type: str + sample: None + grpcEnableTls: + description: True if grpc enable tls is enabled. + returned: always + type: bool + sample: False + grpcMetadata: + description: The grpc metadata of the monitor. + returned: always + type: str + sample: None + grpcMethod: + description: The grpc method of the monitor. + returned: always + type: str + sample: None + grpcProtobuf: + description: The grpc protobuf of the monitor. + returned: always + type: str + sample: None + grpcServiceName: + description: The grpc service name of the monitor. + returned: always + type: str + sample: None + grpcUrl: + description: The grpc url of the monitor. + returned: always + type: str + sample: None ignoreTls: description: True if ignore tls error is enabled. returned: always @@ -167,6 +202,11 @@ returned: always type: list sample: [] + maintenance: + description: True if the monitor is under maintenance. + returned: always + type: bool + sample: False mqttUsername: description: The mqtt username of the monitor. returned: always diff --git a/plugins/modules/notification_info.py b/plugins/modules/notification_info.py index 376d2d0..0708f93 100644 --- a/plugins/modules/notification_info.py +++ b/plugins/modules/notification_info.py @@ -508,6 +508,10 @@ description: webhook provider option. returned: if type is webhook type: str + webhookAdditionalHeaders: + description: webhook provider option. + returned: if type is webhook + type: str webhookURL: description: webhook provider option. returned: if type is webhook @@ -644,10 +648,38 @@ description: ntfy provider option. returned: if type is ntfy type: str + ntfyIcon: + description: ntfy provider option. + returned: if type is ntfy + type: str ntfyserverurl: description: ntfy provider option. returned: if type is ntfy type: str + smseagleEncoding: + description: SMSEagle provider option. + returned: if type is SMSEagle + type: bool + smseaglePriority: + description: SMSEagle provider option. + returned: if type is SMSEagle + type: int + smseagleRecipientType: + description: SMSEagle provider option. + returned: if type is SMSEagle + type: str + smseagleToken: + description: SMSEagle provider option. + returned: if type is SMSEagle + type: str + smseagleRecipient: + description: SMSEagle provider option. + returned: if type is SMSEagle + type: str + smseagleUrl: + description: SMSEagle provider option. + returned: if type is SMSEagle + type: str ''' import traceback diff --git a/plugins/modules/proxy.py b/plugins/modules/proxy.py index 4c2d9de..6e0dd2c 100644 --- a/plugins/modules/proxy.py +++ b/plugins/modules/proxy.py @@ -40,6 +40,7 @@ protocol: description: The protocol of the proxy. type: str + choices: ["https", "http", "socks", "socks5", "socks5h", "socks4"] auth: description: True if the authentication is enabled. type: bool @@ -144,7 +145,7 @@ def main(): id=dict(type="int"), host=dict(type="str", required=True), port=dict(type="int", required=True), - protocol=dict(type="str"), + protocol=dict(type="str", choices=["https", "http", "socks", "socks5", "socks5h", "socks4"]), auth=dict(type="bool"), username=dict(type="str"), password=dict(type="str", no_log=True), diff --git a/plugins/modules/settings.py b/plugins/modules/settings.py index 6399778..913e3ba 100644 --- a/plugins/modules/settings.py +++ b/plugins/modules/settings.py @@ -18,6 +18,52 @@ author: Lucas Held (@lucasheld) short_description: Manages settings. description: Manages settings. + +options: + password: + description: + - The Uptime Kuma password. + - Only required if I(disableAuth) is true. + type: str + checkUpdate: + description: True if update check should be enabled. + type: bool + checkBeta: + description: True if update check for beta versions should be enabled. + type: bool + keepDataPeriodDays: + description: Keep monitor history data for this number of days. + type: int + serverTimezone: + description: The server timezone. + type: str + entryPage: + description: The entry page. + type: str + searchEngineIndex: + description: True if Uptime Kuma should be indexed by search engines. + type: bool + primaryBaseURL: + description: The primary base URL. + type: str + steamAPIKey: + description: The Steam API key for monitoring a Steam game server. + type: str + dnsCache: + description: True if dns cache should be enabled. + type: bool + tlsExpiryNotifyDays: + description: HTTPS monitors trigger notification when the TLS certificate expires in the specified days. + type: list + elements: int + disableAuth: + description: True if authentication should be disabled. + type: bool + trustProxy: + description: + - True to trust 'X-Forwarded-*' headers. + - If you want to get the correct client IP and your Uptime Kuma is behind such as Nginx or Apache, you should enable this. + type: bool ''' EXAMPLES = r''' @@ -66,10 +112,12 @@ def main(): # monitor history keepDataPeriodDays=dict(type="int"), # general + serverTimezone=dict(type="str"), entryPage=dict(type="str"), searchEngineIndex=dict(type="bool"), primaryBaseURL=dict(type="str"), steamAPIKey=dict(type="str"), + dnsCache=dict(type="bool"), # notifications tlsExpiryNotifyDays=dict(type="list", elements="int"), # security diff --git a/plugins/modules/settings_info.py b/plugins/modules/settings_info.py index 7c253a8..da3541f 100644 --- a/plugins/modules/settings_info.py +++ b/plugins/modules/settings_info.py @@ -60,6 +60,11 @@ returned: always type: bool sample: false + serverTimezone: + description: Value of the serverTimezone setting. + returned: always + type: str + sample: Europe/Berlin primaryBaseURL: description: Value of the primaryBaseURL setting. returned: always @@ -80,6 +85,11 @@ returned: always type: bool sample: false + dnsCache: + description: Value of the dnsCache setting. + returned: always + type: bool + sample: true trustProxy: description: Value of the trustProxy setting. returned: always diff --git a/plugins/modules/status_page_info.py b/plugins/modules/status_page_info.py index bef6b82..cb26049 100644 --- a/plugins/modules/status_page_info.py +++ b/plugins/modules/status_page_info.py @@ -105,11 +105,16 @@ returned: always type: dict sample: {'id': 2, 'style': 'danger', 'title': 'sample title', 'content': 'sample content', 'pin': 1, 'createdDate': '2022-08-26 12:07:00', 'lastUpdatedDate': None} + maintenanceList: + description: The maintenance list of the status page. + returned: always + type: list + sample: [] publicGroupList: description: The public group list of the status page. returned: always type: list - sample: [{'id': 5, 'name': 'Services', 'weight': 1, 'monitorList': [{'id': 18, 'name': 'sample monitor'}]}] + sample: [{'id': 5, 'name': 'Services', 'weight': 1, 'monitorList': [{'id': 18, 'maintenance': False, 'name': 'sample monitor'}]}] ''' import traceback diff --git a/run_tests.sh b/run_tests.sh index eb8d5b8..55814c2 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -2,6 +2,7 @@ collection_path="$HOME/.ansible/collections/ansible_collections/lucasheld/uptime_kuma" version="$1" +files="${@:2}" if [ ! -d "$collection_path" ] then @@ -10,14 +11,21 @@ fi cp -r ./{plugins,tests} "$collection_path" cd "$collection_path" -if [ $version ] +if [ $version ] && [ "$version" != "all" ] then versions=("$version") else - versions=(1.18.3 1.17.1) + versions=(1.19.2 1.18.5 1.17.1) fi -for version in $versions +targets="" +for file in ${files[*]} +do + filepath="tests/unit/plugins/module_utils/${file}" + targets+="$filepath " +done + +for version in ${versions[*]} do docker rm -f uptimekuma > /dev/null 2>&1 @@ -30,7 +38,7 @@ do done echo "Running tests..." - ansible-test units -v --target-python default --num-workers 1 + ansible-test units -v --target-python default --num-workers 1 $targets echo "Stopping uptime kuma..." docker stop uptimekuma > /dev/null diff --git a/tests/unit/plugins/module_utils/module_test_case.py b/tests/unit/plugins/module_utils/module_test_case.py index 5010458..374e25b 100644 --- a/tests/unit/plugins/module_utils/module_test_case.py +++ b/tests/unit/plugins/module_utils/module_test_case.py @@ -2,7 +2,7 @@ import copy import unittest import tempfile -from uptime_kuma_api import UptimeKumaApi, Event, MonitorType, DockerType, UptimeKumaException +from uptime_kuma_api import UptimeKumaApi, Event, MonitorType, DockerType, UptimeKumaException, MaintenanceStrategy from packaging.version import parse as parse_version @@ -87,16 +87,14 @@ def add_monitor(self, name="monitor 1"): name=name, url="http://127.0.0.1" ) - monitor_id = r["monitorID"] - return monitor_id + return r["monitorID"] def add_tag(self, name="tag 1"): r = self.api.add_tag( name=name, color="#ffffff" ) - tag_id = r["id"] - return tag_id + return r["id"] def add_notification(self, name="notification 1"): r = self.api.add_notification( @@ -104,13 +102,12 @@ def add_notification(self, name="notification 1"): type="PushByTechulus", pushAPIKey="123456789" ) - notification_id = r["id"] - return notification_id + return r["id"] - def add_status_page(self, slug="slug1"): + def add_status_page(self, slug="slug1", title="status page title"): self.api.add_status_page( slug=slug, - title="status page title" + title=title ) def add_proxy(self, host="127.0.0.1", port=8080): @@ -120,13 +117,18 @@ def add_proxy(self, host="127.0.0.1", port=8080): port=port, active=True ) - proxy_id = r["id"] - return proxy_id + return r["id"] def add_docker_host(self, name="docker host 1"): r = self.api.add_docker_host( name=name, dockerType=DockerType.SOCKET ) - docker_host_id = r["id"] - return docker_host_id + return r["id"] + + def add_maintenance(self, title="maintenance 1"): + r = self.api.add_maintenance( + title=title, + strategy=MaintenanceStrategy.MANUAL + ) + return r["maintenanceID"] diff --git a/tests/unit/plugins/module_utils/test_docker_host.py b/tests/unit/plugins/module_utils/test_docker_host.py index 1c9fb9a..8b386d9 100644 --- a/tests/unit/plugins/module_utils/test_docker_host.py +++ b/tests/unit/plugins/module_utils/test_docker_host.py @@ -43,14 +43,15 @@ def test_docker_host(self): self.assertFalse(result["changed"]) # edit docker host by id + docker_host_id = docker_host["id"] self.params.update({ - "id": docker_host["id"], + "id": docker_host_id, "dockerType": DockerType.TCP, "dockerDaemon": "tcp://localhost:2375", }) result = self.run_module(module, self.params) self.assertTrue(result["changed"]) - docker_host = get_docker_host_by_name(self.api, self.params["name"]) + docker_host = self.api.get_docker_host(docker_host_id) self.assertEqual(docker_host["dockerType"], self.params["dockerType"]) self.assertEqual(docker_host["dockerDaemon"], self.params["dockerDaemon"]) diff --git a/tests/unit/plugins/module_utils/test_login.py b/tests/unit/plugins/module_utils/test_login.py index 7fd3f2b..96247c2 100644 --- a/tests/unit/plugins/module_utils/test_login.py +++ b/tests/unit/plugins/module_utils/test_login.py @@ -9,9 +9,7 @@ def setUp(self): "api_url": "http://127.0.0.1:3001", "api_username": None, "api_password": None, - "api_token": None, - "id": None, - "name": None + "api_token": None } def test_login(self): diff --git a/tests/unit/plugins/module_utils/test_maintenance.py b/tests/unit/plugins/module_utils/test_maintenance.py new file mode 100644 index 0000000..de9c636 --- /dev/null +++ b/tests/unit/plugins/module_utils/test_maintenance.py @@ -0,0 +1,153 @@ +from packaging.version import parse as parse_version +from uptime_kuma_api import MaintenanceStrategy + +import plugins.modules.maintenance as module +from plugins.module_utils.common import get_maintenance_by_title +from .module_test_case import ModuleTestCase + + +class TestMaintenance(ModuleTestCase): + def setUp(self): + super(TestMaintenance, self).setUp() + + if parse_version(self.api.version) < parse_version("1.19"): + super(TestMaintenance, self).tearDown() + self.skipTest("Unsupported in this Uptime Kuma version") + + self.params = { + "api_url": None, + "api_username": None, + "api_password": None, + "api_token": None, + "id": None, + "title": None, + "strategy": None, + "active": None, + "description": None, + "dateRange": None, + "intervalDay": None, + "weekdays": None, + "daysOfMonth": None, + "timeRange": None, + "monitors": None, + "status_pages": None, + "state": "present" + } + + def test_maintenance(self): + # add maintenance by name + monitor_name = "monitor 1" + monitor_id = self.add_monitor(monitor_name) + + status_page_title = "status_page 1" + status_page_slug = "slug1" + self.add_status_page(status_page_slug, status_page_title) + # self.api.save_status_page(status_page_slug) + status_page_id = self.api.get_status_page(status_page_slug)["id"] + + self.params.update({ + "title": "maintenance 1", + "description": "test", + "strategy": MaintenanceStrategy.SINGLE, + "active": True, + "intervalDay": 1, + "dateRange": [ + "2022-12-27 22:36:00", + "2022-12-29 22:36:00" + ], + "timeRange": [ + { + "hours": 2, + "minutes": 0, + "seconds": 0 + }, + { + "hours": 3, + "minutes": 0, + "seconds": 0 + } + ], + "weekdays": [], + "daysOfMonth": [], + "monitors": [ + { + "id": monitor_id, + "name": monitor_name + } + ], + "status_pages": [ + { + "id": status_page_id, + "name": status_page_title + } + ] + }) + + result = self.run_module(module, self.params) + self.assertTrue(result["changed"]) + maintenance = get_maintenance_by_title(self.api, self.params["title"]) + self.assertEqual(maintenance["description"], self.params["description"]) + self.assertEqual(maintenance["strategy"], self.params["strategy"]) + self.assertEqual(maintenance["active"], self.params["active"]) + self.assertEqual(maintenance["intervalDay"], self.params["intervalDay"]) + self.assertEqual(maintenance["dateRange"], self.params["dateRange"]) + self.assertEqual(maintenance["timeRange"], self.params["timeRange"]) + self.assertEqual(maintenance["weekdays"], self.params["weekdays"]) + self.assertEqual(maintenance["daysOfMonth"], self.params["daysOfMonth"]) + + maintenance_id = maintenance["id"] + maintenance_monitors = self.api.get_monitor_maintenance(maintenance_id) + maintenance_status_pages = self.api.get_status_page_maintenance(maintenance_id) + for maintenance_status_page in maintenance_status_pages: + maintenance_status_page["name"] = maintenance_status_page.pop("title") + self.assertEqual(maintenance_monitors, self.params["monitors"]) + self.assertEqual(maintenance_status_pages, self.params["status_pages"]) + + result = self.run_module(module, self.params) + self.assertFalse(result["changed"]) + + # edit maintenance by id + maintenance_id = maintenance["id"] + self.params.update({ + "id": maintenance_id, + "strategy": MaintenanceStrategy.RECURRING_INTERVAL, + "dateRange": [ + "2022-12-27 22:37:00", + "2022-12-31 22:37:00" + ] + }) + result = self.run_module(module, self.params) + self.assertTrue(result["changed"]) + maintenance = self.api.get_maintenance(maintenance_id) + self.assertEqual(maintenance["strategy"], self.params["strategy"]) + self.assertEqual(maintenance["dateRange"], self.params["dateRange"]) + + result = self.run_module(module, self.params) + self.assertFalse(result["changed"]) + + # pause maintenance + self.params.update({ + "state": "paused", + }) + result = self.run_module(module, self.params) + self.assertTrue(result["changed"]) + + result = self.run_module(module, self.params) + self.assertFalse(result["changed"]) + + # resume maintenance + self.params.update({ + "state": "resumed", + }) + result = self.run_module(module, self.params) + self.assertTrue(result["changed"]) + + result = self.run_module(module, self.params) + self.assertFalse(result["changed"]) + + # delete maintenance + self.params.update({ + "state": "absent", + }) + result = self.run_module(module, self.params) + self.assertTrue(result["changed"]) diff --git a/tests/unit/plugins/module_utils/test_maintenance_info.py b/tests/unit/plugins/module_utils/test_maintenance_info.py new file mode 100644 index 0000000..c2b979f --- /dev/null +++ b/tests/unit/plugins/module_utils/test_maintenance_info.py @@ -0,0 +1,48 @@ +from packaging.version import parse as parse_version +import plugins.modules.maintenance_info as module +from .module_test_case import ModuleTestCase + + +class TestMaintenanceInfo(ModuleTestCase): + def setUp(self): + super(TestMaintenanceInfo, self).setUp() + + if parse_version(self.api.version) < parse_version("1.19"): + super(TestMaintenanceInfo, self).tearDown() + self.skipTest("Unsupported in this Uptime Kuma version") + + self.params = { + "api_url": "http://127.0.0.1:3001", + "api_username": None, + "api_password": None, + "api_token": None, + "id": None, + "title": None + } + self.maintenance_id_1 = self.add_maintenance("maintenance 1") + self.maintenance_id_2 = self.add_maintenance("maintenance 2") + + def test_all_maintenances(self): + result = self.run_module(module, self.params) + + self.assertFalse(result["changed"]) + maintenance_ids = [self.maintenance_id_1, self.maintenance_id_2] + self.assertEqual(len(result["maintenances"]), len(maintenance_ids)) + self.assertEqual([i["id"] for i in result["maintenances"]], maintenance_ids) + + def test_maintenance_by_id(self): + self.params["id"] = self.maintenance_id_2 + result = self.run_module(module, self.params) + + self.assertFalse(result["changed"]) + self.assertEqual(len(result["maintenances"]), 1) + self.assertEqual(result["maintenances"][0]["id"], self.maintenance_id_2) + + def test_maintenance_by_title(self): + title = "maintenance 2" + self.params["title"] = title + result = self.run_module(module, self.params) + + self.assertFalse(result["changed"]) + self.assertEqual(len(result["maintenances"]), 1) + self.assertEqual(result["maintenances"][0]["title"], title) diff --git a/tests/unit/plugins/module_utils/test_monitor.py b/tests/unit/plugins/module_utils/test_monitor.py index 0b10777..d1f12ce 100644 --- a/tests/unit/plugins/module_utils/test_monitor.py +++ b/tests/unit/plugins/module_utils/test_monitor.py @@ -40,6 +40,13 @@ def setUp(self): "authDomain": None, "authWorkstation": None, "keyword": None, + "grpcUrl": None, + "grpcEnableTls": None, + "grpcServiceName": None, + "grpcMethod": None, + "grpcProtobuf": None, + "grpcBody": None, + "grpcMetadata": None, "hostname": None, "port": None, "dns_resolve_server": None, @@ -96,13 +103,15 @@ def test_monitor(self): self.assertFalse(result["changed"]) # edit monitor by id + monitor_id = monitor["id"] self.params.update({ + "id": monitor_id, "type": MonitorType.PING, "hostname": "127.0.0.10" }) result = self.run_module(module, self.params) self.assertTrue(result["changed"]) - monitor = get_monitor_by_name(self.api, self.params["name"]) + monitor = self.api.get_monitor(monitor_id) self.assertEqual(monitor["type"], self.params["type"]) self.assertEqual(monitor["hostname"], self.params["hostname"]) diff --git a/tests/unit/plugins/module_utils/test_settings.py b/tests/unit/plugins/module_utils/test_settings.py index 54c2fab..42067c3 100644 --- a/tests/unit/plugins/module_utils/test_settings.py +++ b/tests/unit/plugins/module_utils/test_settings.py @@ -14,10 +14,12 @@ def setUp(self): "checkUpdate": None, "checkBeta": None, "keepDataPeriodDays": None, + "serverTimezone": None, "entryPage": None, "searchEngineIndex": None, "primaryBaseURL": None, "steamAPIKey": None, + "dnsCache": None, "tlsExpiryNotifyDays": None, "disableAuth": None, "trustProxy": None, diff --git a/tests/unit/plugins/module_utils/test_settings_info.py b/tests/unit/plugins/module_utils/test_settings_info.py index de6203b..652993b 100644 --- a/tests/unit/plugins/module_utils/test_settings_info.py +++ b/tests/unit/plugins/module_utils/test_settings_info.py @@ -16,4 +16,4 @@ def test_settings(self): result = self.run_module(module, self.params) self.assertFalse(result["changed"]) - self.assertEqual(len(result["settings"]), 10) + self.assertTrue(result["settings"]["checkUpdate"]) diff --git a/tests/unit/plugins/module_utils/test_status_page.py b/tests/unit/plugins/module_utils/test_status_page.py index bbe3450..2c67e54 100644 --- a/tests/unit/plugins/module_utils/test_status_page.py +++ b/tests/unit/plugins/module_utils/test_status_page.py @@ -1,5 +1,7 @@ -from .module_test_case import ModuleTestCase +from packaging.version import parse as parse_version + import plugins.modules.status_page as module +from .module_test_case import ModuleTestCase class TestStatusPage(ModuleTestCase): @@ -76,6 +78,8 @@ def test_status_page(self): for j in i.get("monitorList", []): j.pop("sendUrl", None) j["name"] = None + if parse_version(self.api.version) >= parse_version("1.19"): + j.pop("maintenance") self.assertEqual(public_group_list, self.params["publicGroupList"]) result = self.run_module(module, self.params)