فهرست منبع

implement exec

Resolves #593

Signed-off-by: Tomas Tomecek <[email protected]>
Tomas Tomecek 10 سال پیش
والد
کامیت
d28c5dda92
5فایلهای تغییر یافته به همراه110 افزوده شده و 1 حذف شده
  1. 4 0
      compose/cli/docopt_command.py
  2. 53 1
      compose/cli/main.py
  3. 6 0
      compose/container.py
  4. 29 0
      docs/reference/exec.md
  5. 18 0
      tests/acceptance/cli_test.py

+ 4 - 0
compose/cli/docopt_command.py

@@ -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)

+ 53 - 1
compose/cli/main.py

@@ -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.

+ 6 - 0
compose/container.py

@@ -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 - 0
docs/reference/exec.md

@@ -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.

+ 18 - 0
tests/acceptance/cli_test.py

@@ -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'])