Last active
May 18, 2022 14:18
-
-
Save hevlastka/dc9b703a0c4ff1114b8bb5e3bb40e04d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/python | |
import os | |
import re | |
import sys | |
import json | |
import socket | |
import typing | |
import shutil | |
import logging | |
import subprocess # pragma: nosec | |
logging.getLogger(__name__) | |
logging.basicConfig(level=logging.INFO) | |
hostname = socket.gethostname() | |
def get_distribution(): | |
if os.path.exists("/etc/os-release"): | |
id_like = None | |
id = None | |
with open("/etc/os-release", "r") as _f: | |
context = _f.readlines() | |
for line in context: | |
line = line.replace("\n", "") | |
if line.startswith("ID_LIKE="): | |
id_like = line.replace("ID_LIKE=", "") | |
if line.startswith("ID="): | |
id = line.replace("ID=", "") | |
return id_like or id | |
def get_bitness(): | |
return "x64" if sys.maxsize > 2**32 else "x86" | |
def default_parser(item) -> typing.Tuple[str, str, str]: | |
package, version = item.split(" ", 1) | |
return package, version, item | |
def snap_parser(item) -> typing.Tuple[str, str, str]: | |
if item.replace(" ", "") == "NameVersionRevTrackingPublisherNotes": | |
raise ValueError("snap Header provided") | |
package, version_detail = item.split(" ", 1) | |
version, detail = version_detail.strip().split(" ", 1) | |
return package, version, item | |
def apt_parser( | |
item, | |
) -> typing.Tuple[str, str, str]: | |
"""apt Package names and versions need cleaning up""" | |
universe_package, version_detail = item.split(" ", 1) | |
package, universe = universe_package.split("/", 1) | |
version, detail = version_detail.split(" ", 1) | |
match = re.match( | |
r"(\d[:-])?(?P<major>\d{1,8})(\.(?P<minor>\d*))?(\.?(?P<patch>\d*))?(([\.-])(?P<build>[a-zA-Z0-9]*))?", | |
version, | |
) | |
if match: | |
version = match.group(0) | |
if ":" in version[1:3]: | |
version = version.split(":")[1] | |
return package, version, item | |
def apk_parser(item) -> typing.Tuple[str, str, str]: | |
match = re.match( | |
r"(?P<package>.*)\-(?P<version>[^\-r]*)(\-(?P<build>r[0-9]))?$", item | |
) | |
if not match: | |
return ("", "", "") | |
return match.group("package"), match.group("version"), item | |
def pacman_parser(item) -> typing.Tuple[str, str, str]: | |
package, version = item.split(" ") | |
if ":" in version[1:3]: | |
version = version.split(":")[1] | |
return package, version, item | |
tools = { | |
"rpm": { | |
"args": "-qa", | |
}, | |
"dnf": { | |
"args": "list installed", | |
}, | |
"zypper": { | |
"args": "se --installed", | |
}, | |
"pacman": {"args": "-Q", "parser": pacman_parser}, | |
"apt": { | |
"args": "list --installed", | |
"parser": apt_parser, | |
}, | |
"dpkg-query": { | |
"args": "-f '${Package} ${Version}\n' -W '*'", | |
}, | |
"flatpack": {"args": "list"}, | |
"snap": {"args": "list", "parser": snap_parser}, | |
"apk": {"args": "info -v", "parser": apk_parser}, | |
} | |
# 'name' 'version' 'tool' 'original' | |
installed_packages = [] | |
def is_tool(name): | |
"""Check whether `name` is on PATH and marked as executable.""" | |
# from whichcraft import which | |
from shutil import which | |
return which(name) is not None | |
def get_installed(): | |
for tool, parameters in tools.items(): | |
if is_tool(tool): | |
logging.info(f"{tool} present, checking reported packages") | |
process = subprocess.Popen( | |
[tool, *parameters["args"].split(" ")], | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
) | |
stdout, stderr = process.communicate() | |
for item in stdout.decode().split("\n"): | |
if len(item) == 0: # missing or EOL | |
continue | |
try: | |
parser = tools[tool].get("parser", default_parser) | |
package, version, original = parser(item) | |
installed_packages.append((package, version, tool, original)) | |
except ValueError as e: | |
print(f'Ignoring line "{item}"...') | |
with open("pkg.manifest", "w") as _f: | |
_f.write( | |
json.dumps( | |
{ | |
"distribution": get_distribution(), | |
"host": hostname, | |
"packages": installed_packages, | |
"architecture": get_bitness(), | |
}, | |
indent=2, | |
) | |
) | |
logging.info("Results saved to pkg.manifest") | |
return | |
if __name__ == "__main__": | |
get_installed() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment