Skip to content

Commit

Permalink
Add support for stopping containers by image
Browse files Browse the repository at this point in the history
  • Loading branch information
alde committed Sep 18, 2017
1 parent 4fb6946 commit 696185c
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 22 deletions.
52 changes: 35 additions & 17 deletions docker_custodian/docker_autostop.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
"""
Stop docker container that have been running longer than the max_run_time and
match some prefix.
match some prefix or image.
"""
import argparse
import logging
Expand All @@ -17,19 +17,18 @@

log = logging.getLogger(__name__)


def stop_containers(client, max_run_time, matcher, dry_run):
for container_summary in client.containers():
container = client.inspect_container(container_summary['Id'])
name = container['Name'].lstrip('/')
if (
matcher(name) and
matcher(container) and
has_been_running_since(container, max_run_time)
):

log.info("Stopping container %s %s: running since %s" % (
log.info("Stopping container %s %s (%s): running since %s" % (
container['Id'][:16],
name,
container['Name'].lstrip('/'),
container['Config']['Image'],
container['State']['StartedAt']))

if not dry_run:
Expand All @@ -45,11 +44,28 @@ def stop_container(client, id):
log.warn("Error stopping %s: %s" % (id, ae))


def build_matcher(opts):
if opts.prefix:
return build_container_matcher(opts.prefix)
if opts.image:
return build_image_matcher(opts.image)


def build_container_matcher(prefixes):
def matcher(name):
return any(name.startswith(prefix) for prefix in prefixes)
def matcher(container):
return any(
container.get('Name', '').lstrip('/').startswith(prefix)
for prefix in prefixes
)
return matcher

def build_image_matcher(images):
def matcher(container):
return any(
container.get('Config',{}).get('Image', '').startswith(image)
for image in images
)
return matcher

def has_been_running_since(container, min_time):
started_at = container.get('State', {}).get('StartedAt')
Expand All @@ -68,7 +84,8 @@ def main():
opts = get_opts()
client = docker.Client(version='auto', timeout=opts.timeout)

matcher = build_container_matcher(opts.prefix)
matcher = build_matcher(opts)

stop_containers(client, opts.max_run_time, matcher, opts.dry_run)


Expand All @@ -80,11 +97,6 @@ def get_opts(args=None):
help="Maximum time a container is allows to run. Time may "
"be specified in any pytimeparse supported format."
)
parser.add_argument(
'--prefix', action="append", default=[],
help="Only stop containers which match one of the "
"prefix."
)
parser.add_argument(
'--dry-run', action="store_true",
help="Only log actions, don't stop anything."
Expand All @@ -93,11 +105,17 @@ def get_opts(args=None):
'-t', '--timeout', type=int, default=60,
help="HTTP timeout in seconds for making docker API calls."
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'--prefix', action="append", default=[],
help="Only stop containers which match one of the prefix."
)
group.add_argument(
'--image', action="append", default=[],
help="Only stop containers that are from one of the given images."
)
opts = parser.parse_args(args=args)

if not opts.prefix:
parser.error("Running with no --prefix will match nothing.")

return opts


Expand Down
3 changes: 3 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def container():
'Id': 'abcdabcdabcdabcd',
'Created': '2013-12-20T17:00:00Z',
'Name': '/container_name',
'Config': {
'Image': 'docker.io/test/image:1234',
},
'State': {
'Running': False,
'FinishedAt': '2014-01-01T17:30:00Z',
Expand Down
20 changes: 15 additions & 5 deletions tests/docker_autostop_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from docker_custodian.docker_autostop import (
build_container_matcher,
build_image_matcher,
get_opts,
has_been_running_since,
main,
Expand All @@ -19,7 +20,7 @@ def test_stop_containers(mock_client, container, now):
mock_client.inspect_container.return_value = container

stop_containers(mock_client, now, matcher, False)
matcher.assert_called_once_with('container_name')
matcher.assert_called_once_with(container)
mock_client.stop.assert_called_once_with(container['Id'])


Expand All @@ -33,10 +34,19 @@ def test_build_container_matcher():
prefixes = ['one_', 'two_']
matcher = build_container_matcher(prefixes)

assert matcher('one_container')
assert matcher('two_container')
assert not matcher('three_container')
assert not matcher('one')
assert matcher({'Name': 'one_container'})
assert matcher({'Name': 'two_container'})
assert not matcher({'Name': 'three_container'})
assert not matcher({'Name': 'one'})


def test_build_image_matcher():
images = ['postgres', 'mysql']
matcher = build_image_matcher(images)
assert matcher({'Config': {'Image': 'postgres:9.6'}})
assert matcher({'Config': {'Image': 'mysql:latest'}})
assert not matcher({'Config': {'Image': 'not_postgres'}})
assert not matcher({'Config': {'Image': 'not_mysql'}})


def test_has_been_running_since_true(container, later_time):
Expand Down

0 comments on commit 696185c

Please sign in to comment.