├── .dockerignore ├── .editorconfig ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── config.ini.example ├── docker-compose.yml ├── requirements.txt ├── run.sh ├── tests └── test_update_status.py ├── tox.ini └── update_status.py /.dockerignore: -------------------------------------------------------------------------------- 1 | config.ini 2 | ids.txt 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.yml] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | config.ini 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6-alpine3.7 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY . . 6 | 7 | CMD sh run.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Axiacore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cachet Uptime Robot 2 | 3 | Cachet is an open source status page system, this repository is a Python script that does two things, **first**, it reads the status of a page in UptimeRobot and updates a cachet component based on that status and **second**, it updates a metric with the historic uptime ratio from Uptime Robot. 4 | 5 | ### Component status 6 | 7 | | Uptime Robot | Cachet | 8 | | --- | --- | 9 | | Not checked yet | Operational | 10 | | Up | Operational | 11 | | Seems down | Partial outage | 12 | | Down | Major outage | 13 | 14 | ### Getting started 15 | 16 | To get started, you have to specify your Cachet and UptimeRobot settings and in **config.ini**. 17 | ```ini 18 | [uptimeRobot] // Global uptimerobot API 19 | UptimeRobotMainApiKey = your-api-key 20 | 21 | [cachet] // Global cachet status API 22 | CachetApiKey = cachet-api-key 23 | CachetUrl = https://status.mycompany.com 24 | 25 | [uptimeRobotMonitorID1] // This will update ComponentId 1 on the global Cachet 26 | ComponentId = 1 27 | 28 | [uptimeRobotMonitorID2] // Still possible to use custom cachet settings 29 | CachetApiKey = cachet-api-key 30 | CachetUrl = https://status.mycompany.com 31 | MetricId = 1 32 | ComponentId = 1 33 | ``` 34 | 35 | * `UptimeRobotMainApiKey`: UptimeRobot API key. 36 | * `uptimeRobotMonitorID`: This exact "monitor" id set in UptimeRobot. You can find the id's by running `python update_status.py config.ini --printIds` 37 | * `CachetApiKey`: Cachet API key. 38 | * `CachetUrl`: URL of the API of the status page you want to show the site availability in. 39 | * `MetricId`: (Optional) Id of the Cachet metric with site availability. 40 | * `ComponentId`: (Optional) Id of the component you want to update on each check. 41 | 42 | Either `MetricId` or `ComponentId` must be defined per `uptimeRobotMonitorID` 43 | 44 | MetricId is special, it will try to sync the graph from UptimeRobot to Cachet. Please keep this in mind at all times. 45 | 46 | ### Command and args 47 | `update_status.py [--printIds]` 48 | `config` is mandantory and must point to the path where a config file can be found. 49 | `--printIds` will print a list with all monitors in UptimeRobot with there name and ID. This ID needed in the config.ini file. 50 | 51 | You can always do `update_status.py -h` for more info. 52 | 53 | ### Usage 54 | Register a cron that runs `update_status.py` every 5 minutes. 55 | ```bash 56 | # Open cron file to edit. 57 | crontab -e 58 | ``` 59 | 60 | Edit the crontab file and add this line: 61 | ```bash 62 | */5 * * * * ~/path/run.sh 63 | ``` 64 | 65 | or if you have you're config in a other location: 66 | ```bash 67 | */5 * * * * python ~/path/update_status.py ~/path/config.ini 68 | ``` 69 | 70 | _Note that the path of the update_status.py & config.ini files may vary depending on the location you cloned the repository_ 71 | 72 | ### Running with docker 73 | First, make sure the `config.ini` and the `docker-compose.yml` are in the same directory. 74 | Then run 75 | ``` 76 | docker-compose run cachet-uptime 77 | ``` 78 | if you want to use cron, add the following line into crontab. 79 | ``` 80 | */5 * * * * docker-compose -f /path/to/compose/file/docker-compose.yml run cachet-uptime 81 | ``` 82 | 83 | ### Running manually 84 | 85 | You can also update your Cachet data manually by running this: 86 | 87 | ```python 88 | python update_status.py 89 | 90 | INFO:cachet-uptime-robot:Updating monitor MySite. URL: http://mysite.co. ID: 12345678 91 | INFO:cachet-uptime-robot:HTTP GET URL: https://status.mycompany.com/api/v1/components/1 92 | INFO:cachet-uptime-robot:No status change on component 1. Skipping update. 93 | INFO:cachet-uptime-robot:Updating monitor MySite Mail. URL: http://mail.mysite.co. ID: 12345687 94 | INFO:cachet-uptime-robot:HTTP GET URL: https://status.mycompany.com/api/v1/components/2 95 | INFO:cachet-uptime-robot:Updating component 2 status: 1 -> 4 96 | ``` 97 | -------------------------------------------------------------------------------- /config.ini.example: -------------------------------------------------------------------------------- 1 | [uptimeRobot] 2 | UptimeRobotMainApiKey = your-api-key 3 | 4 | [cachet] 5 | CachetApiKey = cachet-api-key 6 | CachetUrl = https://status.mycompany.com 7 | 8 | [uptimeRobotMonitorID1] 9 | ComponentId = 1 10 | 11 | [uptimeRobotMonitorID2] 12 | CachetApiKey = cachet-api-key 13 | CachetUrl = https://status2.mycompany.com 14 | MetricId = 1 15 | ComponentId = 2 16 | 17 | [uptimeRobotMonitorID3] 18 | MetricId = 2 19 | ComponentId = 3 20 | 21 | [uptimeRobotMonitorID4] 22 | MetricId = 3 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | cachet-uptime: 4 | container_name: "cachet-uptime" 5 | image: "nyanim/cachet-uptime-robot" 6 | volumes: 7 | - "./config.ini:/usr/src/app/config.ini" 8 | command: sh /usr/src/app/run.sh -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==3.5.0 2 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Detect absolute and full path 4 | cd "$(dirname $0)" 5 | DIR=$(pwd) 6 | cd - > /dev/null 7 | 8 | # Run python 9 | python3 "$DIR/update_status.py" "$DIR/config.ini" 10 | -------------------------------------------------------------------------------- /tests/test_update_status.py: -------------------------------------------------------------------------------- 1 | import unittest.mock as mock 2 | import pytest 3 | import update_status 4 | 5 | 6 | class TestMonitor(object): 7 | def test_send_data_to_cachet_updates_the_component_status(self, monitor, uptimerobot_monitor): 8 | website_config = monitor.monitor_list[uptimerobot_monitor['id']] 9 | 10 | with mock.patch('update_status.CachetHq') as cachet: 11 | monitor.sync_metric = lambda x, y: None 12 | monitor.send_data_to_cachet(uptimerobot_monitor) 13 | 14 | cachet().update_component.assert_called_with( 15 | website_config['component_id'], 16 | int(uptimerobot_monitor['status']) 17 | ) 18 | 19 | @pytest.mark.skip 20 | def test_send_data_to_cachet_updates_data_metrics(self, monitor, uptimerobot_monitor): 21 | website_config = monitor.monitor_list[uptimerobot_monitor['id']] 22 | 23 | with mock.patch('update_status.CachetHq') as cachet: 24 | monitor.sync_metric = lambda x, y: None 25 | monitor.send_data_to_cachet(uptimerobot_monitor) 26 | 27 | cachet().set_data_metrics.assert_called_with( 28 | uptimerobot_monitor['custom_uptime_ratio'], 29 | mock.ANY, 30 | website_config['metric_id'] 31 | ) 32 | 33 | def test_sync_metric(self, monitor, cachet, uptimerobot_monitor, cachet_metric): 34 | future_date = 999999999999 35 | cachet_metric['created_at'] = '2017-01-01 00:00:00' 36 | cachet_metric_unixtime = 1483228800 37 | 38 | cachet_mock = mock.create_autospec(cachet) 39 | cachet_mock.get_last_metric_point.return_value = cachet_metric 40 | 41 | assert len(uptimerobot_monitor['response_times']) >= 3, \ 42 | 'We need at least 3 response times to run the tests' 43 | 44 | uptimerobot_monitor['response_times'][-1]['datetime'] = future_date 45 | uptimerobot_monitor['response_times'][-2]['datetime'] = future_date 46 | 47 | monitor.sync_metric(uptimerobot_monitor, cachet_mock) 48 | 49 | expected_response_times = [ 50 | x for x in uptimerobot_monitor['response_times'] 51 | if x['datetime'] > cachet_metric_unixtime 52 | ] 53 | 54 | assert cachet_mock.set_data_metrics.call_count == len(expected_response_times) 55 | for response_time in expected_response_times: 56 | cachet_mock.set_data_metrics.assert_any_call( 57 | response_time['value'], 58 | response_time['datetime'], 59 | mock.ANY 60 | ) 61 | 62 | 63 | @pytest.fixture 64 | def monitor_list(): 65 | return { 66 | '6516846': { 67 | 'cachet_api_key': 'CACHET_API_KEY', 68 | 'cachet_url': 'http://status.example.org', 69 | 'metric_id': '1', 70 | 'component_id': '1', 71 | }, 72 | } 73 | 74 | 75 | @pytest.fixture 76 | def cachet_metric(): 77 | return { 78 | 'id': 1, 79 | 'metric_id': 1, 80 | 'value': 100, 81 | 'created_at': '2017-08-25 17:17:14', 82 | 'updated_at': '2017-08-25 17:17:14', 83 | 'counter': 1, 84 | 'calculated_value': 100, 85 | } 86 | 87 | 88 | @pytest.fixture 89 | def uptimerobot_monitor(monitor_list): 90 | monitors_ids = [m for m in monitor_list.keys()] 91 | id = monitors_ids[0] 92 | 93 | return { 94 | 'url': 'monitor_url', 95 | 'friendly_name': 'friendly_name', 96 | 'id': id, 97 | 'status': '2', # UP, 98 | 'custom_uptime_ratio': '100', 99 | 'response_times': [ 100 | {'datetime': 1, 'value': 609}, 101 | {'datetime': 2, 'value': 625}, 102 | {'datetime': 3, 'value': 687}, 103 | {'datetime': 4, 'value': 750}, 104 | {'datetime': 5, 'value': 750}, 105 | {'datetime': 6, 'value': 922}, 106 | ] 107 | } 108 | 109 | 110 | @pytest.fixture 111 | def monitor(monitor_list): 112 | api_key = 'UPTIME_ROBOT_API_KEY' 113 | return update_status.Monitor(monitor_list, api_key) 114 | 115 | 116 | @pytest.fixture 117 | def cachet(): 118 | return update_status.CachetHq( 119 | cachet_api_key='CACHET_API_KEY', 120 | cachet_url='CACHET_URL' 121 | ) 122 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py36 4 | lint 5 | skipsdist = True 6 | 7 | [testenv] 8 | deps = 9 | pytest 10 | setenv = 11 | PYTHONPATH = {toxinidir} 12 | commands = 13 | py.test {posargs} 14 | 15 | [testenv:lint] 16 | deps = 17 | pylama 18 | commands = 19 | pylama {toxinidir}/update_status.py 20 | 21 | [pytest] 22 | testpaths = tests 23 | 24 | [pylama] 25 | ignore = E501 26 | -------------------------------------------------------------------------------- /update_status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import configparser 4 | import json 5 | import logging 6 | import sys 7 | from urllib import request 8 | from urllib import parse 9 | from datetime import datetime, timezone 10 | 11 | logging.basicConfig(level=logging.INFO) 12 | logger = logging.getLogger('cachet-uptime-robot') 13 | 14 | CACHETHQ_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' 15 | USER_AGENT = 'CachetUptimeRobotIntegration' 16 | 17 | 18 | class UptimeRobot(object): 19 | """ Intermediate class for setting uptime stats. 20 | """ 21 | def __init__(self, api_key): 22 | self.api_key = api_key 23 | self.base_url = 'https://api.uptimerobot.com/v2/getMonitors' 24 | 25 | def get_monitors(self, response_times=0, logs=0, uptime_ratio=30): 26 | """ 27 | Returns status and response payload for all known monitors. 28 | """ 29 | endpoint = self.base_url 30 | data = parse.urlencode({ 31 | 'api_key': format(self.api_key), 32 | 'format': 'json', 33 | # responseTimes - optional (defines if the response time data of each 34 | # monitor will be returned. Should be set to 1 for getting them. 35 | # Default is 0) 36 | 'response_times': format(response_times), 37 | # logs - optional (defines if the logs of each monitor will be 38 | # returned. Should be set to 1 for getting the logs. Default is 0) 39 | 'logs': format(logs), 40 | # customUptimeRatio - optional (defines the number of days to calculate 41 | # the uptime ratio(s) for. Ex: customUptimeRatio=7-30-45 to get the 42 | # uptime ratios for those periods) 43 | 'custom_uptime_ratios': format(uptime_ratio) 44 | }).encode('utf-8') 45 | 46 | url = request.Request( 47 | url=endpoint, 48 | data=data, 49 | method='POST', 50 | headers={ 51 | 'Content-Type': 'application/x-www-form-urlencoded ', 52 | 'Cache-Control': 'no-cache', 53 | }, 54 | ) 55 | 56 | # Verifying in the response is jsonp in otherwise is error 57 | response = request.urlopen(url) 58 | content = response.read().decode('utf-8') 59 | j_content = json.loads(content) 60 | if j_content.get('stat'): 61 | stat = j_content.get('stat') 62 | if stat == 'ok': 63 | return True, j_content 64 | 65 | return False, j_content 66 | 67 | 68 | class CachetHq(object): 69 | # Uptime Robot status list 70 | UPTIME_ROBOT_PAUSED = 0 71 | UPTIME_ROBOT_NOT_CHECKED_YET = 1 72 | UPTIME_ROBOT_UP = 2 73 | UPTIME_ROBOT_SEEMS_DOWN = 8 74 | UPTIME_ROBOT_DOWN = 9 75 | 76 | # Cachet status list 77 | CACHET_OPERATIONAL = 1 78 | CACHET_PERFORMANCE_ISSUES = 2 79 | CACHET_SEEMS_DOWN = 3 80 | CACHET_DOWN = 4 81 | 82 | def __init__(self, cachet_api_key, cachet_url): 83 | self.cachet_api_key = cachet_api_key 84 | self.cachet_url = cachet_url 85 | 86 | def update_component(self, id_component=1, status=None): 87 | component_status = None 88 | 89 | # Not Checked yet and Up 90 | if status in [self.UPTIME_ROBOT_NOT_CHECKED_YET, self.UPTIME_ROBOT_UP]: 91 | component_status = self.CACHET_OPERATIONAL 92 | 93 | # Seems down 94 | elif status == self.UPTIME_ROBOT_SEEMS_DOWN: 95 | component_status = self.CACHET_SEEMS_DOWN 96 | 97 | # Down 98 | elif status == self.UPTIME_ROBOT_DOWN: 99 | component_status = self.CACHET_DOWN 100 | 101 | if component_status: 102 | component = self.get_component(id_component) 103 | current_component_status = int(component.get('data', {}).get('status')) 104 | if current_component_status == component_status: 105 | # FIXME: This is only necessary for CachetHQ <=2.3. Whenever we 106 | # migrate to 2.4, we can remove this check. 107 | logger.info( 108 | 'No status change on component %s. Skipping update.', 109 | id_component 110 | ) 111 | else: 112 | logger.info( 113 | 'Updating component %s status: %s -> %s.', 114 | id_component, 115 | current_component_status, 116 | component_status 117 | ) 118 | url = '{0}/api/v1/{1}/{2}'.format( 119 | self.cachet_url, 120 | 'components', 121 | id_component 122 | ) 123 | data = { 124 | 'status': component_status, 125 | } 126 | 127 | return self._request('PUT', url, data) 128 | 129 | def get_component(self, id_component): 130 | url = '{0}/api/v1/components/{1}'.format( 131 | self.cachet_url, 132 | id_component 133 | ) 134 | 135 | return self._request('GET', url) 136 | 137 | def set_data_metrics(self, value, timestamp, id_metric=1): 138 | url = '{0}/api/v1/metrics/{1}/points'.format( 139 | self.cachet_url, 140 | id_metric 141 | ) 142 | data = { 143 | 'value': value, 144 | 'timestamp': timestamp, 145 | } 146 | return self._request('POST', url, data) 147 | 148 | def get_last_metric_point(self, id_metric): 149 | url = '{0}/api/v1/metrics/{1}/points'.format( 150 | self.cachet_url, 151 | id_metric 152 | ) 153 | api_response = self._request('GET', url) 154 | 155 | last_page = api_response.get('meta').get('pagination').get('total_pages') 156 | 157 | url = '{0}/api/v1/metrics/{1}/points?page={2}'.format( 158 | self.cachet_url, 159 | id_metric, 160 | last_page 161 | ) 162 | api_response = self._request('GET', url) 163 | 164 | data = api_response.get('data') 165 | if data: 166 | # Return the latest data 167 | fmt = CACHETHQ_DATE_FORMAT 168 | created_at_dates = [ 169 | datetime.strptime(datum['created_at'], fmt) 170 | for datum in data 171 | ] 172 | max_index = created_at_dates.index(max(created_at_dates)) 173 | 174 | data = data[max_index] 175 | else: 176 | data = { 177 | 'created_at': datetime.now().date().strftime( 178 | CACHETHQ_DATE_FORMAT 179 | ) 180 | } 181 | 182 | return data 183 | 184 | def _request(self, method, url, data=None): 185 | logger.info('HTTP %s URL: %s', method, url) 186 | 187 | if data: 188 | data = parse.urlencode(data).encode('utf-8') 189 | 190 | req = request.Request( 191 | url=url, 192 | data=data, 193 | method=method, 194 | headers={ 195 | 'X-Cachet-Token': self.cachet_api_key, 196 | 'Time-Zone': 'Etc/UTC', 197 | 'User-Agent': USER_AGENT, 198 | }, 199 | ) 200 | 201 | response = request.urlopen(req) 202 | content = response.read().decode('utf-8') 203 | 204 | return json.loads(content) 205 | 206 | 207 | class Monitor(object): 208 | def __init__(self, monitor_list, api_key, cachet): 209 | self.monitor_list = monitor_list 210 | self.api_key = api_key 211 | self.cachet = cachet 212 | 213 | def send_data_to_cachet(self, monitor): 214 | """ Posts data to Cachet API. 215 | Data sent is the value of last `Uptime`. 216 | """ 217 | website_config = self._get_website_config(monitor) 218 | 219 | if 'cachet_url' in website_config and 'cachet_api_key' in website_config: 220 | self.cachet = CachetHq( 221 | cachet_api_key=website_config['cachet_api_key'], 222 | cachet_url=website_config['cachet_url'], 223 | ) 224 | 225 | if website_config.get('component_id'): 226 | self.cachet.update_component( 227 | website_config['component_id'], 228 | int(monitor.get('status')) 229 | ) 230 | 231 | if website_config.get('metric_id'): 232 | self.sync_metric(monitor, self.cachet) 233 | 234 | def sync_metric(self, monitor, cachet): 235 | website_config = self._get_website_config(monitor) 236 | latest_metric = cachet.get_last_metric_point(website_config['metric_id']) 237 | 238 | logger.info('Number of response times: %d', len(monitor['response_times'])) 239 | logger.info('Latest metric: %s', latest_metric) 240 | unixtime = self._date_str_to_unixtime(latest_metric['created_at']) 241 | 242 | response_times = [ 243 | x for x in monitor['response_times'] 244 | if x['datetime'] > unixtime 245 | ] 246 | response_times.sort(key=lambda x: x['datetime']) 247 | 248 | logger.info('Number of new response times: %d', len(response_times)) 249 | 250 | for response_time in response_times: 251 | metric = cachet.set_data_metrics( 252 | response_time['value'], 253 | response_time['datetime'], 254 | website_config['metric_id'] 255 | ) 256 | logger.info('Metric created: %s', metric) 257 | 258 | def update(self): 259 | """ Update all monitors uptime and status. 260 | """ 261 | uptime_robot = UptimeRobot(self.api_key) 262 | success, response = uptime_robot.get_monitors(response_times=1) 263 | if success: 264 | monitors = response.get('monitors') 265 | self._log_unknown_monitors(monitors) 266 | for monitor in monitors: 267 | if monitor['id'] in self.monitor_list: 268 | logger.info( 269 | 'Updating monitor %s. URL: %s. ID: %s', 270 | monitor['friendly_name'], 271 | monitor['url'], 272 | monitor['id'] 273 | ) 274 | try: 275 | self.send_data_to_cachet(monitor) 276 | except Exception: 277 | logging.exception( 278 | 'Exception raised when updating monitor %s', 279 | monitor['friendly_name'] 280 | ) 281 | else: 282 | logger.error('No data was returned from UptimeMonitor') 283 | 284 | def _log_unknown_monitors(self, monitors): 285 | configured_monitors = set(self.monitor_list.keys()) 286 | uptimerobot_monitors = set([ 287 | monitor['id'] for monitor in monitors 288 | ]) 289 | 290 | unknown_monitors = configured_monitors - uptimerobot_monitors 291 | 292 | if unknown_monitors: 293 | logger.warning( 294 | 'The following monitors do not exist in UptimeRobot: %s', 295 | unknown_monitors 296 | ) 297 | 298 | def _get_website_config(self, monitor): 299 | try: 300 | return self.monitor_list[monitor.get('id')] 301 | except KeyError: 302 | logger.error('Monitor is not valid') 303 | sys.exit(1) 304 | 305 | def _date_str_to_unixtime(self, date_str, fmt=CACHETHQ_DATE_FORMAT, tzinfo=timezone.utc): 306 | unixtime = datetime.strptime(date_str, fmt) \ 307 | .replace(tzinfo=tzinfo) \ 308 | .timestamp() 309 | 310 | return int(unixtime) 311 | 312 | 313 | def main(): 314 | args = parse_args() 315 | monitor_dict, uptime_robot_api_key, cachet = parse_config(args.config_file) 316 | 317 | if args.print_ids: 318 | uptime_robot = UptimeRobot(uptime_robot_api_key) 319 | success, response = uptime_robot.get_monitors(response_times=1) 320 | if success: 321 | monitors = response.get('monitors') 322 | for monitor in monitors: 323 | print('Monitor ID: {1}, Name: {0}.'.format( 324 | monitor['friendly_name'], 325 | monitor['id'], 326 | )) 327 | else: 328 | print('ERROR: No data was returned from UptimeMonitor') 329 | sys.exit(1) 330 | 331 | Monitor(monitor_list=monitor_dict, api_key=uptime_robot_api_key, cachet=cachet).update() 332 | 333 | 334 | def parse_args(): 335 | parser = argparse.ArgumentParser(description='Send data from UptimeRobot to CachetHQ') 336 | 337 | parser.add_argument( 338 | 'config_file', 339 | nargs='?', 340 | type=argparse.FileType('r'), 341 | help='path to the configuration file (default: config.ini in current folder)', 342 | default='config.ini' 343 | ) 344 | 345 | parser.add_argument( 346 | '--printIds', 347 | '-p', 348 | action='store_true', 349 | help='print list with monitors from UptimeRobot', 350 | dest='print_ids' 351 | ) 352 | 353 | return parser.parse_args() 354 | 355 | 356 | def parse_config(config_file): 357 | config = configparser.ConfigParser() 358 | config.read_file(config_file) 359 | 360 | if not config.sections(): 361 | logger.error('File path is not valid') 362 | sys.exit(1) 363 | 364 | uptime_robot_api_key = None 365 | monitor_dict = {} 366 | for element in config.sections(): 367 | if element == 'uptimeRobot': 368 | uptime_robot_api_key = config[element]['UptimeRobotMainApiKey'] 369 | elif element == 'cachet': 370 | cachet_api_key = config[element]['CachetApiKey'] 371 | cachet_url = config[element]['CachetUrl'] 372 | else: 373 | element_int = int(element) 374 | monitor_dict[element_int] = {} 375 | if 'CachetApiKey' in config[element] and 'CachetUrl' in config[element]: 376 | monitor_dict[element_int].update({ 377 | 'cachet_api_key': config[element]['CachetApiKey'], 378 | 'cachet_url': config[element]['CachetUrl'], 379 | }) 380 | if 'MetricId' in config[element]: 381 | monitor_dict[element_int].update({ 382 | 'metric_id': config[element].get('MetricId'), 383 | }) 384 | if 'ComponentId' in config[element]: 385 | monitor_dict[element_int].update({ 386 | 'component_id': config[element].get('ComponentId'), 387 | }) 388 | 389 | cachet = CachetHq( 390 | cachet_api_key=cachet_api_key, 391 | cachet_url=cachet_url, 392 | ) 393 | 394 | return monitor_dict, uptime_robot_api_key, cachet 395 | 396 | 397 | if __name__ == "__main__": 398 | main() 399 | --------------------------------------------------------------------------------