summaryrefslogtreecommitdiffstats
path: root/ansible_collections/sensu/sensu_go/tools/windows-versions.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/sensu/sensu_go/tools/windows-versions.py
parentInitial commit. (diff)
downloadansible-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-xansible_collections/sensu/sensu_go/tools/windows-versions.py199
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())