-
Notifications
You must be signed in to change notification settings - Fork 30
/
container_monitor.py
159 lines (132 loc) · 5.43 KB
/
container_monitor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
Exports ContainerMonitor to monitor container start/stop events and spawn notifiers.
"""
import logging
import os
import re
from datetime import datetime, timedelta
from fnmatch import fnmatch
import docker
from docker_volume_watcher.container_notifier import ContainerNotifier
def docker_bind_to_windows_path(path):
"""
Converts Hyper-V mount path to Windows path (e.g. [/host_mnt]/C/some-path -> C:/some-path).
Args:
path (str): Hyper-V mount path
Returns:
str: Converts Hyper-V mount path to Windows path (e.g. /C/some-path -> C:/some-path).
"""
expr = re.compile('^(?:/host_mnt)?/([a-zA-Z])/(.*)$')
match = re.match(expr, path)
if not match:
return None
return '%s:\\%s' % match.groups()
class ContainerMonitor(object):
"""
Monitors container start/stop events and creates notifiers for mounts matching patterns.
"""
def __init__(self, container_name_pattern, host_dir_pattern, notifier_options=None):
"""
Initialize new instance of ContainerMonitor
Args:
container_name_pattern (str): Container name pattern
host_dir_pattern (str): Host directory pattern
exclude_patterns (list): List of file name patterns for which changes should be ignored
notifier_options (NotifierOptions): options to be passed to each instance
of ContainerNotifier
"""
self.client = docker.from_env()
self.container_name_pattern = container_name_pattern
self.host_dir_pattern = host_dir_pattern
self.notifiers = {}
self.notifier_options = notifier_options
def __handle_event(self, event):
container_name = event['Actor']['Attributes']['name']
status = event['status']
if not fnmatch(container_name, self.container_name_pattern):
return
if status == 'start':
self.watch_container(container_name)
elif status == 'die':
self.unwatch_container(container_name)
def find_containers(self):
"""
Traverse running containers and spawn notifiers for mounts matching patterns.
"""
notifiers_count = 0
for container in self.client.containers.list():
if fnmatch(container.name, self.container_name_pattern):
notifiers = self.watch_container(container.name)
logging.info(
'Container %s has %i watched directories', container.name, len(notifiers))
notifiers_count += len(notifiers)
if not notifiers_count:
logging.warning(
'No mounts match container name pattern %s and host directory pattern %s',
self.container_name_pattern, self.host_dir_pattern)
def watch_container(self, container_name):
"""
Create notifiers for mounts of container_name matching host_dir_pattern.
Args:
container_name (str): Container name
Returns:
list of :py:class:`ContainerNotifier`: List of spawned ContainerNotifier instances.
"""
container = self.client.containers.get(container_name)
mounts = container.attrs['Mounts']
if container not in self.notifiers:
self.notifiers[container_name] = []
notifiers = self.notifiers[container_name]
for mount in mounts:
if mount['Type'] != 'bind':
continue
host_directory = docker_bind_to_windows_path(mount['Source'])
if not host_directory:
logging.warning(
'Bind of container %s was skipped since it has invalid source path %s',
container_name, mount['Source'])
continue
if not fnmatch(host_directory, self.host_dir_pattern):
continue
if not os.path.isdir(host_directory):
logging.warning(
'Bind of container %s was skipped for path %s as it\'s not a directory',
container_name, mount['Source'])
continue
notifier = ContainerNotifier(
container, host_directory, mount['Destination'], self.notifier_options)
notifiers.append(notifier)
logging.info('Notifier %s created.', notifier)
return notifiers
def unwatch_container(self, container_name):
"""
Destroy all notifiers of container_name.
Args:
container_name (str): Container name
"""
if container_name not in self.notifiers:
return
for notifier in self.notifiers[container_name]:
notifier.stop()
logging.info('Notifier %s destroyed.', notifier)
del self.notifiers[container_name]
def unwatch_all(self):
"""
Destroy all notifiers.
"""
containers = list(self.notifiers)
for name in containers:
self.unwatch_container(name)
def monitor(self):
"""
Start listening and handling of container start/stop events.
"""
delta = timedelta(seconds=2)
since = datetime.utcnow()
until = datetime.utcnow() + delta
filters = {'event': ['start', 'die'], 'type': 'container'}
while True:
for event in self.client.events(since=since, until=until, decode=True, filters=filters):
self.__handle_event(event)
since = until
until = datetime.utcnow() + delta