blob: 9708e9eb37c4d2bb13a47000504ebc323db508fe [file] [log] [blame]
Zsolt Haraszti86be6f12016-09-27 09:56:49 -07001#
2# Copyright 2016 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Some docker related convenience functions
19"""
alshabib7941d402016-11-08 00:11:20 +010020from datetime import datetime
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070021
22import os
alshabib7941d402016-11-08 00:11:20 +010023import socket
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070024from structlog import get_logger
25
alshabib7941d402016-11-08 00:11:20 +010026from docker import Client, errors
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070027
28
alshabibc61999a2016-10-27 16:44:27 -070029docker_socket = os.environ.get('DOCKER_SOCK', 'unix://tmp/docker.sock')
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070030log = get_logger()
31
32
alshabib7941d402016-11-08 00:11:20 +010033def remove_container(id, force=True):
34 try:
35 docker_cli = Client(base_url=docker_socket)
36 containers = docker_cli.remove_container(id, force=force)
37
38 except Exception, e:
39 log.exception('failed', e=e)
40 raise
41
42 return containers
43
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070044def get_my_containers_name():
45 """
46 Return the docker containers name in which this process is running.
47 To look up the container name, we use the container ID extracted from the
48 $HOSTNAME environment variable (which is set by docker conventions).
49 :return: String with the docker container name (or None if any issue is
50 encountered)
51 """
52 my_container_id = os.environ.get('HOSTNAME', None)
53
54 try:
alshabibc61999a2016-10-27 16:44:27 -070055 docker_cli = Client(base_url=docker_socket)
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070056 info = docker_cli.inspect_container(my_container_id)
57
58 except Exception, e:
59 log.exception('failed', my_container_id=my_container_id, e=e)
60 raise
61
62 name = info['Name'].lstrip('/')
63
64 return name
alshabibc67ee3a2016-10-25 23:24:03 -070065
alshabib7941d402016-11-08 00:11:20 +010066def get_all_running_containers():
67 try:
68 docker_cli = Client(base_url=docker_socket)
69 containers = docker_cli.containers()
70
71 except Exception, e:
72 log.exception('failed', e=e)
73 raise
74
75 return containers
76
77def inspect_container(id):
78 try:
79 docker_cli = Client(base_url=docker_socket)
80 info = docker_cli.inspect_container(id)
81
82 except Exception, e:
83 log.exception('failed', e=e)
84 raise
85
86 return info
87
88
alshabibc61999a2016-10-27 16:44:27 -070089def create_host_config(volumes, ports):
90 try:
alshabib7941d402016-11-08 00:11:20 +010091 port_bindings = ports
alshabibc61999a2016-10-27 16:44:27 -070092 binds = ['{0}:{1}'.format(k, v) for k, v in volumes.iteritems()]
93 docker_cli = Client(base_url=docker_socket)
94 host_config = docker_cli.create_host_config(binds=binds,
95 port_bindings=port_bindings)
96 except Exception, e:
97 log.exception('failed host config creation', volumes, ports, e=e)
98 raise
99
100 return host_config
101
alshabib7941d402016-11-08 00:11:20 +0100102def connect_container_to_network(container, net_id, links):
103 try:
104 docker_cli = Client(base_url=docker_socket)
105 docker_cli.connect_container_to_network(container, net_id, links=links)
106 except:
107 log.exception('Failed to connect container {} to network {}'.format(container, net_id))
108 raise
alshabibc61999a2016-10-27 16:44:27 -0700109
alshabib7941d402016-11-08 00:11:20 +0100110def create_networking_config(name, links):
alshabibc67ee3a2016-10-25 23:24:03 -0700111 """
112 Creates a container networks based on a set of containers.
113 :param name: the network name
114 :param links: the set of containers to link
115 :return: a network configuration
116 """
117 try:
alshabibc61999a2016-10-27 16:44:27 -0700118 docker_cli = Client(base_url=docker_socket)
alshabibc67ee3a2016-10-25 23:24:03 -0700119 networking_config = docker_cli.create_networking_config({
alshabibc61999a2016-10-27 16:44:27 -0700120 name : docker_cli.create_endpoint_config(links=links)
alshabibc67ee3a2016-10-25 23:24:03 -0700121 })
122 except Exception, e:
123 log.exception('failed network creation', name, e=e)
124 raise
125
126 return networking_config
127
alshabib7941d402016-11-08 00:11:20 +0100128def stop_container(container, timeout=10):
129 try:
130 docker_cli = Client(base_url=docker_socket)
131 docker_cli.stop(container, timeout=timeout)
132 except Exception, e:
133 log.exception('failed', e=e)
134 raise
alshabibc67ee3a2016-10-25 23:24:03 -0700135
alshabib7941d402016-11-08 00:11:20 +0100136def create_container(args):
137 try:
138 docker_cli = Client(base_url=docker_socket)
139 container = docker_cli.create_container(**args)
140 except Exception, e:
141 log.exception('failed', e=e)
142 raise
143 return container
144
145def start_container(container):
alshabibc67ee3a2016-10-25 23:24:03 -0700146 """
147 Starts a requested container with the appropriate configuration.
148 :param args: contains arguments for container creation
149 (see https://docker-py.readthedocs.io/en/stable/api/#create_container)
150 :return: the containers name
151 """
152 try:
alshabibc61999a2016-10-27 16:44:27 -0700153 docker_cli = Client(base_url=docker_socket)
alshabibc61999a2016-10-27 16:44:27 -0700154 response = docker_cli.start(container=container.get('Id'))
alshabibc67ee3a2016-10-25 23:24:03 -0700155 except Exception, e:
156 log.exception('failed', e=e)
157 raise
alshabibc61999a2016-10-27 16:44:27 -0700158 return response
alshabibc67ee3a2016-10-25 23:24:03 -0700159
alshabib7941d402016-11-08 00:11:20 +0100160class EventProcessor(object):
161 """
162 This class handles the api session and allows for it to
163 be terminated.
164 """
165 def __init__(self):
166 self.client = CustomClient(base_url=docker_socket)
167 self.events = self.client.events(decode=True)
168
169 def stop_listening(self):
170 """
171 Shuts down the socket.
172 :return: None
173 """
174 if self.events is not None:
175 sock = self.client._get_raw_response_socket(self.events.response)
176 sock.shutdown(socket.SHUT_RDWR)
177
178
179 def listen_for_events(self, handlers):
180 """
181 Listens to the docker event stream and applies the functions
182 in the passed handler to each event.
183
184 docker containers can report the following events:
185
186 attach, commit, copy, create, destroy, detach, die,
187 exec_create, exec_detach, exec_start, export,
188 health_status, kill, oom, pause, rename, resize,
189 restart, start, stop, top, unpause, update
190
191 :param handlers: a dict of functions
192 :return: None
193 """
194 if not handlers or len(handlers) == 0:
195 raise ValueError("Handlers cannot be empty")
196
197 for event in self.events:
198 for k in ['time', 'Time']:
199 if k in event:
200 event[k] = datetime.fromtimestamp(event[k])
201 log.debug('docker event: {}'.format(event))
202
203 data = {}
204 i = get_id(event)
205 if i is not None:
206 try:
207 if 'from' in event or 'From' in event:
208 data = self.client.inspect_container(i)
209 else:
210 data = self.client.inspect_image(i)
211 data[i] = data
212 except errors.NotFound:
alshabib9d222022016-11-10 16:11:09 -0800213 log.debug('No data for container {}'.format(i))
alshabib7941d402016-11-08 00:11:20 +0100214
215 status = get_status(event)
216 if status in handlers:
217 handlers[get_status(event)](event, data, handlers['podder_config'])
218 else:
219 log.debug("No handler for {}; skipping...".format(status))
220
221class CustomGenerator(object):
222 """
223 This is a custom ugly class that allows for the generator
224 to be (kind of) cleanly closed.
225 """
226 def __init__(self, stream, response, decode):
227 self.stream = stream
228 self.response = response
229 self.decode = decode
230
231 def __iter__(self):
232 for item in super(CustomClient, self.stream).\
233 _stream_helper(self.response, self.decode):
234 yield item
235
236class CustomClient(Client):
237 def _stream_helper(self, response, decode=False):
238 return CustomGenerator(self, response, decode)
239
240def get_status(event):
241 for k in ['status', 'Status']:
242 if k in event:
243 return event[k]
244
245def get_id(event):
246 for k in ['id', 'ID', 'Id']:
247 if k in event:
248 return event[k]