2020-03-05 22:51:21 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""Stop long running docker iamges."""
|
|
|
|
|
|
|
|
import dateutil.parser
|
2023-01-16 08:10:35 +00:00
|
|
|
import docker
|
2020-03-05 22:51:21 +00:00
|
|
|
import docker.errors
|
|
|
|
import requests.exceptions
|
|
|
|
|
2021-01-03 14:36:04 +00:00
|
|
|
from dockertidy.config import SingleConfig
|
|
|
|
from dockertidy.logger import SingleLog
|
|
|
|
from dockertidy.parser import timedelta
|
2020-03-05 22:51:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AutoStop:
|
2020-03-06 21:39:32 +00:00
|
|
|
"""Autostop object to handle long running containers."""
|
2020-03-05 22:51:21 +00:00
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.config = SingleConfig()
|
|
|
|
self.log = SingleLog()
|
|
|
|
self.logger = SingleLog().logger
|
|
|
|
self.docker = self._get_docker_client()
|
|
|
|
|
|
|
|
def stop_containers(self):
|
2020-03-06 21:39:32 +00:00
|
|
|
"""Identify long running containers and terminate them."""
|
2020-03-05 22:51:21 +00:00
|
|
|
client = self.docker
|
|
|
|
config = self.config.config
|
|
|
|
|
|
|
|
max_run_time = timedelta(config["stop"]["max_run_time"])
|
|
|
|
prefix = config["stop"]["prefix"]
|
|
|
|
dry_run = config["dry_run"]
|
|
|
|
|
|
|
|
matcher = self._build_container_matcher(prefix)
|
|
|
|
|
2020-03-09 09:25:45 +00:00
|
|
|
self.logger.info(
|
|
|
|
"Stopping containers older than '{}'".format(
|
|
|
|
timedelta(config["stop"]["max_run_time"], dt_format="%Y-%m-%d, %H:%M:%S")
|
|
|
|
)
|
|
|
|
)
|
2020-03-05 22:51:21 +00:00
|
|
|
for container_summary in client.containers():
|
|
|
|
container = client.inspect_container(container_summary["Id"])
|
|
|
|
name = container["Name"].lstrip("/")
|
|
|
|
|
2020-03-09 09:25:45 +00:00
|
|
|
if (
|
|
|
|
prefix and matcher(name) and self._has_been_running_since(container, max_run_time)
|
|
|
|
) or (not prefix and self._has_been_running_since(container, max_run_time)):
|
2020-03-05 22:51:21 +00:00
|
|
|
self.logger.info(
|
2020-04-11 12:50:11 +00:00
|
|
|
"Stopping container {id} {name}: running since {started}".format(
|
|
|
|
id=container["Id"][:16],
|
|
|
|
name=name,
|
|
|
|
started=container["State"]["StartedAt"]
|
|
|
|
)
|
2020-03-05 22:51:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if not dry_run:
|
|
|
|
self._stop_container(client, container["Id"])
|
|
|
|
|
|
|
|
def _stop_container(self, client, cid):
|
|
|
|
try:
|
|
|
|
client.stop(cid)
|
|
|
|
except requests.exceptions.Timeout as e:
|
2022-08-21 22:08:31 +00:00
|
|
|
self.logger.warning("Failed to stop container {id}: {msg}".format(id=cid, msg=str(e)))
|
2020-04-11 12:50:11 +00:00
|
|
|
except docker.errors.APIError as e:
|
2022-08-21 22:08:31 +00:00
|
|
|
self.logger.warning("Error stopping {id}: {msg}".format(id=cid, msg=str(e)))
|
2020-03-05 22:51:21 +00:00
|
|
|
|
|
|
|
def _build_container_matcher(self, prefixes):
|
|
|
|
|
|
|
|
def matcher(name):
|
|
|
|
return any(name.startswith(prefix) for prefix in prefixes)
|
|
|
|
|
|
|
|
return matcher
|
|
|
|
|
|
|
|
def _has_been_running_since(self, container, min_time):
|
|
|
|
started_at = container.get("State", {}).get("StartedAt")
|
|
|
|
if not started_at:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return dateutil.parser.parse(started_at) <= min_time
|
|
|
|
|
|
|
|
def _get_docker_client(self):
|
|
|
|
config = self.config.config
|
2020-03-09 00:05:17 +00:00
|
|
|
return docker.APIClient(version="auto", timeout=config["http_timeout"])
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""Autostop main method."""
|
2020-03-09 09:25:45 +00:00
|
|
|
self.logger.info("Start autostop")
|
|
|
|
config = self.config.config
|
|
|
|
|
|
|
|
if config["stop"]["max_run_time"]:
|
|
|
|
self.stop_containers()
|
|
|
|
|
|
|
|
if not config["stop"]["max_run_time"]:
|
2022-08-21 22:08:31 +00:00
|
|
|
self.logger.warning("Skipped, no arguments given")
|