def test_run_cmd(): ret = run_cmd( ["sh", "-c", "for x in `seq 1 5`; do echo $x; sleep 0.01; done"]) assert not ret ret = run_cmd( ["sh", "-c", "for x in `seq 1 5`; do echo $x; sleep 0.01; done"], return_output=True) assert ret == '1\n2\n3\n4\n5\n' with pytest.raises(subprocess.CalledProcessError) as excinfo: ret = run_cmd(["sh", "-c", "exit 5"]) assert not ret assert excinfo.value.returncode == 5 ret = run_cmd(["sh", "-c", "exit 5"], ignore_status=True) assert ret == 5
def __enter__(self): # TODO RFE: use libguestfs if possible # TODO: allow pass partition number to mount exact partition of disc self.loopdevice = run_cmd( ["losetup", "--show", "-f", self.image.local_location], return_output=True).strip() run_cmd(["partprobe", self.loopdevice]) partitions = glob.glob("{}*".format(self.loopdevice)) for part in partitions: try: run_cmd(["mount", part, self.mount_point]) return super(NspawnImageFS, self).__enter__() except Exception as e: logger.debug( ConuException( "unable to mount partition {}".format(part), e))
def machined_restart(): """ Workaround for systemd when machined is blocked on D-bus :return: int: return code """ logger.debug("restart systemd-machined") return run_cmd("systemctl restart systemd-machined", ignore_status=True)
def rmi(self, force=False, via_name=False): """ remove this image :param force: bool, force removal of the image :param via_name: bool, refer to the image via name, if false, refer via ID, not used now :return: None """ return run_cmd(["machinectl", "--no-pager", "remove", self.get_id()])
def mount(self, mount_point=None): """ mount container filesystem :return: str, the location of the mounted file system """ cmd = ["podman", "mount", self._id or self.get_id()] output = run_cmd(cmd, return_output=True).rstrip("\n\r") return output
def _list_all_podman_images(): """ Finds all podman containers :return: list of dicts with image info """ cmdline = ["podman", "images", "--format", "json"] output = run_cmd(cmdline, return_output=True) images = json.loads(output) return images
def extend(self, source, new_image_name, s2i_args=None): """ extend this s2i-enabled image using provided source, raises ConuException if `s2i build` fails :param source: str, source used to extend the image, can be path or url :param new_image_name: str, name of the new, extended image :param s2i_args: list of str, additional options and arguments provided to `s2i build` :return: S2Image instance """ s2i_args = s2i_args or [] c = self._s2i_command(["build"] + s2i_args + [source, self.get_full_name()]) if new_image_name: c.append(new_image_name) try: run_cmd(c) except subprocess.CalledProcessError as ex: raise ConuException("s2i build failed: %s" % ex) return S2IDockerImage(new_image_name)
def _list_podman_containers(filter=None): """ Finds podman containers by filter or all containers :return: list of dicts with containers info """ option = ["--filter", filter] if filter else ["-a"] cmdline = ["podman", "ps"] + option + ["--format", "json"] output = run_cmd(cmdline, return_output=True) containers = json.loads(output) return containers
def mount(self, mount_point=None): """ mount container filesystem :return: str, the location of the mounted file system """ # TODO: In rootless mode you must use buildah unshare first. cmd = ["buildah", "mount", self._id or self.get_id()] output = run_cmd(cmd, return_output=True).rstrip("\n\r") return output
def copy(self, repository=None, tag=None, source_transport=None, target_transport=SkopeoTransport.DOCKER, source_path=None, target_path=None, logs=True): """ Copy this image :param repository to be copied to :param tag :param source_transport Transport :param target_transport Transport :param source_path needed to specify for dir, docker-archive or oci transport :param target_path needed to specify for dir, docker-archive or oci transport :param logs enable/disable logs :return: the new DockerImage """ if not repository: repository = self.name if not tag: tag = self.tag if self.tag else "latest" if target_transport == SkopeoTransport.OSTREE and tag and logs: logging.warning("tag was ignored") target = (DockerImage( repository, tag, pull_policy=DockerImagePullPolicy.NEVER).using_transport( target_transport, target_path)) self.using_transport(source_transport, source_path) try: run_cmd([ "skopeo", "copy", transport_param(self), transport_param(target) ]) except subprocess.CalledProcessError: raise ConuException("There was an error while copying repository", self.name) return target
def wait(self, timeout=None): """ Block until the container stops, then return its exit code. Similar to the ``podman wait`` command. :param timeout: int, microseconds to wait before polling for completion :return: int, exit code """ timeout = ["--interval=%s" % timeout] if timeout else [] cmdline = ["podman", "wait"] + timeout + [self._id or self.get_id()] return run_cmd(cmdline, return_output=True)
def execute(self, command): """ Execute a command in this container -- the container needs to be running. :param command: list of str, command to execute in the container :return: str """ logger.info("running command %s", command) cmd = ["podman", "exec", self.get_id()] + command output = run_cmd(cmd, return_output=True) return output
def get_layer_ids(self, rev=True): """ Get IDs of image layers :param rev: get layers reversed :return: list of strings """ cmdline = ["podman", "history", "--format", "{{.ID}}", self._id or self.get_id()] layers = [layer for layer in run_cmd(cmdline, return_output=True)] if not rev: layers = layers.reverse() return layers
def _systemctl_wait_until_finish(self, machine, unit): """ Internal method workaround for systemd-run without --wait option see _run_systemdrun_decide method :param machine: :param unit: :return: """ while True: metadata = convert_kv_to_dict( run_cmd( ["systemctl", "--no-pager", "show", "-M", machine, unit], return_output=True)) if not metadata["SubState"] in ["exited", "failed"]: time.sleep(0.1) else: break run_cmd(["systemctl", "--no-pager", "-M", machine, "stop", unit], ignore_status=True) return metadata["ExecMainStatus"]
def _run_systemdrun_decide(self): """ Internal method decide if it is possible to use --wait option to systemd for example RHEL7 does not support --wait option :return: bool """ if self.systemd_wait_support is None: self.systemd_wait_support = "--wait" in run_cmd( ["systemd-run", "--help"], return_output=True) return self.systemd_wait_support
def _set_selinux_context(self): """ set SELinux context or fields using chcon program :return: None """ # FIXME: do this using python API if possible if self.selinux_context: logger.debug("setting SELinux context of %s to %s", self.path, self.selinux_context) run_cmd(["chcon", self.selinux_context, self.path]) if any([self.selinux_user, self.selinux_role, self.selinux_type, self.selinux_range]): logger.debug("setting SELinux fields of %s", self.path, self.selinux_context) # chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... pairs = [("-u", self.selinux_user), ("-r", self.selinux_role), ("-l", self.selinux_range), ("-t", self.selinux_type)] c = ["chcon"] for p in pairs: if p[1]: c += p c += [self.path] run_cmd(c)
def execute(self, command, options=None, **kwargs): """ Execute a command in this container :param command: list of str, command to execute in the container :param options: list of str, additional options to run command :return: str """ options = options or [] logger.info("running command %s", command) cmd = ["buildah", "run"] + options + [self.get_id()] + command output = run_cmd(cmd, return_output=True) return output
def pull(self): """ Pull this image from URL. :return: None """ if not os.path.exists(CONU_IMAGES_STORE): os.makedirs(CONU_IMAGES_STORE) logger.debug( "Try to pull: {} -> {}".format(self.location, self.local_location)) if not self._is_local(): compressed_location = self.local_location + ".xz" run_cmd(["curl", "-f", "-L", "-o", compressed_location, self.location]) run_cmd(["xz", "-d", compressed_location]) else: if self.location.endswith("xz"): compressed_location = self.local_location + ".xz" run_cmd(["cp", self.location, compressed_location]) run_cmd(["xz", "-d", compressed_location]) else: run_cmd(["cp", self.location, self.local_location])
def get_status(self): """ Get status of OpenShift cluster, similar to `oc status` :return: str """ try: c = self._oc_command(["status"]) o = run_cmd(c, return_output=True) for line in o.split('\n'): logger.debug(line) return o except subprocess.CalledProcessError as ex: raise ConuException("Cannot obtain OpenShift cluster status: %s" % ex)
def get_version(self): """ return 3-tuple of version info or None :return: (str, str, str) """ raw_version = run_cmd(["buildah", "version"], return_output=True) regex = re.compile(r"Version:\s*(\d+)\.(\d+)\.(\d+)") match = regex.findall(raw_version) try: return match[0] except IndexError: logger.error("unable to parse version from `buildah version`") return
def umount(self, all=False): """ unmount container filesystem :param all: bool, option to unmount all mounted containers :return: str, the output from cmd """ options = [] if all: options.append('--all') cmd = ["buildah", "umount"] + options if not all: cmd += [self.get_id()] return run_cmd(cmd, return_output=True)
def create_app_from_template(self, image_name, name, template, name_in_template, other_images=None, oc_new_app_args=None, project=None): """Helper function to create app from template :param image_name: image to be used as builder image :param name: name of app from template :param template: str, url or local path to a template to use :param name_in_template: dict, {repository:tag} image name used in the template :param other_images: list of dict, some templates need other image to be pushed into the OpenShift registry, specify them in this parameter as list of dict [{<image>:<tag>}], where "<image>" is image name with tag and "<tag>" is a tag under which the image should be available in the OpenShift registry. :param oc_new_app_args: additional parameters for the `oc new-app` :param project: project where app should be created, default: current project :return: None """ self.project = project or self.get_current_project() oc_new_app_args = oc_new_app_args or [] # push images to registry repository, tag = list(name_in_template.items())[0] self.import_image(repository + ":" + tag, image_name) other_images = other_images or [] for o in other_images: image, tag = list(o.items())[0] self.import_image( tag.split(':')[0] + ":" + tag.split(':')[1], image) c = self._oc_command(["new-app"] + [template] + oc_new_app_args + ["-n"] + [project] + ["--name=%s" % name]) logger.info("Creating new app in project %s", project) try: # ignore status because sometimes oc new-app can fail when image # is already pushed in register o = run_cmd(c, return_output=True, ignore_status=True) logger.debug(o) except subprocess.CalledProcessError as ex: raise ConuException("oc new-app failed: %s" % ex) return name
def clean_project(self, app_name): """ Delete all objects in current project in OpenShift cluster :return: None """ logger.info('Deleting app') try: o = run_cmd(self._oc_command( ["delete", "all", "-l app=%s" % app_name]), return_output=True) o_lines = o.split('\n') for line in o_lines: logger.info(line) except subprocess.CalledProcessError as ex: raise ConuException("Cleanup failed: %s" % ex)
def umount(self, all=False, force=True): """ unmount container filesystem :param all: bool, option to unmount all mounted containers :param force: bool, force the unmounting of specified containers' root file system :return: str, the output from cmd """ # FIXME: handle error if unmount didn't work options = [] if force: options.append('--force') if all: options.append('--all') cmd = ["podman", "umount" ] + options + [self.get_id() if not all else ""] return run_cmd(cmd, return_output=True)
def _wait_for_machine_finish(self, name): """ Interna method wait until machine is really destroyed, machine does not exist. :param name: str machine name :return: True or exception """ # TODO: rewrite it using probes module in utils for foo in range(constants.DEFAULT_RETRYTIMEOUT): time.sleep(constants.DEFAULT_SLEEP) out = run_cmd(["machinectl", "--no-pager", "status", name], ignore_status=True, return_output=True) if out != 0: return True raise ConuException("Unable to stop machine %s within %d" % (name, constants.DEFAULT_RETRYTIMEOUT))
def list_containers(self): """ list all available nspawn containers :return: collection of instances of :class:`conu.backend.nspawn.container.NspawnContainer` """ data = run_cmd(["machinectl", "list", "--no-legend", "--no-pager"], return_output=True) output = [] reg = re.compile(r"\s+") for line in data.split("\n"): stripped = line.strip() if stripped: parts = reg.split(stripped) name = parts[0] output.append(self.ContainerClass(None, None, name=name)) return output
def get_current_project(self): """ Get name of current project using `oc project` command. Raise ConuException in case of an error. :return: str, project name """ try: command = self._oc_command(["project", "-q"]) output = run_cmd(command, return_output=True) except subprocess.CalledProcessError as ex: raise ConuException("Failed to obtain current project name : %s" % ex) try: return output.rstrip() # remove '\n' except IndexError: raise ConuException("Failed to obtain project name")
def _list_all_buildah_images(): """ List all buildah images sample image: "id": "9754ce14641df7f1f3751d21e14b3037dce7dca2472cf4cdff38d96891703453", "names": [ "docker.io/library/fedora:30" ] :return: list of dicts with image info """ cmdline = ["buildah", "images", "--json"] output = run_cmd(cmdline, return_output=True) images = json.loads(output) if not images: return [] return images
def get_metadata(self, refresh=True): """ return cached metadata by default :param refresh: bool, returns up to date metadata if set to True :return: dict """ if refresh or not self._metadata: ident = self._id or self.name if not ident: raise ConuException( "This container does not have a valid identifier.") out = run_cmd(["machinectl", "--no-pager", "show", ident], return_output=True, ignore_status=True) if "Could not get path to machine" in out: self._metadata = {} else: self._metadata = convert_kv_to_dict(out) return self._metadata
def system_requirements(): """ Check if all necessary packages are installed on system :return: None or raise exception if some tooling is missing """ command_exists("systemd-nspawn", ["systemd-nspawn", "--version"], "Command systemd-nspawn does not seems to be present on your system" "Do you have system with systemd") command_exists( "machinectl", ["machinectl", "--no-pager", "--help"], "Command machinectl does not seems to be present on your system" "Do you have system with systemd") if "Enforcing" in run_cmd(["getenforce"], return_output=True, ignore_status=True): logger.error("Please disable selinux (setenforce 0), selinux blocks some nspawn operations" "This may lead to strange behaviour")