mirror of https://github.com/docker/compose.git
Merge pull request #5384 from ilinum/1498-docker-compose-services
Implement --filter flag for docker-compose config --services and use it in bash completion
This commit is contained in:
commit
c4fda0834d
|
@ -619,11 +619,24 @@ class TopLevelCommand(object):
|
|||
"""
|
||||
List containers.
|
||||
|
||||
Usage: ps [options] [SERVICE...]
|
||||
Usage: ps [options] [--filter KEY=VAL] [SERVICE...]
|
||||
|
||||
Options:
|
||||
-q Only display IDs
|
||||
-q Only display IDs
|
||||
--services Display services
|
||||
--filter KEY=VAL Filter services by a property
|
||||
"""
|
||||
if options['-q'] and options['--services']:
|
||||
raise UserError('-q and --services cannot be combined')
|
||||
|
||||
if options['--services']:
|
||||
filt = build_filter(options.get('--filter'))
|
||||
services = self.project.services
|
||||
if filt:
|
||||
services = filter_services(filt, services, self.project)
|
||||
print('\n'.join(service.name for service in services))
|
||||
return
|
||||
|
||||
containers = sorted(
|
||||
self.project.containers(service_names=options['SERVICE'], stopped=True) +
|
||||
self.project.containers(service_names=options['SERVICE'], one_off=OneOffFilter.only),
|
||||
|
@ -1352,3 +1365,49 @@ def build_exec_command(options, container_id, command):
|
|||
args += [container_id]
|
||||
args += command
|
||||
return args
|
||||
|
||||
|
||||
def has_container_with_state(containers, state):
|
||||
states = {
|
||||
'running': lambda c: c.is_running,
|
||||
'stopped': lambda c: not c.is_running,
|
||||
'paused': lambda c: c.is_paused,
|
||||
'restarting': lambda c: c.is_restarting,
|
||||
}
|
||||
for container in containers:
|
||||
if state not in states:
|
||||
raise UserError("Invalid state: %s" % state)
|
||||
if states[state](container):
|
||||
return True
|
||||
|
||||
|
||||
def filter_services(filt, services, project):
|
||||
def should_include(service):
|
||||
for f in filt:
|
||||
if f == 'status':
|
||||
state = filt[f]
|
||||
containers = project.containers([service.name], stopped=True)
|
||||
if not has_container_with_state(containers, state):
|
||||
return False
|
||||
elif f == 'source':
|
||||
source = filt[f]
|
||||
if source == 'image' or source == 'build':
|
||||
if source not in service.options:
|
||||
return False
|
||||
else:
|
||||
raise UserError("Invalid value for source filter: %s" % source)
|
||||
else:
|
||||
raise UserError("Invalid filter: %s" % f)
|
||||
return True
|
||||
|
||||
return filter(should_include, services)
|
||||
|
||||
|
||||
def build_filter(arg):
|
||||
filt = {}
|
||||
if arg is not None:
|
||||
if '=' not in arg:
|
||||
raise UserError("Arguments to --filter should be in form KEY=VAL")
|
||||
key, val = arg.split('=', 1)
|
||||
filt[key] = val
|
||||
return filt
|
||||
|
|
|
@ -64,48 +64,32 @@ __docker_compose_services_all() {
|
|||
COMPREPLY=( $(compgen -W "$(___docker_compose_all_services_in_compose_file)" -- "$cur") )
|
||||
}
|
||||
|
||||
# All services that have an entry with the given key in their compose_file section
|
||||
___docker_compose_services_with_key() {
|
||||
# flatten sections under "services" to one line, then filter lines containing the key and return section name
|
||||
__docker_compose_q config \
|
||||
| sed -n -e '/^services:/,/^[^ ]/p' \
|
||||
| sed -n 's/^ //p' \
|
||||
| awk '/^[a-zA-Z0-9]/{printf "\n"};{printf $0;next;}' \
|
||||
| awk -F: -v key=": +$1:" '$0 ~ key {print $1}'
|
||||
}
|
||||
|
||||
# All services that are defined by a Dockerfile reference
|
||||
__docker_compose_services_from_build() {
|
||||
COMPREPLY=( $(compgen -W "$(___docker_compose_services_with_key build)" -- "$cur") )
|
||||
COMPREPLY=( $(compgen -W "$(__docker_compose_q ps --services --filter "source=build")" -- "$cur") )
|
||||
}
|
||||
|
||||
# All services that are defined by an image
|
||||
__docker_compose_services_from_image() {
|
||||
COMPREPLY=( $(compgen -W "$(___docker_compose_services_with_key image)" -- "$cur") )
|
||||
}
|
||||
|
||||
# The services for which containers have been created, optionally filtered
|
||||
# by a boolean expression passed in as argument.
|
||||
__docker_compose_services_with() {
|
||||
local containers names
|
||||
containers="$(__docker_compose_q ps -q)"
|
||||
names=$(docker 2>/dev/null inspect -f "{{if ${1:-true}}}{{range \$k, \$v := .Config.Labels}}{{if eq \$k \"com.docker.compose.service\"}}{{\$v}}{{end}}{{end}}{{end}}" $containers)
|
||||
COMPREPLY=( $(compgen -W "$names" -- "$cur") )
|
||||
COMPREPLY=( $(compgen -W "$(__docker_compose_q ps --services --filter "source=image")" -- "$cur") )
|
||||
}
|
||||
|
||||
# The services for which at least one paused container exists
|
||||
__docker_compose_services_paused() {
|
||||
__docker_compose_services_with '.State.Paused'
|
||||
names=$(__docker_compose_q ps --services --filter "status=paused")
|
||||
COMPREPLY=( $(compgen -W "$names" -- "$cur") )
|
||||
}
|
||||
|
||||
# The services for which at least one running container exists
|
||||
__docker_compose_services_running() {
|
||||
__docker_compose_services_with '.State.Running'
|
||||
names=$(__docker_compose_q ps --services --filter "status=running")
|
||||
COMPREPLY=( $(compgen -W "$names" -- "$cur") )
|
||||
}
|
||||
|
||||
# The services for which at least one stopped container exists
|
||||
__docker_compose_services_stopped() {
|
||||
__docker_compose_services_with 'not .State.Running'
|
||||
names=$(__docker_compose_q ps --services --filter "status=stopped")
|
||||
COMPREPLY=( $(compgen -W "$names" -- "$cur") )
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -491,6 +491,34 @@ class CLITestCase(DockerClientTestCase):
|
|||
assert 'multiplecomposefiles_another_1' not in result.stdout
|
||||
assert 'multiplecomposefiles_yetanother_1' in result.stdout
|
||||
|
||||
def test_ps_services_filter_option(self):
|
||||
self.base_dir = 'tests/fixtures/ps-services-filter'
|
||||
image = self.dispatch(['ps', '--services', '--filter', 'source=image'])
|
||||
build = self.dispatch(['ps', '--services', '--filter', 'source=build'])
|
||||
all_services = self.dispatch(['ps', '--services'])
|
||||
|
||||
assert 'with_build' in all_services.stdout
|
||||
assert 'with_image' in all_services.stdout
|
||||
assert 'with_build' in build.stdout
|
||||
assert 'with_build' not in image.stdout
|
||||
assert 'with_image' in image.stdout
|
||||
assert 'with_image' not in build.stdout
|
||||
|
||||
def test_ps_services_filter_status(self):
|
||||
self.base_dir = 'tests/fixtures/ps-services-filter'
|
||||
self.dispatch(['up', '-d'])
|
||||
self.dispatch(['pause', 'with_image'])
|
||||
paused = self.dispatch(['ps', '--services', '--filter', 'status=paused'])
|
||||
stopped = self.dispatch(['ps', '--services', '--filter', 'status=stopped'])
|
||||
running = self.dispatch(['ps', '--services', '--filter', 'status=running'])
|
||||
|
||||
assert 'with_build' not in stopped.stdout
|
||||
assert 'with_image' not in stopped.stdout
|
||||
assert 'with_build' not in paused.stdout
|
||||
assert 'with_image' in paused.stdout
|
||||
assert 'with_build' in running.stdout
|
||||
assert 'with_image' in running.stdout
|
||||
|
||||
def test_pull(self):
|
||||
result = self.dispatch(['pull'])
|
||||
assert sorted(result.stderr.split('\n'))[1:] == [
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
with_image:
|
||||
image: busybox:latest
|
||||
command: top
|
||||
with_build:
|
||||
build: ../build-ctx/
|
||||
command: top
|
Loading…
Reference in New Issue