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:
Joffrey F 2018-01-09 17:02:04 -08:00 committed by GitHub
commit c4fda0834d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 26 deletions

View File

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

View File

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

View File

@ -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:] == [

View File

@ -0,0 +1,6 @@
with_image:
image: busybox:latest
command: top
with_build:
build: ../build-ctx/
command: top