2020-03-01 17:42:29 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""Stop long running docker iamges."""
|
|
|
|
|
2015-06-30 22:33:43 +00:00
|
|
|
import argparse
|
|
|
|
import logging
|
|
|
|
import sys
|
|
|
|
|
|
|
|
import dateutil.parser
|
|
|
|
import docker
|
|
|
|
import docker.errors
|
|
|
|
import requests.exceptions
|
2018-03-07 13:42:04 +00:00
|
|
|
from docker.utils import kwargs_from_env
|
2020-03-01 17:42:29 +00:00
|
|
|
from docker_custodian.args import timedelta_type
|
2015-06-30 22:33:43 +00:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def stop_containers(client, max_run_time, matcher, dry_run):
|
|
|
|
for container_summary in client.containers():
|
2020-03-01 17:42:29 +00:00
|
|
|
container = client.inspect_container(container_summary["Id"])
|
|
|
|
name = container["Name"].lstrip("/")
|
2015-06-30 22:33:43 +00:00
|
|
|
if (
|
2020-03-01 17:42:29 +00:00
|
|
|
matcher(name) and has_been_running_since(container, max_run_time)
|
2015-06-30 22:33:43 +00:00
|
|
|
):
|
|
|
|
|
|
|
|
log.info("Stopping container %s %s: running since %s" % (
|
2020-03-01 17:42:29 +00:00
|
|
|
container["Id"][:16],
|
2015-06-30 22:33:43 +00:00
|
|
|
name,
|
2020-03-01 17:42:29 +00:00
|
|
|
container["State"]["StartedAt"]))
|
2015-06-30 22:33:43 +00:00
|
|
|
|
|
|
|
if not dry_run:
|
2020-03-01 17:42:29 +00:00
|
|
|
stop_container(client, container["Id"])
|
2015-06-30 22:33:43 +00:00
|
|
|
|
|
|
|
|
2020-03-01 17:42:29 +00:00
|
|
|
def stop_container(client, cid):
|
2015-06-30 22:33:43 +00:00
|
|
|
try:
|
2020-03-01 17:42:29 +00:00
|
|
|
client.stop(cid)
|
2015-06-30 22:33:43 +00:00
|
|
|
except requests.exceptions.Timeout as e:
|
2020-03-01 17:42:29 +00:00
|
|
|
log.warn("Failed to stop container %s: %s" % (cid, e))
|
2015-06-30 22:33:43 +00:00
|
|
|
except docker.errors.APIError as ae:
|
2020-03-01 17:42:29 +00:00
|
|
|
log.warn("Error stopping %s: %s" % (cid, ae))
|
2015-06-30 22:33:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def build_container_matcher(prefixes):
|
|
|
|
def matcher(name):
|
|
|
|
return any(name.startswith(prefix) for prefix in prefixes)
|
|
|
|
return matcher
|
|
|
|
|
|
|
|
|
|
|
|
def has_been_running_since(container, min_time):
|
2020-03-01 17:42:29 +00:00
|
|
|
started_at = container.get("State", {}).get("StartedAt")
|
2015-06-30 22:33:43 +00:00
|
|
|
if not started_at:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return dateutil.parser.parse(started_at) <= min_time
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
logging.basicConfig(
|
|
|
|
level=logging.INFO,
|
|
|
|
format="%(message)s",
|
|
|
|
stream=sys.stdout)
|
|
|
|
|
|
|
|
opts = get_opts()
|
2020-03-01 17:42:29 +00:00
|
|
|
client = docker.APIClient(version="auto",
|
2018-03-07 13:42:04 +00:00
|
|
|
timeout=opts.timeout,
|
|
|
|
**kwargs_from_env())
|
2015-06-30 22:33:43 +00:00
|
|
|
|
|
|
|
matcher = build_container_matcher(opts.prefix)
|
|
|
|
stop_containers(client, opts.max_run_time, matcher, opts.dry_run)
|
|
|
|
|
|
|
|
|
|
|
|
def get_opts(args=None):
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument(
|
2020-03-01 17:42:29 +00:00
|
|
|
"--max-run-time",
|
2015-06-30 22:33:43 +00:00
|
|
|
type=timedelta_type,
|
2016-04-20 17:01:02 +00:00
|
|
|
help="Maximum time a container is allows to run. Time may "
|
|
|
|
"be specified in any pytimeparse supported format."
|
|
|
|
)
|
2015-06-30 22:33:43 +00:00
|
|
|
parser.add_argument(
|
2020-03-01 17:42:29 +00:00
|
|
|
"--prefix", action="append", default=[],
|
2015-06-30 22:33:43 +00:00
|
|
|
help="Only stop containers which match one of the "
|
2016-04-20 17:01:02 +00:00
|
|
|
"prefix."
|
|
|
|
)
|
2015-06-30 22:33:43 +00:00
|
|
|
parser.add_argument(
|
2020-03-01 17:42:29 +00:00
|
|
|
"--dry-run", action="store_true",
|
2016-04-20 17:01:02 +00:00
|
|
|
help="Only log actions, don't stop anything."
|
|
|
|
)
|
2015-06-30 22:33:43 +00:00
|
|
|
parser.add_argument(
|
2020-03-01 17:42:29 +00:00
|
|
|
"-t", "--timeout", type=int, default=60,
|
2016-04-20 17:01:02 +00:00
|
|
|
help="HTTP timeout in seconds for making docker API calls."
|
|
|
|
)
|
2015-06-30 22:33:43 +00:00
|
|
|
opts = parser.parse_args(args=args)
|
|
|
|
|
|
|
|
if not opts.prefix:
|
|
|
|
parser.error("Running with no --prefix will match nothing.")
|
|
|
|
|
|
|
|
return opts
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|