[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Script that attempts to push to a special git repository to verify that git |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 7 | credentials are configured correctly. It also verifies that gclient solution is |
| 8 | configured to use git checkout. |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 9 | |
| 10 | It will be added as gclient hook shortly before Chromium switches to git and |
| 11 | removed after the switch. |
| 12 | |
| 13 | When running as hook in *.corp.google.com network it will also report status |
| 14 | of the push attempt to the server (on appengine), so that chrome-infra team can |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 15 | collect information about misconfigured Git accounts. |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 16 | """ |
| 17 | |
Raul Tambre | 4cec3657 | 2019-09-22 17:30:32 | [diff] [blame] | 18 | from __future__ import print_function |
| 19 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 20 | import contextlib |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 21 | import datetime |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 22 | import errno |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 23 | import getpass |
| 24 | import json |
| 25 | import logging |
| 26 | import netrc |
| 27 | import optparse |
| 28 | import os |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 29 | import pprint |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 30 | import shutil |
| 31 | import socket |
| 32 | import ssl |
| 33 | import subprocess |
| 34 | import sys |
| 35 | import tempfile |
| 36 | import time |
| 37 | import urllib2 |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 38 | import urlparse |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 39 | |
| 40 | |
| 41 | # Absolute path to src/ directory. |
| 42 | REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 43 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 44 | # Absolute path to a file with gclient solutions. |
| 45 | GCLIENT_CONFIG = os.path.join(os.path.dirname(REPO_ROOT), '.gclient') |
| 46 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 47 | # Incremented whenever some changes to scrip logic are made. Change in version |
| 48 | # will cause the check to be rerun on next gclient runhooks invocation. |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 49 | CHECKER_VERSION = 1 |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 50 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 51 | # Do not attempt to upload a report after this date. |
| 52 | UPLOAD_DISABLE_TS = datetime.datetime(2014, 10, 1) |
| 53 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 54 | # URL to POST json with results to. |
| 55 | MOTHERSHIP_URL = ( |
| 56 | 'https://chromium-git-access.appspot.com/' |
| 57 | 'git_access/api/v1/reports/access_check') |
| 58 | |
| 59 | # Repository to push test commits to. |
| 60 | TEST_REPO_URL = 'https://chromium.googlesource.com/a/playground/access_test' |
| 61 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 62 | # Git-compatible gclient solution. |
| 63 | GOOD_GCLIENT_SOLUTION = { |
| 64 | 'name': 'src', |
Vadim Shtayura | daf35ab | 2014-08-23 02:08:53 | [diff] [blame] | 65 | 'deps_file': 'DEPS', |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 66 | 'managed': False, |
| 67 | 'url': 'https://chromium.googlesource.com/chromium/src.git', |
| 68 | } |
| 69 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 70 | # Possible chunks of git push response in case .netrc is misconfigured. |
| 71 | BAD_ACL_ERRORS = ( |
| 72 | '(prohibited by Gerrit)', |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 73 | 'does not match your user account', |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 74 | 'Git repository not found', |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 75 | 'Invalid user name or password', |
[email protected] | e92d872c | 2014-08-19 21:02:02 | [diff] [blame] | 76 | 'Please make sure you have the correct access rights', |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 77 | ) |
| 78 | |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 79 | # Git executable to call. |
| 80 | GIT_EXE = 'git.bat' if sys.platform == 'win32' else 'git' |
| 81 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 82 | |
| 83 | def is_on_bot(): |
| 84 | """True when running under buildbot.""" |
| 85 | return os.environ.get('CHROME_HEADLESS') == '1' |
| 86 | |
| 87 | |
| 88 | def is_in_google_corp(): |
| 89 | """True when running in google corp network.""" |
| 90 | try: |
| 91 | return socket.getfqdn().endswith('.corp.google.com') |
| 92 | except socket.error: |
| 93 | logging.exception('Failed to get FQDN') |
| 94 | return False |
| 95 | |
| 96 | |
| 97 | def is_using_git(): |
| 98 | """True if git checkout is used.""" |
| 99 | return os.path.exists(os.path.join(REPO_ROOT, '.git', 'objects')) |
| 100 | |
| 101 | |
| 102 | def is_using_svn(): |
| 103 | """True if svn checkout is used.""" |
| 104 | return os.path.exists(os.path.join(REPO_ROOT, '.svn')) |
| 105 | |
| 106 | |
| 107 | def read_git_config(prop): |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 108 | """Reads git config property of src.git repo. |
| 109 | |
| 110 | Returns empty string in case of errors. |
| 111 | """ |
| 112 | try: |
| 113 | proc = subprocess.Popen( |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 114 | [GIT_EXE, 'config', prop], stdout=subprocess.PIPE, cwd=REPO_ROOT) |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 115 | out, _ = proc.communicate() |
drott | 7813ff3 | 2015-08-18 06:27:00 | [diff] [blame] | 116 | return out.strip().decode('utf-8') |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 117 | except OSError as exc: |
| 118 | if exc.errno != errno.ENOENT: |
| 119 | logging.exception('Unexpected error when calling git') |
| 120 | return '' |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 121 | |
| 122 | |
| 123 | def read_netrc_user(netrc_obj, host): |
| 124 | """Reads 'user' field of a host entry in netrc. |
| 125 | |
| 126 | Returns empty string if netrc is missing, or host is not there. |
| 127 | """ |
| 128 | if not netrc_obj: |
| 129 | return '' |
| 130 | entry = netrc_obj.authenticators(host) |
| 131 | if not entry: |
| 132 | return '' |
| 133 | return entry[0] |
| 134 | |
| 135 | |
| 136 | def get_git_version(): |
| 137 | """Returns version of git or None if git is not available.""" |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 138 | try: |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 139 | proc = subprocess.Popen([GIT_EXE, '--version'], stdout=subprocess.PIPE) |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 140 | out, _ = proc.communicate() |
| 141 | return out.strip() if proc.returncode == 0 else '' |
| 142 | except OSError as exc: |
| 143 | if exc.errno != errno.ENOENT: |
| 144 | logging.exception('Unexpected error when calling git') |
| 145 | return '' |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 146 | |
| 147 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 148 | def read_gclient_solution(): |
| 149 | """Read information about 'src' gclient solution from .gclient file. |
| 150 | |
| 151 | Returns tuple: |
| 152 | (url, deps_file, managed) |
| 153 | or |
| 154 | (None, None, None) if no such solution. |
| 155 | """ |
| 156 | try: |
| 157 | env = {} |
| 158 | execfile(GCLIENT_CONFIG, env, env) |
[email protected] | 97c58bc | 2014-08-20 18:15:30 | [diff] [blame] | 159 | for sol in (env.get('solutions') or []): |
| 160 | if sol.get('name') == 'src': |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 161 | return sol.get('url'), sol.get('deps_file'), sol.get('managed') |
| 162 | return None, None, None |
| 163 | except Exception: |
| 164 | logging.exception('Failed to read .gclient solution') |
| 165 | return None, None, None |
| 166 | |
| 167 | |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 168 | def read_git_insteadof(host): |
| 169 | """Reads relevant insteadOf config entries.""" |
| 170 | try: |
| 171 | proc = subprocess.Popen([GIT_EXE, 'config', '-l'], stdout=subprocess.PIPE) |
| 172 | out, _ = proc.communicate() |
| 173 | lines = [] |
| 174 | for line in out.strip().split('\n'): |
| 175 | line = line.lower() |
| 176 | if 'insteadof=' in line and host in line: |
| 177 | lines.append(line) |
| 178 | return '\n'.join(lines) |
| 179 | except OSError as exc: |
| 180 | if exc.errno != errno.ENOENT: |
| 181 | logging.exception('Unexpected error when calling git') |
| 182 | return '' |
| 183 | |
| 184 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 185 | def scan_configuration(): |
| 186 | """Scans local environment for git related configuration values.""" |
| 187 | # Git checkout? |
| 188 | is_git = is_using_git() |
| 189 | |
| 190 | # On Windows HOME should be set. |
| 191 | if 'HOME' in os.environ: |
| 192 | netrc_path = os.path.join( |
| 193 | os.environ['HOME'], |
| 194 | '_netrc' if sys.platform.startswith('win') else '.netrc') |
| 195 | else: |
| 196 | netrc_path = None |
| 197 | |
| 198 | # Netrc exists? |
| 199 | is_using_netrc = netrc_path and os.path.exists(netrc_path) |
| 200 | |
| 201 | # Read it. |
| 202 | netrc_obj = None |
| 203 | if is_using_netrc: |
| 204 | try: |
| 205 | netrc_obj = netrc.netrc(netrc_path) |
| 206 | except Exception: |
| 207 | logging.exception('Failed to read netrc from %s', netrc_path) |
| 208 | netrc_obj = None |
| 209 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 210 | # Read gclient 'src' solution. |
| 211 | gclient_url, gclient_deps, gclient_managed = read_gclient_solution() |
| 212 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 213 | return { |
| 214 | 'checker_version': CHECKER_VERSION, |
| 215 | 'is_git': is_git, |
| 216 | 'is_home_set': 'HOME' in os.environ, |
| 217 | 'is_using_netrc': is_using_netrc, |
| 218 | 'netrc_file_mode': os.stat(netrc_path).st_mode if is_using_netrc else 0, |
| 219 | 'git_version': get_git_version(), |
| 220 | 'platform': sys.platform, |
| 221 | 'username': getpass.getuser(), |
| 222 | 'git_user_email': read_git_config('user.email') if is_git else '', |
| 223 | 'git_user_name': read_git_config('user.name') if is_git else '', |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 224 | 'git_insteadof': read_git_insteadof('chromium.googlesource.com'), |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 225 | 'chromium_netrc_email': |
| 226 | read_netrc_user(netrc_obj, 'chromium.googlesource.com'), |
| 227 | 'chrome_internal_netrc_email': |
| 228 | read_netrc_user(netrc_obj, 'chrome-internal.googlesource.com'), |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 229 | 'gclient_deps': gclient_deps, |
| 230 | 'gclient_managed': gclient_managed, |
| 231 | 'gclient_url': gclient_url, |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 232 | } |
| 233 | |
| 234 | |
| 235 | def last_configuration_path(): |
| 236 | """Path to store last checked configuration.""" |
| 237 | if is_using_git(): |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 238 | return os.path.join(REPO_ROOT, '.git', 'check_git_push_access_conf.json') |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 239 | elif is_using_svn(): |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 240 | return os.path.join(REPO_ROOT, '.svn', 'check_git_push_access_conf.json') |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 241 | else: |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 242 | return os.path.join(REPO_ROOT, '.check_git_push_access_conf.json') |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 243 | |
| 244 | |
| 245 | def read_last_configuration(): |
| 246 | """Reads last checked configuration if it exists.""" |
| 247 | try: |
| 248 | with open(last_configuration_path(), 'r') as f: |
| 249 | return json.load(f) |
| 250 | except (IOError, ValueError): |
| 251 | return None |
| 252 | |
| 253 | |
| 254 | def write_last_configuration(conf): |
| 255 | """Writes last checked configuration to a file.""" |
| 256 | try: |
| 257 | with open(last_configuration_path(), 'w') as f: |
| 258 | json.dump(conf, f, indent=2, sort_keys=True) |
| 259 | except IOError: |
| 260 | logging.exception('Failed to write JSON to %s', path) |
| 261 | |
| 262 | |
| 263 | @contextlib.contextmanager |
| 264 | def temp_directory(): |
| 265 | """Creates a temp directory, then nukes it.""" |
| 266 | tmp = tempfile.mkdtemp() |
| 267 | try: |
| 268 | yield tmp |
| 269 | finally: |
| 270 | try: |
| 271 | shutil.rmtree(tmp) |
| 272 | except (OSError, IOError): |
| 273 | logging.exception('Failed to remove temp directory %s', tmp) |
| 274 | |
| 275 | |
| 276 | class Runner(object): |
| 277 | """Runs a bunch of commands in some directory, collects logs from them.""" |
| 278 | |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 279 | def __init__(self, cwd, verbose): |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 280 | self.cwd = cwd |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 281 | self.verbose = verbose |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 282 | self.log = [] |
| 283 | |
| 284 | def run(self, cmd): |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 285 | self.append_to_log('> ' + ' '.join(cmd)) |
| 286 | retcode = -1 |
| 287 | try: |
| 288 | proc = subprocess.Popen( |
| 289 | cmd, |
| 290 | stdout=subprocess.PIPE, |
| 291 | stderr=subprocess.STDOUT, |
| 292 | cwd=self.cwd) |
| 293 | out, _ = proc.communicate() |
| 294 | out = out.strip() |
| 295 | retcode = proc.returncode |
| 296 | except OSError as exc: |
| 297 | out = str(exc) |
| 298 | if retcode: |
| 299 | out += '\n(exit code: %d)' % retcode |
| 300 | self.append_to_log(out) |
| 301 | return retcode |
| 302 | |
| 303 | def append_to_log(self, text): |
| 304 | if text: |
| 305 | self.log.append(text) |
| 306 | if self.verbose: |
| 307 | logging.warning(text) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 308 | |
| 309 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 310 | def check_git_config(conf, report_url, verbose): |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 311 | """Attempts to push to a git repository, reports results to a server. |
| 312 | |
| 313 | Returns True if the check finished without incidents (push itself may |
| 314 | have failed) and should NOT be retried on next invocation of the hook. |
| 315 | """ |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 316 | # Don't even try to push if netrc is not configured. |
| 317 | if not conf['chromium_netrc_email']: |
| 318 | return upload_report( |
| 319 | conf, |
| 320 | report_url, |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 321 | verbose, |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 322 | push_works=False, |
| 323 | push_log='', |
| 324 | push_duration_ms=0) |
| 325 | |
| 326 | # Ref to push to, each user has its own ref. |
| 327 | ref = 'refs/push-test/%s' % conf['chromium_netrc_email'] |
| 328 | |
| 329 | push_works = False |
| 330 | flake = False |
| 331 | started = time.time() |
| 332 | try: |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 333 | logging.warning('Checking push access to the git repository...') |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 334 | with temp_directory() as tmp: |
| 335 | # Prepare a simple commit on a new timeline. |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 336 | runner = Runner(tmp, verbose) |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 337 | runner.run([GIT_EXE, 'init', '.']) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 338 | if conf['git_user_name']: |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 339 | runner.run([GIT_EXE, 'config', 'user.name', conf['git_user_name']]) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 340 | if conf['git_user_email']: |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 341 | runner.run([GIT_EXE, 'config', 'user.email', conf['git_user_email']]) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 342 | with open(os.path.join(tmp, 'timestamp'), 'w') as f: |
| 343 | f.write(str(int(time.time() * 1000))) |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 344 | runner.run([GIT_EXE, 'add', 'timestamp']) |
| 345 | runner.run([GIT_EXE, 'commit', '-m', 'Push test.']) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 346 | # Try to push multiple times if it fails due to issues other than ACLs. |
| 347 | attempt = 0 |
| 348 | while attempt < 5: |
| 349 | attempt += 1 |
| 350 | logging.info('Pushing to %s %s', TEST_REPO_URL, ref) |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 351 | ret = runner.run( |
| 352 | [GIT_EXE, 'push', TEST_REPO_URL, 'HEAD:%s' % ref, '-f']) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 353 | if not ret: |
| 354 | push_works = True |
| 355 | break |
| 356 | if any(x in runner.log[-1] for x in BAD_ACL_ERRORS): |
| 357 | push_works = False |
| 358 | break |
| 359 | except Exception: |
| 360 | logging.exception('Unexpected exception when pushing') |
| 361 | flake = True |
| 362 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 363 | if push_works: |
| 364 | logging.warning('Git push works!') |
| 365 | else: |
| 366 | logging.warning( |
| 367 | 'Git push doesn\'t work, which is fine if you are not a committer.') |
| 368 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 369 | uploaded = upload_report( |
| 370 | conf, |
| 371 | report_url, |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 372 | verbose, |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 373 | push_works=push_works, |
| 374 | push_log='\n'.join(runner.log), |
| 375 | push_duration_ms=int((time.time() - started) * 1000)) |
| 376 | return uploaded and not flake |
| 377 | |
| 378 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 379 | def check_gclient_config(conf): |
| 380 | """Shows warning if gclient solution is not properly configured for git.""" |
[email protected] | 97c58bc | 2014-08-20 18:15:30 | [diff] [blame] | 381 | # Ignore configs that do not have 'src' solution at all. |
| 382 | if not conf['gclient_url']: |
| 383 | return |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 384 | current = { |
| 385 | 'name': 'src', |
Vadim Shtayura | d484d59 | 2014-08-23 02:49:13 | [diff] [blame] | 386 | 'deps_file': conf['gclient_deps'] or 'DEPS', |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 387 | 'managed': conf['gclient_managed'] or False, |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 388 | 'url': conf['gclient_url'], |
| 389 | } |
vadimsh | 910466b | 2014-08-24 23:03:42 | [diff] [blame] | 390 | # After depot_tools r291592 both DEPS and .DEPS.git are valid. |
| 391 | good = GOOD_GCLIENT_SOLUTION.copy() |
| 392 | good['deps_file'] = current['deps_file'] |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 393 | if current == good: |
| 394 | return |
| 395 | # Show big warning if url or deps_file is wrong. |
| 396 | if current['url'] != good['url'] or current['deps_file'] != good['deps_file']: |
Raul Tambre | 4cec3657 | 2019-09-22 17:30:32 | [diff] [blame] | 397 | print('-' * 80) |
| 398 | print('Your gclient solution is not set to use supported git workflow!') |
| 399 | print() |
| 400 | print('Your \'src\' solution (in %s):' % GCLIENT_CONFIG) |
| 401 | print(pprint.pformat(current, indent=2)) |
| 402 | print() |
| 403 | print('Correct \'src\' solution to use git:') |
| 404 | print(pprint.pformat(good, indent=2)) |
| 405 | print() |
| 406 | print('Please update your .gclient file ASAP.') |
| 407 | print('-' * 80) |
[email protected] | b3b918c7 | 2014-08-21 00:08:32 | [diff] [blame] | 408 | # Show smaller (additional) warning about managed workflow. |
| 409 | if current['managed']: |
Raul Tambre | 4cec3657 | 2019-09-22 17:30:32 | [diff] [blame] | 410 | print('-' * 80) |
| 411 | print('You are using managed gclient mode with git, which was deprecated ' |
| 412 | 'on 8/22/13:') |
| 413 | print('https://groups.google.com/a/chromium.org/' |
| 414 | 'forum/#!topic/chromium-dev/n9N5N3JL2_U') |
| 415 | print() |
| 416 | print('It is strongly advised to switch to unmanaged mode. For more ' |
| 417 | 'information about managed mode and reasons for its deprecation see:') |
| 418 | print( |
| 419 | 'http://www.chromium.org/developers/how-tos/get-the-code/gclient-managed-mode' |
| 420 | ) |
| 421 | print() |
| 422 | print('There\'s also a large suite of tools to assist managing git ' |
| 423 | 'checkouts.\nSee \'man depot_tools\' (or read ' |
| 424 | 'depot_tools/man/html/depot_tools.html).') |
| 425 | print('-' * 80) |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 426 | |
| 427 | |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 428 | def upload_report( |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 429 | conf, report_url, verbose, push_works, push_log, push_duration_ms): |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 430 | """Posts report to the server, returns True if server accepted it. |
| 431 | |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 432 | Uploads the report only if script is running in Google corp network. Otherwise |
| 433 | just prints the report. |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 434 | """ |
| 435 | report = conf.copy() |
| 436 | report.update( |
| 437 | push_works=push_works, |
| 438 | push_log=push_log, |
| 439 | push_duration_ms=push_duration_ms) |
| 440 | |
| 441 | as_bytes = json.dumps({'access_check': report}, indent=2, sort_keys=True) |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 442 | if verbose: |
Raul Tambre | 4cec3657 | 2019-09-22 17:30:32 | [diff] [blame] | 443 | print('Status of git push attempt:') |
| 444 | print(as_bytes) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 445 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 446 | # Do not upload it outside of corp or if server side is already disabled. |
| 447 | if not is_in_google_corp() or datetime.datetime.now() > UPLOAD_DISABLE_TS: |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 448 | if verbose: |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 449 | print ( |
| 450 | 'You can send the above report to [email protected] ' |
| 451 | 'if you need help to set up you committer git account.') |
| 452 | return True |
| 453 | |
| 454 | req = urllib2.Request( |
| 455 | url=report_url, |
| 456 | data=as_bytes, |
| 457 | headers={'Content-Type': 'application/json; charset=utf-8'}) |
| 458 | |
| 459 | attempt = 0 |
| 460 | success = False |
| 461 | while not success and attempt < 10: |
| 462 | attempt += 1 |
| 463 | try: |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 464 | logging.warning( |
| 465 | 'Attempting to upload the report to %s...', |
| 466 | urlparse.urlparse(report_url).netloc) |
| 467 | resp = urllib2.urlopen(req, timeout=5) |
| 468 | report_id = None |
| 469 | try: |
| 470 | report_id = json.load(resp)['report_id'] |
| 471 | except (ValueError, TypeError, KeyError): |
| 472 | pass |
| 473 | logging.warning('Report uploaded: %s', report_id) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 474 | success = True |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 475 | except (urllib2.URLError, socket.error, ssl.SSLError) as exc: |
[email protected] | aa52d31 | 2014-08-18 20:28:52 | [diff] [blame] | 476 | logging.warning('Failed to upload the report: %s', exc) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 477 | return success |
| 478 | |
| 479 | |
| 480 | def main(args): |
| 481 | parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) |
| 482 | parser.add_option( |
| 483 | '--running-as-hook', |
| 484 | action='store_true', |
| 485 | help='Set when invoked from gclient hook') |
| 486 | parser.add_option( |
| 487 | '--report-url', |
| 488 | default=MOTHERSHIP_URL, |
| 489 | help='URL to submit the report to') |
| 490 | parser.add_option( |
| 491 | '--verbose', |
| 492 | action='store_true', |
| 493 | help='More logging') |
| 494 | options, args = parser.parse_args() |
| 495 | if args: |
| 496 | parser.error('Unknown argument %s' % args) |
| 497 | logging.basicConfig( |
| 498 | format='%(message)s', |
| 499 | level=logging.INFO if options.verbose else logging.WARN) |
| 500 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 501 | # When invoked not as a hook, always run the check. |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 502 | if not options.running_as_hook: |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 503 | config = scan_configuration() |
| 504 | check_gclient_config(config) |
| 505 | check_git_config(config, options.report_url, True) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 506 | return 0 |
| 507 | |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 508 | # Always do nothing on bots. |
| 509 | if is_on_bot(): |
| 510 | return 0 |
| 511 | |
| 512 | # Read current config, verify gclient solution looks correct. |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 513 | config = scan_configuration() |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 514 | check_gclient_config(config) |
| 515 | |
| 516 | # Do not attempt to push from non-google owned machines. |
| 517 | if not is_in_google_corp(): |
| 518 | logging.info('Skipping git push check: non *.corp.google.com machine.') |
| 519 | return 0 |
| 520 | |
| 521 | # Skip git push check if current configuration was already checked. |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 522 | if config == read_last_configuration(): |
| 523 | logging.info('Check already performed, skipping.') |
| 524 | return 0 |
| 525 | |
| 526 | # Run the check. Mark configuration as checked only on success. Ignore any |
| 527 | # exceptions or errors. This check must not break gclient runhooks. |
| 528 | try: |
[email protected] | 46b32a8 | 2014-08-19 00:37:57 | [diff] [blame] | 529 | ok = check_git_config(config, options.report_url, False) |
[email protected] | 94c6412 | 2014-08-16 02:03:55 | [diff] [blame] | 530 | if ok: |
| 531 | write_last_configuration(config) |
| 532 | else: |
| 533 | logging.warning('Check failed and will be retried on the next run') |
| 534 | except Exception: |
| 535 | logging.exception('Unexpected exception when performing git access check') |
| 536 | return 0 |
| 537 | |
| 538 | |
| 539 | if __name__ == '__main__': |
| 540 | sys.exit(main(sys.argv[1:])) |