Skip to content

Commit

Permalink
Merge pull request docker#1824 from mrfuxi/run-with-ports
Browse files Browse the repository at this point in the history
Allow manual port mapping when using "run" command
  • Loading branch information
dnephin committed Aug 13, 2015
2 parents 2ddce83 + ff87cea commit b87c09b
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 2 deletions.
12 changes: 11 additions & 1 deletion compose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def run(self, project, options):
running. If you do not want to start linked services, use
`docker-compose run --no-deps SERVICE COMMAND [ARGS...]`.
Usage: run [options] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]
Usage: run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]
Options:
--allow-insecure-ssl Deprecated - no effect.
Expand All @@ -293,6 +293,7 @@ def run(self, project, options):
-u, --user="" Run as specified username or uid
--no-deps Don't start linked services.
--rm Remove container after run. Ignored in detached mode.
-p, --publish=[] Publish a container's port(s) to the host
--service-ports Run command with the service's ports enabled and mapped
to the host.
-T Disable pseudo-tty allocation. By default `docker-compose run`
Expand Down Expand Up @@ -344,6 +345,15 @@ def run(self, project, options):
if not options['--service-ports']:
container_options['ports'] = []

if options['--publish']:
container_options['ports'] = options.get('--publish')

if options['--publish'] and options['--service-ports']:
raise UserError(
'Service port mapping and manual port mapping '
'can not be used togather'
)

try:
container = service.create_container(
quiet=True,
Expand Down
2 changes: 1 addition & 1 deletion contrib/completion/bash/docker-compose
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ _docker-compose_run() {

case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-d --entrypoint -e --help --no-deps --rm --service-ports -T --user -u" -- "$cur" ) )
COMPREPLY=( $( compgen -W "-d --entrypoint -e --help --no-deps --rm --service-ports --publish -p -T --user -u" -- "$cur" ) )
;;
*)
__docker-compose_services_all
Expand Down
1 change: 1 addition & 0 deletions contrib/completion/zsh/_docker-compose
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ __docker-compose_subcommand () {
'(-u --user)'{-u,--user=-}'[Run as specified username or uid]:username or uid:_users' \
"--no-deps[Don't start linked services.]" \
'--rm[Remove container after run. Ignored in detached mode.]' \
"--publish[Run command with manually mapped container's port(s) to the host.]" \
"--service-ports[Run command with the service's ports enabled and mapped to the host.]" \
'-T[Disable pseudo-tty allocation. By default `docker-compose run` allocates a TTY.]' \
'(-):services:__docker-compose_services' \
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Options:
-u, --user="" Run as specified username or uid
--no-deps Don't start linked services.
--rm Remove container after run. Ignored in detached mode.
-p, --publish=[] Publish a container's port(s) to the host
--service-ports Run command with the service's ports enabled and mapped to the host.
-T Disable pseudo-tty allocation. By default `docker-compose run` allocates a TTY.
```
Expand All @@ -38,6 +39,10 @@ The second difference is the `docker-compose run` command does not create any of

$ docker-compose run --service-ports web python manage.py shell

Alternatively manual port mapping can be specified. Same as when running Docker's `run` command - using `--publish` or `-p` options:

$ docker-compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell

If you start a service configured with links, the `run` command first checks to see if the linked service is running and starts the service if it is stopped. Once all the linked services are running, the `run` executes the command you passed it. So, for example, you could run:

$ docker-compose run db psql -h db -U docker
Expand Down
38 changes: 38 additions & 0 deletions tests/integration/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,44 @@ def test_run_service_with_map_ports(self, __):
self.assertEqual(port_range[0], "0.0.0.0:49153")
self.assertEqual(port_range[1], "0.0.0.0:49154")

@patch('dockerpty.start')
def test_run_service_with_explicitly_maped_ports(self, __):

# create one off container
self.command.base_dir = 'tests/fixtures/ports-composefile'
self.command.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple'], None)
container = self.project.get_service('simple').containers(one_off=True)[0]

# get port information
port_short = container.get_local_port(3000)
port_full = container.get_local_port(3001)

# close all one off containers we just created
container.stop()

# check the ports
self.assertEqual(port_short, "0.0.0.0:30000")
self.assertEqual(port_full, "0.0.0.0:30001")

@patch('dockerpty.start')
def test_run_service_with_explicitly_maped_ip_ports(self, __):

# create one off container
self.command.base_dir = 'tests/fixtures/ports-composefile'
self.command.dispatch(['run', '-d', '-p', '127.0.0.1:30000:3000', '--publish', '127.0.0.1:30001:3001', 'simple'], None)
container = self.project.get_service('simple').containers(one_off=True)[0]

# get port information
port_short = container.get_local_port(3000)
port_full = container.get_local_port(3001)

# close all one off containers we just created
container.stop()

# check the ports
self.assertEqual(port_short, "127.0.0.1:30000")
self.assertEqual(port_full, "127.0.0.1:30001")

def test_rm(self):
service = self.project.get_service('simple')
service.create_container()
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import mock

from compose.cli.docopt_command import NoSuchCommand
from compose.cli.errors import UserError
from compose.cli.main import TopLevelCommand
from compose.service import Service

Expand Down Expand Up @@ -108,6 +109,7 @@ def test_run_with_environment_merged_with_options_list(self, mock_dockerpty):
'-T': None,
'--entrypoint': None,
'--service-ports': None,
'--publish': [],
'--rm': None,
})

Expand Down Expand Up @@ -136,6 +138,7 @@ def test_run_service_with_restart_always(self):
'-T': None,
'--entrypoint': None,
'--service-ports': None,
'--publish': [],
'--rm': None,
})
_, _, call_kwargs = mock_client.create_container.mock_calls[0]
Expand All @@ -160,7 +163,35 @@ def test_run_service_with_restart_always(self):
'-T': None,
'--entrypoint': None,
'--service-ports': None,
'--publish': [],
'--rm': True,
})
_, _, call_kwargs = mock_client.create_container.mock_calls[0]
self.assertFalse('RestartPolicy' in call_kwargs['host_config'])

def test_command_manula_and_service_ports_together(self):
command = TopLevelCommand()
mock_client = mock.create_autospec(docker.Client)
mock_project = mock.Mock(client=mock_client)
mock_project.get_service.return_value = Service(
'service',
client=mock_client,
restart='always',
image='someimage',
)

with self.assertRaises(UserError):
command.run(mock_project, {
'SERVICE': 'service',
'COMMAND': None,
'-e': [],
'--user': None,
'--no-deps': None,
'--allow-insecure-ssl': None,
'-d': True,
'-T': None,
'--entrypoint': None,
'--service-ports': True,
'--publish': ['80:80'],
'--rm': None,
})

0 comments on commit b87c09b

Please sign in to comment.