Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flag to retain recently used images #53

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ build/
__pycache__

*.egg-info/
.venv
47 changes: 45 additions & 2 deletions docker_custodian/docker_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import fnmatch
import logging
import sys
import time

import dateutil.parser
import docker
Expand Down Expand Up @@ -137,7 +138,13 @@ def get_dangling_volumes(client):
return volumes


def cleanup_images(client, max_image_age, dry_run, exclude_set):
def cleanup_images(
client,
max_image_age,
recent_image_age,
dry_run,
exclude_set
):
# re-fetch container list so that we don't include removed containers

containers = get_all_containers(client)
Expand All @@ -151,10 +158,35 @@ def cleanup_images(client, max_image_age, dry_run, exclude_set):
images = filter_images_in_use_by_id(images, image_ids_in_use)
images = filter_excluded_images(images, exclude_set)

if recent_image_age is not None:
images = without_recently_used_images(client, images, recent_image_age)

for image_summary in reversed(list(images)):
remove_image(client, image_summary, max_image_age, dry_run)


def without_recently_used_images(client, images, recent_image_age):
exclude = set()
for event in client.events(since=recent_image_age,
mks-m marked this conversation as resolved.
Show resolved Hide resolved
until=int(time.time()),
decode=True):
status = event.get('status', '')
if status != 'create' and not status.startswith('exec_start'):
continue
image_ref = event.get('from')
if image_ref is None:
continue
exclude.add(image_ref)
return [
image for image in images
if exclude.intersection(
[image.get('Id')] +
image.get('RepoTags', []) +
image.get('RepoDigests', [])
)
]


def filter_excluded_images(images, exclude_set):
def include_image(image_summary):
image_tags = image_summary.get('RepoTags')
Expand Down Expand Up @@ -314,7 +346,13 @@ def main():
exclude_set = build_exclude_set(
args.exclude_image,
args.exclude_image_file)
cleanup_images(client, args.max_image_age, args.dry_run, exclude_set)
cleanup_images(
client,
args.max_image_age,
args.recent_image_age,
args.dry_run,
exclude_set
)

if args.dangling_volumes:
cleanup_volumes(client, args.dry_run)
Expand All @@ -334,6 +372,11 @@ def get_args(args=None):
help="Maxium age for an image. Images older than this age will be "
"removed. Age can be specified in any pytimeparse supported "
"format.")
parser.add_argument(
'--recent-image-age',
type=timedelta_type, default=None,
help="Images used within specified time interval will not be removed. "
"Age can be specified in any pytimeparse supported format.")
parser.add_argument(
'--dangling-volumes',
action="store_true",
Expand Down
22 changes: 20 additions & 2 deletions tests/docker_gc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,30 @@ def test_cleanup_images(mock_client, now):
]
mock_client.inspect_image.side_effect = iter(mock_images)

docker_gc.cleanup_images(mock_client, max_image_age, False, set())
docker_gc.cleanup_images(mock_client, max_image_age, None, False, set())
assert mock_client.remove_image.mock_calls == [
mock.call(image=image['Id']) for image in reversed(images)
]


def test_without_recently_used_images(mock_client, now):
images = [
dict(Id='gone'),
dict(Id='a'),
dict(RepoTags=['b']),
dict(RepoDigests=['c'])
]
mock_events = [
{'status': 'create', 'from': 'a'},
{'status': 'exec_start', 'from': 'b'},
{'status': 'create', 'from': 'c'}
]
mock_client.events.return_value = mock_events
filtered_images = docker_gc.without_recently_used_images(
mock_client, images, 0)
assert filtered_images == images[1:]


def test_cleanup_volumes(mock_client):
mock_client.volumes.return_value = volumes = {
'Volumes': [
Expand Down Expand Up @@ -238,7 +256,7 @@ def test_filter_images_in_use_by_id(mock_client, now):
'Id': image,
'Created': '2014-01-01T01:01:01Z'
}
docker_gc.cleanup_images(mock_client, now, False, set())
docker_gc.cleanup_images(mock_client, now, None, False, set())
assert mock_client.remove_image.mock_calls == [
mock.call(image=id_) for id_ in ['6', '5', '4', '3']
]
Expand Down