mirror of
https://github.com/thegeeklab/docker-tidy.git
synced 2024-06-02 00:19:41 +02:00
Merge pull request #47 from ATRAN2/add-exclude-container-regex-argument
Add --exclude-container-label argument to dcgc
This commit is contained in:
commit
95b5351ab8
24
README.rst
24
README.rst
|
@ -92,6 +92,30 @@ You also can use basic pattern matching to exclude images with generic tags.
|
||||||
user/repositoryB:?.?
|
user/repositoryB:?.?
|
||||||
user/repositoryC-*:tag
|
user/repositoryC-*:tag
|
||||||
|
|
||||||
|
|
||||||
|
Prevent containers and associated images from being removed
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``dcgc`` also supports a container exclude list based on labels. If there are
|
||||||
|
stopped containers that you'd like to keep, then you can check the labels to
|
||||||
|
prevent them from being removed.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
--exclude-container-label
|
||||||
|
Never remove containers that have the label key=value. =value can be
|
||||||
|
omitted and in that case only the key is checked. May be specified
|
||||||
|
more than once.
|
||||||
|
|
||||||
|
You also can use basic pattern matching to exclude generic labels.
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
foo*
|
||||||
|
com.docker.compose.project=test*
|
||||||
|
com.docker*=*bar*
|
||||||
|
|
||||||
|
|
||||||
dcstop
|
dcstop
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import docker
|
||||||
import docker.errors
|
import docker.errors
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
from docker_custodian.args import timedelta_type
|
from docker_custodian.args import timedelta_type
|
||||||
from docker.utils import kwargs_from_env
|
from docker.utils import kwargs_from_env
|
||||||
|
|
||||||
|
@ -22,15 +23,29 @@ log = logging.getLogger(__name__)
|
||||||
# This seems to be something docker uses for a null/zero date
|
# This seems to be something docker uses for a null/zero date
|
||||||
YEAR_ZERO = "0001-01-01T00:00:00Z"
|
YEAR_ZERO = "0001-01-01T00:00:00Z"
|
||||||
|
|
||||||
|
ExcludeLabel = namedtuple('ExcludeLabel', ['key', 'value'])
|
||||||
|
|
||||||
def cleanup_containers(client, max_container_age, dry_run):
|
|
||||||
|
def cleanup_containers(
|
||||||
|
client,
|
||||||
|
max_container_age,
|
||||||
|
dry_run,
|
||||||
|
exclude_container_labels,
|
||||||
|
):
|
||||||
all_containers = get_all_containers(client)
|
all_containers = get_all_containers(client)
|
||||||
|
filtered_containers = filter_excluded_containers(
|
||||||
for container_summary in reversed(all_containers):
|
all_containers,
|
||||||
container = api_call(client.inspect_container,
|
exclude_container_labels,
|
||||||
container=container_summary['Id'])
|
)
|
||||||
if not container or not should_remove_container(container,
|
for container_summary in reversed(list(filtered_containers)):
|
||||||
max_container_age):
|
container = api_call(
|
||||||
|
client.inspect_container,
|
||||||
|
container=container_summary['Id'],
|
||||||
|
)
|
||||||
|
if not container or not should_remove_container(
|
||||||
|
container,
|
||||||
|
max_container_age,
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log.info("Removing container %s %s %s" % (
|
log.info("Removing container %s %s %s" % (
|
||||||
|
@ -39,8 +54,44 @@ def cleanup_containers(client, max_container_age, dry_run):
|
||||||
container['State']['FinishedAt']))
|
container['State']['FinishedAt']))
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
api_call(client.remove_container, container=container['Id'],
|
api_call(
|
||||||
v=True)
|
client.remove_container,
|
||||||
|
container=container['Id'],
|
||||||
|
v=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_excluded_containers(containers, exclude_container_labels):
|
||||||
|
if not exclude_container_labels:
|
||||||
|
return containers
|
||||||
|
|
||||||
|
def include_container(container):
|
||||||
|
if should_exclude_container_with_labels(
|
||||||
|
container,
|
||||||
|
exclude_container_labels,
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
return filter(include_container, containers)
|
||||||
|
|
||||||
|
|
||||||
|
def should_exclude_container_with_labels(container, exclude_container_labels):
|
||||||
|
for exclude_label in exclude_container_labels:
|
||||||
|
if exclude_label.value:
|
||||||
|
matching_keys = fnmatch.filter(
|
||||||
|
container['Labels'].keys(),
|
||||||
|
exclude_label.key,
|
||||||
|
)
|
||||||
|
label_values_to_check = [
|
||||||
|
container['Labels'][matching_key]
|
||||||
|
for matching_key in matching_keys
|
||||||
|
]
|
||||||
|
if fnmatch.filter(label_values_to_check, exclude_label.value):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if fnmatch.filter(container['Labels'].keys(), exclude_label.key):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def should_remove_container(container, min_date):
|
def should_remove_container(container, min_date):
|
||||||
|
@ -214,6 +265,24 @@ def build_exclude_set(image_tags, exclude_file):
|
||||||
return exclude_set
|
return exclude_set
|
||||||
|
|
||||||
|
|
||||||
|
def format_exclude_labels(exclude_label_args):
|
||||||
|
exclude_labels = []
|
||||||
|
for exclude_label_arg in exclude_label_args:
|
||||||
|
split_exclude_label = exclude_label_arg.split('=', 1)
|
||||||
|
exclude_label_key = split_exclude_label[0]
|
||||||
|
if len(split_exclude_label) == 2:
|
||||||
|
exclude_label_value = split_exclude_label[1]
|
||||||
|
else:
|
||||||
|
exclude_label_value = None
|
||||||
|
exclude_labels.append(
|
||||||
|
ExcludeLabel(
|
||||||
|
key=exclude_label_key,
|
||||||
|
value=exclude_label_value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return exclude_labels
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
|
@ -225,8 +294,17 @@ def main():
|
||||||
timeout=args.timeout,
|
timeout=args.timeout,
|
||||||
**kwargs_from_env())
|
**kwargs_from_env())
|
||||||
|
|
||||||
|
exclude_container_labels = format_exclude_labels(
|
||||||
|
args.exclude_container_label
|
||||||
|
)
|
||||||
|
|
||||||
if args.max_container_age:
|
if args.max_container_age:
|
||||||
cleanup_containers(client, args.max_container_age, args.dry_run)
|
cleanup_containers(
|
||||||
|
client,
|
||||||
|
args.max_container_age,
|
||||||
|
args.dry_run,
|
||||||
|
exclude_container_labels,
|
||||||
|
)
|
||||||
|
|
||||||
if args.max_image_age:
|
if args.max_image_age:
|
||||||
exclude_set = build_exclude_set(
|
exclude_set = build_exclude_set(
|
||||||
|
@ -271,6 +349,10 @@ def get_args(args=None):
|
||||||
type=argparse.FileType('r'),
|
type=argparse.FileType('r'),
|
||||||
help="Path to a file which contains a list of images to exclude, one "
|
help="Path to a file which contains a list of images to exclude, one "
|
||||||
"image tag per line.")
|
"image tag per line.")
|
||||||
|
parser.add_argument(
|
||||||
|
'--exclude-container-label',
|
||||||
|
action='append', type=str, default=[],
|
||||||
|
help="Never remove containers with this label key or label key=value")
|
||||||
|
|
||||||
return parser.parse_args(args=args)
|
return parser.parse_args(args=args)
|
||||||
|
|
||||||
|
|
|
@ -51,24 +51,52 @@ def test_cleanup_containers(mock_client, now):
|
||||||
'Name': 'one',
|
'Name': 'one',
|
||||||
'State': {
|
'State': {
|
||||||
'Running': False,
|
'Running': False,
|
||||||
'FinishedAt': '2014-01-01T01:01:01Z'
|
'FinishedAt': '2014-01-01T01:01:01Z',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'Id': 'abbb',
|
'Id': 'abbb',
|
||||||
'Name': 'two',
|
'Name': 'two',
|
||||||
'State': {
|
'State': {
|
||||||
'Running': True,
|
'Running': True,
|
||||||
'FinishedAt': '2014-01-01T01:01:01Z'
|
'FinishedAt': '2014-01-01T01:01:01Z',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
mock_client.inspect_container.side_effect = iter(mock_containers)
|
mock_client.inspect_container.side_effect = iter(mock_containers)
|
||||||
docker_gc.cleanup_containers(mock_client, max_container_age, False)
|
docker_gc.cleanup_containers(mock_client, max_container_age, False, None)
|
||||||
mock_client.remove_container.assert_called_once_with(container='abcd',
|
mock_client.remove_container.assert_called_once_with(container='abcd',
|
||||||
v=True)
|
v=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_excluded_containers():
|
||||||
|
mock_containers = [
|
||||||
|
{'Labels': {'toot': ''}},
|
||||||
|
{'Labels': {'too': 'lol'}},
|
||||||
|
{'Labels': {'toots': 'lol'}},
|
||||||
|
{'Labels': {'foo': 'bar'}},
|
||||||
|
]
|
||||||
|
result = docker_gc.filter_excluded_containers(mock_containers, None)
|
||||||
|
assert mock_containers == list(result)
|
||||||
|
exclude_labels = [
|
||||||
|
docker_gc.ExcludeLabel(key='too', value=None),
|
||||||
|
docker_gc.ExcludeLabel(key='foo', value=None),
|
||||||
|
]
|
||||||
|
result = docker_gc.filter_excluded_containers(
|
||||||
|
mock_containers,
|
||||||
|
exclude_labels,
|
||||||
|
)
|
||||||
|
assert [mock_containers[0], mock_containers[2]] == list(result)
|
||||||
|
exclude_labels = [
|
||||||
|
docker_gc.ExcludeLabel(key='too*', value='lol'),
|
||||||
|
]
|
||||||
|
result = docker_gc.filter_excluded_containers(
|
||||||
|
mock_containers,
|
||||||
|
exclude_labels,
|
||||||
|
)
|
||||||
|
assert [mock_containers[0], mock_containers[3]] == list(result)
|
||||||
|
|
||||||
|
|
||||||
def test_cleanup_images(mock_client, now):
|
def test_cleanup_images(mock_client, now):
|
||||||
max_image_age = now
|
max_image_age = now
|
||||||
mock_client.images.return_value = images = [
|
mock_client.images.return_value = images = [
|
||||||
|
@ -459,6 +487,19 @@ def test_build_exclude_set():
|
||||||
assert exclude_set == expected
|
assert exclude_set == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_exclude_labels():
|
||||||
|
exclude_label_args = [
|
||||||
|
'voo*',
|
||||||
|
'doo=poo',
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
docker_gc.ExcludeLabel(key='voo*', value=None),
|
||||||
|
docker_gc.ExcludeLabel(key='doo', value='poo'),
|
||||||
|
]
|
||||||
|
exclude_labels = docker_gc.format_exclude_labels(exclude_label_args)
|
||||||
|
assert expected == exclude_labels
|
||||||
|
|
||||||
|
|
||||||
def test_build_exclude_set_empty():
|
def test_build_exclude_set_empty():
|
||||||
exclude_set = docker_gc.build_exclude_set(None, None)
|
exclude_set = docker_gc.build_exclude_set(None, None)
|
||||||
assert exclude_set == set()
|
assert exclude_set == set()
|
||||||
|
@ -477,5 +518,6 @@ def test_main(mock_client):
|
||||||
max_container_age=200,
|
max_container_age=200,
|
||||||
exclude_image=[],
|
exclude_image=[],
|
||||||
exclude_image_file=None,
|
exclude_image_file=None,
|
||||||
|
exclude_container_label=[],
|
||||||
)
|
)
|
||||||
docker_gc.main()
|
docker_gc.main()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user