blob: 07e932381c605cce8a2f0112b62987dd99c9af69 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2013 The ChromiumOS Authors
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Generate and upload tarballs for default apps cache.
6
7Run inside the 'files' dir containing 'external_extensions.json' file:
8$ chromite/bin/chrome_update_extension_cache --create --upload \\
9 chromeos-default-apps-1.0.0
10
11Always increment the version when you update an existing package.
12If no new files are added, increment the third version number.
13 e.g. 1.0.0 -> 1.0.1
14If you change list of default extensions, increment the second version number.
15 e.g. 1.0.0 -> 1.1.0
16
17Also you need to regenerate the Manifest with the new tarball digest.
18Run inside the chroot:
19$ ebuild chromeos-default-apps-1.0.0.ebuild manifest --force
20"""
21
22import json
Chris McDonald59650c32021-07-20 15:29:28 -060023import logging
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070024import os
Ben Reicha2c198d2023-08-03 12:21:24 +100025from typing import Any, Dict
Mike Frysingere852b072021-05-21 12:39:03 -040026import urllib.request
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070027import xml.dom.minidom
28
29from chromite.lib import commandline
30from chromite.lib import cros_build_lib
31from chromite.lib import gs
32from chromite.lib import osutils
Alex Klein73eba212021-09-09 11:43:33 -060033from chromite.utils import pformat
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070034
35
Alex Klein1699fab2022-09-08 08:46:06 -060036UPLOAD_URL_BASE = "gs://chromeos-localmirror-private/distfiles"
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070037
38
Ben Reicha2c198d2023-08-03 12:21:24 +100039def DownloadCrx(ext: str, extension: Dict[str, Any], crxdir: str) -> bool:
40 """Download the extensions CRX from the Chrome web store update URL.
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070041
Ben Reicha2c198d2023-08-03 12:21:24 +100042 Args:
43 ext: The extension ID
44 extension: A key value pair containing information about the extension.
45 crxdir: The directory to save the CRX file in.
46
47 Returns:
48 True if successfully downloaded the CRX.
49 """
50 logging.info('Extension "%s" (%s)...', extension["name"], ext)
51
52 min_version = extension["min_version"] if "min_version" in extension else ""
Alex Klein1699fab2022-09-08 08:46:06 -060053 update_url = (
Ben Reicha2c198d2023-08-03 12:21:24 +100054 f"{extension['external_update_url']}"
55 f"?prodversion=115.0.5790.160&acceptformat=crx3"
56 f"&x=id%3D{ext}%26v%3D{min_version}%26uc"
Alex Klein1699fab2022-09-08 08:46:06 -060057 )
Ben Reicha2c198d2023-08-03 12:21:24 +100058
Alex Klein1699fab2022-09-08 08:46:06 -060059 with urllib.request.urlopen(update_url) as response:
60 if response.getcode() != 200:
61 logging.error(
62 "Cannot get update response, URL: %s, error: %d",
63 update_url,
64 response.getcode(),
65 )
66 return False
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070067
Alex Klein1699fab2022-09-08 08:46:06 -060068 dom = xml.dom.minidom.parse(response)
69 status = dom.getElementsByTagName("app")[0].getAttribute("status")
70 if status != "ok":
71 logging.error("Cannot fetch extension, status: %s", status)
72 return False
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070073
Alex Klein1699fab2022-09-08 08:46:06 -060074 node = dom.getElementsByTagName("updatecheck")[0]
75 if node.getAttribute("status") == "noupdate":
76 logging.info(
77 "No CRX available (may have been removed from the webstore)"
78 )
79 return True
80
81 url = node.getAttribute("codebase")
82 version = node.getAttribute("version")
83 filename = "%s-%s.crx" % (ext, version)
84 with urllib.request.urlopen(url) as response:
85 if response.getcode() != 200:
86 logging.error(
87 "Cannot download extension, URL: %s, error: %d",
88 url,
89 response.getcode(),
90 )
91 return False
92
93 osutils.WriteFile(
94 os.path.join(crxdir, "extensions", filename),
95 response.read(),
96 mode="wb",
97 )
98
99 # Keep external_update_url in json file, ExternalCache will take care about
100 # replacing it with proper external_crx path and version.
101
102 logging.info("Downloaded, current version %s", version)
Alan Cutterbf4d1662020-10-27 13:32:38 +1100103 return True
104
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700105
Don Garrettec5cf902013-09-05 15:49:59 -0700106def CreateValidationFiles(validationdir, crxdir, identifier):
Alex Klein1699fab2022-09-08 08:46:06 -0600107 """Create validation files for all extensions in |crxdir|."""
Don Garrettec5cf902013-09-05 15:49:59 -0700108
Alex Klein1699fab2022-09-08 08:46:06 -0600109 verified_files = []
Don Garrettec5cf902013-09-05 15:49:59 -0700110
Alex Klein1699fab2022-09-08 08:46:06 -0600111 # Discover all extensions to be validated (but not JSON files).
112 for directory, _, filenames in os.walk(os.path.join(crxdir, "extensions")):
Alex Klein1699fab2022-09-08 08:46:06 -0600113 # Make directory relative to output dir by removing crxdir and /.
114 for filename in filenames:
115 verified_files.append(
116 os.path.join(directory[len(crxdir) + 1 :], filename)
117 )
Don Garrettec5cf902013-09-05 15:49:59 -0700118
Alex Klein1699fab2022-09-08 08:46:06 -0600119 validation_file = os.path.join(validationdir, "%s.validation" % identifier)
Don Garrettec5cf902013-09-05 15:49:59 -0700120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 osutils.SafeMakedirs(validationdir)
122 cros_build_lib.run(
123 ["sha256sum"] + verified_files,
124 stdout=validation_file,
125 cwd=crxdir,
126 print_cmd=False,
127 )
128 logging.info("Hashes created.")
Don Garrettec5cf902013-09-05 15:49:59 -0700129
130
131def CreateCacheTarball(extensions, outputdir, identifier, tarball):
Alex Klein1699fab2022-09-08 08:46:06 -0600132 """Cache |extensions| in |outputdir| and pack them in |tarball|."""
Don Garrettec5cf902013-09-05 15:49:59 -0700133
Alex Klein1699fab2022-09-08 08:46:06 -0600134 crxdir = os.path.join(outputdir, "crx")
135 jsondir = os.path.join(outputdir, "json", "extensions")
136 validationdir = os.path.join(outputdir, "validation")
Don Garrettec5cf902013-09-05 15:49:59 -0700137
Alex Klein1699fab2022-09-08 08:46:06 -0600138 osutils.SafeMakedirs(os.path.join(crxdir, "extensions"))
139 osutils.SafeMakedirs(jsondir)
140 was_errors = False
141 for ext in extensions:
142 extension = extensions[ext]
143 # It should not be in use at this moment.
144 if "managed_users" in extension:
145 cros_build_lib.Die(
146 "managed_users is deprecated and not supported. "
147 "Please use user_type."
148 )
149 # In case we work with old type json, use default 'user_type'.
150 # TODO: Update all external_extensions.json files and deprecate this.
151 if "user_type" not in extension:
152 user_type = ["unmanaged"]
153 if extension.get("child_users", "no") == "yes":
154 user_type.append("child")
155 logging.warning(
156 "user_type filter has to be set explicitly for %s, using "
157 "%s by default.",
158 ext,
159 user_type,
160 )
161 extension["user_type"] = user_type
162 else:
163 if "child_users" in extension:
164 cros_build_lib.Die(
165 "child_users is not supported when user_type is " "set."
166 )
khmel@google.com37161cf2019-01-23 09:43:44 -0800167
Alex Klein1699fab2022-09-08 08:46:06 -0600168 # Verify user type is well-formed.
169 allowed_user_types = {
170 "unmanaged",
171 "managed",
172 "child",
173 "supervised",
174 "guest",
175 }
176 if not extension["user_type"]:
177 cros_build_lib.Die("user_type is not set")
178 ext_keys = set(extension["user_type"])
179 unknown_keys = ext_keys - allowed_user_types
180 if unknown_keys:
181 cros_build_lib.Die("user_type %s is not allowed", unknown_keys)
khmel@google.com37161cf2019-01-23 09:43:44 -0800182
Alex Klein1699fab2022-09-08 08:46:06 -0600183 cache_crx = extension.get("cache_crx", "yes")
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700184
Alex Klein1699fab2022-09-08 08:46:06 -0600185 if cache_crx == "yes":
186 if not DownloadCrx(ext, extension, crxdir):
187 was_errors = True
188 elif cache_crx == "no":
189 pass
190 else:
191 cros_build_lib.Die(
192 'Unknown value for "cache_crx" %s for %s', cache_crx, ext
193 )
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700194
Ben Reicha2c198d2023-08-03 12:21:24 +1000195 # Remove fields that shouldn't be in the output file.
196 for key in ("cache_crx", "child_users", "min_version"):
197 extension.pop(key, None)
198
Alex Klein1699fab2022-09-08 08:46:06 -0600199 json_file = os.path.join(jsondir, "%s.json" % ext)
200 pformat.json(extension, fp=json_file)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700201
Alex Klein1699fab2022-09-08 08:46:06 -0600202 if was_errors:
203 cros_build_lib.Die("FAIL to download some extensions")
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700204
Alex Klein1699fab2022-09-08 08:46:06 -0600205 CreateValidationFiles(validationdir, crxdir, identifier)
206 cros_build_lib.CreateTarball(tarball, outputdir)
207 logging.info("Tarball created %s", tarball)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700208
209
210def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600211 parser = commandline.ArgumentParser(
212 "%%(prog)s [options] <version>\n\n%s" % __doc__, caching=True
213 )
214 parser.add_argument("version", nargs=1)
215 parser.add_argument(
216 "--path",
217 default=None,
218 type="path",
219 help="Path of files dir with external_extensions.json",
220 )
221 parser.add_argument(
222 "--create",
223 default=False,
224 action="store_true",
225 help="Create cache tarball with specified name",
226 )
227 parser.add_argument(
228 "--upload",
229 default=False,
230 action="store_true",
231 help="Upload cache tarball with specified name",
232 )
233 options = parser.parse_args(argv)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700234
Alex Klein1699fab2022-09-08 08:46:06 -0600235 if options.path:
236 os.chdir(options.path)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700237
Alex Klein1699fab2022-09-08 08:46:06 -0600238 if not (options.create or options.upload):
239 cros_build_lib.Die("Need at least --create or --upload args")
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700240
Alex Klein1699fab2022-09-08 08:46:06 -0600241 if not os.path.exists("external_extensions.json"):
242 cros_build_lib.Die(
243 "No external_extensions.json in %s. Did you forget the "
244 "--path option?",
245 os.getcwd(),
246 )
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700247
Alex Klein1699fab2022-09-08 08:46:06 -0600248 identifier = options.version[0]
249 tarball = "%s.tar.xz" % identifier
250 if options.create:
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500251 with open("external_extensions.json", "rb") as f:
252 extensions = json.load(f)
Alex Klein1699fab2022-09-08 08:46:06 -0600253 with osutils.TempDir() as tempdir:
254 CreateCacheTarball(
255 extensions, tempdir, identifier, os.path.abspath(tarball)
256 )
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700257
Alex Klein1699fab2022-09-08 08:46:06 -0600258 if options.upload:
259 ctx = gs.GSContext()
260 url = os.path.join(UPLOAD_URL_BASE, tarball)
261 if ctx.Exists(url):
262 cros_build_lib.Die(
263 "This version already exists on Google Storage (%s)!\n"
264 "NEVER REWRITE EXISTING FILE. IT WILL BREAK CHROME OS "
265 "BUILD!!!",
266 url,
267 )
268 ctx.Copy(os.path.abspath(tarball), url, acl="project-private")
269 logging.info("Tarball uploaded %s", url)
270 osutils.SafeUnlink(os.path.abspath(tarball))