blob: 8656bd92a0020dbacda909bf697c9811fdb8bc81 [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
Mike Frysingere852b072021-05-21 12:39:03 -040025import urllib.request
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070026import xml.dom.minidom
27
28from chromite.lib import commandline
29from chromite.lib import cros_build_lib
30from chromite.lib import gs
31from chromite.lib import osutils
Alex Klein73eba212021-09-09 11:43:33 -060032from chromite.utils import pformat
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070033
34
Alex Klein1699fab2022-09-08 08:46:06 -060035UPLOAD_URL_BASE = "gs://chromeos-localmirror-private/distfiles"
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070036
37
Don Garrettec5cf902013-09-05 15:49:59 -070038def DownloadCrx(ext, extension, crxdir):
Alex Klein1699fab2022-09-08 08:46:06 -060039 """Download .crx file from WebStore and update entry."""
40 logging.info('Extension "%s"(%s)...', extension["name"], ext)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070041
Alex Klein1699fab2022-09-08 08:46:06 -060042 update_url = (
43 "%s?prodversion=90.1.1.1&acceptformat=crx3&x=id%%3D%s%%26uc"
44 % (extension["external_update_url"], ext)
45 )
46 with urllib.request.urlopen(update_url) as response:
47 if response.getcode() != 200:
48 logging.error(
49 "Cannot get update response, URL: %s, error: %d",
50 update_url,
51 response.getcode(),
52 )
53 return False
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070054
Alex Klein1699fab2022-09-08 08:46:06 -060055 dom = xml.dom.minidom.parse(response)
56 status = dom.getElementsByTagName("app")[0].getAttribute("status")
57 if status != "ok":
58 logging.error("Cannot fetch extension, status: %s", status)
59 return False
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070060
Alex Klein1699fab2022-09-08 08:46:06 -060061 node = dom.getElementsByTagName("updatecheck")[0]
62 if node.getAttribute("status") == "noupdate":
63 logging.info(
64 "No CRX available (may have been removed from the webstore)"
65 )
66 return True
67
68 url = node.getAttribute("codebase")
69 version = node.getAttribute("version")
70 filename = "%s-%s.crx" % (ext, version)
71 with urllib.request.urlopen(url) as response:
72 if response.getcode() != 200:
73 logging.error(
74 "Cannot download extension, URL: %s, error: %d",
75 url,
76 response.getcode(),
77 )
78 return False
79
80 osutils.WriteFile(
81 os.path.join(crxdir, "extensions", filename),
82 response.read(),
83 mode="wb",
84 )
85
86 # Keep external_update_url in json file, ExternalCache will take care about
87 # replacing it with proper external_crx path and version.
88
89 logging.info("Downloaded, current version %s", version)
Alan Cutterbf4d1662020-10-27 13:32:38 +110090 return True
91
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -070092
Don Garrettec5cf902013-09-05 15:49:59 -070093def CreateValidationFiles(validationdir, crxdir, identifier):
Alex Klein1699fab2022-09-08 08:46:06 -060094 """Create validation files for all extensions in |crxdir|."""
Don Garrettec5cf902013-09-05 15:49:59 -070095
Alex Klein1699fab2022-09-08 08:46:06 -060096 verified_files = []
Don Garrettec5cf902013-09-05 15:49:59 -070097
Alex Klein1699fab2022-09-08 08:46:06 -060098 # Discover all extensions to be validated (but not JSON files).
99 for directory, _, filenames in os.walk(os.path.join(crxdir, "extensions")):
Don Garrettec5cf902013-09-05 15:49:59 -0700100
Alex Klein1699fab2022-09-08 08:46:06 -0600101 # Make directory relative to output dir by removing crxdir and /.
102 for filename in filenames:
103 verified_files.append(
104 os.path.join(directory[len(crxdir) + 1 :], filename)
105 )
Don Garrettec5cf902013-09-05 15:49:59 -0700106
Alex Klein1699fab2022-09-08 08:46:06 -0600107 validation_file = os.path.join(validationdir, "%s.validation" % identifier)
Don Garrettec5cf902013-09-05 15:49:59 -0700108
Alex Klein1699fab2022-09-08 08:46:06 -0600109 osutils.SafeMakedirs(validationdir)
110 cros_build_lib.run(
111 ["sha256sum"] + verified_files,
112 stdout=validation_file,
113 cwd=crxdir,
114 print_cmd=False,
115 )
116 logging.info("Hashes created.")
Don Garrettec5cf902013-09-05 15:49:59 -0700117
118
119def CreateCacheTarball(extensions, outputdir, identifier, tarball):
Alex Klein1699fab2022-09-08 08:46:06 -0600120 """Cache |extensions| in |outputdir| and pack them in |tarball|."""
Don Garrettec5cf902013-09-05 15:49:59 -0700121
Alex Klein1699fab2022-09-08 08:46:06 -0600122 crxdir = os.path.join(outputdir, "crx")
123 jsondir = os.path.join(outputdir, "json", "extensions")
124 validationdir = os.path.join(outputdir, "validation")
Don Garrettec5cf902013-09-05 15:49:59 -0700125
Alex Klein1699fab2022-09-08 08:46:06 -0600126 osutils.SafeMakedirs(os.path.join(crxdir, "extensions"))
127 osutils.SafeMakedirs(jsondir)
128 was_errors = False
129 for ext in extensions:
130 extension = extensions[ext]
131 # It should not be in use at this moment.
132 if "managed_users" in extension:
133 cros_build_lib.Die(
134 "managed_users is deprecated and not supported. "
135 "Please use user_type."
136 )
137 # In case we work with old type json, use default 'user_type'.
138 # TODO: Update all external_extensions.json files and deprecate this.
139 if "user_type" not in extension:
140 user_type = ["unmanaged"]
141 if extension.get("child_users", "no") == "yes":
142 user_type.append("child")
143 logging.warning(
144 "user_type filter has to be set explicitly for %s, using "
145 "%s by default.",
146 ext,
147 user_type,
148 )
149 extension["user_type"] = user_type
150 else:
151 if "child_users" in extension:
152 cros_build_lib.Die(
153 "child_users is not supported when user_type is " "set."
154 )
khmel@google.com37161cf2019-01-23 09:43:44 -0800155
Alex Klein1699fab2022-09-08 08:46:06 -0600156 # Verify user type is well-formed.
157 allowed_user_types = {
158 "unmanaged",
159 "managed",
160 "child",
161 "supervised",
162 "guest",
163 }
164 if not extension["user_type"]:
165 cros_build_lib.Die("user_type is not set")
166 ext_keys = set(extension["user_type"])
167 unknown_keys = ext_keys - allowed_user_types
168 if unknown_keys:
169 cros_build_lib.Die("user_type %s is not allowed", unknown_keys)
khmel@google.com37161cf2019-01-23 09:43:44 -0800170
Alex Klein1699fab2022-09-08 08:46:06 -0600171 cache_crx = extension.get("cache_crx", "yes")
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 # Remove fields that shouldn't be in the output file.
174 for key in ("cache_crx", "child_users"):
175 extension.pop(key, None)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 if cache_crx == "yes":
178 if not DownloadCrx(ext, extension, crxdir):
179 was_errors = True
180 elif cache_crx == "no":
181 pass
182 else:
183 cros_build_lib.Die(
184 'Unknown value for "cache_crx" %s for %s', cache_crx, ext
185 )
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 json_file = os.path.join(jsondir, "%s.json" % ext)
188 pformat.json(extension, fp=json_file)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 if was_errors:
191 cros_build_lib.Die("FAIL to download some extensions")
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 CreateValidationFiles(validationdir, crxdir, identifier)
194 cros_build_lib.CreateTarball(tarball, outputdir)
195 logging.info("Tarball created %s", tarball)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700196
197
198def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600199 parser = commandline.ArgumentParser(
200 "%%(prog)s [options] <version>\n\n%s" % __doc__, caching=True
201 )
202 parser.add_argument("version", nargs=1)
203 parser.add_argument(
204 "--path",
205 default=None,
206 type="path",
207 help="Path of files dir with external_extensions.json",
208 )
209 parser.add_argument(
210 "--create",
211 default=False,
212 action="store_true",
213 help="Create cache tarball with specified name",
214 )
215 parser.add_argument(
216 "--upload",
217 default=False,
218 action="store_true",
219 help="Upload cache tarball with specified name",
220 )
221 options = parser.parse_args(argv)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700222
Alex Klein1699fab2022-09-08 08:46:06 -0600223 if options.path:
224 os.chdir(options.path)
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700225
Alex Klein1699fab2022-09-08 08:46:06 -0600226 if not (options.create or options.upload):
227 cros_build_lib.Die("Need at least --create or --upload args")
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700228
Alex Klein1699fab2022-09-08 08:46:06 -0600229 if not os.path.exists("external_extensions.json"):
230 cros_build_lib.Die(
231 "No external_extensions.json in %s. Did you forget the "
232 "--path option?",
233 os.getcwd(),
234 )
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700235
Alex Klein1699fab2022-09-08 08:46:06 -0600236 identifier = options.version[0]
237 tarball = "%s.tar.xz" % identifier
238 if options.create:
239 extensions = json.load(open("external_extensions.json", "r"))
240 with osutils.TempDir() as tempdir:
241 CreateCacheTarball(
242 extensions, tempdir, identifier, os.path.abspath(tarball)
243 )
Dmitry Polukhincbdd21c2013-08-13 10:42:04 -0700244
Alex Klein1699fab2022-09-08 08:46:06 -0600245 if options.upload:
246 ctx = gs.GSContext()
247 url = os.path.join(UPLOAD_URL_BASE, tarball)
248 if ctx.Exists(url):
249 cros_build_lib.Die(
250 "This version already exists on Google Storage (%s)!\n"
251 "NEVER REWRITE EXISTING FILE. IT WILL BREAK CHROME OS "
252 "BUILD!!!",
253 url,
254 )
255 ctx.Copy(os.path.abspath(tarball), url, acl="project-private")
256 logging.info("Tarball uploaded %s", url)
257 osutils.SafeUnlink(os.path.abspath(tarball))