diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/sensu/sensu_go/tools/windows-versions.py | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/sensu/sensu_go/tools/windows-versions.py')
-rwxr-xr-x | ansible_collections/sensu/sensu_go/tools/windows-versions.py | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/ansible_collections/sensu/sensu_go/tools/windows-versions.py b/ansible_collections/sensu/sensu_go/tools/windows-versions.py new file mode 100755 index 000000000..1706a0a07 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tools/windows-versions.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import argparse +import gzip +import pathlib +import shutil +import subprocess +import sys + +from urllib import request +from xml.etree import ElementTree + +import yaml + + +BASE_REPO_URL = "https://packagecloud.io/sensu/stable/el/8/x86_64/" +FILENAME_TEMPLATE = "sensu-go-agent_{0}.{1}.{2}.{3}_en-US.{arch}.msi" +DOWNLOAD_URL_TEMPLATE = ( + "https://s3-us-west-2.amazonaws.com/sensu.io/sensu-go/{0}.{1}.{2}/" + + FILENAME_TEMPLATE +) +MINIMAL_VERSION = (5, 20, 0) + + +class ArgParser(argparse.ArgumentParser): + """An argument parser that displays help on error.""" + + def error(self, message): + sys.stderr.write("error: {0}\n".format(message)) + self.print_help() + sys.exit(2) + + def add_subparsers(self, **kwargs): + # Workaround for http://bugs.python.org/issue9253 + subparsers = super(ArgParser, self).add_subparsers() + subparsers.required = True + subparsers.dest = "command" + return subparsers + + +def _fetch_available_versions(): + available_versions = set() + + response = request.urlopen(BASE_REPO_URL + "repodata/repomd.xml", timeout=30) + root = ElementTree.parse(response).getroot() + for data in root.iter("{http://linux.duke.edu/metadata/repo}data"): + if data.get("type") == "primary": + break + else: + return available_versions + + location = next(data.iter("{http://linux.duke.edu/metadata/repo}location")) + path = location.attrib["href"] + + response = request.urlopen(BASE_REPO_URL + path, timeout=30) + root = ElementTree.fromstring(gzip.decompress(response.read())) + for package in root.iter("{http://linux.duke.edu/metadata/common}package"): + name = next(package.iter("{http://linux.duke.edu/metadata/common}name")) + if name.text != "sensu-go-agent": + continue + + version = next(package.iter("{http://linux.duke.edu/metadata/common}version")) + version_tuple = tuple(int(c) for c in version.get("ver").split(".")) + if version_tuple < MINIMAL_VERSION: + continue + + available_versions.add(version_tuple + (int(version.get("rel")), )) + + return available_versions + + +def _load_versions_from_vars(vars): + return set( + (tuple(int(c) for c in item["version"].split(".")) + (item["build"],)) + for item in vars["_msi_lookup"].values() + ) + + +def _sync_versions(vars, available_versions, cache_dir): + new_vars = dict(vars, _msi_lookup={}) + + old_msis = vars["_msi_lookup"] + new_msis = new_vars["_msi_lookup"] + + cache = pathlib.Path(cache_dir) + + for version in sorted(available_versions): + version_str = ".".join(map(str, version[:3])) + + if version_str in old_msis: + # Happy path: we already have this version sorted + new_msis[version_str] = old_msis[version_str] + continue + + # Sad path: we need to download packages and extract product codes + product_codes = {} + for arch in ("x86", "x64"): + url = DOWNLOAD_URL_TEMPLATE.format(*version, arch=arch) + filename = FILENAME_TEMPLATE.format(*version, arch=arch) + file = cache / filename + + if not file.is_file(): + print("Downloading " + filename) + with open(file, "wb") as fp: + response = request.urlopen(url) + shutil.copyfileobj(response, fp) + else: + print("Reusing " + filename) + + process = subprocess.run( + ("msiinfo", "export", str(file), "Property"), capture_output=True, + check=True + ) + for line in process.stdout.splitlines(): + field, value = line.split(b"\t") + if field == b"ProductCode": + product_codes[arch] = value.decode("ascii") + + new_msis[version_str] = dict( + version=version_str, build=version[-1], product_codes=product_codes + ) + + new_msis["latest"] = new_msis[version_str] + + return new_vars + + +def _load_windows_vars_file(filename): + with open(filename, "r") as fd: + return yaml.safe_load(fd) + + +def _save_windows_vars_file(filename, vars): + with open(filename, "w") as fd: + yaml.safe_dump(vars, fd) + + +def _check(args): + vars_data = _load_windows_vars_file(args.vars) + current = _load_versions_from_vars(vars_data) + available = _fetch_available_versions() + + missing = available - current + obsolete = current - available + + if missing: + print("The following versions are missing: {0}".format( + ", ".join(".".join(map(str, v)) for v in missing) + )) + if obsolete: + print("The following versions are obsolete: {0}".format( + ", ".join(".".join(map(str, v)) for v in obsolete) + )) + + return len(missing) + len(obsolete) + + +def _update(args): + vars_data = _load_windows_vars_file(args.vars) + current = _load_versions_from_vars(vars_data) + available = _fetch_available_versions() + + if current == available: + return 0 + + new_vars_data = _sync_versions(vars_data, available, args.cache) + _save_windows_vars_file(args.vars, new_vars_data) + + return 0 + + +def main(): + parser = ArgParser( + description="Windows agent version updater", + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + subparsers = parser.add_subparsers() + + check = subparsers.add_parser("check", help="Check for version updates") + check.add_argument("vars", help="Variable file with Windows lookup table") + check.set_defaults(func=_check) + + update = subparsers.add_parser("update", help="Update lookup table") + update.add_argument("vars", help="Variable file with Windows lookup table") + update.add_argument( + "--cache", help="Directory used for caching downloads", default="/tmp" + ) + update.set_defaults(func=_update) + + args = parser.parse_args() + + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) |