mirror of
https://github.com/docker/compose.git
synced 2025-07-01 02:44:25 +02:00
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.
|
List containers.
|
||||||
|
|
||||||
Usage: ps [options] [SERVICE...]
|
Usage: ps [options] [--filter KEY=VAL] [SERVICE...]
|
||||||
|
|
||||||
Options:
|
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(
|
containers = sorted(
|
||||||
self.project.containers(service_names=options['SERVICE'], stopped=True) +
|
self.project.containers(service_names=options['SERVICE'], stopped=True) +
|
||||||
self.project.containers(service_names=options['SERVICE'], one_off=OneOffFilter.only),
|
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 += [container_id]
|
||||||
args += command
|
args += command
|
||||||
return args
|
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") )
|
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
|
# All services that are defined by a Dockerfile reference
|
||||||
__docker_compose_services_from_build() {
|
__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
|
# All services that are defined by an image
|
||||||
__docker_compose_services_from_image() {
|
__docker_compose_services_from_image() {
|
||||||
COMPREPLY=( $(compgen -W "$(___docker_compose_services_with_key image)" -- "$cur") )
|
COMPREPLY=( $(compgen -W "$(__docker_compose_q ps --services --filter "source=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") )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# The services for which at least one paused container exists
|
# The services for which at least one paused container exists
|
||||||
__docker_compose_services_paused() {
|
__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
|
# The services for which at least one running container exists
|
||||||
__docker_compose_services_running() {
|
__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
|
# The services for which at least one stopped container exists
|
||||||
__docker_compose_services_stopped() {
|
__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_another_1' not in result.stdout
|
||||||
assert 'multiplecomposefiles_yetanother_1' 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):
|
def test_pull(self):
|
||||||
result = self.dispatch(['pull'])
|
result = self.dispatch(['pull'])
|
||||||
assert sorted(result.stderr.split('\n'))[1:] == [
|
assert sorted(result.stderr.split('\n'))[1:] == [
|
||||||
|
6
tests/fixtures/ps-services-filter/docker-compose.yml
vendored
Normal file
6
tests/fixtures/ps-services-filter/docker-compose.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
with_image:
|
||||||
|
image: busybox:latest
|
||||||
|
command: top
|
||||||
|
with_build:
|
||||||
|
build: ../build-ctx/
|
||||||
|
command: top
|
Loading…
x
Reference in New Issue
Block a user