1
0
mirror of https://github.com/projectatomic/atomic.git synced 2026-02-06 21:45:24 +01:00
Files
atomic/Atomic/containers.py
Dan Walsh 8c28d61592 Fix container handling bug in atomic container
Closes: #723
Approved by: rhatdan
2016-10-24 21:39:23 +00:00

254 lines
11 KiB
Python

import argparse
import json
import os
from . import util
from . import Atomic
from .client import AtomicDocker
import datetime
from dateutil.parser import parse as dateparse
from docker.errors import NotFound, APIError
try:
from subprocess import DEVNULL # pylint: disable=no-name-in-module
except ImportError:
DEVNULL = open(os.devnull, 'wb')
def cli(subparser):
# atomic containers
c = subparser.add_parser("containers",
help=_("operate on containers"))
containers_subparser = c.add_subparsers(title='containers subcommands',
description="operate on containers",
help='additional help')
# atomic containers delete
delete_parser = containers_subparser.add_parser("delete",
help=_("delete specified containers"))
delete_parser.set_defaults(_class=Containers, func='delete_image')
delete_parser.add_argument("-f", "--force", action='store_true',
dest="force",
default=False,
help=_("Force removal of specified running containers"))
delete_group = delete_parser.add_mutually_exclusive_group()
delete_group.add_argument("-a", "--all", action='store_true',dest="all",
default=False,
help=_("Delete all containers"))
delete_group.add_argument("container", nargs='?', action="append",
help=_("Specify one or more containers. Must be final arguments."))
delete_parser.add_argument("containers", nargs=argparse.REMAINDER,
help=argparse.SUPPRESS)
delete_parser.set_defaults(_class=Containers, func='delete')
# atomic containers list
pss = containers_subparser.add_parser("list",
help=_("list the containers"),
epilog="By default this shows only the running containers.")
pss.set_defaults(_class=Containers, func='ps_tty')
pss.add_argument("-a", "--all", action='store_true',dest="all", default=False,
help=_("show all containers"))
pss.add_argument("-f", "--filter", metavar='FILTER', action='append', dest="filter",
help=_("Filter output based on conditions given in the VARIABLE=VALUE form"))
pss.add_argument("--json", action='store_true',dest="json", default=False,
help=_("print in a machine parseable form"))
pss.add_argument("-n", "--noheading", dest="heading", default=True,
action="store_false",
help=_("do not print heading when listing the containers"))
pss.add_argument("--no-trunc", action='store_false', dest="truncate", default=True,
help=_("Don't truncate output"))
pss.add_argument("-q", "--quiet", action='store_true', dest="quiet", default=False,
help=_("Only display container IDs"))
# atomic containers trim
trimp = containers_subparser.add_parser("trim",
help=_("discard unused blocks (fstrim) on running containers"),
epilog="Discard unused blocks (fstrim) on rootfs of running containers.")
trimp.set_defaults(_class=Containers, func='fstrim')
class Containers(Atomic):
def fstrim(self):
with AtomicDocker() as client:
for container in client.containers():
containerId = container["Id"]
ret = self._inspect_container(name=containerId)
pid = ret["State"]["Pid"]
mp = "/proc/%d/root" % (pid)
util.write_out("Trimming container id {0}".format(containerId[0:12]))
util.check_call(["/usr/sbin/fstrim", "-v", mp], stdout=DEVNULL)
return
def ps_tty(self):
all_container_info = self.ps()
all_containers = []
for each in all_container_info:
if each["Type"] == "system":
container = each["Id"]
status = "exited"
created = datetime.datetime.fromtimestamp(each["Created"])
info = self.syscontainers.get_container_runtime_info(container)
if 'status' in info:
status = info["status"]
if not self.args.all and status != "running":
continue
image = each['Image']
imageId = each['ImageID']
command = each["Command"]
created = created.strftime("%F %H:%M") # pylint: disable=no-member
container_info = {"type" : "system", "container" : container,
"image" : image, "command" : command, "image_id" : imageId,
"created" : created, "status" : status,
"runtime" : "runc", "vulnerable" : each["vulnerable"]}
if self.args.filter:
if not self._filter_include_container(container_info):
continue
all_containers.append(container_info)
elif each["Type"] == "docker":
# Collect the docker containers
container = each["Id"]
ret = self._inspect_container(name=container)
status = ret["State"]["Status"]
image = ret['Config']['Image']
imageId = ret['Image']
command = u' '.join(ret['Config']['Cmd']) if ret['Config']['Cmd'] else ""
created = dateparse(ret['Created']).strftime("%F %H:%M") # pylint: disable=no-member
container_info = {"type" : "docker", "container" : container,
"image" : image, "image_id" : imageId, "command" : command,
"created" : created, "status" : status,
"runtime" : "Docker", "vulnerable" : each["vulnerable"]}
if self.args.filter:
if not self._filter_include_container(container_info):
continue
all_containers.append(container_info)
if not all_containers:
return
if self.args.json:
util.write_out(json.dumps(all_containers))
return
if self.args.truncate:
max_len_container = 12
max_len_image = 20
max_len_command = 20
else:
max_len_container = max(max([len(s["container"]) for s in all_containers]), 12)
max_len_image = max(max([len(s["image"]) for s in all_containers]), 20)
max_len_command = max(max([len(s["command"]) for s in all_containers]), 20)
if self.args.quiet:
for container in all_containers:
util.write_out(container["container"][0:max_len_container])
return
col_out = "{0:2} {1:%s} {2:%s} {3:%s} {4:16} {5:9} {6:10}" % (max_len_container, max_len_image, max_len_command)
if self.args.heading:
util.write_out(col_out.format(" ",
"CONTAINER ID",
"IMAGE",
"COMMAND",
"CREATED",
"STATUS",
"RUNTIME"))
#if self.args.truncate:
for container in all_containers:
indicator = ""
if container["vulnerable"]:
if util.is_python2:
indicator = indicator + self.skull + " "
else:
indicator = indicator + str(self.skull, "utf-8") + " "
util.write_out(col_out.format(indicator,
container["container"][0:max_len_container],
container["image"][0:max_len_image],
container["command"][0:max_len_command],
container["created"][0:16],
container["status"][0:9],
container["runtime"][0:10]))
def ps(self):
all_containers = []
vuln_ids = self.get_vulnerable_ids()
all_vuln_info = json.loads(self.get_all_vulnerable_info())
# Collect the system containers
for i in self.syscontainers.get_containers():
i["vulnerable"] = i['Id'] in vuln_ids
if i["vulnerable"]:
i["vuln_info"] = all_vuln_info[i['Id']]
else:
i["vuln_info"] = dict()
all_containers.append(i)
# Collect the docker containers
for container in [x["Id"] for x in self.d.containers(all=self.args.all)]:
ret = self._inspect_container(name=container)
ret["Type"] = "docker"
ret["vulnerable"] = ret["Image"] in vuln_ids
if ret["vulnerable"]:
ret["vuln_info"] = all_vuln_info[ret["Image"]]
else:
ret["vuln_info"] = dict()
all_containers.append(ret)
return all_containers
def _filter_include_container(self, container_info):
filterables = ["container", "image", "command", "created", "status", "runtime"]
for j in self.args.filter:
var, value = str(j).split("=")
var = var.lower()
if var == "id" or var == "containerid":
var = "container"
if var not in filterables: # If the filter does not exist, default to allowing all containers through
continue
if value not in container_info[var]:
return False
return True
def delete(self):
results = 0
sys_targets=[]
docker_targets=[]
if self.args.all:
for c in self.get_containers():
docker_targets.append(c["Id"])
for c in self.syscontainers.get_containers():
sys_targets.append(c["Id"])
else:
if not self.args.container and len(self.args.containers) == 0:
raise ValueError("No containers selected")
for c in [ self.args.container ] + self.args.containers:
if self.syscontainers.get_checkout(c):
sys_targets.append(c)
else:
docker_targets.append(c)
for target in docker_targets:
try:
self.d.remove_container(target, force=self.args.force)
except NotFound as e:
util.write_err("Failed to delete container {}: {}".format(target, e))
results += 1
except APIError as e:
util.write_err("Failed operation for delete container {}: {}".format(target, e))
results += 1
for target in sys_targets:
try:
self.syscontainers.uninstall(target)
except IOError as e:
util.write_err("Failed to delete container {}: {}".format(target, e))
results += 1
return results