forked from vcsaturninus/builder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
container.py
138 lines (113 loc) · 3.68 KB
/
container.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
"""
Abstraction layer for interacting with varying container/jail OS technologies.
"""
from abc import ABC, abstractmethod
import os
import docker
import utils
class Container(ABC):
@abstractmethod
def set_mount_configs(self, mounts):
"""
Make the container object aware of any directories or files that must be mounted.
:param mounts a list of tuples, where each tuple if of the form:
(<host path>, <container path>, <mount type>). The type parameter
(and whether it's required) depends on the container technology.
"""
@abstractmethod
def run(self, cmd, interactive=False, ephemeral=False):
pass
@abstractmethod
def start(self):
pass
@abstractmethod
def stop(self):
pass
@abstractmethod
def wait(self):
pass
@abstractmethod
def id(self):
pass
@abstractmethod
def destroy(self):
pass
@abstractmethod
def logs(self):
pass
class Docker(Container):
def __init__(self, img, env=None, interactive=False, ephemeral=False):
self.client = docker.client.from_env()
self.container = None
self.image = img
self.mounts = []
self.mount_tuples = []
self.env = env or {}
self.interactive = interactive
self.ephemeral = ephemeral
self.exited = False
self.exitcode = 0
def set_mounts(self, mounts):
for host_path, container_path, mount_type in mounts:
mount = docker.types.Mount(
source=host_path,
target=container_path,
type=mount_type
)
self.mounts.append(mount)
self.mount_tuples=mounts
return mounts
def interact(self, cmd):
"""
There's no simple way to get a cli-interactive container instance via the python API.
As such, we'll need to call the docker cli client instead in a subprocesss.
"""
cli = f"docker run {'--rm' if self.ephemeral else ''}"
for k,v in self.env.items():
cli += f" -e '{k}={v}'"
for host_path,container_path,mount_type in self.mount_tuples:
cli += f" --mount type='{mount_type}',source='{host_path}',target='{container_path}'"
cli += f" -it {self.image} {cmd}"
rc = utils.interact(cli)
self.exited=True
self.exitcode = rc
def run(self, cmd):
if self.interactive:
return self.interact(cmd)
client = self.client
mounts = self.mounts
container = client.containers.run(
image=self.image,
command=cmd,
environment=self.env,
mounts = mounts,
detach=True
)
self.container = container
def logs(self):
if self.interactive:
raise RuntimeError("Interactive containers do not return logs")
logs = self.container.logs(stream=True)
for log in logs:
yield log.decode('utf8').rstrip()
def wait(self):
if self.exited:
return self.exitcode
status = self.container.wait()
return (status['StatusCode'])
def id(self):
return self.container.id
def destroy(self):
pass
def stop(self):
pass
def start(self):
pass
def set_mount_configs(self):
pass
def get(container_tech):
known_tech = {"docker": Docker}
interface = known_tech.get(container_tech)
if not interface:
raise NotImplementedError(f"No interface for '{container_tech}'")
return interface