implement exec

Resolves #593

Signed-off-by: Tomas Tomecek <ttomecek@redhat.com>
This commit is contained in:
Tomas Tomecek 2015-09-10 13:17:55 +02:00
parent 1502c5a14d
commit d28c5dda92
5 changed files with 110 additions and 1 deletions

View File

@ -43,6 +43,10 @@ class DocoptCommand(object):
def get_handler(self, command): def get_handler(self, command):
command = command.replace('-', '_') command = command.replace('-', '_')
# we certainly want to have "exec" command, since that's what docker client has
# but in python exec is a keyword
if command == "exec":
command = "exec_command"
if not hasattr(self, command): if not hasattr(self, command):
raise NoSuchCommand(command, self) raise NoSuchCommand(command, self)

View File

@ -43,7 +43,7 @@ from .utils import yesno
if not IS_WINDOWS_PLATFORM: if not IS_WINDOWS_PLATFORM:
from dockerpty.pty import PseudoTerminal, RunOperation from dockerpty.pty import PseudoTerminal, RunOperation, ExecOperation
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
console_handler = logging.StreamHandler(sys.stderr) console_handler = logging.StreamHandler(sys.stderr)
@ -152,6 +152,7 @@ class TopLevelCommand(DocoptCommand):
create Create services create Create services
down Stop and remove containers, networks, images, and volumes down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers events Receive real time events from containers
exec Execute a command in a running container
help Get help on a command help Get help on a command
kill Kill containers kill Kill containers
logs View output from containers logs View output from containers
@ -298,6 +299,57 @@ class TopLevelCommand(DocoptCommand):
print(formatter(event)) print(formatter(event))
sys.stdout.flush() sys.stdout.flush()
def exec_command(self, project, options):
"""
Execute a command in a running container
Usage: exec [options] SERVICE COMMAND [ARGS...]
Options:
-d Detached mode: Run command in the background.
--privileged Give extended privileges to the process.
--user USER Run the command as this user.
-T Disable pseudo-tty allocation. By default `docker-compose exec`
allocates a TTY.
--index=index index of the container if there are multiple
instances of a service [default: 1]
"""
index = int(options.get('--index'))
service = project.get_service(options['SERVICE'])
try:
container = service.get_container(number=index)
except ValueError as e:
raise UserError(str(e))
command = [options['COMMAND']] + options['ARGS']
tty = not options["-T"]
create_exec_options = {
"privileged": options["--privileged"],
"user": options["--user"],
"tty": tty,
"stdin": tty,
}
exec_id = container.create_exec(command, **create_exec_options)
if options['-d']:
container.start_exec(exec_id, tty=tty)
return
signals.set_signal_handler_to_shutdown()
try:
operation = ExecOperation(
project.client,
exec_id,
interactive=tty,
)
pty = PseudoTerminal(project.client, operation)
pty.start()
except signals.ShutdownException:
log.info("received shutdown exception: closing")
exit_code = project.client.exec_inspect(exec_id).get("ExitCode")
sys.exit(exit_code)
def help(self, project, options): def help(self, project, options):
""" """
Get help on a command. Get help on a command.

View File

@ -216,6 +216,12 @@ class Container(object):
def remove(self, **options): def remove(self, **options):
return self.client.remove_container(self.id, **options) return self.client.remove_container(self.id, **options)
def create_exec(self, command, **options):
return self.client.exec_create(self.id, command, **options)
def start_exec(self, exec_id, **options):
return self.client.exec_start(exec_id, **options)
def rename_to_tmp_name(self): def rename_to_tmp_name(self):
"""Rename the container to a hopefully unique temporary container name """Rename the container to a hopefully unique temporary container name
by prepending the short id. by prepending the short id.

29
docs/reference/exec.md Normal file
View File

@ -0,0 +1,29 @@
<!--[metadata]>
+++
title = "exec"
description = "exec"
keywords = ["fig, composition, compose, docker, orchestration, cli, exec"]
[menu.main]
identifier="exec.compose"
parent = "smn_compose_cli"
+++
<![end-metadata]-->
# exec
```
Usage: exec [options] SERVICE COMMAND [ARGS...]
Options:
-d Detached mode: Run command in the background.
--privileged Give extended privileges to the process.
--user USER Run the command as this user.
-T Disable pseudo-tty allocation. By default `docker-compose exec`
allocates a TTY.
--index=index index of the container if there are multiple
instances of a service [default: 1]
```
This is equivalent of `docker exec`. With this subcommand you can run arbitrary
commands in your services. Commands are by default allocating a TTY, so you can
do e.g. `docker-compose exec web sh` to get an interactive prompt.

View File

@ -752,6 +752,24 @@ class CLITestCase(DockerClientTestCase):
self.project.stop(['simple']) self.project.stop(['simple'])
wait_on_condition(ContainerCountCondition(self.project, 0)) wait_on_condition(ContainerCountCondition(self.project, 0))
def test_exec_without_tty(self):
self.base_dir = 'tests/fixtures/links-composefile'
self.dispatch(['up', '-d', 'console'])
self.assertEqual(len(self.project.containers()), 1)
stdout, stderr = self.dispatch(['exec', '-T', 'console', 'ls', '-1d', '/'])
self.assertEquals(stdout, "/\n")
self.assertEquals(stderr, "")
def test_exec_custom_user(self):
self.base_dir = 'tests/fixtures/links-composefile'
self.dispatch(['up', '-d', 'console'])
self.assertEqual(len(self.project.containers()), 1)
stdout, stderr = self.dispatch(['exec', '-T', '--user=operator', 'console', 'whoami'])
self.assertEquals(stdout, "operator\n")
self.assertEquals(stderr, "")
def test_run_service_without_links(self): def test_run_service_without_links(self):
self.base_dir = 'tests/fixtures/links-composefile' self.base_dir = 'tests/fixtures/links-composefile'
self.dispatch(['run', 'console', '/bin/true']) self.dispatch(['run', 'console', '/bin/true'])