Fix gae.py's detection of current active credentials.

There are two problems:
1. Looks like gcloud is not using ~/.config/cloud/credentials anymore. We now
   use public API to check for current active account instead.
2. Looks like appcfg.py is not aware of gcloud's credentials anymore, so we need
   to reintroduce appcfg-specific login command again. We call it appcfg_login.
   This will go away soon-ish once we switch to gcloud as the only supported
   mode of operation.

R=nodir@chromium.org, maruel@chromium.org
BUG=747588

Review-Url: https://codereview.chromium.org/2987613002
diff --git a/appengine/components/tool_support/gae_sdk_utils.py b/appengine/components/tool_support/gae_sdk_utils.py
index acb1c22..61b96fd 100644
--- a/appengine/components/tool_support/gae_sdk_utils.py
+++ b/appengine/components/tool_support/gae_sdk_utils.py
@@ -338,11 +338,12 @@
     """Runs appcfg.py <args>, deserializes its output and returns it."""
     if USE_GCLOUD:
       raise Error('Attempting to run appcfg.py %s' % ' '.join(args))
-    if not is_gcloud_oauth2_token_cached():
-      raise LoginRequiredError('Login first using \'gcloud auth login\'.')
+    if not is_appcfg_oauth_token_cached():
+      raise LoginRequiredError('Login first using \'gae.py appcfg_login\'.')
     cmd = [
       sys.executable,
       os.path.join(self._gae_sdk, 'appcfg.py'),
+      '--skip_sdk_update_check',
       '--application', self.app_id,
     ]
     if self._verbose:
@@ -353,7 +354,7 @@
   def run_gcloud(self, args):
     """Runs 'gcloud <args> --project ... --format ...' and parses the output."""
     gcloud = find_gcloud()
-    if not is_gcloud_oauth2_token_cached():
+    if not is_gcloud_auth_set():
       raise LoginRequiredError('Login first using \'gcloud auth login\'')
     raw = self.run_cmd(
         [gcloud] + args + ['--project', self.app_id, '--format', 'json'])
@@ -713,16 +714,49 @@
     return raw_input('Continue? [y/N] ') in ('y', 'Y')
 
 
-def is_gcloud_oauth2_token_cached():
+def is_gcloud_auth_set():
   """Returns false if 'gcloud auth login' needs to be run."""
-  p = os.path.join(os.path.expanduser('~'), '.config', 'gcloud', 'credentials')
   try:
-    with open(p) as f:
-      return len(json.load(f)['data']) != 0
-  except (KeyError, IOError, OSError, ValueError):
+    # This returns an email address of currently active account or empty string
+    # if no account is active.
+    output = subprocess.check_output([
+      find_gcloud(), 'auth', 'list',
+      '--filter=status:ACTIVE', '--format=value(account)',
+    ])
+    return bool(output.strip())
+  except subprocess.CalledProcessError as exc:
+    logging.error('Failed to check active gcloud account: %s', exc)
     return False
 
 
+# TODO(vadimsh): Can be removed if using 'gcloud'.
+def _appcfg_oauth2_tokens():
+  return os.path.join(os.path.expanduser('~'), '.appcfg_oauth2_tokens')
+
+
+# TODO(vadimsh): Can be removed if using 'gcloud'.
+def is_appcfg_oauth_token_cached():
+  """Returns true if ~/.appcfg_oauth2_tokens exists."""
+  return os.path.exists(_appcfg_oauth2_tokens())
+
+
+# TODO(vadimsh): Can be removed if using 'gcloud'.
+def appcfg_login(app):
+  """Starts appcfg.py's login flow."""
+  if not _GAE_SDK_PATH:
+    raise ValueError('Call setup_gae_sdk first')
+  if os.path.exists(_appcfg_oauth2_tokens()):
+    os.remove(_appcfg_oauth2_tokens())
+  # HACK: Call a command with no side effect to launch the flow.
+  subprocess.call([
+    sys.executable,
+    os.path.join(_GAE_SDK_PATH, 'appcfg.py'),
+    '--application', app.app_id,
+    '--noauth_local_webserver',
+    'list_versions',
+  ], cwd=app.app_dir)
+
+
 def setup_gae_env():
   """Sets up App Engine Python test environment by modifying sys.path."""
   sdk_path = find_gae_sdk()
diff --git a/appengine/components/tools/gae.py b/appengine/components/tools/gae.py
index ada7216..673adfc 100755
--- a/appengine/components/tools/gae.py
+++ b/appengine/components/tools/gae.py
@@ -72,6 +72,20 @@
 ##
 
 
+def CMDappcfg_login(parser, args):
+  """Sets up authentication for appcfg.py usage [DEPRECATED]."""
+  app, _, _ = parser.parse_args(args)
+  print (
+      'Since appcfg.py doesn\'t support explicit login command, we\'ll run '
+      'innocent "list_version" instead. It will trigger appcfg\'s login flow. '
+      '\n'
+      'It\'s fine if "list_version" call itself fails - at this point we have '
+      'the necessary credentials cached and other subcommands should be able '
+      'to use them.\n')
+  gae_sdk_utils.appcfg_login(app)
+  return 0
+
+
 def CMDactive(parser, args):
   """Prints the active versions on the server.