Просмотр исходного кода

Merge pull request #7543 from jarulsamy/7540-flag_nullifier

Add `--` as a separator of flags and arguments.
Anca Iordache 5 лет назад
Родитель
Сommit
debcdde27d

+ 16 - 16
compose/cli/main.py

@@ -179,7 +179,7 @@ class TopLevelCommand(object):
     """Define and run multi-container applications with Docker.
 
     Usage:
-      docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
+      docker-compose [-f <arg>...] [options] [--] [COMMAND] [ARGS...]
       docker-compose -h|--help
 
     Options:
@@ -256,7 +256,7 @@ class TopLevelCommand(object):
         e.g. `composetest_db`. If you change a service's `Dockerfile` or the
         contents of its build directory, you can run `docker-compose build` to rebuild it.
 
-        Usage: build [options] [--build-arg key=val...] [SERVICE...]
+        Usage: build [options] [--build-arg key=val...] [--] [SERVICE...]
 
         Options:
             --build-arg key=val     Set build-time variables for services.
@@ -422,7 +422,7 @@ class TopLevelCommand(object):
         """
         Receive real time events from containers.
 
-        Usage: events [options] [SERVICE...]
+        Usage: events [options] [--] [SERVICE...]
 
         Options:
             --json      Output events as a stream of json objects
@@ -447,7 +447,7 @@ class TopLevelCommand(object):
         """
         Execute a command in a running container
 
-        Usage: exec [options] [-e KEY=VAL...] SERVICE COMMAND [ARGS...]
+        Usage: exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]
 
         Options:
             -d, --detach      Detached mode: Run command in the background.
@@ -535,7 +535,7 @@ class TopLevelCommand(object):
     def images(self, options):
         """
         List images used by the created containers.
-        Usage: images [options] [SERVICE...]
+        Usage: images [options] [--] [SERVICE...]
 
         Options:
             -q, --quiet  Only display IDs
@@ -590,7 +590,7 @@ class TopLevelCommand(object):
         """
         Force stop service containers.
 
-        Usage: kill [options] [SERVICE...]
+        Usage: kill [options] [--] [SERVICE...]
 
         Options:
             -s SIGNAL         SIGNAL to send to the container.
@@ -604,7 +604,7 @@ class TopLevelCommand(object):
         """
         View output from containers.
 
-        Usage: logs [options] [SERVICE...]
+        Usage: logs [options] [--] [SERVICE...]
 
         Options:
             --no-color          Produce monochrome output.
@@ -647,7 +647,7 @@ class TopLevelCommand(object):
         """
         Print the public port for a port binding.
 
-        Usage: port [options] SERVICE PRIVATE_PORT
+        Usage: port [options] [--] SERVICE PRIVATE_PORT
 
         Options:
             --protocol=proto  tcp or udp [default: tcp]
@@ -668,7 +668,7 @@ class TopLevelCommand(object):
         """
         List containers.
 
-        Usage: ps [options] [SERVICE...]
+        Usage: ps [options] [--] [SERVICE...]
 
         Options:
             -q, --quiet          Only display IDs
@@ -724,7 +724,7 @@ class TopLevelCommand(object):
         """
         Pulls images for services defined in a Compose file, but does not start the containers.
 
-        Usage: pull [options] [SERVICE...]
+        Usage: pull [options] [--] [SERVICE...]
 
         Options:
             --ignore-pull-failures  Pull what it can and ignores images with pull failures.
@@ -747,7 +747,7 @@ class TopLevelCommand(object):
         """
         Pushes images for services.
 
-        Usage: push [options] [SERVICE...]
+        Usage: push [options] [--] [SERVICE...]
 
         Options:
             --ignore-push-failures  Push what it can and ignores images with push failures.
@@ -766,7 +766,7 @@ class TopLevelCommand(object):
 
         Any data which is not in a volume will be lost.
 
-        Usage: rm [options] [SERVICE...]
+        Usage: rm [options] [--] [SERVICE...]
 
         Options:
             -f, --force   Don't ask to confirm removal
@@ -814,7 +814,7 @@ class TopLevelCommand(object):
         `docker-compose run --no-deps SERVICE COMMAND [ARGS...]`.
 
         Usage:
-            run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...]
+            run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] [--]
                 SERVICE [COMMAND] [ARGS...]
 
         Options:
@@ -904,7 +904,7 @@ class TopLevelCommand(object):
 
         They can be started again with `docker-compose start`.
 
-        Usage: stop [options] [SERVICE...]
+        Usage: stop [options] [--] [SERVICE...]
 
         Options:
           -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds.
