mirror of
				https://github.com/docker/compose.git
				synced 2025-11-03 21:25:21 +01: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.
 | 
			
		||||
 | 
			
		||||
        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:] == [
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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