diff --git a/README.md b/README.md index 7a34935..831dfc5 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Cioban will try to update your services every 5 minutes by default. The followin | **Variable** | **Default** | **Description** | |:----------------------------|:-----------:|:--------------------------------------------------------------------------------------------------------| | `SLEEP_TIME` | `6h` | Adjust the sleeping time. Accepted are numbers ending in one of `s`, `m`, `h`, `d`, `w`| +| `SCHEDULE_TIME` | - | Cron-Style string for scheduled runs. This will **disable** `SLEEP_TIME` | | `BLACKLIST_SERVICES` | - | Space-separated list of service names to exclude from updates | | `FILTER_SERVICES` | - | Anything accepted by the filtering flag in `docker service ls`. Example: `label=ai.ix.auto-update=true` | | `TELEGRAM_TOKEN` | - | See the [Telegram documentation](https://core.telegram.org/bots#creating-a-new-bot) how to get a new token | @@ -49,6 +50,10 @@ Additionally, these environment variables are [supported](https://docker-py.read | `DOCKER_CERT_PATH` | A path to a directory containing TLS certificates to use when connecting to the Docker host. (**Note**: this path needs to be present inside the `registry.gitlab.com/ix.ai/cioban` image) | +## Cron-Style Scheduling + +`cioban` is using [cronsim](https://github.com/cuu508/cronsim) for parsing the `SCHEDULE_TIME`. For accepted values, please consult the [cronsim](https://github.com/cuu508/cronsim) documentation. + ## Webhooks Starting with version `0.12.0`, `registry.gitlab.com/ix.ai/cioban` supports simple webhooks for each service, that are configured in the service labels. diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 diff --git a/cioban/__main__.py b/cioban/__main__.py index 52033c2..b720d92 100644 --- a/cioban/__main__.py +++ b/cioban/__main__.py @@ -20,7 +20,7 @@ c = cioban.Cioban(**options) -startup_message = (f"Starting **{__package__} {version}**. Exposing metrics on port {c.get_port()}") +startup_message = f"Starting **{__package__} {version}**. Exposing metrics on port {c.get_port()}" log.warning(startup_message) c.notify(title="Startup", message=startup_message) diff --git a/cioban/cioban.py b/cioban/cioban.py index 6cc6164..75171b1 100644 --- a/cioban/cioban.py +++ b/cioban/cioban.py @@ -3,10 +3,12 @@ """ A docker swarm service for automatically updating your services to the latest image tag push. """ import logging +from datetime import datetime import requests import pause import docker from prometheus_client import start_http_server +from cronsim import CronSim, CronSimError from .lib import constants from .lib import prometheus from .lib import notifiers @@ -21,6 +23,7 @@ class Cioban(): 'filter_services': {}, 'blacklist_services': {}, 'sleep_time': '6h', + 'schedule_time': False, 'prometheus_port': 9308, 'notifiers': [], 'notify_include_image': False, @@ -37,6 +40,15 @@ def __init__(self, **kwargs): else: log.debug(f'{k} not found in settings. Ignoring.') prometheus.PROM_INFO.info({'version': f'{constants.VERSION}-{constants.BUILD}'}) + + # Test schedule_time + if self.settings['schedule_time']: + log.debug('Disabling SLEEP_TIME because SCHEDULE_TIME is set.') + try: + CronSim(self.settings['schedule_time'], datetime.now()) + except (CronSimError) as e: + raise CronSimError(f"{self.settings['schedule_time']} not understood. The error: {e}") from CronSimError + relation = { 's': 'seconds', 'm': 'minutes', @@ -44,19 +56,21 @@ def __init__(self, **kwargs): 'd': 'days', 'w': 'weeks', } - if any(s.isalpha() for s in self.settings['sleep_time']): + + try: + self.sleep = int(self.settings['sleep_time']) + self.sleep_type = 'minutes' + except ValueError as exc: try: self.sleep = int(self.settings['sleep_time'][:-1]) - except ValueError: - raise ValueError(f"{self.settings['sleep_time']} not understood") from ValueError + except ValueError as e: + raise ValueError(f"{self.settings['sleep_time']} not understood. The error: {e}") from e if self.settings['sleep_time'][-1] in relation: self.sleep_type = relation[self.settings['sleep_time'][-1]] else: - raise ValueError(f"{self.settings['sleep_time']} not understood") - else: - self.sleep = int(self.settings['sleep_time']) - self.sleep_type = 'minutes' + raise ValueError(f"{self.settings['sleep_time']} not understood") from exc + self.register_notifiers(**kwargs) log.debug('Cioban initialized') @@ -83,13 +97,20 @@ def run(self): start_http_server(self.settings['prometheus_port']) # starts the prometheus metrics server log.info(f"Listening on port {self.settings['prometheus_port']}") while True: - prometheus.PROM_STATE_ENUM.state('running') - log.info('Starting update run') - self._run() + self.__set_timer() log.info(f'Sleeping for {self.sleep} {self.sleep_type}') prometheus.PROM_STATE_ENUM.state('sleeping') wait = getattr(pause, self.sleep_type) wait(self.sleep) + prometheus.PROM_STATE_ENUM.state('running') + log.info('Starting update run') + self._run() + + def __set_timer(self): + self.sleep_type = 'seconds' + cron_timer = CronSim(self.settings['schedule_time'], datetime.now()) + self.sleep = (next(cron_timer) - datetime.now()).seconds + 1 + log.debug(f"Based on the cron schedule '{self.settings['schedule_time']}', next run is in {self.sleep}s") def __get_updated_image(self, image, image_sha): """ checks if an image has an update """ diff --git a/cioban/lib/helpers.py b/cioban/lib/helpers.py index 7f93a39..ff957fe 100644 --- a/cioban/lib/helpers.py +++ b/cioban/lib/helpers.py @@ -28,6 +28,7 @@ def gather_environ(keys=None) -> dict: 'notify_include_image': 'boolean', 'notify_include_new_image': 'boolean', 'notify_include_old_image': 'boolean', + 'schedule_time': 'string', 'sleep_time': 'string', 'prometheus_port': 'int', } diff --git a/cioban/requirements.txt b/cioban/requirements.txt index d6c323b..bb64434 100644 --- a/cioban/requirements.txt +++ b/cioban/requirements.txt @@ -1,5 +1,7 @@ -prometheus_client==0.15.0 +prometheus_client==0.16.0 docker==6.0.1 pause==0.3 pygelf==0.4.2 ix-notifiers==0.0.259196408 +cronsim==2.3 +