Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: watchextensions.py

Issue 29762573: Issue 6602 - Introduce watchextensions
Left Patch Set: Created May 8, 2018, 2:22 p.m.
Right Patch Set: Created May 16, 2018, 9:47 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « tox.ini ('k') | watchextensions.ini.example » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # This Source Code Form is subject to the terms of the Mozilla Public 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 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/. 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 """ 4 """
5 Track differences between versions of arbitrary extensions. 5 Track differences between versions of arbitrary extensions.
6 6
7 Running this module direcly will attempt to fetch new versions for every 7 Running this module direcly will attempt to fetch new versions for every
8 in ~/extwatcher.ini or /etc/extwatcher.ini configured and enabled extension 8 in ~/extwatcher.ini or /etc/extwatcher.ini configured and enabled extension
9 from the Chrome Web Store. When no parameters are given, you will simply be 9 from the Chrome Web Store. When no parameters are given, you will simply be
10 notified about whether a new version was found. 10 notified about whether a new version was found.
11 11
12 Providing -k/--keep-repository will leave the git repository (with unstaged 12 Providing -k/--keep-repository will leave the git repository (with unstaged
13 differences between the latest tracked version and the newest) on your drive, 13 differences between the latest tracked version and the newest) on your drive,
14 in order to examine the differences locally. 14 in order to examine the differences locally.
15 15
16 Providing -p/--push will stage all differences and push them to the configured 16 Providing -p/--push will stage all differences and push them to the configured
17 tracking repository. 17 tracking repository.
18
19 When the configuration files are incomplete or missing, a ConfigurationError
Vasily Kuznetsov 2018/05/15 16:49:31 Nit: this line is probably not very useful for the
tlucas 2018/05/16 09:52:38 Acknowledged.
20 will be raised.
21 """ 18 """
22 from __future__ import print_function 19 from __future__ import print_function
23 20
24 import argparse 21 import argparse
25 from configparser import ConfigParser, NoSectionError, NoOptionError 22 from configparser import ConfigParser, NoSectionError, NoOptionError
26 import json 23 import json
27 import logging 24 import logging
28 import os 25 import os
29 import shutil 26 import shutil
30 import subprocess 27 import subprocess
28 import sys
31 import tempfile 29 import tempfile
32 from zipfile import ZipFile 30 from zipfile import ZipFile
33 31
34 try: # Python 3 only 32 try: # Python 3 only
35 from urllib import request as urllib 33 from urllib import request as urllib
36 except ImportError: # Python 2 only 34 except ImportError: # Python 2 only
37 import urllib 35 import urllib
38 from xml.etree import ElementTree 36 from xml.etree import ElementTree
39 37
40 logging.basicConfig(level=logging.INFO) 38 logging.basicConfig(level=logging.INFO)
41 39
40 OMAHA_URL = 'https://omahaproxy.appspot.com/all.json?os=win'
41 SERVICE_URL = 'https://clients2.google.com/service/update2/crx'
42
42 43
43 class SensitiveFilter(logging.Filter): 44 class SensitiveFilter(logging.Filter):
Vasily Kuznetsov 2018/05/15 16:49:32 If I understand it correctly, the purpose of this
tlucas 2018/05/16 09:52:38 You are right about the purpose - I added a docume
Vasily Kuznetsov 2018/05/16 17:54:55 I was thinking about avoiding this filter class al
45 """Filter sensitive ouput from the logs.
46
47 This class is meant to be registered in python's logging facility. It will
48 mask potentially sensitive data from the message that is to be logged.
49
50
51 """
52
44 def __init__(self, patterns): 53 def __init__(self, patterns):
54 """Initialize a SensitiveFilter with the desired patterns."""
45 self.patterns = patterns 55 self.patterns = patterns
46 super(SensitiveFilter, self).__init__() 56 super(SensitiveFilter, self).__init__()
47 57
48 def filter(self, record): 58 def filter(self, record):
49 msg = record.msg 59 msg = record.msg
50 if isinstance(msg, BaseException): 60 if isinstance(msg, BaseException):
51 msg = str(msg) 61 msg = str(msg)
52 record.msg = self.mask(msg) 62 record.msg = self.mask(msg)
53 return True 63 return True
54 64
55 def mask(self, msg): 65 def mask(self, msg):
56 try: 66 try:
57 for pattern in self.patterns: 67 for pattern in self.patterns:
58 msg = msg.replace( 68 msg = msg.replace(
59 pattern, 69 pattern,
60 '/'.join(['******', pattern.rsplit('/', 1)[-1]]), 70 '/'.join(['******', pattern.rsplit('/', 1)[-1]]),
61 ) 71 )
62 except AttributeError: 72 except AttributeError:
63 pass 73 pass
64 return msg 74 return msg
65 75
66 76
77 class ConfigurationError(Exception):
78 pass
79
80
67 def read_config(path=None): 81 def read_config(path=None):
68 config_path = path or [ 82 config_path = path or [
69 os.path.expanduser('~/watchextensions.ini'), 83 os.path.expanduser('~/watchextensions.ini'),
70 '/etc/watchextensions.ini', 84 '/etc/watchextensions.ini',
71 ] 85 ]
72 86
73 config = ConfigParser() 87 config = ConfigParser()
74 if not config.read(config_path): 88 if not config.read(config_path):
75 raise ConfigurationError('No configuration file was found. Please ' 89 raise ConfigurationError('No configuration file was found. Please '
76 'provide ~/watchextensions.ini or ' 90 'provide ~/watchextensions.ini or '
77 '/etc/watchextensions.ini or specify a valid ' 91 '/etc/watchextensions.ini or specify a valid '
78 'path.') 92 'path.')
79 return config 93 return config
80
81
82 class ConfigurationError(Exception):
Vasily Kuznetsov 2018/05/15 16:49:31 Nit: moving this to above `read_config` would make
tlucas 2018/05/16 09:52:37 Done.
83 pass
84 94
85 95
86 class ExtWatcher(object): 96 class ExtWatcher(object):
87 section = 'extensions' 97 section = 'extensions'
88 98
89 def __init__(self, ext_name, config, push, keep_repo): 99 def __init__(self, ext_name, config, push, keep_repo):
90 self.logger = logging.getLogger(name=ext_name) 100 self.logger = logging.getLogger(name=ext_name)
91 self.config = config 101 self.config = config
92 102
93 self.ext_name = ext_name 103 self.ext_name = ext_name
94 self.push = push 104 self.push = push
95 self.keep_repo = keep_repo 105 self.keep_repo = keep_repo
96 106
97 self._cws_ext_url = None 107 self._cws_ext_url = None
98 self._current_ext_version = None 108 self._current_ext_version = None
99 109
100 self.downloaded_file = None 110 self.downloaded_file = None
101 111
102 super(ExtWatcher, self).__init__() 112 super(ExtWatcher, self).__init__()
103 113
104 def _git_cmd(self, cmds, relative=False): 114 def _git_cmd(self, cmds, relative=False):
105 base = ['git'] 115 base = ['git']
106 if not relative: 116 if not relative:
107 base += ['-C', self.tempdir] 117 base += ['-C', self.tempdir]
108 suffix = [] 118 suffix = []
109 if not any(x in cmds for x in ['status', 'add', 'diff']): 119 if not any(x in cmds for x in ['status', 'add', 'diff']):
110 suffix += ['--quiet'] 120 suffix += ['--quiet']
111 return subprocess.check_output(base + list(cmds) + suffix) 121 return subprocess.check_output(base + list(cmds) + suffix)
112 122
113 def _assure_local_repository(self): 123 def _clone_repository(self):
Vasily Kuznetsov 2018/05/15 16:49:31 This name seems a bit strange. Shouldn't it be som
tlucas 2018/05/16 09:52:38 You are right. "_assure_local_repository" is an ar
114 self.logger.info('Cloning ' + self.repository) 124 self.logger.info('Cloning ' + self.repository)
115 self._git_cmd(['clone', '-b', 'master', '--single-branch', 125 self._git_cmd(['clone', '-b', 'master', '--single-branch',
116 self.repository, self.tempdir], 126 self.repository, self.tempdir],
117 relative=True) 127 relative=True)
118 128
119 def _parse_config(self): 129 def _parse_config(self):
120 err_msg = '"{}" is not fully configured!'.format(self.ext_name) 130 err_msg = '"{}" is not fully configured!'.format(self.ext_name)
121 try: 131 try:
122 self.ext_id = self.config.get(self.section, self.ext_name + '_id') 132 self.ext_id = self.config.get(self.section, self.ext_name + '_id')
123 self.repository = self.config.get(self.section, 133 self.repository = self.config.get(self.section,
(...skipping 20 matching lines...) Expand all
144 shutil.rmtree(self.tempdir, ignore_errors=True) 154 shutil.rmtree(self.tempdir, ignore_errors=True)
145 else: 155 else:
146 print('Repository for {} available at {}'.format(self.ext_name, 156 print('Repository for {} available at {}'.format(self.ext_name,
147 self.tempdir)) 157 self.tempdir))
148 if self.downloaded_file: 158 if self.downloaded_file:
149 os.remove(self.downloaded_file) 159 os.remove(self.downloaded_file)
150 160
151 return True 161 return True
152 162
153 def _fetch_latest_chrome_version(self): 163 def _fetch_latest_chrome_version(self):
154 omaha_url = 'https://omahaproxy.appspot.com/all.json?os=win' 164 response = urllib.urlopen(OMAHA_URL).read()
Vasily Kuznetsov 2018/05/15 16:49:31 Maybe this should be a constant somewhere at the t
tlucas 2018/05/16 09:52:38 Done.
155 response = urllib.urlopen(omaha_url).read()
156 165
157 data = json.loads(response.decode('utf-8')) 166 data = json.loads(response.decode('utf-8'))
158 167
159 stable = [x for x in data[0]['versions'] if x['channel'] == 'stable'] 168 stable = [x for x in data[0]['versions'] if x['channel'] == 'stable']
160 return stable[0]['current_version'] 169 return stable[0]['current_version']
161 170
162 @property 171 @property
163 def cws_ext_url(self): 172 def cws_ext_url(self):
164 if not self._cws_ext_url: 173 if not self._cws_ext_url:
Vasily Kuznetsov 2018/05/15 16:49:31 You could just slap a `functools.lru_cache` decora
tlucas 2018/05/16 09:52:38 I'll use this comment to also reply to what you as
165 service_url = 'https://clients2.google.com/service/update2/crx' 174 ext_url = SERVICE_URL + '?prodversion={}&x=id%3D{}%26uc'.format(
166 ext_url = service_url + '?prodversion={}&x=id%3D{}%26uc'.format(
167 self._fetch_latest_chrome_version(), self.ext_id, 175 self._fetch_latest_chrome_version(), self.ext_id,
168 ) 176 )
169 self._cws_ext_url = ext_url 177 self._cws_ext_url = ext_url
170 178
171 return self._cws_ext_url 179 return self._cws_ext_url
172 180
173 @property 181 @property
174 def current_ext_version(self): 182 def current_ext_version(self):
175 if not self._current_ext_version: 183 if not self._current_ext_version:
176 updatecheck_url = self.cws_ext_url + '&response=updatecheck' 184 updatecheck_url = self.cws_ext_url + '&response=updatecheck'
(...skipping 24 matching lines...) Expand all
201 209
202 def _track_new_contents(self, message): 210 def _track_new_contents(self, message):
203 status = self._git_cmd(['status']) 211 status = self._git_cmd(['status'])
204 if b'nothing to commit' not in status: 212 if b'nothing to commit' not in status:
205 self._git_cmd(['add', '--all']) 213 self._git_cmd(['add', '--all'])
206 self._git_cmd(['commit', '-m', message]) 214 self._git_cmd(['commit', '-m', message])
207 self._git_cmd(['push', 'origin', 'master']) 215 self._git_cmd(['push', 'origin', 'master'])
208 216
209 def run(self): 217 def run(self):
210 self._parse_config() 218 self._parse_config()
211 self._assure_local_repository() 219 self._clone_repository()
212 220
213 next_version = self.current_ext_version 221 next_version = self.current_ext_version
214 if next_version not in self._get_tracked_version(): 222 if next_version not in self._get_tracked_version():
215 self.logger.info('New untracked version {} found!' 223 self.logger.info('New untracked version {} found!'
216 ''.format(next_version)) 224 ''.format(next_version))
217 if self.push or self.keep_repo: 225 if self.push or self.keep_repo:
218 self._download_ext_crx() 226 self._download_ext_crx()
219 self._unzip_to_repository() 227 self._unzip_to_repository()
220 if self.push: 228 if self.push:
221 self._track_new_contents(next_version) 229 self._track_new_contents(next_version)
(...skipping 20 matching lines...) Expand all
242 logging.disable(logging.INFO) 250 logging.disable(logging.INFO)
243 251
244 try: 252 try:
245 config = read_config(args.config_path) 253 config = read_config(args.config_path)
246 254
247 for ext_name in config.options('enabled'): 255 for ext_name in config.options('enabled'):
248 with ExtWatcher(ext_name, config, args.push, 256 with ExtWatcher(ext_name, config, args.push,
249 args.keep_repository) as watcher: 257 args.keep_repository) as watcher:
250 watcher.run() 258 watcher.run()
251 except ConfigurationError as e: 259 except ConfigurationError as e:
252 logging.error(e.message) 260 sys.exit(e.message)
Vasily Kuznetsov 2018/05/15 16:49:31 Perhaps `sys.exit(e.message)` would be more approp
tlucas 2018/05/16 09:52:37 Done.
LEFTRIGHT

Powered by Google App Engine
This is Rietveld