Add support for differentiating one-off containers

This is a basic start, the API is pretty shonky.
This commit is contained in:
Ben Firshman 2013-12-20 10:46:55 +00:00
parent 68e4341fbf
commit 2f28265d10
4 changed files with 52 additions and 22 deletions

View File

@ -85,8 +85,10 @@ class TopLevelCommand(Command):
Options:
-q Only display IDs
"""
containers = self.project.containers(stopped=True) + self.project.containers(one_off=True)
if options['-q']:
for container in self.project.containers(all=True):
for container in containers:
print container.id
else:
headers = [
@ -96,7 +98,7 @@ class TopLevelCommand(Command):
'Ports',
]
rows = []
for container in self.project.containers(all=True):
for container in containers:
rows.append([
container.name,
container.human_readable_command,
@ -117,7 +119,7 @@ class TopLevelCommand(Command):
container_options = {
'command': [options['COMMAND']] + options['ARGS'],
}
container = service.create_container(**container_options)
container = service.create_container(one_off=True, **container_options)
stream = container.logs(stream=True)
service.start_container(container, ports=None)
for data in stream:
@ -142,7 +144,7 @@ class TopLevelCommand(Command):
if len(s.containers()) == 0:
unstarted.append((s, s.create_container()))
else:
running += s.containers(all=False)
running += s.containers(stopped=False)
log_printer = LogPrinter(running + [c for (s, c) in unstarted])
@ -168,7 +170,7 @@ class TopLevelCommand(Command):
Usage: logs
"""
containers = self.project.containers(all=False)
containers = self.project.containers(stopped=False)
print "Attaching to", list_containers(containers)
LogPrinter(containers, attach_params={'logs': True}).run()

View File

@ -124,3 +124,11 @@ class Container(object):
def attach_socket(self, **kwargs):
return self.client.attach_socket(self.id, **kwargs)
def __repr__(self):
return '<Container: %s>' % self.name
def __eq__(self, other):
if type(self) != type(other):
return False
return self.id == other.id

View File

@ -27,11 +27,11 @@ class Service(object):
self.links = links or []
self.options = options
def containers(self, all=False):
def containers(self, stopped=False, one_off=False):
l = []
for container in self.client.containers(all=all):
for container in self.client.containers(all=stopped):
name = get_container_name(container)
if not is_valid_name(name):
if not is_valid_name(name, one_off):
continue
project, name, number = parse_name(name)
if project == self.project and name == self.name:
@ -52,12 +52,12 @@ class Service(object):
while len(self.containers()) > num:
self.stop_container()
def create_container(self, **override_options):
def create_container(self, one_off=False, **override_options):
"""
Create a container for this service. If the image doesn't exist, attempt to pull
it.
"""
container_options = self._get_container_options(override_options)
container_options = self._get_container_options(override_options, one_off=one_off)
try:
return Container.create(self.client, **container_options)
except APIError, e:
@ -104,11 +104,14 @@ class Service(object):
container.kill()
container.remove()
def next_container_name(self):
return '%s_%s_%s' % (self.project, self.name, self.next_container_number())
def next_container_name(self, one_off=False):
bits = [self.project, self.name]
if one_off:
bits.append('run')
return '_'.join(bits + [unicode(self.next_container_number())])
def next_container_number(self):
numbers = [parse_name(c.name)[2] for c in self.containers(all=True)]
numbers = [parse_name(c.name)[2] for c in self.containers(stopped=True)]
if len(numbers) == 0:
return 1
@ -122,12 +125,12 @@ class Service(object):
links[container.name[1:]] = container.name[1:]
return links
def _get_container_options(self, override_options):
def _get_container_options(self, override_options, one_off=False):
keys = ['image', 'command', 'hostname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'volumes_from']
container_options = dict((k, self.options[k]) for k in keys if k in self.options)
container_options.update(override_options)
container_options['name'] = self.next_container_name()
container_options['name'] = self.next_container_name(one_off)
if 'ports' in container_options:
container_options['ports'] = [unicode(p).split(':')[0] for p in container_options['ports']]
@ -160,16 +163,22 @@ class Service(object):
return image_id
NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(\d+)$')
NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$')
def is_valid_name(name):
return (NAME_RE.match(name) is not None)
def parse_name(name):
def is_valid_name(name, one_off=False):
match = NAME_RE.match(name)
(project, service_name, suffix) = match.groups()
if match is None:
return False
if one_off:
return match.group(3) == 'run_'
else:
return match.group(3) is None
def parse_name(name, one_off=False):
match = NAME_RE.match(name)
(project, service_name, _, suffix) = match.groups()
return (project, service_name, int(suffix))

View File

@ -41,6 +41,12 @@ class ServiceTest(DockerClientTestCase):
self.assertIn('/default_bar_1', names)
self.assertIn('/default_bar_2', names)
def test_containers_one_off(self):
db = self.create_service('db')
container = db.create_container(one_off=True)
self.assertEqual(db.containers(stopped=True), [])
self.assertEqual(db.containers(one_off=True, stopped=True), [container])
def test_project_is_added_to_container_name(self):
service = self.create_service('web', project='myproject')
service.start()
@ -68,6 +74,11 @@ class ServiceTest(DockerClientTestCase):
service.stop()
self.assertEqual(len(service.containers()), 0)
def test_create_container_with_one_off(self):
db = self.create_service('db')
container = db.create_container(one_off=True)
self.assertEqual(container.name, '/default_db_run_1')
def test_start_container_passes_through_options(self):
db = self.create_service('db')
db.start_container(environment={'FOO': 'BAR'})