mirror of
https://github.com/projectatomic/atomic.git
synced 2026-02-06 12:45:57 +01:00
Refactor images
Covers all but verify and generate. This is a refactoring of the images subverbs (i.e. info, version, delete, ...) Added in a unittest for list and info. Closes: #771 Approved by: baude
This commit is contained in:
@@ -625,7 +625,7 @@ class Atomic(object):
|
||||
as a JSON string so it can then be parsed appropriately.
|
||||
"""
|
||||
try:
|
||||
return open(os.path.join(self.results, "scan_summary.json"), "r").read()
|
||||
return json.loads(open(os.path.join(self.results, "scan_summary.json"), "r").read())
|
||||
except IOError:
|
||||
return "{}"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from docker import errors
|
||||
|
||||
import Atomic.util as util
|
||||
from Atomic.backends.backend import Backend
|
||||
from Atomic.client import get_docker_client
|
||||
from Atomic.client import AtomicDocker
|
||||
from Atomic.objects.image import Image
|
||||
from Atomic.objects.container import Container
|
||||
from requests import exceptions
|
||||
@@ -18,7 +18,7 @@ class DockerBackend(Backend):
|
||||
@property
|
||||
def d(self):
|
||||
if not self._d:
|
||||
self._d = get_docker_client()
|
||||
self._d = AtomicDocker()
|
||||
self._ping()
|
||||
return self._d
|
||||
return self._d
|
||||
@@ -82,6 +82,7 @@ class DockerBackend(Backend):
|
||||
img_obj.repotags = img_struct['RepoTags']
|
||||
img_obj.created = img_struct['Created']
|
||||
img_obj.size = img_struct['Size']
|
||||
img_obj.virtual_size = img_struct['VirtualSize']
|
||||
|
||||
if deep:
|
||||
img_obj.deep = True
|
||||
@@ -240,11 +241,14 @@ class DockerBackend(Backend):
|
||||
util.is_insecure_registry(self.d.info()['RegistryConfig'], util.strip_port(registry)))
|
||||
|
||||
def prune(self):
|
||||
for iid in self._get_images(get_all=True, quiet=True, filters={"dangling": True}):
|
||||
for iid in self.get_dangling_images():
|
||||
self.delete_image(iid, force=True)
|
||||
util.write_out("Removed dangling Image {}".format(iid))
|
||||
return 0
|
||||
|
||||
def get_dangling_images(self):
|
||||
return self._get_images(get_all=True, quiet=True, filters={"dangling": True})
|
||||
|
||||
def install(self, image, name):
|
||||
pass
|
||||
|
||||
@@ -257,15 +261,15 @@ class DockerBackend(Backend):
|
||||
def version(self, image):
|
||||
return self.get_layers(image)
|
||||
|
||||
def _get_layer(self, image):
|
||||
def get_layer(self, image):
|
||||
return Layer(self.inspect_image(image))
|
||||
|
||||
def get_layers(self, image):
|
||||
layers = []
|
||||
layer = self._get_layer(image)
|
||||
layer = self.get_layer(image)
|
||||
layers.append(layer)
|
||||
while layer.parent:
|
||||
layer = self._get_layer(layer.parent)
|
||||
layer = self.get_layer(layer.parent)
|
||||
layers.append(layer)
|
||||
return layers
|
||||
|
||||
|
||||
@@ -42,29 +42,29 @@ class OSTreeBackend(Backend):
|
||||
|
||||
def _make_image(self, image, info):
|
||||
name = info['Id']
|
||||
image = Image(image, backend=self, remote=False)
|
||||
image.name = name
|
||||
image.config = info
|
||||
image.backend = self
|
||||
image.id = name
|
||||
image.registry = None
|
||||
image.repo = None
|
||||
image.image = name
|
||||
image.tag = name
|
||||
image.repotags = info['RepoTags']
|
||||
image.created = info['Created']
|
||||
image.size = None
|
||||
image.original_structure = info
|
||||
image.input_name = info['Id']
|
||||
image.deep = True
|
||||
image.labels = info['Labels']
|
||||
image.version = image.get_label("Version")
|
||||
image.release = image.get_label("Release")
|
||||
image.digest = None
|
||||
image.os = image.get_label("Os")
|
||||
image.arch = image.get_label("Arch")
|
||||
image.graph_driver = None
|
||||
return image
|
||||
img_obj = Image(image, backend=self, remote=False)
|
||||
img_obj.input_name = image
|
||||
img_obj.name = image
|
||||
img_obj.config = info
|
||||
img_obj.backend = self
|
||||
img_obj.id = name
|
||||
img_obj.registry = None
|
||||
img_obj.repo = None
|
||||
img_obj.image = name
|
||||
img_obj.tag = name
|
||||
img_obj.repotags = info['RepoTags']
|
||||
img_obj.created = info['Created']
|
||||
img_obj.size = None
|
||||
img_obj.original_structure = info
|
||||
img_obj.deep = True
|
||||
img_obj.labels = info['Labels']
|
||||
img_obj.version = img_obj.get_label("Version")
|
||||
img_obj.release = img_obj.get_label("Release")
|
||||
img_obj.digest = None
|
||||
img_obj.os = img_obj.get_label("Os")
|
||||
img_obj.arch = img_obj.get_label("Arch")
|
||||
img_obj.graph_driver = None
|
||||
return img_obj
|
||||
|
||||
def has_image(self, img):
|
||||
if self.syscontainers.has_image(img):
|
||||
@@ -137,3 +137,6 @@ class OSTreeBackend(Backend):
|
||||
layer = self._get_layer(layer.parent)
|
||||
layers.append(layer)
|
||||
return layers
|
||||
|
||||
def get_dangling_images(self):
|
||||
return []
|
||||
|
||||
@@ -9,7 +9,7 @@ class BackendUtils(object):
|
||||
|
||||
BACKENDS = [DockerBackend, OSTreeBackend]
|
||||
|
||||
def _get_backend_from_string(self, str_backend):
|
||||
def get_backend_from_string(self, str_backend):
|
||||
for _backend in self.BACKENDS:
|
||||
backend_obj = _backend()
|
||||
if backend_obj.backend == str_backend:
|
||||
@@ -27,7 +27,7 @@ class BackendUtils(object):
|
||||
def backend_has_container(backend, container):
|
||||
return True if backend.has_container(container) else False
|
||||
|
||||
def get_backend_for_image(self, img, str_preferred_backend=None):
|
||||
def get_backend_and_image(self, img, str_preferred_backend=None):
|
||||
"""
|
||||
Given an image name (str) and optionally a str reference to a backend,
|
||||
this method looks for the image firstly on the preferred backend and
|
||||
@@ -36,12 +36,13 @@ class BackendUtils(object):
|
||||
:param str_preferred_backend: i.e. 'docker'
|
||||
:return: backend object
|
||||
"""
|
||||
backends = self.BACKENDS
|
||||
backends = list(self.BACKENDS)
|
||||
# Check preferred backend first
|
||||
if str_preferred_backend:
|
||||
be = self._get_backend_from_string(str_preferred_backend)
|
||||
if be.has_image(img):
|
||||
return be
|
||||
be = self.get_backend_from_string(str_preferred_backend)
|
||||
img_obj = be.has_image(img)
|
||||
if img_obj:
|
||||
return be, img_obj
|
||||
|
||||
# Didnt find in preferred, need to remove it from the list now
|
||||
del backends[self._get_backend_index_from_string(str_preferred_backend)]
|
||||
@@ -50,8 +51,9 @@ class BackendUtils(object):
|
||||
img_in_backends = []
|
||||
for backend in backends:
|
||||
be = backend()
|
||||
if be.has_image(img):
|
||||
img_in_backends.append(be)
|
||||
img_obj = be.has_image(img)
|
||||
if img_obj:
|
||||
img_in_backends.append((be, img_obj))
|
||||
|
||||
if len(img_in_backends) == 1:
|
||||
return img_in_backends[0]
|
||||
@@ -69,10 +71,10 @@ class BackendUtils(object):
|
||||
:param str_preferred_backend: i.e. 'docker'
|
||||
:return: backend object
|
||||
"""
|
||||
backends = self.BACKENDS
|
||||
backends = list(self.BACKENDS)
|
||||
# Check preferred backend first
|
||||
if str_preferred_backend:
|
||||
be = self._get_backend_from_string(str_preferred_backend)
|
||||
be = self.get_backend_from_string(str_preferred_backend)
|
||||
if be.has_container(container):
|
||||
return be
|
||||
# Didnt find in preferred, need to remove it from the list now
|
||||
@@ -90,5 +92,19 @@ class BackendUtils(object):
|
||||
raise ValueError("Found {} in multiple storage backends: {}".
|
||||
format(container, ', '.join([x.backend for x in container_in_backends])))
|
||||
|
||||
def get_images(self, get_all=False):
|
||||
backends = self.BACKENDS
|
||||
img_objs = []
|
||||
for backend in backends:
|
||||
be = backend()
|
||||
img_objs += be.get_images(get_all=get_all)
|
||||
return img_objs
|
||||
|
||||
def get_containers(self):
|
||||
backends = self.BACKENDS
|
||||
con_objs = []
|
||||
for backend in backends:
|
||||
be = backend()
|
||||
con_objs += be.get_containers()
|
||||
return con_objs
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ class Containers(Atomic):
|
||||
def ps(self):
|
||||
all_containers = []
|
||||
vuln_ids = self.get_vulnerable_ids()
|
||||
all_vuln_info = json.loads(self.get_all_vulnerable_info())
|
||||
all_vuln_info = self.get_all_vulnerable_info()
|
||||
|
||||
# Collect the system containers
|
||||
for i in self.syscontainers.get_containers():
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from . import Atomic
|
||||
from . import util
|
||||
from docker.errors import NotFound
|
||||
from docker.errors import APIError
|
||||
import sys
|
||||
from Atomic.backendutils import BackendUtils
|
||||
|
||||
class Delete(Atomic):
|
||||
def __init__(self):
|
||||
@@ -13,34 +12,62 @@ class Delete(Atomic):
|
||||
Mark given image(s) for deletion from registry
|
||||
:return: 0 if all images marked for deletion, otherwise 2 on any failure
|
||||
"""
|
||||
if self.args.debug:
|
||||
util.write_out(str(self.args))
|
||||
|
||||
beu = BackendUtils()
|
||||
# Ensure the input values match up first
|
||||
delete_objects = []
|
||||
|
||||
# We need to decide on new returns for dbus because we now check image
|
||||
# validity prior to executing the delete. If there is going to be a
|
||||
# failure, it will be here.
|
||||
#
|
||||
# The failure here is basically that it couldnt verify/find the image.
|
||||
|
||||
for image in self.args.delete_targets:
|
||||
be, img_obj = beu.get_backend_and_image(image, str_preferred_backend=self.args.storage)
|
||||
delete_objects.append((be, img_obj))
|
||||
|
||||
if self.args.remote:
|
||||
return self._delete_remote(self.args.delete_targets)
|
||||
|
||||
max_img_name = max([len(x.input_name) for _, x in delete_objects]) + 2
|
||||
|
||||
if not self.args.assumeyes:
|
||||
confirm = util.input("Do you wish to delete {}? (y/N) ".format(self.args.delete_targets))
|
||||
util.write_out("Do you wish to delete the following images?\n")
|
||||
two_col = " {0:" + str(max_img_name) + "} {1}"
|
||||
util.write_out(two_col.format("IMAGE", "STORAGE"))
|
||||
for del_obj in delete_objects:
|
||||
be, img_obj = del_obj
|
||||
util.write_out(two_col.format(img_obj.input_name, be.backend))
|
||||
confirm = util.input("\nConfirm (y/N) ")
|
||||
confirm = confirm.strip().lower()
|
||||
if not confirm in ['y', 'yes']:
|
||||
util.write_err("User aborted delete operation for {}".format(self.args.delete_targets))
|
||||
sys.exit(2)
|
||||
|
||||
if self.args.remote:
|
||||
results = self._delete_remote(self.args.delete_targets)
|
||||
else:
|
||||
results = self._delete_local(self.args.delete_targets, self.args.force)
|
||||
return results
|
||||
# Perform the delete
|
||||
for del_obj in delete_objects:
|
||||
be, img_obj = del_obj
|
||||
be.delete_image(img_obj.input_name, force=self.args.force)
|
||||
|
||||
# We need to return something here for dbus
|
||||
return
|
||||
|
||||
def prune_images(self):
|
||||
"""
|
||||
Remove dangling images from registry
|
||||
:return: 0 if all images deleted or no dangling images found
|
||||
"""
|
||||
self.syscontainers.prune_ostree_images()
|
||||
|
||||
results = self.d.images(filters={"dangling":True}, quiet=True)
|
||||
if len(results) == 0:
|
||||
return 0
|
||||
if self.args.debug:
|
||||
util.write_out(str(self.args))
|
||||
|
||||
for backend in BackendUtils.BACKENDS:
|
||||
be = backend()
|
||||
be.prune()
|
||||
|
||||
for img in results:
|
||||
self.d.remove_image(img, force=True)
|
||||
util.write_out("Removed dangling Image {}".format(img))
|
||||
return 0
|
||||
|
||||
def _delete_remote(self, targets):
|
||||
@@ -67,44 +94,4 @@ class Delete(Atomic):
|
||||
results = 2
|
||||
return results
|
||||
|
||||
def _delete_local(self, targets, force=False):
|
||||
results = 0
|
||||
for target in targets:
|
||||
if not self.args.storage:
|
||||
if self.is_duplicate_image(target):
|
||||
util.write_err("Failed to delete Image {}: has duplicate naming; please specify "
|
||||
"--storage to delete from a specific storage.".format(target))
|
||||
results = 2
|
||||
else:
|
||||
if self.syscontainers.has_image(target):
|
||||
self.syscontainers.delete_image(target)
|
||||
else:
|
||||
try:
|
||||
self.d.remove_image(target, force=force)
|
||||
except NotFound as e:
|
||||
util.write_err("Failed to delete Image {}: {}".format(target, e))
|
||||
results = 2
|
||||
except APIError as e:
|
||||
util.write_err("Failed operation for delete Image {}: {}".format(target, e))
|
||||
results = 2
|
||||
|
||||
elif self.args.storage.lower() == "ostree":
|
||||
if not self.syscontainers.has_image(target):
|
||||
util.write_err("Failed to delete Image {}: does not exist in ostree.".format(target))
|
||||
self.syscontainers.delete_image(target)
|
||||
|
||||
elif self.args.storage.lower() == "docker":
|
||||
try:
|
||||
self.d.remove_image(target, force=force)
|
||||
except NotFound as e:
|
||||
util.write_err("Failed to delete Image {}: {}".format(target, e))
|
||||
results = 2
|
||||
except APIError as e:
|
||||
util.write_err("Failed operation for delete Image {}: {}".format(target, e))
|
||||
results = 2
|
||||
|
||||
else:
|
||||
util.write_err("{} is not a valid storage".format(self.args.storage))
|
||||
results = 2
|
||||
|
||||
return results
|
||||
|
||||
@@ -261,7 +261,7 @@ class RegistryInspect():
|
||||
self.rc = RegistryConnection(debug=self.debug)
|
||||
self._setup_rc()
|
||||
try:
|
||||
util.write_out("Trying {}".format(self.assemble_fqdn(include_tag=True)))
|
||||
self.assemble_fqdn(include_tag=True)
|
||||
self.ping()
|
||||
except RegistryInspectError as e:
|
||||
util.write_out(str(e))
|
||||
|
||||
194
Atomic/images.py
194
Atomic/images.py
@@ -6,13 +6,14 @@ from Atomic import help as Help
|
||||
from Atomic.mount import Mount
|
||||
from Atomic.delete import Delete
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import math
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import argparse
|
||||
from Atomic import backendutils
|
||||
|
||||
ATOMIC_CONFIG = util.get_atomic_config()
|
||||
storage = ATOMIC_CONFIG.get('default_storage', "docker")
|
||||
|
||||
def convert_size(size):
|
||||
if size > 0:
|
||||
@@ -48,7 +49,7 @@ def cli(subparser):
|
||||
action="store_true",
|
||||
help=_("Delete image from remote repository"))
|
||||
|
||||
delete_parser.add_argument("--storage", default="", dest="storage",
|
||||
delete_parser.add_argument("--storage", default=storage, dest="storage",
|
||||
help=_("Specify the storage from which to delete the image from. "
|
||||
"If not specified and there are images with the same name in "
|
||||
"different storages, you will be prompted to specify."))
|
||||
@@ -109,6 +110,7 @@ def cli(subparser):
|
||||
class Images(Atomic):
|
||||
def __init__(self):
|
||||
super(Images, self).__init__()
|
||||
self.be_utils = backendutils.BackendUtils()
|
||||
|
||||
def display_all_image_info(self):
|
||||
def get_col_lengths(_images):
|
||||
@@ -118,106 +120,80 @@ class Images(Atomic):
|
||||
:return: a set with len of repository and tag
|
||||
If there are no images, return 1, 1
|
||||
'''
|
||||
repo_tags = [[i["repo"], i["tag"]] for i in _images]
|
||||
repo_tags = [y for x in _images if x.repotags for y in x.split_repotags]
|
||||
|
||||
if repo_tags:
|
||||
return max([len(x[0]) for x in repo_tags]) + 2,\
|
||||
max([len(x[1]) for x in repo_tags]) + 2
|
||||
else:
|
||||
return 1, 1
|
||||
|
||||
_images = self.images()
|
||||
if self.args.debug:
|
||||
util.write_out(str(self.args))
|
||||
|
||||
_images = self._get_images()
|
||||
|
||||
if self.args.json:
|
||||
json.dump(_images, sys.stdout)
|
||||
util.output_json(self.return_json(_images))
|
||||
return 0
|
||||
|
||||
if len(_images) == 0:
|
||||
return
|
||||
|
||||
if len(_images) >= 0:
|
||||
_max_repo, _max_tag = get_col_lengths(_images)
|
||||
if self.args.truncate:
|
||||
_max_id = 14
|
||||
_max_repo, _max_tag = get_col_lengths(_images)
|
||||
|
||||
if self.args.truncate:
|
||||
_max_id = 14
|
||||
else:
|
||||
_max_id = 65
|
||||
col_out = "{0:2} {1:" + str(_max_repo) + "} {2:" + str(_max_tag) + \
|
||||
"} {3:" + str(_max_id) + "} {4:18} {5:14} {6:10}"
|
||||
|
||||
if self.args.heading and not self.args.quiet:
|
||||
util.write_out(col_out.format(" ",
|
||||
"REPOSITORY",
|
||||
"TAG",
|
||||
"IMAGE ID",
|
||||
"CREATED",
|
||||
"VIRTUAL SIZE",
|
||||
"TYPE"))
|
||||
for image in _images:
|
||||
if self.args.filter:
|
||||
if not self._filter_include_image(image):
|
||||
continue
|
||||
if self.args.quiet:
|
||||
util.write_out(image.id)
|
||||
|
||||
else:
|
||||
_max_id = 65
|
||||
indicator = ""
|
||||
if image.is_dangling:
|
||||
indicator += "*"
|
||||
elif image.used:
|
||||
indicator += ">"
|
||||
if image.vulnerable:
|
||||
space = " " if len(indicator) < 1 else ""
|
||||
if util.is_python2:
|
||||
indicator = indicator + self.skull + space
|
||||
else:
|
||||
indicator = indicator + str(self.skull, "utf-8") + space
|
||||
repo, tag = image.split_repotags[0]
|
||||
_id = image.short_id if self.args.truncate else image.id
|
||||
util.write_out(col_out.format(indicator, repo or "<none>", tag or "<none>", _id, image.timestamp,
|
||||
image.virtual_size, image.backend.backend))
|
||||
util.write_out("")
|
||||
return
|
||||
|
||||
col_out = "{0:2} {1:" + str(_max_repo) + "} {2:" + str(_max_tag) + \
|
||||
"} {3:" + str(_max_id) + "} {4:18} {5:14} {6:10}"
|
||||
def _get_images(self):
|
||||
_images = self.be_utils.get_images(get_all=self.args.all)
|
||||
|
||||
if self.args.heading and not self.args.quiet:
|
||||
util.write_out(col_out.format(" ",
|
||||
"REPOSITORY",
|
||||
"TAG",
|
||||
"IMAGE ID",
|
||||
"CREATED",
|
||||
"VIRTUAL SIZE",
|
||||
"TYPE"))
|
||||
self._mark_used(_images)
|
||||
self._mark_vulnerable(_images)
|
||||
|
||||
for image in _images:
|
||||
if self.args.filter:
|
||||
image_info = {"repo" : image['repo'], "tag" : image['tag'], "id" : image['id'],
|
||||
"created" : image['created'], "size" : image['virtual_size'], "type" : image['type'],
|
||||
"dangling": "{}".format(image['is_dangling'])}
|
||||
if not self._filter_include_image(image_info):
|
||||
continue
|
||||
if self.args.quiet:
|
||||
util.write_out(image['id'])
|
||||
|
||||
else:
|
||||
indicator = ""
|
||||
if image["is_dangling"]:
|
||||
indicator += "*"
|
||||
elif image["used_image"]:
|
||||
indicator += ">"
|
||||
if image["vulnerable"]:
|
||||
space = " " if len(indicator) < 1 else ""
|
||||
if util.is_python2:
|
||||
indicator = indicator + self.skull + space
|
||||
else:
|
||||
indicator = indicator + str(self.skull, "utf-8") + space
|
||||
util.write_out(col_out.format(indicator, image['repo'], image['tag'], image['id'], image['created'], image['virtual_size'], image['type']))
|
||||
util.write_out("")
|
||||
return
|
||||
return _images
|
||||
|
||||
def images(self):
|
||||
_images = self.get_images(get_all=self.args.all)
|
||||
all_image_info = []
|
||||
if len(_images) >= 0:
|
||||
vuln_ids = self.get_vulnerable_ids()
|
||||
all_vuln_info = json.loads(self.get_all_vulnerable_info())
|
||||
used_image_ids = [x['ImageID'] for x in self.get_containers()]
|
||||
for image in _images:
|
||||
image_dict = dict()
|
||||
if not image["RepoTags"]:
|
||||
continue
|
||||
if ':' in image["RepoTags"][0]:
|
||||
repo, tag = image["RepoTags"][0].rsplit(":", 1)
|
||||
else:
|
||||
repo, tag = image["RepoTags"][0], ""
|
||||
if "Created" in image:
|
||||
created = time.strftime("%F %H:%M", time.localtime(image["Created"]))
|
||||
else:
|
||||
created = ""
|
||||
if "VirtualSize" in image:
|
||||
virtual_size = convert_size(image["VirtualSize"])
|
||||
else:
|
||||
virtual_size = ""
|
||||
return self.return_json(self._get_images())
|
||||
|
||||
image_dict["is_dangling"] = self.is_dangling(repo)
|
||||
image_dict["used_image"] = image["Id"] in used_image_ids
|
||||
image_dict["vulnerable"] = image["Id"] in vuln_ids
|
||||
image_id = image["Id"][:12] if self.args.truncate else image["Id"]
|
||||
image_type = image['ImageType']
|
||||
image_dict["repo"] = repo
|
||||
image_dict["tag"] = tag
|
||||
image_dict["id"] = image_id
|
||||
image_dict["created"] = created
|
||||
image_dict["virtual_size"] = virtual_size
|
||||
image_dict["type"] = image_type
|
||||
image_dict["image_id"] = image["ImageId"]
|
||||
if image_dict["vulnerable"]:
|
||||
image_dict["vuln_info"] = all_vuln_info[image["Id"]]
|
||||
else:
|
||||
image_dict["vuln_info"] = dict()
|
||||
|
||||
all_image_info.append(image_dict)
|
||||
return all_image_info
|
||||
|
||||
def generate_validation_manifest(self):
|
||||
"""
|
||||
@@ -254,10 +230,13 @@ class Images(Atomic):
|
||||
f.write(r.stdout)
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def _filter_include_image(self, image_info):
|
||||
def _filter_include_image(self, image_obj):
|
||||
filterables = ["repo", "tag", "id", "created", "size", "type", "dangling"]
|
||||
for i in self.args.filter:
|
||||
var, value = str(i).split("=")
|
||||
try:
|
||||
var, value = str(i).split("=")
|
||||
except ValueError:
|
||||
raise ValueError("The filter {} is not formatted correctly. It should be VAR=VALUE".format(i))
|
||||
var = var.lower()
|
||||
if var == "repository":
|
||||
var = "repo"
|
||||
@@ -267,8 +246,39 @@ class Images(Atomic):
|
||||
|
||||
if var not in filterables: # Default to allowing all images through for non-existing filterable
|
||||
continue
|
||||
|
||||
if value not in image_info[var].lower():
|
||||
if getattr(image_obj, var, None) != value:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _mark_used(self, images):
|
||||
assert isinstance(images, list)
|
||||
all_containers = [x.id for x in self.be_utils.get_containers()]
|
||||
for image in images:
|
||||
if image.id in all_containers:
|
||||
image.used = True
|
||||
|
||||
def _mark_vulnerable(self, images):
|
||||
assert isinstance(images, list)
|
||||
vulnerable_uuids = self.get_vulnerable_ids()
|
||||
for image in images:
|
||||
if image.id in vulnerable_uuids:
|
||||
image.vulnerable = True
|
||||
|
||||
def return_json(self, images):
|
||||
all_image_info = []
|
||||
all_vuln_info = self.get_all_vulnerable_info()
|
||||
keys = ['is_dangling', 'used', 'vulnerable', 'id', 'type', 'created', 'virtual_size']
|
||||
for img_obj in images:
|
||||
if not img_obj.repotags:
|
||||
continue
|
||||
img_dict = dict()
|
||||
img_dict['repo'], img_dict['tag'] = img_obj.split_repotags[0]
|
||||
for key in keys:
|
||||
img_dict[key] = getattr(img_obj, key, None)
|
||||
img_dict['vuln_info'] = \
|
||||
dict() if not img_obj.vulnerable else all_vuln_info.get(img_obj.id, None) # pylint: disable=no-member
|
||||
all_image_info.append(img_dict)
|
||||
return all_image_info
|
||||
|
||||
|
||||
|
||||
219
Atomic/info.py
219
Atomic/info.py
@@ -6,9 +6,18 @@ try:
|
||||
except ImportError:
|
||||
from atomic import Atomic # pylint: disable=relative-import
|
||||
|
||||
from .atomic import AtomicError
|
||||
from docker.errors import NotFound
|
||||
import requests.exceptions
|
||||
from Atomic.util import get_atomic_config
|
||||
from Atomic.backendutils import BackendUtils
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
from contextlib import closing
|
||||
from Atomic.discovery import RegistryInspectError
|
||||
|
||||
ATOMIC_CONFIG = get_atomic_config()
|
||||
storage = ATOMIC_CONFIG.get('default_storage', "docker")
|
||||
|
||||
def cli(subparser, hidden=False):
|
||||
# atomic info
|
||||
@@ -25,7 +34,7 @@ def cli(subparser, hidden=False):
|
||||
infop.add_argument("--remote", dest="force",
|
||||
action='store_true', default=False,
|
||||
help=_('ignore local images and only scan registries'))
|
||||
infop.add_argument("--storage", default="", dest="storage",
|
||||
infop.add_argument("--storage", default=storage, dest="storage",
|
||||
help=_("Specify the storage of the image. "
|
||||
"If not specified and there are images with the same name in "
|
||||
"different storages, you will be prompted to specify."))
|
||||
@@ -44,183 +53,81 @@ def cli_version(subparser, hidden=False):
|
||||
versionp.add_argument("-r", "--recurse", default=False, dest="recurse",
|
||||
action="store_true",
|
||||
help=_("recurse through all layers"))
|
||||
versionp.add_argument("--storage", default="", dest="storage",
|
||||
versionp.add_argument("--storage", default=storage, dest="storage",
|
||||
help=_("Specify the storage of the image. "
|
||||
"If not specified and there are images with the same name in "
|
||||
"different storages, you will be prompted to specify."))
|
||||
versionp.set_defaults(_class=Info, func='print_version')
|
||||
versionp.set_defaults(_class=Info, func='version')
|
||||
versionp.add_argument("image", help=_("container image"))
|
||||
|
||||
|
||||
class Info(Atomic):
|
||||
def __init__(self):
|
||||
super(Info, self).__init__()
|
||||
self.beu = BackendUtils()
|
||||
|
||||
def version(self):
|
||||
if not self.args.storage:
|
||||
if self.is_duplicate_image(self.image):
|
||||
raise ValueError("Found more than one Image with name {}; "
|
||||
"please specify with --storage.".format(self.image))
|
||||
else:
|
||||
if self.syscontainers.has_image(self.image):
|
||||
return self.syscontainers.version(self.image)
|
||||
try:
|
||||
self.d.inspect_image(self.image)
|
||||
except (NotFound, requests.exceptions.ConnectionError):
|
||||
self._no_such_image()
|
||||
layer_objects = self.get_layer_objects()
|
||||
max_version_len = max([len(x.long_version) for x in layer_objects])
|
||||
max_version_len = max_version_len if max_version_len > 9 else 9
|
||||
max_img_len = len(max([y for x in layer_objects for y in x.repotags], key=len)) + 9
|
||||
max_img_len = max_img_len if max_img_len > 12 else 12
|
||||
col_out = "{0:" + str(max_img_len) + "} {1:" + str(max_version_len) + "} {2:10}"
|
||||
util.write_out(col_out.format("IMAGE NAME", "VERSION", "IMAGE ID"))
|
||||
for layer in layer_objects:
|
||||
for int_img_name in range(len(layer.repotags)):
|
||||
version = layer.long_version if int_img_name < 1 else ""
|
||||
iid = layer.id[:12] if int_img_name < 1 else ""
|
||||
space = "" if int_img_name < 1 else " Tag: "
|
||||
util.write_out(col_out.format(space + layer.repotags[int_img_name], version, iid))
|
||||
|
||||
elif self.args.storage.lower() == "ostree":
|
||||
if self.syscontainers.has_image(self.image):
|
||||
return self.syscontainers.version(self.image)
|
||||
self._no_such_image()
|
||||
def get_layer_objects(self):
|
||||
_, img_obj = self.beu.get_backend_and_image(self.image, str_preferred_backend=self.args.storage)
|
||||
return img_obj.layers
|
||||
|
||||
elif self.args.storage.lower() == "docker":
|
||||
try:
|
||||
self.d.inspect_image(self.image)
|
||||
except (NotFound, requests.exceptions.ConnectionError):
|
||||
self._no_such_image()
|
||||
|
||||
else:
|
||||
raise ValueError("{} is not a valid storage".format(self.args.storage))
|
||||
|
||||
if self.args.recurse:
|
||||
return self.get_layers()
|
||||
else:
|
||||
return [self._get_layer(self.image)]
|
||||
|
||||
def get_version(self):
|
||||
def dbus_version(self):
|
||||
layer_objects = self.get_layer_objects()
|
||||
versions = []
|
||||
for layer in self.version():
|
||||
version = "None"
|
||||
if "Version" in layer and layer["Version"] != '':
|
||||
version = layer["Version"]
|
||||
versions.append({"Image": layer['RepoTags'], "Version": version, "iid": layer['Id']})
|
||||
return versions
|
||||
for layer in layer_objects:
|
||||
versions.append({"Image": layer.repotags, "Version": layer.long_version, "iid": layer.id})
|
||||
|
||||
def info_tty(self):
|
||||
if self.args.debug:
|
||||
util.write_out(str(self.args))
|
||||
util.write_out(self.info())
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
Retrieve and print all LABEL information for a given image.
|
||||
"""
|
||||
buf = ""
|
||||
|
||||
def _no_label():
|
||||
return ""
|
||||
if not self.args.storage:
|
||||
if self.is_duplicate_image(self.image):
|
||||
raise ValueError("Found more than one Image with name {}; "
|
||||
"please specify with --storage.".format(self.image))
|
||||
|
||||
if self.syscontainers.has_image(self.image):
|
||||
if not self.args.force:
|
||||
buf += ("Image Name: {}".format(self.image))
|
||||
manifest = self.syscontainers.inspect_system_image(self.image)
|
||||
labels = manifest["Labels"]
|
||||
for label in labels:
|
||||
buf += ('\n{0}: {1}'.format(label, labels[label]))
|
||||
|
||||
template_variables, template_variables_to_set = self.syscontainers.get_template_variables(self.image)
|
||||
buf += ("\n\nEnvironment variables with default value, but overridable with --set:")
|
||||
for variable in template_variables.keys():
|
||||
buf += ('\n{}: {}'.format(variable, template_variables.get(variable)))
|
||||
|
||||
if template_variables_to_set:
|
||||
buf += ("\n\nEnvironment variables that has no default value, and must be set with --set:")
|
||||
for variable in template_variables_to_set.keys():
|
||||
buf += ('\n{}: {}'.format(variable, template_variables_to_set.get(variable)))
|
||||
return buf
|
||||
# Check if the input is an image id associated with more than one
|
||||
# repotag. If so, error out.
|
||||
else:
|
||||
try:
|
||||
iid = self._is_image(self.image)
|
||||
self.image = self.get_fq_name(self._inspect_image(iid))
|
||||
except AtomicError:
|
||||
if self.args.force:
|
||||
self.image = util.find_remote_image(self.d, self.image)
|
||||
if self.image is None:
|
||||
self._no_such_image()
|
||||
|
||||
elif self.args.storage.lower() == "ostree":
|
||||
if self.syscontainers.has_image(self.image):
|
||||
if not self.args.force:
|
||||
buf += ("Image Name: {}".format(self.image))
|
||||
manifest = self.syscontainers.inspect_system_image(self.image)
|
||||
labels = manifest["Labels"]
|
||||
for label in labels:
|
||||
buf += ('\n{0}: {1}'.format(label, labels[label]))
|
||||
|
||||
template_variables, template_variables_to_set = self.syscontainers.get_template_variables(self.image)
|
||||
buf += ("\n\nEnvironment variables with default value, but overridable with --set:")
|
||||
for variable in template_variables.keys():
|
||||
buf += ('\n{}: {}'.format(variable, template_variables.get(variable)))
|
||||
|
||||
if template_variables_to_set:
|
||||
buf += ("\n\nEnvironment variables that has no default value, and must be set with --set:")
|
||||
for variable in template_variables_to_set.keys():
|
||||
buf += ('\n{}: {}'.format(variable, template_variables_to_set.get(variable)))
|
||||
return buf
|
||||
else:
|
||||
self._no_such_image()
|
||||
|
||||
elif self.args.storage.lower() == "docker":
|
||||
if self.is_iid():
|
||||
self.get_fq_name(self._inspect_image())
|
||||
# The input is not an image id
|
||||
else:
|
||||
try:
|
||||
iid = self._is_image(self.image)
|
||||
self.image = self.get_fq_name(self._inspect_image(iid))
|
||||
except AtomicError:
|
||||
if self.args.force:
|
||||
self.image = util.find_remote_image(self.d, self.image)
|
||||
if self.image is None:
|
||||
self._no_such_image()
|
||||
if self.args.storage == 'ostree' and self.args.force:
|
||||
# Ostree and remote combos are illegal
|
||||
raise ValueError("The --remote option cannot be used with the 'ostree' storage option.")
|
||||
|
||||
if self.args.force:
|
||||
# The user wants information on a remote image
|
||||
be = self.beu.get_backend_from_string(self.args.storage)
|
||||
img_obj = be.make_remote_image(self.image)
|
||||
else:
|
||||
raise ValueError("{} is not a valid storage".format(self.args.storage))
|
||||
# The image is local
|
||||
be, img_obj = self.beu.get_backend_and_image(self.image, str_preferred_backend=self.args.storage)
|
||||
|
||||
buf += ("Image Name: {}".format(self.image))
|
||||
inspection = None
|
||||
if not self.args.force:
|
||||
inspection = self._inspect_image(self.image)
|
||||
# No such image locally, but fall back to remote
|
||||
if inspection is None:
|
||||
# Shut up pylint in case we're on a machine with upstream
|
||||
# docker-py, which lacks the remote keyword arg.
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
inspection = util.skopeo_inspect("docker://" + self.image)
|
||||
# image does not exist on any configured registry
|
||||
if 'Config' in inspection and 'Labels' in inspection['Config']:
|
||||
labels = inspection['Config']['Labels']
|
||||
elif 'Labels' in inspection:
|
||||
labels = inspection['Labels']
|
||||
else:
|
||||
_no_label()
|
||||
with closing(StringIO()) as buf:
|
||||
try:
|
||||
info_name = img_obj.fq_name
|
||||
except RegistryInspectError:
|
||||
info_name = img_obj.input_name
|
||||
buf.write("Image Name: {}\n".format(info_name))
|
||||
buf.writelines(sorted(["{}: {}\n".format(k, v) for k,v in list(img_obj.labels.items())]))
|
||||
if img_obj.template_variables_set:
|
||||
buf.write("\n\nTemplate variables with default value, but overridable with --set:\n")
|
||||
buf.writelines(["{}: {}\n".format(k, v) for k,v in
|
||||
list(sorted(img_obj.template_variables_set.items()))])
|
||||
if img_obj.template_variables_unset:
|
||||
buf.write("\n\nTemplate variables that has no default value, and must be set with --set:\n")
|
||||
buf.writelines(["{}: {}\n".format(k, v) for k,v in
|
||||
list(sorted(img_obj.template_variables_unset.items()))])
|
||||
return buf.getvalue()
|
||||
|
||||
if labels is not None and len(labels) is not 0:
|
||||
for label in labels:
|
||||
buf += ('\n{0}: {1}'.format(label, labels[label]))
|
||||
else:
|
||||
_no_label()
|
||||
return buf
|
||||
|
||||
def print_version(self):
|
||||
versions = self.get_version()
|
||||
max_version_len = len(max([x['Version'] for x in versions], key=len)) + 2
|
||||
max_version_len = max_version_len if max_version_len > 9 else 9
|
||||
max_img_len = len(max([y for x in versions for y in x['Image']], key=len)) + 9
|
||||
max_img_len = max_img_len if max_img_len > 12 else 12
|
||||
col_out = "{0:" + str(max_img_len) + "} {1:" + str(max_version_len) + "} {2:10}"
|
||||
util.write_out("")
|
||||
util.write_out(col_out.format("IMAGE NAME", "VERSION", "IMAGE ID"))
|
||||
for layer in versions:
|
||||
for int_img_name in range(len(layer['Image'])):
|
||||
version = layer['Version'] if int_img_name < 1 else ""
|
||||
iid = layer['iid'][:12] if int_img_name < 1 else ""
|
||||
space = "" if int_img_name < 1 else " Tag: "
|
||||
util.write_out(col_out.format(space + layer['Image'][int_img_name], version, iid))
|
||||
util.write_out("")
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class Container(object):
|
||||
|
||||
def _setup_common(self):
|
||||
# Items common to backends can go here.
|
||||
print("")
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Helper function to dump out known variables in pretty-print style
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from Atomic.util import Decompose, output_json
|
||||
from Atomic.discovery import RegistryInspect
|
||||
from Atomic.objects.layer import Layer
|
||||
import math
|
||||
import time
|
||||
|
||||
|
||||
class Image(object):
|
||||
@@ -20,6 +23,7 @@ class Image(object):
|
||||
self._backend = backend
|
||||
self.input_name = input_name
|
||||
self.deep = False
|
||||
self._virtual_size = None
|
||||
|
||||
# Deeper
|
||||
self.version = None
|
||||
@@ -32,8 +36,16 @@ class Image(object):
|
||||
self.graph_driver = None
|
||||
self.config = {}
|
||||
self._fq_name = None
|
||||
|
||||
self._used = False
|
||||
self._vulnerable = False
|
||||
|
||||
self._template_variables_set = None
|
||||
self._template_variables_unset = None
|
||||
|
||||
self._instantiate()
|
||||
|
||||
|
||||
def __gt__(self, other):
|
||||
"""
|
||||
Custom greater than comparison between image objects. This allows you to
|
||||
@@ -85,6 +97,10 @@ class Image(object):
|
||||
if self._fq_name:
|
||||
return self._fq_name
|
||||
|
||||
if self.backend.backend == 'ostree':
|
||||
self._fq_name = self.input_name
|
||||
return self._fq_name
|
||||
|
||||
if self.fully_qualified:
|
||||
img = self.registry
|
||||
if self.repo:
|
||||
@@ -95,7 +111,6 @@ class Image(object):
|
||||
return img
|
||||
|
||||
if not self.registry:
|
||||
print(self.image)
|
||||
ri = RegistryInspect(registry=self.registry, repo=self.repo, image=self.image,
|
||||
tag=self.tag, orig_input=self.input_name)
|
||||
self._fq_name = ri.find_image_on_registry()
|
||||
@@ -149,4 +164,108 @@ class Image(object):
|
||||
_version += "-{}".format(self.version)
|
||||
if self.release:
|
||||
_version += "-{}".format(self.release)
|
||||
return _version
|
||||
return _version
|
||||
|
||||
@property
|
||||
def is_dangling(self):
|
||||
if self.id in self.backend.get_dangling_images():
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def virtual_size(self):
|
||||
size = self._virtual_size or self.size
|
||||
if size:
|
||||
return convert_size(self._virtual_size)
|
||||
return ""
|
||||
|
||||
@virtual_size.setter
|
||||
def virtual_size(self, value):
|
||||
self._virtual_size = value
|
||||
|
||||
@property
|
||||
def split_repotags(self):
|
||||
_repotags = []
|
||||
if not self.repotags:
|
||||
return [('<none>', '<none')]
|
||||
for _repotag in self.repotags:
|
||||
if ':' in _repotag:
|
||||
repo, tag = _repotag.rsplit(':', 1)
|
||||
else:
|
||||
repo = tag = ""
|
||||
_repotags.append((repo, tag))
|
||||
return _repotags
|
||||
|
||||
@property
|
||||
def used(self):
|
||||
return self._used
|
||||
|
||||
@used.setter
|
||||
def used(self, value):
|
||||
assert isinstance(value, bool)
|
||||
self._used = value
|
||||
|
||||
@property
|
||||
def vulnerable(self):
|
||||
return self._vulnerable
|
||||
|
||||
@vulnerable.setter
|
||||
def vulnerable(self, value):
|
||||
assert isinstance(value, bool)
|
||||
self._vulnerable = value
|
||||
|
||||
@property
|
||||
def short_id(self):
|
||||
return self.id[:12]
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
return time.strftime("%F %H:%M", time.localtime(self.created))
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self.backend.backend
|
||||
|
||||
def _get_template_info(self):
|
||||
self._template_variables_set, self._template_variables_unset = self.backend.syscontainers.\
|
||||
get_template_variables(self.image)
|
||||
|
||||
@property
|
||||
def template_variables_set(self):
|
||||
if self.backend.backend != 'ostree':
|
||||
return self._template_variables_set
|
||||
|
||||
if not self._template_variables_set and not self.template_variables_unset:
|
||||
self._get_template_info()
|
||||
|
||||
return self._template_variables_set
|
||||
|
||||
@property
|
||||
def template_variables_unset(self):
|
||||
if self.backend.backend != ' ostree':
|
||||
return self._template_variables_unset
|
||||
|
||||
if not self._template_variables_set and not self.template_variables_unset:
|
||||
self._get_template_info()
|
||||
return self._template_variables_unset
|
||||
|
||||
@property
|
||||
def layers(self):
|
||||
layer_objects = []
|
||||
# Create the first layer
|
||||
layer = Layer(self)
|
||||
layer_objects.append(layer)
|
||||
while layer.parent:
|
||||
layer = self.backend.get_layer(layer.parent)
|
||||
layer_objects.append(layer)
|
||||
return layer_objects
|
||||
|
||||
def convert_size(size):
|
||||
if size > 0:
|
||||
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = int(math.floor(math.log(size, 1000)))
|
||||
p = math.pow(1000, i)
|
||||
s = round(size/p, 2) # pylint: disable=round-builtin,old-division
|
||||
if s > 0:
|
||||
return '%s %s' % (s, size_name[i])
|
||||
return '0B'
|
||||
|
||||
@@ -822,3 +822,4 @@ class Decompose(object):
|
||||
@property
|
||||
def all(self):
|
||||
return self._registry, self._repo, self._image, self._tag, self._digest
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ class atomic_dbus(slip.dbus.service.Object):
|
||||
args = self.Args()
|
||||
args.all=True
|
||||
images.set_args(args)
|
||||
i = images.images()
|
||||
i = images.display_all_image_info()
|
||||
return json.dumps(i)
|
||||
|
||||
# atomic containers section
|
||||
@@ -566,7 +566,7 @@ class atomic_dbus(slip.dbus.service.Object):
|
||||
args.image = image
|
||||
args.recurse = recurse
|
||||
info.set_args(args)
|
||||
return json.dumps(info.get_version())
|
||||
return json.dumps(info.dbus_version())
|
||||
|
||||
if __name__ == "__main__":
|
||||
mainloop = GObject.MainLoop()
|
||||
|
||||
@@ -97,7 +97,7 @@ __atomic_image_repos_and_tags() {
|
||||
}
|
||||
|
||||
__atomic_system_containers_images() {
|
||||
local images="$(atomic images list --no-trunc -f type=system |
|
||||
local images="$(atomic images list --no-trunc -f type=ostree|
|
||||
awk 'NR>1 { if ($1 == ">" || $1 == "*") print $2; else print $1; }')"
|
||||
COMPREPLY+=( $(compgen -W "$images" -- "$cur") )
|
||||
}
|
||||
|
||||
2
setup.py
2
setup.py
@@ -12,7 +12,7 @@ setup(
|
||||
version=_Atomic.__version__,
|
||||
author=_Atomic.__author__,
|
||||
author_email=_Atomic.__author_email__,
|
||||
packages=["Atomic"],
|
||||
packages=["Atomic", "Atomic/backends", "Atomic/objects"],
|
||||
data_files=[('/etc/dbus-1/system.d/', ["org.atomic.conf"]),
|
||||
('/usr/share/dbus-1/system-services', ["org.atomic.service"]),
|
||||
('/usr/share/polkit-1/actions/', ["org.atomic.policy"]),
|
||||
|
||||
@@ -252,7 +252,7 @@ ostree --repo=${ATOMIC_OSTREE_REPO} refs > refs
|
||||
assert_matches busybox refs
|
||||
${ATOMIC} --assumeyes images delete -f --storage ostree docker.io/busybox
|
||||
|
||||
BUSYBOX_IMAGE_ID=$(${ATOMIC} images list -f type=system | grep busybox | awk '{print $3}')
|
||||
BUSYBOX_IMAGE_ID=$(${ATOMIC} images list -f type=ostree | grep busybox | awk '{print $3}')
|
||||
${ATOMIC} --assumeyes images delete -f ${BUSYBOX_IMAGE_ID}
|
||||
|
||||
ostree --repo=${ATOMIC_OSTREE_REPO} refs > refs
|
||||
@@ -270,8 +270,8 @@ image_digest=$(ostree --repo=${ATOMIC_OSTREE_REPO} show --print-metadata-key=doc
|
||||
${ATOMIC} images list > images.out
|
||||
grep "busybox.*$image_digest" images.out
|
||||
|
||||
${ATOMIC} images list -f type=system > images.out
|
||||
${ATOMIC} images list -f type=system --all > images.all.out
|
||||
${ATOMIC} images list -f type=ostree > images.out
|
||||
${ATOMIC} images list -f type=ostree --all > images.all.out
|
||||
test $(wc -l < images.out) -lt $(wc -l < images.all.out)
|
||||
assert_matches '<none>' images.all.out
|
||||
assert_not_matches '<none>' images.out
|
||||
@@ -280,19 +280,15 @@ ${ATOMIC} --assumeyes images delete -f --storage ostree busybox
|
||||
${ATOMIC} images prune
|
||||
|
||||
# Test there are still intermediate layers left after prune
|
||||
${ATOMIC} images list -f type=system --all > images.all.out
|
||||
${ATOMIC} images list -f type=ostree --all > images.all.out
|
||||
assert_matches "<none>" images.all.out
|
||||
|
||||
# Check to see if deleting a duplicate image will error
|
||||
OUTPUT=$(! ${ATOMIC} --assumeyes images delete -f atomic-test-system 2>&1)
|
||||
grep "Failed to delete Image atomic-test-system: has duplicate naming" <<< $OUTPUT
|
||||
|
||||
# Now delete from ostree
|
||||
${ATOMIC} --assumeyes images delete --storage ostree atomic-test-system
|
||||
${ATOMIC} images prune
|
||||
|
||||
# Test there are not intermediate layers left layers now
|
||||
${ATOMIC} images list -f type=system --all > images.all.out
|
||||
${ATOMIC} images list -f type=ostree --all > images.all.out
|
||||
assert_not_matches "<none>" images.all.out
|
||||
|
||||
# Verify there are no branches left in the repository as well
|
||||
|
||||
90
tests/unit/test_images.py
Normal file
90
tests/unit/test_images.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#pylint: skip-file
|
||||
import unittest
|
||||
from Atomic.backendutils import BackendUtils
|
||||
from Atomic.backends._docker import DockerBackend
|
||||
from Atomic.backends._ostree import OSTreeBackend
|
||||
from Atomic.info import Info
|
||||
from Atomic.images import Images
|
||||
|
||||
no_mock = True
|
||||
try:
|
||||
from unittest.mock import MagicMock, patch
|
||||
no_mock = False
|
||||
except ImportError:
|
||||
try:
|
||||
from mock import MagicMock, patch
|
||||
no_mock = False
|
||||
except ImportError:
|
||||
# Mock is already set to False
|
||||
pass
|
||||
|
||||
|
||||
_centos_inspect_image = {u'Comment': u'', u'Container': u'58aeaa4866c2845b48ab998b7cba3856a9fb64a681f92544cb035b85066b5102', u'DockerVersion': u'1.12.1', u'Parent': u'', u'Created': u'2016-11-02T19:52:09.463959047Z', u'Config': {u'Tty': False, u'Cmd': [u'/bin/bash'], u'Volumes': None, u'Domainname': u'', u'WorkingDir': u'', u'Image': u'5a2725191d75eb64e9b7c969cd23d8c67c6e8af9979e521a417bbfa34434fb83', u'Hostname': u'd6dcf178f680', u'StdinOnce': False, u'Labels': {u'build-date': u'20161102', u'vendor': u'CentOS', u'name': u'CentOS Base Image', u'license': u'GPLv2'}, u'AttachStdin': False, u'User': u'', u'Env': [u'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], u'Entrypoint': None, u'OnBuild': None, u'AttachStderr': False, u'AttachStdout': False, u'OpenStdin': False}, u'Author': u'https://github.com/CentOS/sig-cloud-instance-images', u'GraphDriver': {u'Data': {u'DeviceName': u'docker-253:1-20984667-e3af0c61256f885331fb1a3adc27ea509a10ba9a0ba9175c1a149f81bddcd30d', u'DeviceSize': u'10737418240', u'DeviceId': u'2'}, u'Name': u'devicemapper'}, u'VirtualSize': 196509652, u'Os': u'linux', u'Architecture': u'amd64', u'ContainerConfig': {u'Tty': False, u'Cmd': [u'/bin/sh', u'-c', u'#(nop) ', u'CMD ["/bin/bash"]'], u'Volumes': None, u'Domainname': u'', u'WorkingDir': u'', u'Image': u'5a2725191d75eb64e9b7c969cd23d8c67c6e8af9979e521a417bbfa34434fb83', u'Hostname': u'd6dcf178f680', u'StdinOnce': False, u'Labels': {u'build-date': u'20161102', u'vendor': u'CentOS', u'name': u'CentOS Base Image', u'license': u'GPLv2'}, u'AttachStdin': False, u'User': u'', u'Env': [u'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], u'Entrypoint': None, u'OnBuild': None, u'AttachStderr': False, u'AttachStdout': False, u'OpenStdin': False}, u'Size': 196509652, u'RepoDigests': [u'docker.io/centos@b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c'], u'Id': u'0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a', u'RepoTags': [u'docker.io/centos:latest']}
|
||||
_docker_centos_result = 'Image Name: docker.io/library/centos:latest\nbuild-date: 20161102\nlicense: GPLv2\nname: CentOS Base Image\nvendor: CentOS\n'
|
||||
_ostree_centos_result = 'Image Name: docker.io/library/centos:latest\nbuild-date: 20161102\nlicense: GPLv2\nname: CentOS Base Image\nvendor: CentOS\n\n\nTemplate variables with default value, but overridable with --set:\nRUN_DIRECTORY: {SET_BY_OS}\nSTATE_DIRECTORY: {SET_BY_OS}\n'
|
||||
_centos_ostree_inspect = {'Version': 'centos', 'Labels': {u'build-date': u'20161102', u'vendor': u'CentOS', u'name': u'CentOS Base Image', u'license': u'GPLv2'}, 'Names': [], 'Created': 1480352808, 'OSTree-rev': 'd2122127d30f94ae12ebe5afa542abdb1870201b0b9750bae3ceb74aa6ed18e6', 'RepoTags': ['centos'], 'Id': u'b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c', 'ImageType': 'system', 'ImageId': u'b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c'}
|
||||
|
||||
|
||||
@unittest.skipIf(no_mock, "Mock not found")
|
||||
class TestInfo(unittest.TestCase):
|
||||
class Args():
|
||||
def __init__(self):
|
||||
self.storage = None
|
||||
self.force = False
|
||||
self.json = False
|
||||
|
||||
def test_docker_info(self):
|
||||
db = DockerBackend()
|
||||
db._inspect_image = MagicMock(return_value=_centos_inspect_image)
|
||||
img_obj = db.inspect_image('docker.io/library/centos:latest')
|
||||
info = Info()
|
||||
args = self.Args()
|
||||
args.storage = 'docker'
|
||||
info.set_args(args)
|
||||
info.beu.get_backend_and_image = MagicMock(return_value=(db, img_obj))
|
||||
result = info.info()
|
||||
self.assertEqual(result, _docker_centos_result)
|
||||
|
||||
def test_ostree_info(self):
|
||||
ob = OSTreeBackend()
|
||||
ob.syscontainers.inspect_system_image = MagicMock(return_value=_centos_ostree_inspect)
|
||||
img_obj = ob.inspect_image('docker.io/library/centos:latest')
|
||||
img_obj._template_variables_set = {'RUN_DIRECTORY': '{SET_BY_OS}', 'STATE_DIRECTORY': '{SET_BY_OS}'}
|
||||
img_obj._template_variables_unset = {}
|
||||
info = Info()
|
||||
args = self.Args()
|
||||
args.storage = 'ostree'
|
||||
info.set_args(args)
|
||||
info.beu.get_backend_and_image = MagicMock(return_value=(ob, img_obj))
|
||||
result = info.info()
|
||||
self.assertEqual(result, _ostree_centos_result)
|
||||
|
||||
|
||||
_docker_images = [{'VirtualSize': 196509652, 'Labels': {'vendor': 'CentOS', 'license': 'GPLv2', 'build-date': '20161102', 'name': 'CentOS Base Image'}, 'RepoTags': ['docker.io/centos:latest'], 'ParentId': '', 'Id': '0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a', 'Size': 196509652, 'Created': 1478116329, 'RepoDigests': ['docker.io/centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c']}, {'VirtualSize': 1093484, 'Labels': {}, 'RepoTags': ['docker.io/busybox:latest'], 'ParentId': '', 'Id': 'e02e811dd08fd49e7f6032625495118e63f597eb150403d02e3238af1df240ba', 'Size': 1093484, 'Created': 1475874238, 'RepoDigests': ['docker.io/busybox@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912']}]
|
||||
_system_images = [{'Labels': {'vendor': 'CentOS', 'license': 'GPLv2', 'build-date': '20161102', 'name': 'CentOS Base Image'}, 'ImageId': 'b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c', 'Version': 'centos:latest', 'OSTree-rev': 'd2122127d30f94ae12ebe5afa542abdb1870201b0b9750bae3ceb74aa6ed18e6', 'ImageType': 'system', 'Id': 'b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c', 'Created': 1480352808, 'Names': [], 'RepoTags': ['centos:latest']}, {'Labels': {}, 'ImageId': '29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912', 'Version': 'busybox:latest', 'OSTree-rev': 'f0cbd09116e348782fc353f99db2b111a59fdf929e9a0180f3a8450c145ed8bc', 'ImageType': 'system', 'Id': '29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912', 'Created': 1480348080, 'Names': [], 'RepoTags': ['busybox:latest']}]
|
||||
|
||||
@unittest.skipIf(no_mock, "Mock not found")
|
||||
class TestImages(unittest.TestCase):
|
||||
class Args():
|
||||
def __init__(self):
|
||||
self.storage = None
|
||||
self.force = False
|
||||
self.json = False
|
||||
self.debug = False
|
||||
self.name = None
|
||||
self.image = None
|
||||
self.all = False
|
||||
|
||||
def test_images(self):
|
||||
db = DockerBackend()
|
||||
db._inspect_image = MagicMock(return_value=_docker_images)
|
||||
ob = OSTreeBackend()
|
||||
ob.syscontainers.get_system_images = MagicMock(return_value=_system_images)
|
||||
images = Images()
|
||||
args = self.Args()
|
||||
args.storage = 'docker'
|
||||
args.json = True
|
||||
images.set_args(args)
|
||||
return_value = images.display_all_image_info()
|
||||
self.assertEqual(return_value, 0)
|
||||
|
||||
Reference in New Issue
Block a user