@@ -917,7 +917,7 @@ class TopLevelCommand(object):
         """
         Restart running containers.
 
-        Usage: restart [options] [SERVICE...]
+        Usage: restart [options] [--] [SERVICE...]
 
         Options:
           -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds.
@@ -982,7 +982,7 @@ class TopLevelCommand(object):
         If you want to force Compose to stop and recreate all containers, use the
         `--force-recreate` flag.
 
-        Usage: up [options] [--scale SERVICE=NUM...] [SERVICE...]
+        Usage: up [options] [--scale SERVICE=NUM...] [--] [SERVICE...]
 
         Options:
             -d, --detach               Detached mode: Run containers in the background,

+ 127 - 1
tests/acceptance/cli_test.py

@@ -187,7 +187,7 @@ class CLITestCase(DockerClientTestCase):
     def test_help(self):
         self.base_dir = 'tests/fixtures/no-composefile'
         result = self.dispatch(['help', 'up'], returncode=0)
-        assert 'Usage: up [options] [--scale SERVICE=NUM...] [SERVICE...]' in result.stdout
+        assert 'Usage: up [options] [--scale SERVICE=NUM...] [--] [SERVICE...]' in result.stdout
         # Prevent tearDown from trying to create a project
         self.base_dir = None
 
@@ -2862,3 +2862,129 @@ services:
         assert re.search(r'foo1.+test[ \t]+dev', result.stdout) is not None
         assert re.search(r'foo2.+test[ \t]+prod', result.stdout) is not None
         assert re.search(r'foo3.+test[ \t]+latest', result.stdout) is not None
+
+    def test_build_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        result = self.dispatch(['build', '--pull', '--', '--test-service'])
+
+        assert BUILD_PULL_TEXT in result.stdout
+
+    def test_events_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        events_proc = start_process(self.base_dir, ['events', '--json', '--', '--test-service'])
+        self.dispatch(['up', '-d', '--', '--test-service'])
+        wait_on_condition(ContainerCountCondition(self.project, 1))
+
+        os.kill(events_proc.pid, signal.SIGINT)
+        result = wait_on_process(events_proc, returncode=1)
+        lines = [json.loads(line) for line in result.stdout.rstrip().split('\n')]
+        assert Counter(e['action'] for e in lines) == {'create': 1, 'start': 1}
+
+    def test_exec_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service'])
+        assert len(self.project.containers()) == 1
+
+        stdout, stderr = self.dispatch(['exec', '-T', '--', '--test-service', 'ls', '-1d', '/'])
+
+        assert stderr == ""
+        assert stdout == "/\n"
+
+    def test_images_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service'])
+        result = self.dispatch(['images', '--', '--test-service'])
+
+        assert "busybox" in result.stdout
+
+    def test_kill_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service'])
+        service = self.project.get_service('--test-service')
+
+        assert len(service.containers()) == 1
+        assert service.containers()[0].is_running
+
+        self.dispatch(['kill', '--', '--test-service'])
+
+        assert len(service.containers(stopped=True)) == 1
+        assert not service.containers(stopped=True)[0].is_running
+
+    def test_logs_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--log-service'])
+        result = self.dispatch(['logs', '--', '--log-service'])
+
+        assert 'hello' in result.stdout
+        assert 'exited with' not in result.stdout
+
+    def test_port_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service'])
+        result = self.dispatch(['port', '--', '--test-service', '80'])
+
+        assert result.stdout.strip() == "0.0.0.0:8080"
+
+    def test_ps_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service'])
+
+        result = self.dispatch(['ps', '--', '--test-service'])
+
+        assert 'flag-as-service-name_--test-service_1' in result.stdout
+
+    def test_pull_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        result = self.dispatch(['pull', '--', '--test-service'])
+
+        assert 'Pulling --test-service' in result.stderr
+        assert 'failed' not in result.stderr
+
+    def test_rm_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '--no-start', '--', '--test-service'])
+        service = self.project.get_service('--test-service')
+        assert len(service.containers(stopped=True)) == 1
+
+        self.dispatch(['rm', '--force', '--', '--test-service'])
+        assert len(service.containers(stopped=True)) == 0
+
+    def test_run_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        result = self.dispatch(['run', '--no-deps', '--', '--test-service', 'echo', '-hello'])
+
+        assert 'hello' in result.stdout
+        assert len(self.project.containers()) == 0
+
+    def test_stop_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service'])
+        service = self.project.get_service('--test-service')
+        assert len(service.containers()) == 1
+        assert service.containers()[0].is_running
+
+        self.dispatch(['stop', '-t', '1', '--', '--test-service'])
+
+        assert len(service.containers(stopped=True)) == 1
+        assert not service.containers(stopped=True)[0].is_running
+
+    def test_restart_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service'])
+        service = self.project.get_service('--test-service')
+        assert len(service.containers()) == 1
+        assert service.containers()[0].is_running
+
+        self.dispatch(['restart', '-t', '1', '--', '--test-service'])
+
+        assert len(service.containers()) == 1
+        assert service.containers()[0].is_running
+
+    def test_up_with_stop_process_flag(self):
+        self.base_dir = 'tests/fixtures/flag-as-service-name'
+        self.dispatch(['up', '-d', '--', '--test-service', '--log-service'])
+
+        service = self.project.get_service('--test-service')
+        another = self.project.get_service('--log-service')
+        assert len(service.containers()) == 1
+        assert len(another.containers()) == 1

+ 3 - 0
tests/fixtures/flag-as-service-name/Dockerfile

@@ -0,0 +1,3 @@
+FROM busybox:1.27.2
+LABEL com.docker.compose.test_image=true
+CMD echo "success"

+ 12 - 0
tests/fixtures/flag-as-service-name/docker-compose.yml

@@ -0,0 +1,12 @@
+version: "2"
+services:
+  --test-service:
+    image: busybox:1.27.0.2
+    build: .
+    command: top
+    ports:
+      - "8080:80"
+
+  --log-service:
+    image: busybox:1.31.0-uclibc
+    command: sh -c "echo hello && tail -f /dev/null"