def _get_scan_list(self): beu = BackendUtils() if self.args.images: scan_list = beu.get_images() elif self.args.containers: scan_list = beu.get_containers() elif self.args.all: scan_list = beu.get_images() + beu.get_containers() else: scan_list = [] for scan_target in self.args.scan_targets: try: # get_backend_and_container throws a ValueError when it cannot find anything _, scan_obj = beu.get_backend_and_container_obj( scan_target) except ValueError: try: # get_backend_and_image throws a ValueError when it cannot find anything _, scan_obj = beu.get_backend_and_image_obj( scan_target) except ValueError: raise ValueError( "Unable to locate the container or image '{}'". format(scan_target)) scan_list.append(scan_obj) return scan_list
def tag_image(self): """ Tag an image with a different name :return: 0 if the tag was created """ if self.args.debug: util.write_out(str(self.args)) beu = BackendUtils() backend = None if self.args.storage: backend = beu.get_backend_from_string(self.args.storage) image = backend.has_image(self.args.src) else: backend, image = beu.get_backend_and_image_obj(self.args.src, required=False) if not backend or not image: raise ValueError("Cannot find image {}.".format(self.args.src)) backend.tag_image(self.args.src, self.args.target) # We need to return something here for dbus return 0
def update(self): if self.args.debug: write_out(str(self.args)) if self.args.all and self.args.image is not None: raise ValueError("Cannot specify both --all and an image name") if self.args.all and self.args.force: raise ValueError("Cannot specify both --all and --force") if self.args.all and self.args.storage is None: raise ValueError("Please specify --storage") beu = BackendUtils() if self.args.all: be = beu.get_backend_from_string(self.args.storage) return self.update_all_images(be, self.args.debug) try: be, img_obj = beu.get_backend_and_image_obj( self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) input_name = img_obj.input_name except ValueError: raise ValueError("{} not found locally. Unable to update".format( self.image)) be.update(input_name, debug=self.args.debug, force=self.args.force, image_object=img_obj) return 0
def delete_image(self): """ 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)) if len(self.args.delete_targets) > 0 and self.args.all: raise ValueError("You must select --all or provide a list of images to delete.") beu = BackendUtils() 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. if self.args.all: delete_objects = beu.get_images(get_all=True) else: for image in self.args.delete_targets: _, img_obj = beu.get_backend_and_image_obj(image, str_preferred_backend=self.args.storage) delete_objects.append(img_obj) if self.args.remote: return self._delete_remote(self.args.delete_targets) _image_names = [] for del_obj in delete_objects: if del_obj.repotags: _image_names.append(len(del_obj.repotags[0])) else: _image_names.append(len(del_obj.id)) max_img_name = max(_image_names) + 2 if not self.args.assumeyes: 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: image = None if not del_obj.repotags else del_obj.repotags[0] if image is None or "<none>" in image: image = del_obj.id[0:12] util.write_out(two_col.format(image, del_obj.backend.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) # Perform the delete for del_obj in delete_objects: del_obj.backend.delete_image(del_obj.input_name, force=self.args.force) # We need to return something here for dbus return
def update(self): if self.args.debug: write_out(str(self.args)) beu = BackendUtils() try: be, img_obj = beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) input_name = img_obj.input_name except ValueError: raise ValueError("{} not found locally. Unable to update".format(self.image)) be.update(input_name, self.args)
def update(self): if self.args.debug: write_out(str(self.args)) beu = BackendUtils() try: be, img_obj = beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) input_name = img_obj.input_name except ValueError: raise ValueError("{} not found locally. Unable to update".format(self.image)) be.update(input_name, debug=self.args.debug, force=self.args.force, image_object=img_obj) return 0
def uninstall(self): if self.args.debug: util.write_out(str(self.args)) beu = BackendUtils() try: be, img_obj = beu.get_backend_and_image_obj(self.args.image, str_preferred_backend=self.args.storage) except ValueError as e: if 'ostree' in [x().backend for x in beu.available_backends]: ost = OSTreeBackend() img_obj = ost.has_container(self.args.image) if not img_obj: raise ValueError(e) be = ost be.uninstall(img_obj, name=self.args.name, atomic=self, ignore=self.args.ignore) return 0
def uninstall(self): if self.args.debug: util.write_out(str(self.args)) beu = BackendUtils() try: be, img_obj = beu.get_backend_and_image_obj( self.args.image, str_preferred_backend=self.args.storage) except ValueError as e: if 'ostree' in [x().backend for x in beu.available_backends]: ost = OSTreeBackend() img_obj = ost.has_container(self.args.image) if not img_obj: raise ValueError(e) be = ost be.uninstall(img_obj, name=self.args.name, atomic=self) return 0
def _get_scan_list(self): beu = BackendUtils() if self.args.images: scan_list = beu.get_images() elif self.args.containers: scan_list = beu.get_containers() elif self.args.all: scan_list = beu.get_images() + beu.get_containers() else: scan_list = [] for scan_target in self.args.scan_targets: try: # get_backend_and_container throws a ValueError when it cannot find anything _, scan_obj = beu.get_backend_and_container_obj(scan_target) except ValueError: try: # get_backend_and_image throws a ValueError when it cannot find anything _, scan_obj = beu.get_backend_and_image_obj(scan_target) except ValueError: raise ValueError("Unable to locate the container or image '{}' locally. Check the " "input name for typos or pull the image first.".format(scan_target)) scan_list.append(scan_obj) return scan_list
class Info(Atomic): def __init__(self): super(Info, self).__init__() self.beu = BackendUtils() def version(self): self._version(util.write_out) def _version(self, write_func): 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}" write_func(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: " write_func(col_out.format(space + layer.repotags[int_img_name], version, iid)) write_func("") def get_layer_objects(self): _, img_obj = self.beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage) return img_obj.layers def dbus_version(self): layer_objects = self.get_layer_objects() versions = [] for layer in layer_objects: versions.append({"Image": layer.repotags, "Version": layer.long_version, "iid": layer.id}) return versions 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. """ 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: # The image is local be, img_obj = self.beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage) 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()
class Verify(Atomic): def __init__(self): super(Verify, self).__init__() self.debug = False self.backend_utils = BackendUtils() def _layers_match(self, local, remote): _match = [] for _layer_int in range(len(local)): if local[_layer_int] == remote[_layer_int]: _match.append(True) else: _match.append(False) return all(_match) def verify(self): if self.args.debug: util.write_out(str(self.args)) local_layers, remote_layers = self._verify() if not self._layers_match(local_layers, remote_layers) or self.args.verbose: col = "{0:30} {1:20} {2:20} {3:1}" util.write_out("\n{} contains the following images:\n".format(self.image)) util.write_out(col.format("NAME", "LOCAL VERSION", "REMOTE VERSION", "DIFFERS")) for layer_int in range(len(local_layers)): differs = 'NO' if remote_layers[layer_int] == local_layers[layer_int] else 'YES' util.write_out(col.format(local_layers[layer_int].name[:30], local_layers[layer_int].long_version[:20], remote_layers[layer_int].long_version[:20], differs)) util.write_out("\n") def verify_dbus(self): local_layers, remote_layers = self._verify() layers = [] for layer_int in range(len(local_layers)): layer = {} layer['name'] = local_layers[layer_int].name layer['local_version'] = local_layers[layer_int].long_version layer['remote_version'] = remote_layers[layer_int].long_version layer['differs'] = False if remote_layers[layer_int] == local_layers[layer_int] else True layers.append(layer) return layers def _verify(self): be, img_obj = self.backend_utils.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) remote_img_name = "{}:latest".format(util.Decompose(img_obj.fq_name).no_tag) remote_img_obj = be.make_remote_image(remote_img_name) return img_obj.layers, remote_img_obj.layers def get_tagged_images(self, names, layers): """ Returns a dict with image names and its tag name. :param names: :param layers: :return: list of sorted dicts (by index) """ base_images = [] for name in names: _match = next((x for x in layers if x['Name'] == name and x['RepoTags'] is not ''), None) registry, repo, image, tag, digest = util.Decompose(self.get_fq_image_name(_match['RepoTags'][0])).all tag = "latest" ri = RegistryInspect(registry=registry, repo=repo, image=image, tag=tag, digest=digest, debug=self.debug) remote_inspect = ri.inspect() release = remote_inspect.get("Labels", None).get("Release", None) version = remote_inspect.get("Labels", None).get("Version", None) if release and version: remote_version = "{}-{}-{}".format(name, version, release) else: # Check if the blob exists on the registry by the ID remote_id = no_shaw(ri.remote_id) _match['Version'] = _match['Id'] remote_version = remote_id if remote_id is not None else "" _match['Remote Version'] = remote_version base_images.append(_match) return sorted(base_images, key=itemgetter('index')) @staticmethod def _mismatch(layer): if layer['Version'] != layer['Remote Version'] and layer['Remote Version'] != layer['Id']: return "Yes" if layer['Version'] == '' and layer['Remote Version'] == '': return "!" return "No" @staticmethod def print_verify(base_images, image, verbose=False): """ Implements a verbose printout of layers. Can be called with atomic verify -v or if we detect some layer does not have versioning information. :param base_images: :param image: :return: None """ def check_for_updates(base_images): for i in base_images: if Verify._mismatch(i) in ['Yes', '!']: return True return False has_updates = check_for_updates(base_images) if has_updates or verbose: col = "{0:30} {1:20} {2:20} {3:1}" util.write_out("\n{} contains the following images:\n".format(image)) util.write_out(col.format("NAME", "LOCAL VERSION", "REMOTE VERSION", "DIFFERS")) for _image in base_images: util.write_out(col.format(_image['Name'][:30], _image['Version'][:20], _image['Remote Version'][:20], Verify._mismatch(_image))) util.write_out("\n") def verify_system_image(self): manifest = self.syscontainers.get_manifest(self.image) name = json.loads(manifest).get('Name', self.image) if manifest: layers = SystemContainers.get_layers_from_manifest(manifest) else: layers = [self.image] if not getattr(self.args,"no_validate", False): self.validate_system_image_manifests(layers) if not manifest: return remote = True try: remote_manifest = self.syscontainers.get_manifest(self.image, remote=True) remote_layers = SystemContainers.get_layers_from_manifest(remote_manifest) except subprocess.CalledProcessError: remote_layers = [] remote = False if hasattr(itertools, 'izip_longest'): zip_longest = getattr(itertools, 'izip_longest') else: zip_longest = getattr(itertools, 'zip_longest') images = [] for local, remote in zip_longest(layers, remote_layers): images.append({'Name': name, 'Version': no_shaw(local), 'Id': no_shaw(local), 'Remote Version': no_shaw(remote), 'remote': remote, 'no_version' : True, 'Repo Tags': self.image, }) self.print_verify(images, self.image, verbose=self.args.verbose) def validate_system_image_manifests(self,layers): """ Validate a system image's layers against the the associated validation manifests created from those image layers on atomic pull. :param layers: list of the names of the layers to validate :return: None """ for layer in layers: mismatches = self.syscontainers.validate_layer(layer) if len(mismatches) > 0: util.write_out("modifications in layer %s layer:\n" % layer) for m in mismatches: util.write_out("file '%s' changed checksum from '%s' to '%s'" % (m["name"], m["old-checksum"], m["new-checksum"])) def validate_image_manifest(self): """ Validates a docker image by mounting the image on a rootfs and validate that rootfs against the manifests that were created. Note that it won't be validated layer by layer. :param: :return: None """ iid = self._is_image(self.image) manifestname = os.path.join(util.ATOMIC_VAR_LIB, "gomtree-manifests/%s.mtree" % iid) if not os.path.exists(manifestname): return tmpdir = tempfile.mkdtemp() m = Mount() m.args = [] m.image = self.image m.mountpoint = tmpdir m.mount() r = util.validate_manifest(manifestname, img_rootfs=tmpdir, keywords="type,uid,gid,mode,size,sha256digest") m.unmount() if r.return_code != 0: util.write_err(r.stdout) shutil.rmtree(tmpdir) @staticmethod def get_gomtree_manifest(layer, root=os.path.join(util.ATOMIC_VAR_LIB, "gomtree-manifests")): manifestpath = os.path.join(root,"%s.mtree" % layer) if os.path.isfile(manifestpath): return manifestpath return None
def update(self): beu = BackendUtils() be, img_obj = beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage) be.update(img_obj.input_name, force=self.args.force)
class Verify(Atomic): def __init__(self): super(Verify, self).__init__() self.debug = False self.backend_utils = BackendUtils() def _layers_match(self, local, remote): _match = [] for _layer_int in range(len(local)): if local[_layer_int] == remote[_layer_int]: _match.append(True) else: _match.append(False) return all(_match) def verify(self): if self.args.image and self.args.all: raise ValueError("Incompatible options specified. --all doesn't support an image name") if not self.args.all and not self.args.image: raise ValueError("Please specify the image name") if self.args.all and not self.args.storage: raise ValueError("Please specify --storage") if self.args.all: be = BackendUtils().get_backend_from_string(self.args.storage) images = be.get_images() for i in images: if i.repotags is None: continue img_name = i.repotags[0] d = util.Decompose(img_name) if d.registry == "": util.write_err("Image {} not fully qualified: skipping".format(img_name)) continue self._verify_one_image(img_name) else: return self._verify_one_image(self.args.image) def _verify_one_image(self, image): if self.args.debug: util.write_out(str(self.args)) be, local_layers, remote_layers = self._verify(image) if not self._layers_match(local_layers, remote_layers) or self.args.verbose: col = "{0:30} {1:20} {2:20} {3:1}" util.write_out("\n{} contains the following images:\n".format(image)) util.write_out(col.format("NAME", "LOCAL VERSION", "REMOTE VERSION", "DIFFERS")) for layer_int in range(len(local_layers)): differs = 'NO' if remote_layers[layer_int] == local_layers[layer_int] else 'YES' util.write_out(col.format(local_layers[layer_int].name[:30], local_layers[layer_int].long_version[:20], remote_layers[layer_int].long_version[:20], differs)) util.write_out("\n") if not self.args.no_validate: be.validate_layer(image) def verify_dbus(self): _, local_layers, remote_layers = self._verify(self.args.image) layers = [] for layer_int in range(len(local_layers)): layer = {} layer['name'] = local_layers[layer_int].name layer['local_version'] = local_layers[layer_int].long_version layer['remote_version'] = remote_layers[layer_int].long_version layer['differs'] = False if remote_layers[layer_int] == local_layers[layer_int] else True layers.append(layer) return layers def _verify(self, image): be, img_obj = self.backend_utils.get_backend_and_image_obj(image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) remote_img_name = "{}:latest".format(util.Decompose(img_obj.fq_name).no_tag) remote_img_obj = be.make_remote_image(remote_img_name) return be, img_obj.layers, remote_img_obj.layers def get_tagged_images(self, names, layers): """ Returns a dict with image names and its tag name. :param names: :param layers: :return: list of sorted dicts (by index) """ base_images = [] for name in names: _match = next((x for x in layers if x['Name'] == name and x['RepoTags'] is not ''), None) registry, repo, image, tag, digest = util.Decompose(self.get_fq_image_name(_match['RepoTags'][0])).all tag = "latest" ri = RegistryInspect(registry=registry, repo=repo, image=image, tag=tag, digest=digest, debug=self.debug) remote_inspect = ri.inspect() release = remote_inspect.get("Labels", None).get("Release", None) version = remote_inspect.get("Labels", None).get("Version", None) if release and version: remote_version = "{}-{}-{}".format(name, version, release) else: # Check if the blob exists on the registry by the ID remote_id = no_shaw(ri.remote_id) _match['Version'] = _match['Id'] remote_version = remote_id if remote_id is not None else "" _match['Remote Version'] = remote_version base_images.append(_match) return sorted(base_images, key=itemgetter('index')) @staticmethod def _mismatch(layer): if layer['Version'] != layer['Remote Version'] and layer['Remote Version'] != layer['Id']: return "Yes" if layer['Version'] == '' and layer['Remote Version'] == '': return "!" return "No" @staticmethod def print_verify(base_images, image, verbose=False): """ Implements a verbose printout of layers. Can be called with atomic verify -v or if we detect some layer does not have versioning information. :param base_images: :param image: :return: None """ def check_for_updates(base_images): for i in base_images: if Verify._mismatch(i) in ['Yes', '!']: return True return False has_updates = check_for_updates(base_images) if has_updates or verbose: col = "{0:30} {1:20} {2:20} {3:1}" util.write_out("\n{} contains the following images:\n".format(image)) util.write_out(col.format("NAME", "LOCAL VERSION", "REMOTE VERSION", "DIFFERS")) for _image in base_images: util.write_out(col.format(_image['Name'][:30], _image['Version'][:20], _image['Remote Version'][:20], Verify._mismatch(_image))) util.write_out("\n")
class Info(Atomic): def __init__(self): super(Info, self).__init__() self.beu = BackendUtils() def version(self): self._version(util.write_out) def _version(self, write_func): 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}" write_func(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: " write_func( col_out.format(space + layer.repotags[int_img_name], version, iid)) write_func("") def get_layer_objects(self): _, img_obj = self.beu.get_backend_and_image_obj( self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) return img_obj.layers def dbus_version(self): layer_objects = self.get_layer_objects() versions = [] for layer in layer_objects: versions.append({ "Image": layer.repotags, "Version": layer.long_version, "iid": layer.id }) return versions 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. """ 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( str_backend=self.args.storage or storage) img_obj = be.make_remote_image(self.image) else: # The image is local be, img_obj = self.beu.get_backend_and_image_obj( self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) 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)) # pylint: disable=no-member if img_obj.labels: buf.writelines( sorted([ "{}: {}\n".format(k, v) for k, v in list(img_obj.labels.items()) ])) # pylint: disable=no-member if img_obj.template_variables_set: buf.write( "\n\nTemplate variables with default value, but overridable with --set:\n" ) # pylint: disable=no-member buf.writelines([ "{}: {}\n".format(k, v) for k, v in # pylint: disable=no-member 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" ) # pylint: disable=no-member buf.writelines([ "{}: {}\n".format(k, v) for k, v in # pylint: disable=no-member list(sorted(img_obj.template_variables_unset.items())) ]) return buf.getvalue() # pylint: disable=no-member
class Verify(Atomic): def __init__(self): super(Verify, self).__init__() self.debug = False self.backend_utils = BackendUtils() def _layers_match(self, local, remote): _match = [] for _layer_int in range(len(local)): if local[_layer_int] == remote[_layer_int]: _match.append(True) else: _match.append(False) return all(_match) def verify(self): if self.args.debug: util.write_out(str(self.args)) local_layers, remote_layers = self._verify() if not self._layers_match(local_layers, remote_layers) or self.args.verbose: col = "{0:30} {1:20} {2:20} {3:1}" util.write_out("\n{} contains the following images:\n".format(self.image)) util.write_out(col.format("NAME", "LOCAL VERSION", "REMOTE VERSION", "DIFFERS")) for layer_int in range(len(local_layers)): differs = 'NO' if remote_layers[layer_int] == local_layers[layer_int] else 'YES' util.write_out(col.format(local_layers[layer_int].name[:30], local_layers[layer_int].long_version[:20], remote_layers[layer_int].long_version[:20], differs)) util.write_out("\n") def verify_dbus(self): local_layers, remote_layers = self._verify() layers = [] for layer_int in range(len(local_layers)): layer = {} layer['name'] = local_layers[layer_int].name layer['local_version'] = local_layers[layer_int].long_version layer['remote_version'] = remote_layers[layer_int].long_version layer['differs'] = False if remote_layers[layer_int] == local_layers[layer_int] else True layers.append(layer) return layers def _verify(self): be, img_obj = self.backend_utils.get_backend_and_image_obj(self.image, self.args.storage) remote_img_name = "{}:latest".format(util.Decompose(img_obj.fq_name).no_tag) remote_img_obj = be.make_remote_image(remote_img_name) return img_obj.layers, remote_img_obj.layers def get_tagged_images(self, names, layers): """ Returns a dict with image names and its tag name. :param names: :param layers: :return: list of sorted dicts (by index) """ base_images = [] for name in names: _match = next((x for x in layers if x['Name'] == name and x['RepoTags'] is not ''), None) registry, repo, image, tag, _ = util.Decompose(self.get_fq_image_name(_match['RepoTags'][0])).all tag = "latest" ri = RegistryInspect(registry=registry, repo=repo, image=image, tag=tag, debug=self.debug) ri.ping() remote_inspect = ri.inspect() release = remote_inspect.get("Labels", None).get("Release", None) version = remote_inspect.get("Labels", None).get("Version", None) if release and version: remote_version = "{}-{}-{}".format(name, version, release) else: # Check if the blob exists on the registry by the ID remote_id = no_shaw(ri.rc.manifest_json.get("config", None).get("digest", None)) _match['Version'] = _match['Id'] remote_version = remote_id if remote_id is not None else "" _match['Remote Version'] = remote_version base_images.append(_match) return sorted(base_images, key=itemgetter('index')) @staticmethod def _mismatch(layer): if layer['Version'] != layer['Remote Version'] and layer['Remote Version'] != layer['Id']: return "Yes" if layer['Version'] == '' and layer['Remote Version'] == '': return "!" return "No" @staticmethod def print_verify(base_images, image, verbose=False): """ Implements a verbose printout of layers. Can be called with atomic verify -v or if we detect some layer does not have versioning information. :param base_images: :param image: :return: None """ def check_for_updates(base_images): for i in base_images: if Verify._mismatch(i) in ['Yes', '!']: return True return False has_updates = check_for_updates(base_images) if has_updates or verbose: col = "{0:30} {1:20} {2:20} {3:1}" util.write_out("\n{} contains the following images:\n".format(image)) util.write_out(col.format("NAME", "LOCAL VERSION", "REMOTE VERSION", "DIFFERS")) for _image in base_images: util.write_out(col.format(_image['Name'][:30], _image['Version'][:20], _image['Remote Version'][:20], Verify._mismatch(_image))) util.write_out("\n") def verify_system_image(self): manifest = self.syscontainers.get_manifest(self.image) name = json.loads(manifest).get('Name', self.image) if manifest: layers = SystemContainers.get_layers_from_manifest(manifest) else: layers = [self.image] if not getattr(self.args,"no_validate", False): self.validate_system_image_manifests(layers) if not manifest: return remote = True try: remote_manifest = self.syscontainers.get_manifest(self.image, remote=True) remote_layers = SystemContainers.get_layers_from_manifest(remote_manifest) except subprocess.CalledProcessError: remote_layers = [] remote = False if hasattr(itertools, 'izip_longest'): zip_longest = getattr(itertools, 'izip_longest') else: zip_longest = getattr(itertools, 'zip_longest') images = [] for local, remote in zip_longest(layers, remote_layers): images.append({'Name': name, 'Version': no_shaw(local), 'Id': no_shaw(local), 'Remote Version': no_shaw(remote), 'remote': remote, 'no_version' : True, 'Repo Tags': self.image, }) self.print_verify(images, self.image, verbose=self.args.verbose) def validate_system_image_manifests(self,layers): """ Validate a system image's layers against the the associated validation manifests created from those image layers on atomic pull. :param layers: list of the names of the layers to validate :return: None """ for layer in layers: mismatches = self.syscontainers.validate_layer(layer) if len(mismatches) > 0: util.write_out("modifications in layer %s layer:\n" % layer) for m in mismatches: util.write_out("file '%s' changed checksum from '%s' to '%s'" % (m["name"], m["old-checksum"], m["new-checksum"])) def validate_image_manifest(self): """ Validates a docker image by mounting the image on a rootfs and validate that rootfs against the manifests that were created. Note that it won't be validated layer by layer. :param: :return: None """ iid = self._is_image(self.image) manifestname = os.path.join(util.ATOMIC_VAR_LIB, "gomtree-manifests/%s.mtree" % iid) if not os.path.exists(manifestname): return tmpdir = tempfile.mkdtemp() m = Mount() m.args = [] m.image = self.image m.mountpoint = tmpdir m.mount() r = util.validate_manifest(manifestname, img_rootfs=tmpdir, keywords="type,uid,gid,mode,size,sha256digest") m.unmount() if r.return_code != 0: util.write_err(r.stdout) shutil.rmtree(tmpdir) @staticmethod def get_gomtree_manifest(layer, root=os.path.join(util.ATOMIC_VAR_LIB, "gomtree-manifests")): manifestpath = os.path.join(root,"%s.mtree" % layer) if os.path.isfile(manifestpath): return manifestpath return None
def delete_image(self): """ 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)) if len(self.args.delete_targets) > 0 and self.args.all: raise ValueError( "You must select --all or provide a list of images to delete.") beu = BackendUtils() 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. if self.args.all: delete_objects = beu.get_images(get_all=True) else: for image in self.args.delete_targets: _, img_obj = beu.get_backend_and_image_obj( image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) delete_objects.append(img_obj) if self.args.remote: return self._delete_remote(self.args.delete_targets) _image_names = [] for del_obj in delete_objects: if del_obj.repotags: _image_names.append(len(del_obj.repotags[0])) else: _image_names.append(len(del_obj.id)) max_img_name = max(_image_names) + 2 if not self.args.assumeyes: 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: image = None if not del_obj.repotags else del_obj.repotags[0] if image is None or "<none>" in image: image = del_obj.id[0:12] util.write_out(two_col.format(image, del_obj.backend.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) # Perform the delete for del_obj in delete_objects: del_obj.backend.delete_image(del_obj.input_name, force=self.args.force) # We need to return something here for dbus return