checkGmpBalrog.py (8034B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import argparse 6 import hashlib 7 import re 8 from datetime import datetime 9 from urllib.parse import urlparse 10 from xml.etree import ElementTree 11 12 import requests 13 14 15 def check_hash(plugin, url, valid_urls): 16 if url in valid_urls: 17 return 18 valid_urls[url] = True 19 20 response = requests.get(url) 21 response.raise_for_status() 22 23 if "hashValue" in plugin.attrib: 24 hashValue = hashlib.sha512(response.content).hexdigest() 25 if hashValue != plugin.attrib["hashValue"]: 26 raise Exception( 27 "Given hash {} and calculated hash {} differ", 28 plugin.attrib["hashValue"], 29 hashValue, 30 ) 31 if "size" in plugin.attrib: 32 size = len(response.content) 33 if size != int(plugin.attrib["size"]): 34 raise Exception( 35 "Given size {} and calculated size {} differ", 36 int(plugin.attrib["size"]), 37 size, 38 ) 39 40 41 def fetch_balrog_xml( 42 url_base: str, plugin_id, version: str, buildid: str, channels, targets, checkHash 43 ) -> str: 44 url = "{url_base}/{version}/{buildid}/{target}/en-US/{channel}/default/default/default/update.xml" 45 valid_urls = {} 46 results = {} 47 for channel in channels: 48 results[channel] = {} 49 for target in targets: 50 balrog_url = url.format_map({ 51 "url_base": url_base, 52 "buildid": buildid, 53 "channel": channel, 54 "version": version, 55 "target": target, 56 }) 57 58 response = requests.get(balrog_url) 59 response.raise_for_status() 60 61 plugin_urls = [] 62 tree = ElementTree.fromstring(response.content) 63 for plugin in tree.findall("./addons/addon"): 64 if not "id" in plugin.attrib: 65 continue 66 if plugin.attrib["id"] != plugin_id: 67 continue 68 if "URL" in plugin.attrib: 69 if checkHash: 70 check_hash(plugin, plugin.attrib["URL"], valid_urls) 71 plugin_urls.append(plugin.attrib["URL"]) 72 for mirror in plugin.findall("./mirror"): 73 if "URL" in mirror.attrib: 74 if checkHash: 75 check_hash(plugin, plugin.attrib["URL"], valid_urls) 76 plugin_urls.append(mirror.attrib["URL"]) 77 78 results[channel][target] = plugin_urls 79 80 matching_channels = {} 81 for channel in channels: 82 matching_channels[channel] = [channel] 83 for other_channel in channels: 84 if ( 85 channel == other_channel 86 or channel not in results 87 or other_channel not in results 88 ): 89 continue 90 if results[channel] == results[other_channel]: 91 matching_channels[channel].append(other_channel) 92 del results[other_channel] 93 94 for channel in results: 95 print(", ".join(matching_channels[channel])) 96 for target in targets: 97 print(f"\t{target}") 98 for url in results[channel][target]: 99 print(f"\t\t{url}") 100 101 102 def main(): 103 examples = """examples: 104 python dom/media/tools/checkGmpBalrog.py widevine 133.0 105 python dom/media/tools/checkGmpBalrog.py widevine 133.0 --target Darwin_aarch64-gcc3 Darwin_x86_64-gcc3 106 python dom/media/tools/checkGmpBalrog.py --url http://localhost:8080 openh264 125.0 107 python dom/media/tools/checkGmpBalrog.py widevine_l1 115.14.0 --staging --channel nightly beta""" 108 109 parser = argparse.ArgumentParser( 110 description="Check Balrog XML for GMP plugin updates", 111 epilog=examples, 112 formatter_class=argparse.RawDescriptionHelpFormatter, 113 ) 114 parser.add_argument( 115 "plugin", 116 help="which plugin: openh264, widevine, widevine_l1", 117 ) 118 parser.add_argument("version", help="version of Firefox") 119 parser.add_argument( 120 "--channel", action="extend", nargs="+", help="check specific channel(s)" 121 ) 122 parser.add_argument( 123 "--list-channels", action="store_true", help="list the supported channels" 124 ) 125 parser.add_argument( 126 "--target", action="extend", nargs="+", help="check specific target(s)" 127 ) 128 parser.add_argument( 129 "--list-targets", action="store_true", help="list the supported targets" 130 ) 131 parser.add_argument("--buildid", help="override generated build ID to be specific") 132 parser.add_argument( 133 "--url", help="override base URL from which to fetch the balrog configuration" 134 ) 135 parser.add_argument( 136 "--staging", 137 action="store_true", 138 help="using the balrog staging URL instead of production", 139 ) 140 parser.add_argument( 141 "--checkHash", 142 action="store_true", 143 help="download plugins and validate the size/hash", 144 ) 145 args = parser.parse_args() 146 147 valid_channels = ["esr", "release", "beta", "nightly", "nightlytest"] 148 if args.list_channels: 149 for channel in valid_channels: 150 print(channel) 151 return 152 if args.channel is not None: 153 for channel in args.channel: 154 if channel not in valid_channels: 155 parser.error("`%s` is invalid, see --list-channels" % channel) 156 return 157 channels = args.channel 158 else: 159 channels = valid_channels 160 161 valid_targets = [ 162 "Darwin_aarch64-gcc3", 163 "Darwin_x86_64-gcc3", 164 "Linux_aarch64-gcc3", 165 "Linux_x86-gcc3", 166 "Linux_x86_64-gcc3", 167 "WINNT_aarch64-msvc-aarch64", 168 "WINNT_x86-msvc", 169 "WINNT_x86_64-msvc", 170 ] 171 valid_aliases = [ 172 "Linux_x86_64-gcc3-asan", 173 "WINNT_x86-msvc-x64", 174 "WINNT_x86-msvc-x86", 175 "WINNT_x86_64-msvc-x64", 176 "WINNT_x86_64-msvc-x64-asan", 177 ] 178 if args.list_targets: 179 for target in valid_targets: 180 print(target) 181 for target in valid_aliases: 182 print("%s (alias)" % target) 183 return 184 if args.target is not None: 185 for target in args.target: 186 if target not in valid_targets and target not in valid_aliases: 187 parser.error("`%s` is invalid, see --list-targets" % target) 188 return 189 targets = args.target 190 else: 191 targets = valid_targets 192 193 if args.buildid is not None: 194 if not re.match(r"^\d{14}$", args.buildid): 195 parser.error("`%s` is invalid, build id must be 14 digits") 196 return 197 buildid = args.buildid 198 199 else: 200 buildid = datetime.today().strftime("%y%m%d%H%M%S") 201 202 url_base = "https://aus5.mozilla.org" 203 if args.staging: 204 url_base = "https://stage.balrog.nonprod.cloudops.mozgcp.net" 205 if args.url is not None: 206 url_base = args.url 207 if url_base[-1] == "/": 208 url_base = url_base[:-1] 209 url_base += "/update/3/GMP" 210 211 parsed_url = urlparse(url_base) 212 if parsed_url.scheme not in ("http", "https"): 213 parser.error("expected http(s) scheme, got `%s`" % parsed_url.scheme) 214 return 215 if parsed_url.path != "/update/3/GMP": 216 parser.error("expected url path of `/update/3/GMP`, got `%s`" % parsed_url.path) 217 return 218 219 if args.plugin == "openh264": 220 plugin = "gmp-gmpopenh264" 221 elif args.plugin == "widevine": 222 plugin = "gmp-widevinecdm" 223 elif args.plugin == "widevine_l1": 224 plugin = "gmp-widevinecdm-l1" 225 else: 226 parser.error("plugin not recognized") 227 return 228 229 if not re.match(r"^\d+\.\d+(\.\d+)?$", args.version): 230 parser.error("version must be of the form ###.###(.###)") 231 return 232 233 fetch_balrog_xml( 234 url_base, plugin, args.version, buildid, channels, targets, args.checkHash 235 ) 236 237 238 main()