Skip to content

Commit 3ef4dfa

Browse files
Merge pull request #173 from pyouroboros/develop
v1.1.1 Merge
2 parents fbf7cb1 + a3f62df commit 3ef4dfa

File tree

11 files changed

+218
-81
lines changed

11 files changed

+218
-81
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Change Log
22

3+
## [1.1.1](https://github.com/pyouroboros/ouroboros/tree/1.1.1) (2019-02-01)
4+
[Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.0...1.1.1)
5+
6+
**Implemented enhancements:**
7+
8+
- Support for adding an identifier \(hostname?\) to notifications [\#158](https://github.com/pyouroboros/ouroboros/issues/158)
9+
- Influx config data + ocd cleanup [\#162](https://github.com/pyouroboros/ouroboros/pull/162) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice))
10+
- add cli arg for cron [\#157](https://github.com/pyouroboros/ouroboros/pull/157) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
11+
12+
**Fixed bugs:**
13+
14+
- Ouroboros does not respect MONITOR= [\#166](https://github.com/pyouroboros/ouroboros/issues/166)
15+
- Docker TLS over TCP connections [\#154](https://github.com/pyouroboros/ouroboros/issues/154) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)]
16+
- Patch/group 4 [\#169](https://github.com/pyouroboros/ouroboros/pull/169) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice))
17+
- Recheck properly for only non lists [\#164](https://github.com/pyouroboros/ouroboros/pull/164) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
18+
- add some missing passthrough info for restart [\#163](https://github.com/pyouroboros/ouroboros/pull/163) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
19+
20+
**Other Pull Requests**
21+
22+
- v1.1.1 Merge [\#173](https://github.com/pyouroboros/ouroboros/pull/173) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
23+
- v1.1.1 to develop [\#172](https://github.com/pyouroboros/ouroboros/pull/172) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
24+
- Patch/group 3 [\#167](https://github.com/pyouroboros/ouroboros/pull/167) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
25+
- Add hostname to the notifications [\#161](https://github.com/pyouroboros/ouroboros/pull/161) ([tlkamp](https://github.com/tlkamp))
26+
- Patch/group 2 [\#155](https://github.com/pyouroboros/ouroboros/pull/155) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
27+
328
## [1.1.0](https://github.com/pyouroboros/ouroboros/tree/1.1.0) (2019-01-26)
429
[Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.0.0...1.1.0)
530

pyouroboros/__init__.py

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

pyouroboros/config.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1+
from os import environ
12
from logging import getLogger
23
from pyouroboros.logger import BlacklistFilter
34

45

56
class Config(object):
67
options = ['INTERVAL', 'PROMETHEUS', 'DOCKER_SOCKETS', 'MONITOR', 'IGNORE', 'LOG_LEVEL', 'PROMETHEUS_ADDR',
7-
'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST',
8+
'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST', 'CRON',
89
'INFLUX_URL', 'INFLUX_PORT', 'INFLUX_USERNAME', 'INFLUX_PASSWORD', 'INFLUX_DATABASE', 'INFLUX_SSL',
9-
'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS_VERIFY', 'LABELS_ONLY',
10-
'DRY_RUN']
10+
'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY',
11+
'DRY_RUN', 'HOSTNAME', 'DOCKER_TLS_VERIFY']
1112

13+
hostname = environ.get('HOSTNAME')
1214
interval = 300
15+
cron = None
1316
docker_sockets = 'unix://var/run/docker.sock'
14-
docker_tls_verify = False
17+
docker_tls = False
18+
docker_tls_verify = True
1519
monitor = []
1620
ignore = []
1721
data_export = None
@@ -76,23 +80,27 @@ def config_blacklist(self):
7680
def parse(self):
7781
for option in Config.options:
7882
if self.environment_vars.get(option):
83+
env_opt = self.environment_vars[option]
84+
if isinstance(env_opt, str):
85+
# Clean out quotes, both single/double and whitespace
86+
env_opt = env_opt.strip("'").strip('"').strip(' ')
7987
if option in ['INTERVAL', 'PROMETHEUS_PORT', 'INFLUX_PORT']:
8088
try:
81-
opt = int(self.environment_vars[option])
89+
opt = int(env_opt)
8290
setattr(self, option.lower(), opt)
8391
except ValueError as e:
8492
print(e)
8593
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']:
94+
'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DOCKER_TLS_VERIFY']:
95+
if env_opt.lower() in ['true', 'yes']:
8896
setattr(self, option.lower(), True)
89-
elif self.environment_vars[option].lower() in ['false', 'no']:
97+
elif env_opt.lower() in ['false', 'no']:
9098
setattr(self, option.lower(), False)
9199
else:
92-
self.logger.error('%s is not true/yes, nor false/no for %s. Assuming false',
93-
self.environment_vars[option], option)
100+
self.logger.error('%s is not true/yes, nor false/no for %s. Assuming %s',
101+
env_opt, option, getattr(self, option))
94102
else:
95-
setattr(self, option.lower(), self.environment_vars[option])
103+
setattr(self, option.lower(), env_opt)
96104
elif vars(self.cli_args).get(option):
97105
setattr(self, option.lower(), vars(self.cli_args).get(option))
98106

@@ -106,9 +114,19 @@ def parse(self):
106114
for option in ['docker_sockets', 'notifiers', 'monitor', 'ignore']:
107115
if isinstance(getattr(self, option), str):
108116
string_list = getattr(self, option)
109-
setattr(self, option, [string.strip(' ').strip('"') for string in string_list.split(' ')])
117+
setattr(self, option, [string for string in string_list.split(' ')])
110118

111119
# Config sanity checks
120+
if self.cron:
121+
cron_times = self.cron.strip().split(' ')
122+
if len(cron_times) != 5:
123+
self.logger.error("Cron must be in cron syntax. e.g. * * * * * (5 places). Ignoring and using interval")
124+
self.cron = None
125+
else:
126+
self.logger.info("Cron configuration is valid. Using Cron schedule %s", cron_times)
127+
self.cron = cron_times
128+
self.interval = None
129+
112130
if self.data_export == 'influxdb' and not self.influx_database:
113131
self.logger.error("You need to specify an influx database if you want to export to influxdb. Disabling "
114132
"influxdb data export.")
@@ -120,4 +138,11 @@ def parse(self):
120138
self.logger.warning("Dry run is designed to be ran with run once. Setting for you.")
121139
self.run_once = True
122140

141+
# Remove default config that is not used for cleaner logs
142+
if self.data_export != 'prometheus':
143+
self.prometheus_addr, self.prometheus_port = None, None
144+
145+
if self.data_export != 'influxdb':
146+
self.influx_url, self.influx_port, self.influx_username, self.influx_password = None, None, None, None
147+
123148
self.config_blacklist()

pyouroboros/dataexporters.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ def add(self, label, socket):
2424
elif self.config.data_export == "influxdb" and self.enabled:
2525
if label == "all":
2626
self.logger.debug("Total containers updated %s", self.total_updated[socket])
27-
self.influx.write_points(label, socket)
28-
else:
29-
self.influx.write_points(label, socket)
27+
28+
self.influx.write_points(label, socket)
3029

3130
def set(self, socket):
3231
if self.config.data_export == "prometheus" and self.enabled:
@@ -109,6 +108,13 @@ def write_points(self, label, socket):
109108
"tags": {'socket': clean_socket},
110109
"time": now,
111110
"fields": {}
111+
},
112+
{
113+
"measurement": "Ouroboros",
114+
"tags": {'configuration': self.config.hostname},
115+
"time": now,
116+
"fields": {key: (value if not isinstance(value, list) else ' '.join(value)) for key, value in
117+
vars(self.config).items() if key.upper() in self.config.options}
112118
}
113119
]
114120
if label == "all":

pyouroboros/dockerclient.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from time import sleep
22
from logging import getLogger
3-
from docker import DockerClient
3+
from docker import DockerClient, tls
4+
from os.path import isdir, isfile, join
45
from docker.errors import DockerException, APIError, NotFound
56

67
from pyouroboros.helpers import set_properties
@@ -10,14 +11,51 @@ class Docker(object):
1011
def __init__(self, socket, config, data_manager, notification_manager):
1112
self.config = config
1213
self.socket = socket
13-
self.client = DockerClient(base_url=socket, tls=self.config.docker_tls_verify)
14+
self.client = self.connect()
1415
self.data_manager = data_manager
1516
self.data_manager.total_updated[self.socket] = 0
1617
self.logger = getLogger()
1718
self.monitored = self.monitor_filter()
1819

1920
self.notification_manager = notification_manager
2021

22+
def connect(self):
23+
if self.config.docker_tls:
24+
try:
25+
cert_paths = {
26+
'cert_top_dir': '/etc/docker/certs.d/',
27+
'clean_socket': self.socket.split('//')[1]
28+
}
29+
cert_paths['cert_dir'] = join(cert_paths['cert_top_dir'], cert_paths['clean_socket'])
30+
cert_paths['cert_files'] = {
31+
'client_cert': join(cert_paths['cert_dir'], 'client.cert'),
32+
'client_key': join(cert_paths['cert_dir'], 'client.key'),
33+
'ca_crt': join(cert_paths['cert_dir'], 'ca.crt')
34+
}
35+
36+
if not isdir(cert_paths['cert_dir']):
37+
self.logger.error('%s is not a valid cert folder', cert_paths['cert_dir'])
38+
raise ValueError
39+
40+
for cert_file in cert_paths['cert_files'].values():
41+
if not isfile(cert_file):
42+
self.logger.error('%s does not exist', cert_file)
43+
raise ValueError
44+
45+
tls_config = tls.TLSConfig(
46+
ca_cert=cert_paths['cert_files']['ca_crt'],
47+
verify=cert_paths['cert_files']['ca_crt'] if self.config.docker_tls_verify else False,
48+
client_cert=(cert_paths['cert_files']['client_cert'], cert_paths['cert_files']['client_key'])
49+
)
50+
client = DockerClient(base_url=self.socket, tls=tls_config)
51+
except ValueError:
52+
self.logger.error('Invalid Docker TLS config for %s, reverting to unsecured', self.socket)
53+
client = DockerClient(base_url=self.socket)
54+
else:
55+
client = DockerClient(base_url=self.socket)
56+
57+
return client
58+
2159
def get_running(self):
2260
"""Return running container objects list, except ouroboros itself"""
2361
running_containers = []
@@ -52,7 +90,8 @@ def monitor_filter(self):
5290
monitored_containers.append(container)
5391
else:
5492
continue
55-
elif not self.config.labels_only and self.config.monitor and container.name not in self.config.ignore:
93+
elif not self.config.labels_only and self.config.monitor and container.name in self.config.monitor \
94+
and container.name not in self.config.ignore:
5695
monitored_containers.append(container)
5796
elif not self.config.labels_only and container.name not in self.config.ignore:
5897
monitored_containers.append(container)
@@ -144,12 +183,18 @@ def update_containers(self):
144183

145184
# If current running container is running latest image
146185
if current_image.id != latest_image.id:
147-
if container.name in ['ouroboros', 'ouroboros-updated']:
148-
self.update_self(old_container=container, new_image=latest_image, count=1)
149-
150186
updated_container_tuples.append(
151187
(container, current_image, latest_image)
152188
)
189+
190+
if container.name in ['ouroboros', 'ouroboros-updated']:
191+
self.data_manager.total_updated[self.socket] += 1
192+
self.data_manager.add(label=container.name, socket=self.socket)
193+
self.data_manager.add(label='all', socket=self.socket)
194+
self.notification_manager.send(container_tuples=updated_container_tuples,
195+
socket=self.socket, kind='update')
196+
self.update_self(old_container=container, new_image=latest_image, count=1)
197+
153198
self.logger.info('%s will be updated', container.name)
154199

155200
# Get container list to restart after update complete

pyouroboros/helpers.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
from string import Template
2-
3-
41
def set_properties(old, new, self_name=None):
52
"""Store object for spawning new container in place of the one with outdated image"""
63
properties = {
74
'name': self_name if self_name else old.name,
5+
'hostname': old.attrs['Config']['Hostname'],
6+
'user': old.attrs['Config']['User'],
7+
'domainname': old.attrs['Config']['Domainname'],
8+
'tty': old.attrs['Config']['Tty'],
9+
'ports': None if not old.attrs['Config'].get('ExposedPorts') else [
10+
(p.split('/')[0], p.split('/')[1]) for p in old.attrs['Config']['ExposedPorts'].keys()
11+
],
12+
'volumes': None if not old.attrs['Config'].get('Volumes') else [
13+
v for v in old.attrs['Config']['Volumes'].keys()
14+
],
15+
'working_dir': old.attrs['Config']['WorkingDir'],
816
'image': new.tags[0],
917
'command': old.attrs['Config']['Cmd'],
1018
'host_config': old.attrs['HostConfig'],
@@ -14,12 +22,3 @@ def set_properties(old, new, self_name=None):
1422
}
1523

1624
return properties
17-
18-
19-
NotificationTemplate = Template(
20-
'Host Socket: ${HOST_SOCKET}\n'
21-
'Containers Monitored: ${CONTAINERS_MONITORED}\n'
22-
'Containers Updated: ${CONTAINERS_UPDATED}\n'
23-
'Containers Updated This Pass: {CONTAINERS_THIS_PASS}'
24-
'${CONTAINER_UPDATES}'
25-
)

pyouroboros/logger.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ class BlacklistFilter(Filter):
66
Log filter for blacklisted tokens and passwords
77
"""
88

9-
blacklisted_keys = ['repo_user', 'repo_pass', 'auth_json', 'webhook_urls', 'docker_sockets', 'pushover_user',
10-
'prometheus_addr', 'influx_username', 'influx_password', 'influx_url', 'pushover_token',
11-
'pushover_device', 'smtp_host', 'smtp_username', 'smtp_password', 'smtp_recipients',
12-
'smtp_from_email']
9+
blacklisted_keys = ['repo_user', 'repo_pass', 'auth_json', 'docker_sockets', 'prometheus_addr',
10+
'influx_username', 'influx_password', 'influx_url', 'notifiers']
1311

1412
def __init__(self, filteredstrings):
1513
super().__init__()

pyouroboros/notifiers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import apprise
22

33
from logging import getLogger
4-
from datetime import datetime, timezone, timedelta
4+
from datetime import datetime, timezone
55

66

77
class NotificationManager(object):
@@ -32,18 +32,18 @@ def build_apprise(self):
3232

3333
return apprise_obj
3434

35-
def send(self, container_tuples=None, socket=None, kind='update'):
35+
def send(self, container_tuples=None, socket=None, kind='update', next_run=None):
3636
if kind == 'startup':
3737
now = datetime.now(timezone.utc).astimezone()
3838
title = f'Ouroboros has started'
3939
body_fields = [
40+
f'Host: {self.config.hostname}',
4041
f'Time: {now.strftime("%Y-%m-%d %H:%M:%S")}',
41-
f'Next Run: {(now + timedelta(0, self.config.interval)).strftime("%Y-%m-%d %H:%M:%S")}'
42-
]
42+
f'Next Run: {next_run}']
4343
else:
4444
title = 'Ouroboros has updated containers!'
4545
body_fields = [
46-
f"Host Socket: {socket.split('//')[1]}",
46+
f"Host/Socket: {self.config.hostname} / {socket.split('//')[1]}",
4747
f"Containers Monitored: {self.data_manager.monitored_containers[socket]}",
4848
f"Total Containers Updated: {self.data_manager.total_updated[socket]}",
4949
f"Containers updated this pass: {len(container_tuples)}"
@@ -57,7 +57,7 @@ def send(self, container_tuples=None, socket=None, kind='update'):
5757
) for container, old_image, new_image in container_tuples
5858
]
5959
)
60-
body = '\n'.join(body_fields)
60+
body = '\r\n'.join(body_fields)
6161

6262
if self.apprise.servers:
6363
self.apprise.notify(title=title, body=body)

0 commit comments

Comments
 (0)