diff --git a/CHANGELOG.md b/CHANGELOG.md index 64809276..458c7755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Change Log -## [1.0.0](https://github.com/pyouroboros/ouroboros/tree/1.0.0) (2019-01-22) +## [1.1.0](https://github.com/pyouroboros/ouroboros/tree/1.1.0) (2019-01-26) +[Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.0.0...1.1.0) + +**Implemented enhancements:** + +- Notification via Telegram [\#146](https://github.com/pyouroboros/ouroboros/issues/146) +- Add flag to allow a labels\_only condition [\#142](https://github.com/pyouroboros/ouroboros/issues/142) +- DRY\_RUN flag [\#140](https://github.com/pyouroboros/ouroboros/issues/140) +- Notification on startup [\#138](https://github.com/pyouroboros/ouroboros/issues/138) +- Start/Stop containers in sequence [\#106](https://github.com/pyouroboros/ouroboros/issues/106) +- Refactor/notifications with apprise [\#151](https://github.com/pyouroboros/ouroboros/pull/151) [[breaking change](https://github.com/pyouroboros/ouroboros/labels/breaking%20change)] [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) + +**Fixed bugs:** + +- Catch invalid docker socket config [\#148](https://github.com/pyouroboros/ouroboros/issues/148) +- Explicitly Define true/false [\#141](https://github.com/pyouroboros/ouroboros/issues/141) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] + +**Other Pull Requests** + +- v1.1.0 Merge [\#153](https://github.com/pyouroboros/ouroboros/pull/153) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- v1.1.0 to develop [\#152](https://github.com/pyouroboros/ouroboros/pull/152) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- Patch/group 1 [\#150](https://github.com/pyouroboros/ouroboros/pull/150) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- Add volume for docker socket path [\#144](https://github.com/pyouroboros/ouroboros/pull/144) ([mauvehed](https://github.com/mauvehed)) + +## [1.0.0](https://github.com/pyouroboros/ouroboros/tree/1.0.0) (2019-01-23) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.6.0...1.0.0) **Implemented enhancements:** @@ -29,8 +53,8 @@ **Other Pull Requests** -- v1.0.0 to develop [\#136](https://github.com/pyouroboros/ouroboros/pull/136) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.0.0 Merge [\#137](https://github.com/pyouroboros/ouroboros/pull/137) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- v1.0.0 to develop [\#136](https://github.com/pyouroboros/ouroboros/pull/136) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Clean old legacy files [\#134](https://github.com/pyouroboros/ouroboros/pull/134) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Cleanup/qemu logic [\#128](https://github.com/pyouroboros/ouroboros/pull/128) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - fix readme wording for monitoring remote hosts [\#126](https://github.com/pyouroboros/ouroboros/pull/126) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ([circa10a](https://github.com/circa10a)) diff --git a/README.md b/README.md index a95f3549..b04c69ce 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A python-based successor to [watchtower](https://github.com/v2tec/watchtower) Ouroboros will monitor (all or specified) running docker containers and update them to the (latest or tagged) available image in the remote registry. The updated container uses the same tag and parameters that were used when the container was first created such as volume/bind mounts, docker network connections, environment variables, restart policies, entrypoints, commands, etc. - Push your image to your registry and simply wait your defined interval for ouroboros to find the new image and redeploy your container autonomously. -- Notify you via email or platform customized webhooks. (Currently: Discord/Slack/Pushover/HealthChecks/Generic) +- Notify you via many platforms courtesy of [Apprise](https://github.com/caronc/apprise) - Serve metrics for trend monitoring (Currently: Prometheus/Influxdb) - Limit your server ssh access - `ssh -i key server.domainname "docker pull ... && docker run ..."` is for scrubs @@ -55,7 +55,7 @@ pip install ouroboros-cli And can then be invoked using the `ouroboros` command: ```bash -$ ouroboros --interval 300 --loglevel debug +$ ouroboros --interval 300 --log-level debug ``` > This can be useful if you would like to create a `systemd` service or similar daemon that doesn't run in a container diff --git a/docker-compose.yml b/docker-compose.yml index 1ae3665d..253b7285 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,3 +12,5 @@ services: - IGNORE=mongo influxdb postgres mariadb - TZ=America/Chicago restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock diff --git a/pyouroboros/__init__.py b/pyouroboros/__init__.py index 9e506d7b..833dd2d3 100644 --- a/pyouroboros/__init__.py +++ b/pyouroboros/__init__.py @@ -1,2 +1,2 @@ -VERSION = "1.0.0" +VERSION = "1.1.0" BRANCH = "master" diff --git a/pyouroboros/config.py b/pyouroboros/config.py index 9581127a..7b99852d 100644 --- a/pyouroboros/config.py +++ b/pyouroboros/config.py @@ -4,11 +4,10 @@ class Config(object): options = ['INTERVAL', 'PROMETHEUS', 'DOCKER_SOCKETS', 'MONITOR', 'IGNORE', 'LOG_LEVEL', 'PROMETHEUS_ADDR', - 'PROMETHEUS_PORT', 'WEBHOOK_URLS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST', + 'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST', 'INFLUX_URL', 'INFLUX_PORT', 'INFLUX_USERNAME', 'INFLUX_PASSWORD', 'INFLUX_DATABASE', 'INFLUX_SSL', - 'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'PUSHOVER_TOKEN', 'PUSHOVER_USER', 'PUSHOVER_DEVICE', 'SMTP_HOST', - 'SMTP_PORT', 'SMTP_STARTTLS', 'SMTP_USERNAME', 'SMTP_PASSWORD', 'SMTP_RECIPIENTS', 'SMTP_FROM_EMAIL', - 'SMTP_FROM_NAME', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY'] + 'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY', 'LABELS_ONLY', + 'DRY_RUN'] interval = 300 docker_sockets = 'unix://var/run/docker.sock' @@ -20,8 +19,10 @@ class Config(object): latest = False cleanup = False run_once = False + dry_run = False self_update = False label_enable = False + labels_only = False repo_user = None repo_pass = None @@ -39,20 +40,7 @@ class Config(object): influx_password = 'root' influx_database = None - webhook_urls = [] - - pushover_token = None - pushover_user = None - pushover_device = None - - smtp_host = None - smtp_port = 587 - smtp_starttls = False - smtp_username = None - smtp_password = None - smtp_recipients = None - smtp_from_email = None - smtp_from_name = 'Ouroboros' + notifiers = [] def __init__(self, environment_vars, cli_args): self.cli_args = cli_args @@ -88,18 +76,20 @@ def config_blacklist(self): def parse(self): for option in Config.options: if self.environment_vars.get(option): - if option in ['INTERVAL', 'PROMETHEUS_PORT', 'INFLUX_PORT', 'SMTP_PORT']: + if option in ['INTERVAL', 'PROMETHEUS_PORT', 'INFLUX_PORT']: try: opt = int(self.environment_vars[option]) setattr(self, option.lower(), opt) except ValueError as e: print(e) - elif option in ['LATEST', 'CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', - 'SMTP_STARTTLS', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY']: - if self.environment_vars[option].lower() == 'true': + elif option in ['LATEST', 'CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DRY_RUN', + 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY', 'LABELS_ONLY']: + if self.environment_vars[option].lower() in ['true', 'yes']: setattr(self, option.lower(), True) + elif self.environment_vars[option].lower() in ['false', 'no']: + setattr(self, option.lower(), False) else: - self.logger.error('%s is not a valid option for %s. setting to false', + self.logger.error('%s is not true/yes, nor false/no for %s. Assuming false', self.environment_vars[option], option) else: setattr(self, option.lower(), self.environment_vars[option]) @@ -113,10 +103,10 @@ def parse(self): if self.interval < 30: self.interval = 30 - for option in ['docker_sockets', 'webhook_urls', 'smtp_recipients', 'monitor', 'ignore']: + for option in ['docker_sockets', 'notifiers', 'monitor', 'ignore']: if isinstance(getattr(self, option), str): string_list = getattr(self, option) - setattr(self, option, [string.strip(' ') for string in string_list.split(' ')]) + setattr(self, option, [string.strip(' ').strip('"') for string in string_list.split(' ')]) # Config sanity checks if self.data_export == 'influxdb' and not self.influx_database: @@ -126,17 +116,8 @@ def parse(self): if self.data_export == 'prometheus' and self.self_update: self.logger.warning("If you bind a port to ouroboros, it will be lost when it updates itself.") - pushover_config = [self.pushover_token, self.pushover_device, self.pushover_user] - if any(pushover_config) and not all(pushover_config): - self.logger.error('You must specify a pushover user, token, and device to use pushover. Disabling ' - 'pushover notifications') - elif all(pushover_config): - self.webhook_urls.append('https://api.pushover.net/1/messages.json') - - email_config = [self.smtp_host, self.smtp_recipients, self.smtp_from_email] - if any(email_config) and not all(email_config): - self.logger.error('To use email notifications, you need to specify at least smtp host/recipients/from ' - 'email. Disabling email notifications') - self.smtp_host = None + if self.dry_run and not self.run_once: + self.logger.warning("Dry run is designed to be ran with run once. Setting for you.") + self.run_once = True self.config_blacklist() diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 8fb6a719..7a3e5dab 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -52,9 +52,9 @@ def monitor_filter(self): monitored_containers.append(container) else: continue - elif self.config.monitor and container.name not in self.config.ignore: + elif not self.config.labels_only and self.config.monitor and container.name not in self.config.ignore: monitored_containers.append(container) - elif container.name not in self.config.ignore: + elif not self.config.labels_only and container.name not in self.config.ignore: monitored_containers.append(container) self.data_manager.monitored_containers[self.socket] = len(monitored_containers) @@ -80,22 +80,29 @@ def pull(self, image_object): tag = ':'.join(split_tag[:-1]) tag = f'{tag}:latest' - self.logger.debug('Pulling tag: %s', tag) + self.logger.debug('Checking tag: %s', tag) try: - if self.config.auth_json: - return_image = self.client.images.pull(tag, auth_config=self.config.auth_json) + if self.config.dry_run: + registry_data = self.client.images.get_registry_data(tag) + return registry_data else: - return_image = self.client.images.pull(tag) - return return_image - + if self.config.auth_json: + return_image = self.client.images.pull(tag, auth_config=self.config.auth_json) + else: + return_image = self.client.images.pull(tag) + return return_image except APIError as e: - self.logger.critical(e) if '' in str(e): self.logger.debug("Docker api issue. Ignoring") raise ConnectionError elif 'unauthorized' in str(e): - self.logger.critical("Invalid Credentials. Exiting") - exit(1) + if self.config.dry_run: + self.logger.error('dry run : Upstream authentication issue while checking %s. See: ' + 'https://github.com/docker/docker-py/issues/2225', tag) + raise ConnectionError + else: + self.logger.critical("Invalid Credentials. Exiting") + exit(1) elif 'Client.Timeout' in str(e): self.logger.critical("Couldn't find an image on docker.com for %s. Local Build?", image.tags[0]) raise ConnectionError @@ -106,7 +113,7 @@ def pull(self, image_object): def update_containers(self): updated_count = 0 updated_container_tuples = [] - + depends_on_list = [] self.monitored = self.monitor_filter() if not self.monitored: @@ -128,6 +135,13 @@ def update_containers(self): except ConnectionError: continue + if self.config.dry_run: + # Ugly hack for repo digest + repo_digest_id = current_image.attrs['RepoDigests'][0].split('@')[1] + if repo_digest_id != latest_image.id: + self.logger.info('dry run : %s would be updated', container.name) + continue + # If current running container is running latest image if current_image.id != latest_image.id: if container.name in ['ouroboros', 'ouroboros-updated']: @@ -138,6 +152,10 @@ def update_containers(self): ) self.logger.info('%s will be updated', container.name) + # Get container list to restart after update complete + depends_on = container.labels.get('com.ouroboros.depends-on', False) + if depends_on: + depends_on_list.extend([name.strip() for name in depends_on.split(',')]) # new container dict to create new container from new_config = set_properties(old=container, new=latest_image) @@ -176,11 +194,21 @@ def update_containers(self): self.data_manager.add(label=container.name, socket=self.socket) self.data_manager.add(label='all', socket=self.socket) - if updated_count > 0: - self.notification_manager.send(container_tuples=updated_container_tuples, socket=self.socket, - notification_type='data') + if depends_on_list: + depends_on_containers = [] + for name in list(set(depends_on_list)): + try: + depends_on_containers.append(self.client.containers.get(name)) + except NotFound: + self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket) - self.notification_manager.send(notification_type='keep_alive') + if depends_on_containers: + for container in depends_on_containers: + self.logger.debug('Restarting dependant container %s', container.name) + container.restart() + + if updated_count > 0: + self.notification_manager.send(container_tuples=updated_container_tuples, socket=self.socket, kind='update') def update_self(self, count=None, old_container=None, me_list=None, new_image=None): if count == 2: diff --git a/pyouroboros/helpers.py b/pyouroboros/helpers.py index 76cf466e..01de8084 100644 --- a/pyouroboros/helpers.py +++ b/pyouroboros/helpers.py @@ -16,9 +16,10 @@ def set_properties(old, new, self_name=None): return properties -EMAIL_TEMPLATE = Template( +NotificationTemplate = Template( 'Host Socket: ${HOST_SOCKET}\n' 'Containers Monitored: ${CONTAINERS_MONITORED}\n' 'Containers Updated: ${CONTAINERS_UPDATED}\n' + 'Containers Updated This Pass: {CONTAINERS_THIS_PASS}' '${CONTAINER_UPDATES}' ) diff --git a/pyouroboros/notifiers.py b/pyouroboros/notifiers.py index d74a0fee..a669c361 100644 --- a/pyouroboros/notifiers.py +++ b/pyouroboros/notifiers.py @@ -1,12 +1,7 @@ -import requests +import apprise -from email.message import EmailMessage -from smtplib import SMTP, SMTPConnectError, SMTPAuthenticationError, SMTPServerDisconnected, SMTPException from logging import getLogger -from datetime import datetime, timezone -from requests.exceptions import RequestException - -from pyouroboros.helpers import EMAIL_TEMPLATE +from datetime import datetime, timezone, timedelta class NotificationManager(object): @@ -15,184 +10,54 @@ def __init__(self, config, data_manager): self.data_manager = data_manager self.logger = getLogger() - self.email = Email(self.config, self.data_manager) - self.webhooks = Webhooks(self.config, self.data_manager) - - def send(self, container_tuples=None, socket=None, notification_type='data'): - if self.email.server and notification_type == 'data': - self.email.send(container_tuples, socket) - - if self.config.webhook_urls: - self.webhooks.send(container_tuples, socket, notification_type) - - -class Email(object): - def __init__(self, config, data_manager): - self.config = config - self.data_manager = data_manager - - self.logger = getLogger() - if self.config.smtp_host: - self.server = True + self.apprise = self.build_apprise() + + def build_apprise(self): + asset = apprise.AppriseAsset( + image_url_mask='https://bin.cajun.pro/images/ouroboros/notifications/ouroboros-logo-{XY}{EXTENSION}', + default_extension='.png' + ) + asset.app_id = "Ouroboros" + asset.app_desc = "Ouroboros" + asset.app_url = "https://github.com/pyouroboros/ouroboros" + asset.html_notify_map['info'] = '#5F87C6' + asset.image_url_logo = 'https://bin.cajun.pro/images/ouroboros/notifications/ouroboros-logo-256x256.png' + + apprise_obj = apprise.Apprise(asset=asset) + + for notifier in self.config.notifiers: + add = apprise_obj.add(notifier) + if not add: + self.logger.error('Could not add notifier %s', notifier) + + return apprise_obj + + def send(self, container_tuples=None, socket=None, kind='update'): + if kind == 'startup': + now = datetime.now(timezone.utc).astimezone() + title = f'Ouroboros has started' + body_fields = [ + f'Time: {now.strftime("%Y-%m-%d %H:%M:%S")}', + f'Next Run: {(now + timedelta(0, self.config.interval)).strftime("%Y-%m-%d %H:%M:%S")}' + ] else: - self.server = False - - def get_server(self): - try: - server = SMTP( - host=self.config.smtp_host, - port=self.config.smtp_port - ) - if self.config.smtp_starttls: - server.starttls() - if self.config.smtp_username and self.config.smtp_password: - server.login(self.config.smtp_username, self.config.smtp_password) - return server - except SMTPConnectError as e: - self.logger.error('Could not connect to SMTP host %s on port %s. Disabling SMTP. Error: %s', - self.config.smtp_host, self.config.smtp_port, e) - return - except SMTPAuthenticationError as e: - self.logger.error('SMTP host did not accept credentials. Disabling SMTP. Error %s', e) - return - - def send(self, container_tuples, socket): - for address in self.config.smtp_recipients: - msg = EmailMessage() - msg['Subject'] = 'Ouroboros has updated containers!' - msg['From'] = f"{self.config.smtp_from_name} <{self.config.smtp_from_email}>" - msg['To'] = address - - container_updates = '' - for container, old_image, new_image in container_tuples: - container_updates += "{} updated from {} to {}\n".format( - container.name, - old_image.short_id.split(":")[1], - new_image.short_id.split(":")[1] - ) - - template = EMAIL_TEMPLATE.substitute( - CONTAINERS_MONITORED=self.data_manager.monitored_containers[socket], - CONTAINERS_UPDATED=self.data_manager.total_updated[socket], - HOST_SOCKET=socket.split("//")[1], - CONTAINER_UPDATES=container_updates) - - msg.set_content(template) - try: - server = self.get_server() - server.send_message(msg) - except SMTPServerDisconnected as e: - self.server = False - self.logger.error('Could not properly talk to SMTP server. Disabling SMTP. Error: %s', e) - except SMTPException as e: - self.server = False - self.logger.error('SMTP Error: %s', e) - - -class Webhooks(object): - def __init__(self, config, data_manager): - self.config = config - self.data_manager = data_manager - - self.logger = getLogger() - - def send(self, container_tuples, socket, notification_type): - formatted_webhooks = [] - for webhook_url in self.config.webhook_urls: - if notification_type == "keep_alive": - if "hc-ping" in webhook_url: - formatted_webhooks.append((webhook_url, {})) - else: - if 'discord' in webhook_url: - format_type = 'discord' - elif 'slack' in webhook_url: - format_type = 'slack' - elif 'pushover' in webhook_url: - format_type = 'pushover' - elif 'hc-ping' in webhook_url: - continue - else: - format_type = 'default' - - formatted_webhooks.append((webhook_url, self.format(container_tuples, socket, format_type))) - - self.post(formatted_webhooks) - - def format(self, container_tuples, socket, format_type): - clean_socket = socket.split("//")[1] - now = str(datetime.now(timezone.utc)).replace(" ", "T") - if format_type in ['slack', 'default', 'pushover']: - text = "Host Socket: {}\n".format(clean_socket) - text += "Containers Monitored: {}\n".format(self.data_manager.monitored_containers[socket]) - text += "Containers Updated: {}\n".format(self.data_manager.total_updated[socket]) - for container, old_image, new_image in container_tuples: - text += "{} updated from {} to {}\n".format( - container.name, - old_image.short_id.split(":")[1], - new_image.short_id.split(":")[1] - ) - text += now - if format_type == 'pushover': - json = { - "html": 1, - "token": self.config.pushover_token, - "user": self.config.pushover_user, - "device": self.config.pushover_device, - "title": "Ouroboros has updated containers!", - "message": text - } - else: - json = {"text": text} - return json - - elif format_type == 'discord': - json = { - "embeds": [ - { - "title": "Ouroboros has updated containers!", - "description": "Breakdown:", - "color": 316712, - "timestamp": now, - "thumbnail": { - "url": "https://bin.cajun.pro/images/ouroboros/ouroboros_logo_primary_cropped.png" - }, - "fields": [ - { - "name": "Socket:", - "value": f"{clean_socket}" - }, - { - "name": "Containers Monitored", - "value": f"{self.data_manager.monitored_containers[socket]}", - "inline": True - }, - { - "name": "Containers Updated", - "value": f"{self.data_manager.total_updated[socket]}", - "inline": True - } - ] - } + title = 'Ouroboros has updated containers!' + body_fields = [ + f"Host Socket: {socket.split('//')[1]}", + f"Containers Monitored: {self.data_manager.monitored_containers[socket]}", + f"Total Containers Updated: {self.data_manager.total_updated[socket]}", + f"Containers updated this pass: {len(container_tuples)}" + ] + body_fields.extend( + [ + "{} updated from {} to {}".format( + container.name, + old_image.short_id.split(':')[1], + new_image.short_id.split(':')[1] + ) for container, old_image, new_image in container_tuples ] - } - for container, old_image, new_image in container_tuples: - json['embeds'][0]['fields'].append( - { - "name": container.name, - "value": 'Old SHA: {} | New SHA: {}'.format( - old_image.short_id.split(":")[1], - new_image.short_id.split(":")[1] - ) - } - ) - return json + ) + body = '\n'.join(body_fields) - def post(self, webhook_tuples): - """POST webhook for notifications""" - for url, json in webhook_tuples: - try: - headers = {'Content-Type': 'application/json', 'user-agent': 'ouroboros'} - p = requests.post(url, json=json, headers=headers) - self.logger.debug("Sent webhook successfully to %s | status code %s", url, p) - except RequestException as e: - self.logger.error("Error Posting to Webhook url %s | error %s", url, e) + if self.apprise.servers: + self.apprise.notify(title=title, body=body) diff --git a/pyouroboros/ouroboros.py b/pyouroboros/ouroboros.py index f4a2330f..dfdf9c1a 100644 --- a/pyouroboros/ouroboros.py +++ b/pyouroboros/ouroboros.py @@ -2,6 +2,7 @@ from time import sleep from os import environ +from requests.exceptions import ConnectionError from argparse import ArgumentParser, RawTextHelpFormatter from pyouroboros.config import Config @@ -42,6 +43,14 @@ def main(): core_group.add_argument('-o', '--run-once', default=False, action='store_true', dest='RUN_ONCE', help='Single run') + core_group.add_argument('-A', '--dry-run', default=False, action='store_true', dest='DRY_RUN', + help='Run without making changes. Best used with run-once') + + core_group.add_argument('-N', '--notifiers', nargs='+', default=Config.notifiers, dest='NOTIFIERS', + help='Apprise formatted notifiers\n' + 'EXAMPLE: -N discord://1234123412341234/jasdfasdfasdfasddfasdf ' + 'mailto://user:pass@gmail.com') + docker_group = parser.add_argument_group("Docker", "Configuration of docker functionality") docker_group.add_argument('-m', '--monitor', nargs='+', default=Config.monitor, dest='MONITOR', help='Which container(s) to monitor\n' @@ -52,8 +61,13 @@ def main(): 'EXAMPLE: -n container1 container2') docker_group.add_argument('-k', '--label-enable', default=False, dest='LABEL_ENABLE', action='store_true', - help='Only watch ouroboros enable labeled containers\n' - 'Note: labels take precedence over monitor/ignore' + help='Enable label monitoring for ouroboros label options\n' + 'Note: labels take precedence' + 'DEFAULT: False') + + docker_group.add_argument('-M', '--labels-only', default=False, dest='LABELS_ONLY', action='store_true', + help='Only watch containers that utilize labels\n' + 'This allows a more strict compliance for environments' 'DEFAULT: False') docker_group.add_argument('-c', '--cleanup', default=False, dest='CLEANUP', action='store_true', @@ -107,54 +121,6 @@ def main(): data_group.add_argument('-V', '--influx-verify-ssl', default=False, dest='INFLUX_VERIFY_SSL', action='store_true', help='Verify SSL certificate when connecting to influxdb') - notification_group = parser.add_argument_group('Notifications', 'Configuration of notification functionality') - notification_group.add_argument('-w', '--webhook-urls', nargs='+', default=Config.webhook_urls, dest='WEBHOOK_URLS', - help='Webhook POST urls\n' - 'EXAMPLE: -w https://domain.tld/1234/asdf http://123.123.123.123:4040/re235') - - notification_group.add_argument('-y', '--pushover-token', default=Config.pushover_token, dest='PUSHOVER_TOKEN', - help='Pushover token to authenticate against application\n' - 'EXAMPLE: -y af2r52352asd') - - notification_group.add_argument('-Y', '--pushover-device', default=Config.pushover_device, dest='PUSHOVER_DEVICE', - help='Device to receive pushover notification\n' - 'EXAMPLE: -Y SamsungGalaxyS8') - - notification_group.add_argument('-z', '--pushover-user', default=Config.pushover_user, dest='PUSHOVER_USER', - help='Pushover user bound to application\n' - 'EXAMPLE: -z asdfweawefasdfawef') - - notification_group.add_argument('-e', '--smtp-host', default=Config.smtp_host, dest='SMTP_HOST', - help='SMTP relay hostname\n' - 'EXAMPLE: -e smtp.gmail.com') - - notification_group.add_argument('-E', '--smtp-port', default=Config.smtp_port, type=int, dest='SMTP_PORT', - help='SMTP relay port\n' - 'EXAMPLE: -E 587') - - notification_group.add_argument('-f', '--smtp-starttls', default=False, dest='SMTP_STARTTLS', action='store_true', - help='SMTP relay uses STARTTLS') - - notification_group.add_argument('-F', '--smtp-username', default=Config.smtp_username, dest='SMTP_USERNAME', - help='SMTP relay username\n' - 'EXAMPLE: -F ouroboros@ouroboros.com') - - notification_group.add_argument('-g', '--smtp-password', default=Config.smtp_password, dest='SMTP_PASSWORD', - help='SMTP relay password\n' - 'EXAMPLE: -g MyPa$$w0rd') - - notification_group.add_argument('-G', '--smtp-recipients', default=Config.smtp_recipients, dest='SMTP_RECIPIENTS', - nargs='+', help='SMTP notification recipients\n' - 'EXAMPLE: -G ouroboros@ouroboros.com ouroboros2@ouroboros.com') - - notification_group.add_argument('-j', '--smtp-from-email', default=Config.smtp_from_email, dest='SMTP_FROM_EMAIL', - help='SMTP from email\n' - 'EXAMPLE: -g notifications@ouroboros.com') - - notification_group.add_argument('-J', '--smtp-from-name', default=Config.smtp_from_name, dest='SMTP_FROM_NAME', - help='SMTP from name\n' - 'DEFAULT: Ouroboros') - args = parser.parse_args() if environ.get('LOG_LEVEL'): @@ -169,14 +135,18 @@ def main(): data_manager = DataManager(config) notification_manager = NotificationManager(config, data_manager) + notification_manager.send(kind='startup') for socket in config.docker_sockets: - docker = Docker(socket, config, data_manager, notification_manager) - schedule.every(config.interval).seconds.do(docker.update_containers).tag(f'update-containers-{socket}') + try: + docker = Docker(socket, config, data_manager, notification_manager) + schedule.every(config.interval).seconds.do(docker.update_containers).tag(f'update-containers-{socket}') + except ConnectionError: + ol.logger.error("Could not connect to socket %s. Check your config", socket) schedule.run_all() - if args.RUN_ONCE: + if config.run_once: for socket in config.docker_sockets: schedule.clear(f'update-containers-{socket}') diff --git a/requirements.txt b/requirements.txt index ce495af5..ff82f09f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ docker>=3.7.0 schedule>=0.5.0 prometheus_client>=0.5.0 requests>=2.21.0 -influxdb>=5.2.1 \ No newline at end of file +influxdb>=5.2.1 +apprise>=0.5.2 \ No newline at end of file diff --git a/setup.py b/setup.py index c9161dcf..2bdc8ecb 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,8 @@ 'schedule>=0.5.0', 'prometheus_client>=0.5.0', 'requests>=2.21.0', - 'influxdb>=5.2.1'] + 'influxdb>=5.2.1', + 'apprise>=0.5.2'] def readme():