-
Notifications
You must be signed in to change notification settings - Fork 359
/
engine.py
366 lines (300 loc) · 9.73 KB
/
engine.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
"""
Interface for a repo2docker container engine
"""
import json
import os
from abc import ABC, abstractmethod
from traitlets import Dict, default
from traitlets.config import LoggingConfigurable
# Based on https://docker-py.readthedocs.io/en/4.2.0/containers.html
class Container(ABC):
"""
Abstract container returned by repo2docker engines
"""
@abstractmethod
def reload(self):
"""
Refresh container attributes
"""
@abstractmethod
def logs(self, *, stream=False, timestamps=False, since=None):
"""
Get the container logs.
Parameters
----------
stream : bool
If `True` return an iterator over the log lines, otherwise return all logs
timestamps : bool
If `True` log lines will be prefixed with iso8601 timestamps followed by space
since : str
A timestamp string
Should be in the same format as the timestamp prefix given
when `timestamps=True`
If given, start logs from this point,
instead of from container start.
Returns
-------
str or generator of log strings
"""
@abstractmethod
def kill(self, *, signal="KILL"):
"""
Send a signal to the container
Parameters
----------
signal : str
The signal, default `KILL`
"""
@abstractmethod
def remove(self):
"""
Remove the container
"""
@abstractmethod
def stop(self, *, timeout=10):
"""
Stop the container
Parameters
----------
timeout : If the container doesn't gracefully stop after this timeout kill it
"""
@abstractmethod
def wait(self):
"""
Wait for the container to stop
"""
@property
@abstractmethod
def exitcode(self):
"""
The container exit code if exited
"""
@property
@abstractmethod
def status(self):
"""
The status of the container
Returns
-------
str : The status of the container.
Values include `created` `running` `exited`.
Full list of statuses:
https://github.com/moby/moby/blob/v19.03.5/api/swagger.yaml#L4832
"""
class Image:
"""
Information about a container image
"""
def __init__(self, *, tags, config=None):
self._tags = tags or []
self._config = config
@property
def tags(self):
"""
A list of tags associated with an image.
If locally built images have a localhost prefix this prefix should be removed or the image may not be recognised.
If there are no tags [] will be returned.
"""
return self._tags
@property
def config(self):
"""
A dictionary of image configuration information
If this is `None` the information has not been loaded.
If not `None` this must include the following fields:
- WorkingDir: The default working directory
"""
return self._config
def __repr__(self):
return f"Image(tags={self.tags},config={self.config})"
class ContainerEngine(LoggingConfigurable):
"""
Abstract container engine.
Inherits from LoggingConfigurable, which means it has a log property.
Initialised with a reference to the parent so can also be configured using traitlets.
"""
registry_credentials = Dict(
help="""
Credentials dictionary, if set will be used to authenticate with
the registry. Typically this will include the keys:
- `username`: The registry username
- `password`: The registry password or token
- `registry`: The registry URL
This can also be set by passing a JSON object in the
CONTAINER_ENGINE_REGISTRY_CREDENTIALS environment variable.
""",
config=True,
)
@default("registry_credentials")
def _registry_credentials_default(self):
"""
Set the registry credentials from CONTAINER_ENGINE_REGISTRY_CREDENTIALS
"""
obj = os.getenv("CONTAINER_ENGINE_REGISTRY_CREDENTIALS")
if obj:
try:
return json.loads(obj)
except json.JSONDecodeError:
self.log.error(
"CONTAINER_ENGINE_REGISTRY_CREDENTIALS is not valid JSON"
)
raise
return {}
string_output = True
"""
Whether progress events should be strings or an object.
Originally Docker was the only container engine supported by repo2docker.
Some operations including build() and push() would return generators of events in a Docker specific format.
This format of events is not easily constructable with other engines so the default is to return strings and raise an exception if an error occurs.
If an engine returns docker style events set this variable to False.
"""
def __init__(self, *, parent):
"""
Initialise the container engine
Parameters
----------
parent: Application
Reference to the parent application so that its configuration file can be used in this plugin.
"""
super().__init__(parent=parent)
# Based on https://docker-py.readthedocs.io/en/4.2.0/api.html#module-docker.api.build
def build(
self,
*,
buildargs={},
cache_from=[],
container_limits={},
tag="",
custom_context=False,
dockerfile="",
fileobj=None,
path="",
labels=None,
platform=None,
**kwargs,
):
"""
Build a container
Parameters
----------
buildargs : dict
Dictionary of build arguments
cache_from : list[str]
List of images to chech for caching
container_limits : dict
Dictionary of resources limits. These keys are supported:
- `cpusetcpus`
- `cpushares`
- `memory`
- `memswap`
tag : str
Tag to add to the image
custom_context : bool
If `True` fileobj is a Tar file object containing the build context
dockerfile : str
Path to Dockerfile within the build context
fileobj : tarfile
A tar file-like object containing the build context
path : str
path to the Dockerfile
labels : dict
Dictionary of labels to set on the image
platform: str
Platform to build for
Returns
-------
A generator of strings. If an error occurs an exception must be thrown.
If `string_output=True` this should instead be whatever Docker returns:
https://github.com/jupyter/repo2docker/blob/0.11.0/repo2docker/app.py#L725-L735
"""
raise NotImplementedError("build not implemented")
def images(self):
"""
List images
Returns
-------
list[Image] : List of Image objects.
"""
raise NotImplementedError("images not implemented")
def inspect_image(self, image):
"""
Get information about an image
TODO: This is specific to the engine, can we convert it to a standard format?
Parameters
----------
image : str
The image
Returns
-------
Image object with .config dict.
"""
raise NotImplementedError("inspect_image not implemented")
def push(self, image_spec):
"""
Push image to a registry
If the registry_credentials traitlets is set it should be used to
authenticate with the registry before pushing.
Parameters
----------
image_spec : str
The repository spec to push to
Returns
-------
A generator of strings. If an error occurs an exception must be thrown.
If `string_output=True` this should instead be whatever Docker returns:
https://github.com/jupyter/repo2docker/blob/0.11.0/repo2docker/app.py#L469-L495
"""
raise NotImplementedError("push not implemented")
# Note this is different from the Docker client which has Client.containers.run
def run(
self,
image_spec,
*,
command=[],
environment=[],
ports={},
publish_all_ports=False,
remove=False,
volumes={},
**kwargs,
):
"""
Run a container
Parameters
----------
image_spec : str
The image to run
command : list[str]
The command to run
environment : list[str]
List of environment variables in the form `ENVVAR=value`
ports : dict
Container port bindings in the form generated by `repo2docker.utils.validate_and_generate_port_mapping`
https://github.com/jupyter/repo2docker/blob/0.11.0/repo2docker/utils.py#L95
publish_all_ports : bool
If `True` publish all ports to host
remove : bool
If `True` delete container when it completes
volumes : dict
Volume bindings in the form `{src : dest}`
Returns
-------
Container : the running container
Raises
------
NotImplementedError
This engine does not support running containers
"""
raise NotImplementedError("Running containers not supported")
class ContainerEngineException(Exception):
"""
Base class for exceptions in the container engine
"""
class BuildError(ContainerEngineException):
"""
Container build error
"""
class ImageLoadError(ContainerEngineException):
"""
Container load/push error
"""