Skip to content

Commit fbf7cb1

Browse files
v1.1.0 Merge
v1.1.0 Merge
2 parents 0354c2c + 456234f commit fbf7cb1

File tree

11 files changed

+172
-299
lines changed

11 files changed

+172
-299
lines changed

CHANGELOG.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
# Change Log
22

3-
## [1.0.0](https://github.com/pyouroboros/ouroboros/tree/1.0.0) (2019-01-22)
3+
## [1.1.0](https://github.com/pyouroboros/ouroboros/tree/1.1.0) (2019-01-26)
4+
[Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.0.0...1.1.0)
5+
6+
**Implemented enhancements:**
7+
8+
- Notification via Telegram [\#146](https://github.com/pyouroboros/ouroboros/issues/146)
9+
- Add flag to allow a labels\_only condition [\#142](https://github.com/pyouroboros/ouroboros/issues/142)
10+
- DRY\_RUN flag [\#140](https://github.com/pyouroboros/ouroboros/issues/140)
11+
- Notification on startup [\#138](https://github.com/pyouroboros/ouroboros/issues/138)
12+
- Start/Stop containers in sequence [\#106](https://github.com/pyouroboros/ouroboros/issues/106)
13+
- 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))
14+
15+
**Fixed bugs:**
16+
17+
- Catch invalid docker socket config [\#148](https://github.com/pyouroboros/ouroboros/issues/148)
18+
- Explicitly Define true/false [\#141](https://github.com/pyouroboros/ouroboros/issues/141) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)]
19+
20+
**Other Pull Requests**
21+
22+
- v1.1.0 Merge [\#153](https://github.com/pyouroboros/ouroboros/pull/153) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
23+
- v1.1.0 to develop [\#152](https://github.com/pyouroboros/ouroboros/pull/152) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
24+
- Patch/group 1 [\#150](https://github.com/pyouroboros/ouroboros/pull/150) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
25+
- Add volume for docker socket path [\#144](https://github.com/pyouroboros/ouroboros/pull/144) ([mauvehed](https://github.com/mauvehed))
26+
27+
## [1.0.0](https://github.com/pyouroboros/ouroboros/tree/1.0.0) (2019-01-23)
428
[Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.6.0...1.0.0)
529

630
**Implemented enhancements:**
@@ -29,8 +53,8 @@
2953

3054
**Other Pull Requests**
3155

32-
- v1.0.0 to develop [\#136](https://github.com/pyouroboros/ouroboros/pull/136) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
3356
- v1.0.0 Merge [\#137](https://github.com/pyouroboros/ouroboros/pull/137) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
57+
- v1.0.0 to develop [\#136](https://github.com/pyouroboros/ouroboros/pull/136) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
3458
- 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))
3559
- Cleanup/qemu logic [\#128](https://github.com/pyouroboros/ouroboros/pull/128) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice))
3660
- 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))

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ A python-based successor to [watchtower](https://github.com/v2tec/watchtower)
1818
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.
1919

2020
- Push your image to your registry and simply wait your defined interval for ouroboros to find the new image and redeploy your container autonomously.
21-
- Notify you via email or platform customized webhooks. (Currently: Discord/Slack/Pushover/HealthChecks/Generic)
21+
- Notify you via many platforms courtesy of [Apprise](https://github.com/caronc/apprise)
2222
- Serve metrics for trend monitoring (Currently: Prometheus/Influxdb)
2323
- Limit your server ssh access
2424
- `ssh -i key server.domainname "docker pull ... && docker run ..."` is for scrubs
@@ -55,7 +55,7 @@ pip install ouroboros-cli
5555
And can then be invoked using the `ouroboros` command:
5656

5757
```bash
58-
$ ouroboros --interval 300 --loglevel debug
58+
$ ouroboros --interval 300 --log-level debug
5959
```
6060

6161
> This can be useful if you would like to create a `systemd` service or similar daemon that doesn't run in a container

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ services:
1212
- IGNORE=mongo influxdb postgres mariadb
1313
- TZ=America/Chicago
1414
restart: unless-stopped
15+
volumes:
16+
- /var/run/docker.sock:/var/run/docker.sock

pyouroboros/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
VERSION = "1.0.0"
1+
VERSION = "1.1.0"
22
BRANCH = "master"

pyouroboros/config.py

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44

55
class Config(object):
66
options = ['INTERVAL', 'PROMETHEUS', 'DOCKER_SOCKETS', 'MONITOR', 'IGNORE', 'LOG_LEVEL', 'PROMETHEUS_ADDR',
7-
'PROMETHEUS_PORT', 'WEBHOOK_URLS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST',
7+
'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST',
88
'INFLUX_URL', 'INFLUX_PORT', 'INFLUX_USERNAME', 'INFLUX_PASSWORD', 'INFLUX_DATABASE', 'INFLUX_SSL',
9-
'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'PUSHOVER_TOKEN', 'PUSHOVER_USER', 'PUSHOVER_DEVICE', 'SMTP_HOST',
10-
'SMTP_PORT', 'SMTP_STARTTLS', 'SMTP_USERNAME', 'SMTP_PASSWORD', 'SMTP_RECIPIENTS', 'SMTP_FROM_EMAIL',
11-
'SMTP_FROM_NAME', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY']
9+
'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY', 'LABELS_ONLY',
10+
'DRY_RUN']
1211

1312
interval = 300
1413
docker_sockets = 'unix://var/run/docker.sock'
@@ -20,8 +19,10 @@ class Config(object):
2019
latest = False
2120
cleanup = False
2221
run_once = False
22+
dry_run = False
2323
self_update = False
2424
label_enable = False
25+
labels_only = False
2526

2627
repo_user = None
2728
repo_pass = None
@@ -39,20 +40,7 @@ class Config(object):
3940
influx_password = 'root'
4041
influx_database = None
4142

42-
webhook_urls = []
43-
44-
pushover_token = None
45-
pushover_user = None
46-
pushover_device = None
47-
48-
smtp_host = None
49-
smtp_port = 587
50-
smtp_starttls = False
51-
smtp_username = None
52-
smtp_password = None
53-
smtp_recipients = None
54-
smtp_from_email = None
55-
smtp_from_name = 'Ouroboros'
43+
notifiers = []
5644

5745
def __init__(self, environment_vars, cli_args):
5846
self.cli_args = cli_args
@@ -88,18 +76,20 @@ def config_blacklist(self):
8876
def parse(self):
8977
for option in Config.options:
9078
if self.environment_vars.get(option):
91-
if option in ['INTERVAL', 'PROMETHEUS_PORT', 'INFLUX_PORT', 'SMTP_PORT']:
79+
if option in ['INTERVAL', 'PROMETHEUS_PORT', 'INFLUX_PORT']:
9280
try:
9381
opt = int(self.environment_vars[option])
9482
setattr(self, option.lower(), opt)
9583
except ValueError as e:
9684
print(e)
97-
elif option in ['LATEST', 'CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL',
98-
'SMTP_STARTTLS', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY']:
99-
if self.environment_vars[option].lower() == 'true':
85+
elif option in ['LATEST', 'CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DRY_RUN',
86+
'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY', 'LABELS_ONLY']:
87+
if self.environment_vars[option].lower() in ['true', 'yes']:
10088
setattr(self, option.lower(), True)
89+
elif self.environment_vars[option].lower() in ['false', 'no']:
90+
setattr(self, option.lower(), False)
10191
else:
102-
self.logger.error('%s is not a valid option for %s. setting to false',
92+
self.logger.error('%s is not true/yes, nor false/no for %s. Assuming false',
10393
self.environment_vars[option], option)
10494
else:
10595
setattr(self, option.lower(), self.environment_vars[option])
@@ -113,10 +103,10 @@ def parse(self):
113103
if self.interval < 30:
114104
self.interval = 30
115105

116-
for option in ['docker_sockets', 'webhook_urls', 'smtp_recipients', 'monitor', 'ignore']:
106+
for option in ['docker_sockets', 'notifiers', 'monitor', 'ignore']:
117107
if isinstance(getattr(self, option), str):
118108
string_list = getattr(self, option)
119-
setattr(self, option, [string.strip(' ') for string in string_list.split(' ')])
109+
setattr(self, option, [string.strip(' ').strip('"') for string in string_list.split(' ')])
120110

121111
# Config sanity checks
122112
if self.data_export == 'influxdb' and not self.influx_database:
@@ -126,17 +116,8 @@ def parse(self):
126116
if self.data_export == 'prometheus' and self.self_update:
127117
self.logger.warning("If you bind a port to ouroboros, it will be lost when it updates itself.")
128118

129-
pushover_config = [self.pushover_token, self.pushover_device, self.pushover_user]
130-
if any(pushover_config) and not all(pushover_config):
131-
self.logger.error('You must specify a pushover user, token, and device to use pushover. Disabling '
132-
'pushover notifications')
133-
elif all(pushover_config):
134-
self.webhook_urls.append('https://api.pushover.net/1/messages.json')
135-
136-
email_config = [self.smtp_host, self.smtp_recipients, self.smtp_from_email]
137-
if any(email_config) and not all(email_config):
138-
self.logger.error('To use email notifications, you need to specify at least smtp host/recipients/from '
139-
'email. Disabling email notifications')
140-
self.smtp_host = None
119+
if self.dry_run and not self.run_once:
120+
self.logger.warning("Dry run is designed to be ran with run once. Setting for you.")
121+
self.run_once = True
141122

142123
self.config_blacklist()

pyouroboros/dockerclient.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ def monitor_filter(self):
5252
monitored_containers.append(container)
5353
else:
5454
continue
55-
elif self.config.monitor and container.name not in self.config.ignore:
55+
elif not self.config.labels_only and self.config.monitor and container.name not in self.config.ignore:
5656
monitored_containers.append(container)
57-
elif container.name not in self.config.ignore:
57+
elif not self.config.labels_only and container.name not in self.config.ignore:
5858
monitored_containers.append(container)
5959

6060
self.data_manager.monitored_containers[self.socket] = len(monitored_containers)
@@ -80,22 +80,29 @@ def pull(self, image_object):
8080
tag = ':'.join(split_tag[:-1])
8181
tag = f'{tag}:latest'
8282

83-
self.logger.debug('Pulling tag: %s', tag)
83+
self.logger.debug('Checking tag: %s', tag)
8484
try:
85-
if self.config.auth_json:
86-
return_image = self.client.images.pull(tag, auth_config=self.config.auth_json)
85+
if self.config.dry_run:
86+
registry_data = self.client.images.get_registry_data(tag)
87+
return registry_data
8788
else:
88-
return_image = self.client.images.pull(tag)
89-
return return_image
90-
89+
if self.config.auth_json:
90+
return_image = self.client.images.pull(tag, auth_config=self.config.auth_json)
91+
else:
92+
return_image = self.client.images.pull(tag)
93+
return return_image
9194
except APIError as e:
92-
self.logger.critical(e)
9395
if '<html>' in str(e):
9496
self.logger.debug("Docker api issue. Ignoring")
9597
raise ConnectionError
9698
elif 'unauthorized' in str(e):
97-
self.logger.critical("Invalid Credentials. Exiting")
98-
exit(1)
99+
if self.config.dry_run:
100+
self.logger.error('dry run : Upstream authentication issue while checking %s. See: '
101+
'https://github.com/docker/docker-py/issues/2225', tag)
102+
raise ConnectionError
103+
else:
104+
self.logger.critical("Invalid Credentials. Exiting")
105+
exit(1)
99106
elif 'Client.Timeout' in str(e):
100107
self.logger.critical("Couldn't find an image on docker.com for %s. Local Build?", image.tags[0])
101108
raise ConnectionError
@@ -106,7 +113,7 @@ def pull(self, image_object):
106113
def update_containers(self):
107114
updated_count = 0
108115
updated_container_tuples = []
109-
116+
depends_on_list = []
110117
self.monitored = self.monitor_filter()
111118

112119
if not self.monitored:
@@ -128,6 +135,13 @@ def update_containers(self):
128135
except ConnectionError:
129136
continue
130137

138+
if self.config.dry_run:
139+
# Ugly hack for repo digest
140+
repo_digest_id = current_image.attrs['RepoDigests'][0].split('@')[1]
141+
if repo_digest_id != latest_image.id:
142+
self.logger.info('dry run : %s would be updated', container.name)
143+
continue
144+
131145
# If current running container is running latest image
132146
if current_image.id != latest_image.id:
133147
if container.name in ['ouroboros', 'ouroboros-updated']:
@@ -138,6 +152,10 @@ def update_containers(self):
138152
)
139153
self.logger.info('%s will be updated', container.name)
140154

155+
# Get container list to restart after update complete
156+
depends_on = container.labels.get('com.ouroboros.depends-on', False)
157+
if depends_on:
158+
depends_on_list.extend([name.strip() for name in depends_on.split(',')])
141159
# new container dict to create new container from
142160
new_config = set_properties(old=container, new=latest_image)
143161

@@ -176,11 +194,21 @@ def update_containers(self):
176194
self.data_manager.add(label=container.name, socket=self.socket)
177195
self.data_manager.add(label='all', socket=self.socket)
178196

179-
if updated_count > 0:
180-
self.notification_manager.send(container_tuples=updated_container_tuples, socket=self.socket,
181-
notification_type='data')
197+
if depends_on_list:
198+
depends_on_containers = []
199+
for name in list(set(depends_on_list)):
200+
try:
201+
depends_on_containers.append(self.client.containers.get(name))
202+
except NotFound:
203+
self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket)
182204

183-
self.notification_manager.send(notification_type='keep_alive')
205+
if depends_on_containers:
206+
for container in depends_on_containers:
207+
self.logger.debug('Restarting dependant container %s', container.name)
208+
container.restart()
209+
210+
if updated_count > 0:
211+
self.notification_manager.send(container_tuples=updated_container_tuples, socket=self.socket, kind='update')
184212

185213
def update_self(self, count=None, old_container=None, me_list=None, new_image=None):
186214
if count == 2:

pyouroboros/helpers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ def set_properties(old, new, self_name=None):
1616
return properties
1717

1818

19-
EMAIL_TEMPLATE = Template(
19+
NotificationTemplate = Template(
2020
'Host Socket: ${HOST_SOCKET}\n'
2121
'Containers Monitored: ${CONTAINERS_MONITORED}\n'
2222
'Containers Updated: ${CONTAINERS_UPDATED}\n'
23+
'Containers Updated This Pass: {CONTAINERS_THIS_PASS}'
2324
'${CONTAINER_UPDATES}'
2425
)

0 commit comments

Comments
 (0)