def remove(module, details=False): ''' Attempt to remove a Perl module that was installed from CPAN. Because the ``cpan`` command doesn't actually support "uninstall"-like functionality, this function will attempt to do what it can, with what it has from CPAN. Until this function is declared stable, USE AT YOUR OWN RISK! CLI Example: .. code-block:: bash salt '*' cpan.remove Old::Package ''' ret = { 'old': None, 'new': None, } info = show(module) if 'error' in info: return {'error': info['error']} version = info.get('installed version', None) if version is None: return ret ret['old'] = version if 'cpan build dirs' not in info: return {'error': 'No CPAN data available to use for uninstalling'} mod_pathfile = module.replace('::', '/') + '.pm' ins_path = info['installed file'].replace(mod_pathfile, '') files = [] for build_dir in info['cpan build dirs']: contents = os.listdir(build_dir) if 'MANIFEST' not in contents: continue mfile = os.path.join(build_dir, 'MANIFEST') with salt.utils.files.fopen(mfile, 'r') as fh_: for line in fh_.readlines(): if line.startswith('lib/'): files.append(line.replace('lib/', ins_path).strip()) rm_details = {} for file_ in files: if file_ in rm_details: continue log.trace('Removing {0}'.format(file_)) if __salt__['file.remove'](file_): rm_details[file_] = 'removed' else: rm_details[file_] = 'unable to remove' if details: ret['details'] = rm_details return ret
def _get_all_files(self, path, *exclude): """ Walk implementation. Version in python 2.x and 3.x works differently. """ files = list() dirs = list() links = list() if os.access(path, os.R_OK): for obj in os.listdir(path): obj = os.path.join(path, obj) valid = True for ex_obj in exclude: if obj.startswith(str(ex_obj)): valid = False continue if not valid or not os.path.exists(obj) or not os.access(obj, os.R_OK): continue if salt.utils.path.islink(obj): links.append(obj) elif os.path.isdir(obj): dirs.append(obj) f_obj, d_obj, l_obj = self._get_all_files(obj, *exclude) files.extend(f_obj) dirs.extend(d_obj) links.extend(l_obj) elif os.path.isfile(obj): files.append(obj) return sorted(files), sorted(dirs), sorted(links)
def _trim_files(files, trim_output): # Trim the file list for output count = 100 if not isinstance(trim_output, bool): count = trim_output if not(isinstance(trim_output, bool) and trim_output is False) and len(files) > count: files = files[:count] files.append("List trimmed after {0} files.".format(count)) return files
def _trim_files(files, trim_output): ''' Trim the file list for output. ''' count = 100 if not isinstance(trim_output, bool): count = trim_output if not(isinstance(trim_output, bool) and trim_output is False) and len(files) > count: files = files[:count] files.append("List trimmed after {0} files.".format(count)) return files
def file_list(*packages): ''' List the files that belong to a package. Not specifying any packages will return a list of _every_ file on the system's package database (not generally recommended). CLI Examples: .. code-block:: bash salt '*' lowpkg.file_list httpd salt '*' lowpkg.file_list httpd postfix salt '*' lowpkg.file_list ''' errors = [] ret = set([]) pkgs = {} cmd = 'dpkg -l {0}'.format(' '.join(packages)) out = __salt__['cmd.run_all'](cmd, python_shell=False) if out['retcode'] != 0: msg = 'Error: ' + out['stderr'] log.error(msg) return msg out = out['stdout'] for line in out.splitlines(): if line.startswith('ii '): comps = line.split() pkgs[comps[1]] = { 'version': comps[2], 'description': ' '.join(comps[3:]) } if 'No packages found' in line: errors.append(line) for pkg in pkgs: files = [] cmd = 'dpkg -L {0}'.format(pkg) for line in __salt__['cmd.run'](cmd, python_shell=False).splitlines(): files.append(line) fileset = set(files) ret = ret.union(fileset) return {'errors': errors, 'files': list(ret)}
def _list_zip(name, cached): ''' List the contents of a zip archive. Password-protected ZIP archives can still be listed by zipfile, so there is no reason to invoke the unzip command. ''' dirs = set() files = [] links = [] try: with contextlib.closing(zipfile.ZipFile(cached)) as zip_archive: for member in zip_archive.infolist(): path = member.filename if salt.utils.platform.is_windows(): if path.endswith('/'): # zipfile.ZipInfo objects on windows use forward # slash at end of the directory name. dirs.add(path) else: files.append(path) else: mode = member.external_attr >> 16 if stat.S_ISLNK(mode): links.append(path) elif stat.S_ISDIR(mode): dirs.add(path) else: files.append(path) _files = copy.deepcopy(files) for path in _files: # ZIP files created on Windows do not add entries # to the archive for directories. So, we'll need to # manually add them. dirname = ''.join(path.rpartition('/')[:2]) if dirname: dirs.add(dirname) if dirname in files: files.remove(dirname) return list(dirs), files, links except zipfile.BadZipfile: raise CommandExecutionError('{0} is not a ZIP file'.format(name))
def file_dict(*packages, **kwargs): # pylint: disable=unused-argument """ List the files that belong to a package, grouped by package. Not specifying any packages will return a list of _every_ file on the system's package database (not generally recommended). CLI Examples: .. code-block:: bash salt '*' pkg.file_list httpd salt '*' pkg.file_list httpd postfix salt '*' pkg.file_list """ errors = [] ret = {} cmd_files = ["opkg", "files"] if not packages: packages = list(list_pkgs().keys()) for package in packages: files = [] cmd = cmd_files[:] cmd.append(package) out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False) for line in out["stdout"].splitlines(): if line.startswith("/"): files.append(line) elif line.startswith(" * "): errors.append(line[3:]) break else: continue if files: ret[package] = files return {"errors": errors, "packages": ret}
def file_dict(*packages): ''' List the files that belong to a package, grouped by package. Not specifying any packages will return a list of _every_ file on the system's package database (not generally recommended). CLI Examples: .. code-block:: bash salt '*' pkg.file_list httpd salt '*' pkg.file_list httpd postfix salt '*' pkg.file_list ''' errors = [] ret = {} cmd_files = ['opkg', 'files'] if not packages: packages = list(list_pkgs().keys()) for package in packages: files = [] cmd = cmd_files[:] cmd.append(package) out = __salt__['cmd.run_all'](cmd, output_loglevel='trace', python_shell=False) for line in out['stdout'].splitlines(): if line.startswith('/'): files.append(line) elif line.startswith(' * '): errors.append(line[3:]) break else: continue if files: ret[package] = files return {'errors': errors, 'packages': ret}
def file_list(*packages): """ List the files that belong to a package. Not specifying any packages will return a list of _every_ file on the system's package database (not generally recommended). CLI Examples: .. code-block:: bash salt '*' lowpkg.file_list httpd salt '*' lowpkg.file_list httpd postfix salt '*' lowpkg.file_list """ errors = [] ret = set([]) pkgs = {} cmd = "dpkg -l {0}".format(" ".join(packages)) out = __salt__["cmd.run_all"](cmd, python_shell=False) if out["retcode"] != 0: msg = "Error: " + out["stderr"] log.error(msg) return msg out = out["stdout"] for line in out.splitlines(): if line.startswith("ii "): comps = line.split() pkgs[comps[1]] = {"version": comps[2], "description": " ".join(comps[3:])} if "No packages found" in line: errors.append(line) for pkg in pkgs: files = [] cmd = "dpkg -L {0}".format(pkg) for line in __salt__["cmd.run"](cmd, python_shell=False).splitlines(): files.append(line) fileset = set(files) ret = ret.union(fileset) return {"errors": errors, "files": list(ret)}
def _list_rar(name, cached): ''' List the contents of a rar archive. ''' dirs = [] files = [] if HAS_RARFILE: with rarfile.RarFile(cached) as rf: for member in rf.infolist(): path = member.filename.replace('\\', '/') if member.isdir(): dirs.append(path + '/') else: files.append(path) else: if not salt.utils.which('rar'): raise CommandExecutionError( 'rar command not available, is it installed?' ) output = __salt__['cmd.run']( ['rar', 'lt', name], python_shell=False, ignore_retcode=False) matches = re.findall(r'Name:\s*([^\n]+)\s*Type:\s*([^\n]+)', output) for path, type_ in matches: if type_ == 'Directory': dirs.append(path + '/') else: files.append(path) if not dirs and not files: raise CommandExecutionError( 'Failed to list {0}, is it a rar file? If so, the ' 'installed version of rar may be too old to list data in ' 'a parsable format. Installing the rarfile Python module ' 'may be an easier workaround if newer rar is not readily ' 'available.'.format(name), info={'error': output} ) return dirs, files, []
def _list_rar(name, cached): """ List the contents of a rar archive. """ dirs = [] files = [] if HAS_RARFILE: with rarfile.RarFile(cached) as rf: for member in rf.infolist(): path = member.filename.replace("\\", "/") if member.isdir(): dirs.append(path + "/") else: files.append(path) else: if not salt.utils.path.which("rar"): raise CommandExecutionError( "rar command not available, is it installed?" ) output = __salt__["cmd.run"]( ["rar", "lt", name], python_shell=False, ignore_retcode=False ) matches = re.findall(r"Name:\s*([^\n]+)\s*Type:\s*([^\n]+)", output) for path, type_ in matches: if type_ == "Directory": dirs.append(path + "/") else: files.append(path) if not dirs and not files: raise CommandExecutionError( "Failed to list {}, is it a rar file? If so, the " "installed version of rar may be too old to list data in " "a parsable format. Installing the rarfile Python module " "may be an easier workaround if newer rar is not readily " "available.".format(name), info={"error": output}, ) return dirs, files, []
def _list_zip(name, cached): ''' List the contents of a zip archive. Password-protected ZIP archives can still be listed by zipfile, so there is no reason to invoke the unzip command. ''' dirs = [] files = [] links = [] try: with contextlib.closing(zipfile.ZipFile(cached)) as zip_archive: for member in zip_archive.infolist(): mode = member.external_attr >> 16 path = member.filename if stat.S_ISLNK(mode): links.append(path) elif stat.S_ISDIR(mode): dirs.append(path) else: files.append(path) return dirs, files, links except zipfile.BadZipfile: raise CommandExecutionError('{0} is not a ZIP file'.format(name))
def _list_tar(name, cached, decompress_cmd, failhard=False): """ List the contents of a tar archive. """ dirs = [] files = [] links = [] try: open_kwargs = ( {"name": cached} if not isinstance(cached, subprocess.Popen) else {"fileobj": cached.stdout, "mode": "r|"} ) with contextlib.closing(tarfile.open(**open_kwargs)) as tar_archive: for member in tar_archive.getmembers(): _member = salt.utils.data.decode(member.name) if member.issym(): links.append(_member) elif member.isdir(): dirs.append(_member + "/") else: files.append(_member) return dirs, files, links except tarfile.ReadError: if failhard: if isinstance(cached, subprocess.Popen): stderr = cached.communicate()[1] if cached.returncode != 0: raise CommandExecutionError( "Failed to decompress {}".format(name), info={"error": stderr}, ) else: if not salt.utils.path.which("tar"): raise CommandExecutionError("'tar' command not available") if decompress_cmd is not None and isinstance(decompress_cmd, str): # Guard against shell injection try: decompress_cmd = [ shlex.quote(x) for x in shlex.split(decompress_cmd) ] except AttributeError: raise CommandExecutionError("Invalid CLI options") else: if ( salt.utils.path.which("xz") and __salt__["cmd.retcode"]( ["xz", "-t", cached], python_shell=False, ignore_retcode=True, ) == 0 ): decompress_cmd = ["xz", "--decompress", "--stdout"] if decompress_cmd: decompressed = subprocess.Popen( decompress_cmd + [shlex.quote(cached)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) return _list_tar(name, decompressed, None, True) raise CommandExecutionError( "Unable to list contents of {}. If this is an XZ-compressed tar " "archive, install XZ Utils to enable listing its contents. If it " "is compressed using something other than XZ, it may be necessary " "to specify CLI options to decompress the archive. See the " "documentation for details.".format(name) )
def _list_tar(name, cached, decompress_cmd, failhard=False): ''' List the contents of a tar archive. ''' dirs = [] files = [] links = [] try: open_kwargs = {'name': cached} \ if not isinstance(cached, subprocess.Popen) \ else {'fileobj': cached.stdout, 'mode': 'r|'} with contextlib.closing( tarfile.open(**open_kwargs)) as tar_archive: for member in tar_archive.getmembers(): if member.issym(): links.append(member.name) elif member.isdir(): dirs.append(member.name + '/') else: files.append(member.name) return dirs, files, links except tarfile.ReadError: if failhard: if isinstance(cached, subprocess.Popen): stderr = cached.communicate()[1] if cached.returncode != 0: raise CommandExecutionError( 'Failed to decompress {0}'.format(name), info={'error': stderr}) else: if not salt.utils.path.which('tar'): raise CommandExecutionError( '\'tar\' command not available') if decompress_cmd is not None: # Guard against shell injection try: decompress_cmd = ' '.join( [_quote(x) for x in shlex.split(decompress_cmd)]) except AttributeError: raise CommandExecutionError('Invalid CLI options') else: if salt.utils.path.which('xz') \ and __salt__['cmd.retcode'](['xz', '-t', cached], python_shell=False, ignore_retcode=True) == 0: decompress_cmd = 'xz --decompress --stdout' if decompress_cmd: decompressed = subprocess.Popen('{0} {1}'.format( decompress_cmd, _quote(cached)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return _list_tar(name, decompressed, None, True) raise CommandExecutionError( 'Unable to list contents of {0}. If this is an XZ-compressed tar ' 'archive, install XZ Utils to enable listing its contents. If it ' 'is compressed using something other than XZ, it may be necessary ' 'to specify CLI options to decompress the archive. See the ' 'documentation for details.'.format(name))
def remove(module, details=False): """ Attempt to remove a Perl module that was installed from CPAN. Because the ``cpan`` command doesn't actually support "uninstall"-like functionality, this function will attempt to do what it can, with what it has from CPAN. Until this function is declared stable, USE AT YOUR OWN RISK! CLI Example: .. code-block:: bash salt '*' cpan.remove Old::Package """ ret = { "old": None, "new": None, } info = show(module) if "error" in info: return {"error": info["error"]} version = info.get("installed version", None) if version is None: return ret ret["old"] = version if "cpan build dirs" not in info: return {"error": "No CPAN data available to use for uninstalling"} mod_pathfile = module.replace("::", "/") + ".pm" ins_path = info["installed file"].replace(mod_pathfile, "") files = [] for build_dir in info["cpan build dirs"]: contents = os.listdir(build_dir) if "MANIFEST" not in contents: continue mfile = os.path.join(build_dir, "MANIFEST") with salt.utils.files.fopen(mfile, "r") as fh_: for line in fh_.readlines(): line = salt.utils.stringutils.to_unicode(line) if line.startswith("lib/"): files.append(line.replace("lib/", ins_path).strip()) rm_details = {} for file_ in files: if file_ in rm_details: continue log.trace("Removing {0}".format(file_)) if __salt__["file.remove"](file_): rm_details[file_] = "removed" else: rm_details[file_] = "unable to remove" if details: ret["details"] = rm_details return ret
def _list_tar(name, cached, decompress_cmd, failhard=False): ''' List the contents of a tar archive. ''' dirs = [] files = [] links = [] try: with contextlib.closing(tarfile.open(cached)) as tar_archive: for member in tar_archive.getmembers(): if member.issym(): links.append(member.name) elif member.isdir(): dirs.append(member.name + '/') else: files.append(member.name) return dirs, files, links except tarfile.ReadError: if not failhard: if not salt.utils.which('tar'): raise CommandExecutionError( '\'tar\' command not available') if decompress_cmd is not None: # Guard against shell injection try: decompress_cmd = ' '.join( [_quote(x) for x in shlex.split(decompress_cmd)]) except AttributeError: raise CommandExecutionError('Invalid CLI options') else: if salt.utils.which('xz') \ and __salt__['cmd.retcode'](['xz', '-t', cached], python_shell=False, ignore_retcode=True) == 0: decompress_cmd = 'xz --decompress --stdout' if decompress_cmd: fd, decompressed = tempfile.mkstemp() os.close(fd) try: cmd = '{0} {1} > {2}'.format(decompress_cmd, _quote(cached), _quote(decompressed)) result = __salt__['cmd.run_all'](cmd, python_shell=True) if result['retcode'] != 0: raise CommandExecutionError( 'Failed to decompress {0}'.format(name), info={'error': result['stderr']}) return _list_tar(name, decompressed, None, True) finally: try: os.remove(decompressed) except OSError as exc: if exc.errno != errno.ENOENT: log.warning( 'Failed to remove intermediate ' 'decompressed archive %s: %s', decompressed, exc.__str__()) raise CommandExecutionError( 'Unable to list contents of {0}. If this is an XZ-compressed tar ' 'archive, install XZ Utils to enable listing its contents. If it ' 'is compressed using something other than XZ, it may be necessary ' 'to specify CLI options to decompress the archive. See the ' 'documentation for details.'.format(name))
def remove(module, details=False, bin_env=None): ''' Attempt to remove a Perl module that was installed from CPAN. Because the ``cpan`` command doesn't actually support 'uninstall'-like functionality, this function will attempt to do what it can, with what it has from CPAN. Until this function is declared stable, USE AT YOUR OWN RISK! CLI Example: .. code-block:: bash salt '*' cpan.remove Old::Package ''' ret = {'error': None, 'old': None, 'new': None} info = show(module, bin_env=bin_env) ret['error'] = info.get('error', None) cpan_version = info.get('installed_version', None) if (cpan_version is None) or ('not installed' in cpan_version): log.debug( 'Module "{}" already removed, no changes made'.format(module)) else: mod_pathfile = module.replace('::', '/') + '.pm' ins_path = info['installed_file'].replace(mod_pathfile, '') rm_details = {} if 'cpan_build_dirs' in info: log.warning('No CPAN data available to use for uninstalling') files = [] for build_dir in info['cpan_build_dirs']: # Check if the build directory exists, if not then skip if not os.path.isdir(build_dir): log.warning( 'Could not find CPAN build dir: {}'.format(build_dir)) continue # If the manifest is moving then skip contents = os.listdir(build_dir) if 'MANIFEST' not in contents: continue mfile = os.path.join(build_dir, 'MANIFEST') with salt.utils.files.fopen(mfile, 'r') as fh_: for line in fh_.readlines(): line = salt.utils.stringutils.to_unicode(line) if line.startswith('lib/'): files.append(line.replace('lib/', ins_path).strip()) for file_ in files: if file_ in rm_details: log.trace('Removing {}'.format(file_)) continue if __salt__['file.remove'](file_): rm_details[file_] = 'removed' else: rm_details[file_] = 'unable to remove' new_info = show(module, bin_env=bin_env) if details: ret['details'] = rm_details # Only report changes, remove values that are the same before and after for k in info.copy().keys(): if info.get(k) == new_info[k]: info.pop(k) new_info.pop(k, None) ret['old'] = info ret['new'] = new_info return ret