mirror of
https://github.com/thegeeklab/docker-tidy.git
synced 2024-11-24 21:20:42 +00:00
add garbage colletor run method
This commit is contained in:
parent
371789c258
commit
1561264542
@ -68,4 +68,8 @@ class AutoStop:
|
||||
|
||||
def _get_docker_client(self):
|
||||
config = self.config.config
|
||||
return docker.APIClient(version="auto", timeout=config["timeout"])
|
||||
return docker.APIClient(version="auto", timeout=config["http_timeout"])
|
||||
|
||||
def run(self):
|
||||
"""Autostop main method."""
|
||||
print("run stop")
|
||||
|
@ -5,7 +5,9 @@ import argparse
|
||||
|
||||
import dockertidy.Exception
|
||||
from dockertidy import __version__
|
||||
from dockertidy.Autostop import AutoStop
|
||||
from dockertidy.Config import SingleConfig
|
||||
from dockertidy.GarbageCollector import GarbageCollector
|
||||
from dockertidy.Logger import SingleLog
|
||||
from dockertidy.Parser import timedelta_validator
|
||||
|
||||
@ -18,6 +20,9 @@ class DockerTidy:
|
||||
self.logger = self.log.logger
|
||||
self.args = self._cli_args()
|
||||
self.config = self._get_config()
|
||||
self.gc = GarbageCollector()
|
||||
self.stop = AutoStop()
|
||||
self.run()
|
||||
|
||||
def _cli_args(self):
|
||||
"""
|
||||
@ -54,6 +59,7 @@ class DockerTidy:
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="sub-command help")
|
||||
subparsers.required = True
|
||||
|
||||
parser_gc = subparsers.add_parser("gc", help="Run docker garbage collector.")
|
||||
parser_gc.add_argument(
|
||||
@ -84,7 +90,7 @@ class DockerTidy:
|
||||
"--exclude-image",
|
||||
action="append",
|
||||
type=str,
|
||||
dest="gc.exclude_image",
|
||||
dest="gc.exclude_images",
|
||||
metavar="EXCLUDE_IMAGE",
|
||||
help="Never remove images with this tag."
|
||||
)
|
||||
@ -92,7 +98,7 @@ class DockerTidy:
|
||||
"--exclude-container-label",
|
||||
action="append",
|
||||
type=str,
|
||||
dest="gc.exclude_container_label",
|
||||
dest="gc.exclude_container_labels",
|
||||
metavar="EXCLUDE_CONTAINER_LABEL",
|
||||
help="Never remove containers with this label key "
|
||||
"or label key=value"
|
||||
@ -127,13 +133,19 @@ class DockerTidy:
|
||||
except dockertidy.Exception.ConfigError as e:
|
||||
self.log.sysexit_with_message(e)
|
||||
|
||||
print(config.config)
|
||||
|
||||
try:
|
||||
self.log.set_level(config.config["logging"]["level"])
|
||||
except ValueError as e:
|
||||
self.log.sysexit_with_message("Can not set log level.\n{}".format(str(e)))
|
||||
|
||||
self.logger.info("Using config file {}".format(config.config_file))
|
||||
self.logger.debug("Config dump: {}".format(config.config))
|
||||
|
||||
return config
|
||||
|
||||
def run(self):
|
||||
"""Cli main method."""
|
||||
if self.config.config["command"] == "gc":
|
||||
self.gc.run()
|
||||
elif self.config.config["command"] == "stop":
|
||||
self.stop.run()
|
||||
|
@ -63,37 +63,37 @@ class Config():
|
||||
"type": environs.Env().bool
|
||||
},
|
||||
"gc.max_container_age": {
|
||||
"default": "1day",
|
||||
"default": "",
|
||||
"env": "GC_MAX_CONTAINER_AGE",
|
||||
"file": True,
|
||||
"type": env.timedelta_validator
|
||||
},
|
||||
"gc.max_image_age": {
|
||||
"default": "1day",
|
||||
"default": "",
|
||||
"env": "GC_MAX_IMAGE_AGE",
|
||||
"file": True,
|
||||
"type": env.timedelta_validator
|
||||
},
|
||||
"gc.dangling_volumes": {
|
||||
"default": False,
|
||||
"env": "GC_EXCLUDE_IMAGE",
|
||||
"env": "GC_DANGLING_VOLUMES",
|
||||
"file": True,
|
||||
"type": environs.Env().bool
|
||||
},
|
||||
"gc.exclude_image": {
|
||||
"gc.exclude_images": {
|
||||
"default": [],
|
||||
"env": "GC_DANGLING_VOLUMES",
|
||||
"env": "GC_EXCLUDE_IMAGES",
|
||||
"file": True,
|
||||
"type": environs.Env().list
|
||||
},
|
||||
"gc.exclude_container_label": {
|
||||
"gc.exclude_container_labels": {
|
||||
"default": [],
|
||||
"env": "GC_EXCLUDE_CONTAINER_LABEL",
|
||||
"env": "GC_EXCLUDE_CONTAINER_LABELS",
|
||||
"file": True,
|
||||
"type": environs.Env().list
|
||||
},
|
||||
"stop.max_run_time": {
|
||||
"default": "3days",
|
||||
"default": "",
|
||||
"env": "STOP_MAX_RUN_TIME",
|
||||
"file": True,
|
||||
"type": env.timedelta_validator
|
||||
|
@ -11,6 +11,7 @@ import requests.exceptions
|
||||
|
||||
from dockertidy.Config import SingleConfig
|
||||
from dockertidy.Logger import SingleLog
|
||||
from dockertidy.Parser import timedelta
|
||||
|
||||
|
||||
class GarbageCollector:
|
||||
@ -30,10 +31,14 @@ class GarbageCollector:
|
||||
"""Identify old containers and remove them."""
|
||||
config = self.config.config
|
||||
client = self.docker
|
||||
all_containers = self._get_all_containers(client)
|
||||
filtered_containers = self._filter_excluded_containers(
|
||||
all_containers,
|
||||
config["gc"]["exclude_container_labels"],
|
||||
all_containers = self._get_all_containers()
|
||||
|
||||
filtered_containers = self._filter_excluded_containers(all_containers)
|
||||
|
||||
self.logger.info(
|
||||
"Removing containers older than '{}'".format(
|
||||
timedelta(config["gc"]["max_container_age"], dt_format="%Y-%m-%d, %H:%M:%S")
|
||||
)
|
||||
)
|
||||
|
||||
for container_summary in reversed(list(filtered_containers)):
|
||||
@ -43,14 +48,14 @@ class GarbageCollector:
|
||||
)
|
||||
if not container or not self._should_remove_container(
|
||||
container,
|
||||
config["gc"]["max_container_age"],
|
||||
timedelta(config["gc"]["max_container_age"]),
|
||||
):
|
||||
continue
|
||||
|
||||
self.logger.info(
|
||||
"Removing container %s %s %s" % (
|
||||
container["Id"][:16], container.get("Name", "").lstrip("/"),
|
||||
container["State"]["FinishedAt"]
|
||||
"Removing container {} {} {}".format(
|
||||
container["Id"][:16],
|
||||
container.get("Name", "").lstrip("/"), container["State"]["FinishedAt"]
|
||||
)
|
||||
)
|
||||
|
||||
@ -152,16 +157,19 @@ class GarbageCollector:
|
||||
images = self._filter_images_in_use_by_id(images, image_ids_in_use)
|
||||
images = self._filter_excluded_images(images, exclude_set)
|
||||
|
||||
for image_summary in reversed(list(images)):
|
||||
self._rmove_image(
|
||||
client, image_summary, config["gc"]["max_image_age"], config["dry_run"]
|
||||
self.logger.info(
|
||||
"Removing images older than '{}'".format(
|
||||
timedelta(config["gc"]["max_image_age"], dt_format="%Y-%m-%d, %H:%M:%S")
|
||||
)
|
||||
)
|
||||
for image_summary in reversed(list(images)):
|
||||
self._remove_image(image_summary, timedelta(config["gc"]["max_image_age"]))
|
||||
|
||||
def _filter_excluded_images(self, images, exclude_set):
|
||||
|
||||
def include_image(image_summary):
|
||||
image_tags = image_summary.get("RepoTags")
|
||||
if self.no_image_tags(image_tags):
|
||||
if self._no_image_tags(image_tags):
|
||||
return True
|
||||
for exclude_pattern in exclude_set:
|
||||
if fnmatch.filter(image_tags, exclude_pattern):
|
||||
@ -197,7 +205,7 @@ class GarbageCollector:
|
||||
def _no_image_tags(self, image_tags):
|
||||
return not image_tags or image_tags == ["<none>:<none>"]
|
||||
|
||||
def _remove_image(self, client, image_summary, min_date):
|
||||
def _remove_image(self, image_summary, min_date):
|
||||
config = self.config.config
|
||||
client = self.docker
|
||||
image = self._api_call(client.inspect_image, image=image_summary["Id"])
|
||||
@ -219,8 +227,9 @@ class GarbageCollector:
|
||||
for image_tag in image_tags:
|
||||
self._api_call(client.remove_image, image=image_tag)
|
||||
|
||||
def _remove_volume(self, client, volume):
|
||||
def _remove_volume(self, volume):
|
||||
config = self.config.config
|
||||
client = self.docker
|
||||
if not volume:
|
||||
return
|
||||
|
||||
@ -230,13 +239,14 @@ class GarbageCollector:
|
||||
|
||||
self._api_call(client.remove_volume, name=volume["Name"])
|
||||
|
||||
def cleanup_volumes(self, client):
|
||||
def cleanup_volumes(self):
|
||||
"""Identify old volumes and remove them."""
|
||||
dangling_volumes = self._get_dangling_volumes(client)
|
||||
|
||||
dangling_volumes = self._get_dangling_volumes()
|
||||
|
||||
for volume in reversed(dangling_volumes):
|
||||
self.logger.info("Removing dangling volume %s", volume["Name"])
|
||||
self._remove_volume(client, volume)
|
||||
self._remove_volume(volume)
|
||||
|
||||
def _api_call(self, func, **kwargs):
|
||||
try:
|
||||
@ -260,7 +270,7 @@ class GarbageCollector:
|
||||
|
||||
def _build_exclude_set(self):
|
||||
config = self.config.config
|
||||
exclude_set = set(config["gc"]["exclude_image"])
|
||||
exclude_set = set(config["gc"]["exclude_images"])
|
||||
|
||||
def is_image_tag(line):
|
||||
return line and not line.startswith("#")
|
||||
@ -288,23 +298,26 @@ class GarbageCollector:
|
||||
|
||||
def _get_docker_client(self):
|
||||
config = self.config.config
|
||||
return docker.APIClient(version="auto", timeout=config["timeout"])
|
||||
return docker.APIClient(version="auto", timeout=config["http_timeout"])
|
||||
|
||||
def run(self):
|
||||
"""Garbage collector main method."""
|
||||
self.logger.info("Start garbage collection")
|
||||
config = self.config.config
|
||||
|
||||
# def main():
|
||||
# exclude_container_labels = format_exclude_labels(args.exclude_container_label)
|
||||
if config["gc"]["max_container_age"]:
|
||||
self.cleanup_containers()
|
||||
|
||||
# if args.max_container_age:
|
||||
# cleanup_containers(
|
||||
# client,
|
||||
# args.max_container_age,
|
||||
# args.dry_run,
|
||||
# exclude_container_labels,
|
||||
# )
|
||||
if config["gc"]["max_image_age"]:
|
||||
exclude_set = self._build_exclude_set()
|
||||
self.cleanup_images(exclude_set)
|
||||
|
||||
# if args.max_image_age:
|
||||
# exclude_set = build_exclude_set(args.exclude_image, args.exclude_image_file)
|
||||
# cleanup_images(client, args.max_image_age, args.dry_run, exclude_set)
|
||||
if config["gc"]["dangling_volumes"]:
|
||||
self.logger.info("Remove dangling volumes")
|
||||
self.cleanup_volumes()
|
||||
|
||||
# if args.dangling_volumes:
|
||||
# cleanup_volumes(client, args.dry_run)
|
||||
if (
|
||||
not config["gc"]["max_container_age"] or not config["gc"]["max_image_age"]
|
||||
or not config["gc"]["dangling_volumes"]
|
||||
):
|
||||
self.logger.info("Skipped, no arguments given")
|
||||
|
@ -26,7 +26,7 @@ def timedelta_validator(value):
|
||||
raise
|
||||
|
||||
|
||||
def timedelta(value):
|
||||
def timedelta(value, dt_format=None):
|
||||
"""Return the :class:`datetime.datetime.DateTime` for a time in the past.
|
||||
|
||||
:param value: a string containing a time format supported by
|
||||
@ -34,7 +34,12 @@ def timedelta(value):
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
return _datetime_seconds_ago(timeparse.timeparse(value))
|
||||
|
||||
timedelta = _datetime_seconds_ago(timeparse.timeparse(value))
|
||||
if dt_format:
|
||||
timedelta = timedelta.strftime(dt_format)
|
||||
|
||||
return timedelta
|
||||
|
||||
|
||||
def _datetime_seconds_ago(seconds):
|
||||
|
Loading…
Reference in New Issue
Block a user