| Index: appengine/components/tool_support/gae_sdk_utils.py
|
| diff --git a/appengine/components/tool_support/gae_sdk_utils.py b/appengine/components/tool_support/gae_sdk_utils.py
|
| index 97d015023eff893a9077bcd1234e7b672e852923..bd498ed0b92c4d7aa38f3b25c20c185d47f82535 100644
|
| --- a/appengine/components/tool_support/gae_sdk_utils.py
|
| +++ b/appengine/components/tool_support/gae_sdk_utils.py
|
| @@ -33,25 +33,40 @@ RUNTIME_TO_SDK = {
|
| 'python27': PYTHON_GAE_SDK,
|
| }
|
|
|
| -# Exe name => instructions how to install it.
|
| -KNOWN_TOOLS = {
|
| - 'gcloud':
|
| - 'Download and install the Google Cloud SDK from '
|
| - 'https://cloud.google.com/sdk/',
|
| - 'aedeploy':
|
| - 'Install with: go install '
|
| - 'google.golang.org/appengine/cmd/aedeploy',
|
| -}
|
| -
|
| -
|
| # Path to a current SDK, set in setup_gae_sdk, accessible by gae_sdk_path.
|
| _GAE_SDK_PATH = None
|
|
|
|
|
| -class BadEnvironmentConfig(Exception):
|
| +class Error(Exception):
|
| + """Base class for a fatal error."""
|
| +
|
| +
|
| +class BadEnvironmentError(Error):
|
| """Raised when required tools or environment are missing."""
|
|
|
|
|
| +class UnsupportedModuleError(Error):
|
| + """Raised when trying to deploy MVM or Flex module."""
|
| +
|
| +
|
| +class LoginRequiredError(Error):
|
| + """Raised by Application methods if use has to go through login flow."""
|
| +
|
| +
|
| +def find_gcloud():
|
| + """Searches for 'gcloud' binary in PATH and returns absolute path to it.
|
| +
|
| + Raises BadEnvironmentError error if it's not there.
|
| + """
|
| + for path in os.environ['PATH'].split(os.pathsep):
|
| + exe_file = os.path.join(path, 'gcloud')
|
| + if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):
|
| + return exe_file
|
| + raise BadEnvironmentError(
|
| + 'Can\'t find "gcloud" in PATH. Install the Google Cloud SDK from '
|
| + 'https://cloud.google.com/sdk/')
|
| +
|
| +
|
| def find_gae_sdk(sdk_name=PYTHON_GAE_SDK, search_dir=TOOLS_DIR):
|
| """Returns the path to GAE SDK if found, else None."""
|
| # First search up the directories up to root.
|
| @@ -190,10 +205,6 @@ def gae_sdk_path():
|
| return _GAE_SDK_PATH
|
|
|
|
|
| -class LoginRequiredError(Exception):
|
| - """Raised by Application methods if use has to go through login flow."""
|
| -
|
| -
|
| ModuleFile = collections.namedtuple('ModuleFile', ['path', 'data'])
|
|
|
|
|
| @@ -283,14 +294,18 @@ class Application(object):
|
| return subprocess.call(cmd, cwd=self._app_dir) == 0
|
|
|
| def run_cmd(self, cmd, cwd=None):
|
| - """Runs subprocess, capturing the output."""
|
| + """Runs subprocess, capturing the output.
|
| +
|
| + Doesn't close stdin, since gcloud may be asking for user input. If this is
|
| + undesirable (e.g when gae.py is used from scripts), close 'stdin' of gae.py
|
| + process itself.
|
| + """
|
| logging.debug('Running %s', cmd)
|
| proc = subprocess.Popen(
|
| cmd,
|
| cwd=cwd or self._app_dir,
|
| - stdout=subprocess.PIPE,
|
| - stdin=subprocess.PIPE)
|
| - output, _ = proc.communicate(None)
|
| + stdout=subprocess.PIPE)
|
| + output, _ = proc.communicate()
|
| if proc.returncode:
|
| sys.stderr.write('\n' + output + '\n')
|
| raise subprocess.CalledProcessError(proc.returncode, cmd, output)
|
| @@ -312,10 +327,10 @@ class Application(object):
|
|
|
| def run_gcloud(self, args):
|
| """Runs gcloud <args>."""
|
| + gcloud = find_gcloud()
|
| if not is_gcloud_oauth2_token_cached():
|
| raise LoginRequiredError('Login first using \'gcloud auth login\'')
|
| - check_tool_in_path('gcloud')
|
| - return self.run_cmd(['gcloud'] + args)
|
| + return self.run_cmd([gcloud] + args)
|
|
|
| def list_versions(self):
|
| """List all uploaded versions.
|
| @@ -349,27 +364,23 @@ class Application(object):
|
|
|
| Supports deploying modules both with Managed VMs and AppEngine v1 runtime.
|
| """
|
| - reg_modules = []
|
| - mvm_modules = []
|
| + mods = []
|
| try:
|
| for m in sorted(modules or self.modules):
|
| mod = self._modules[m]
|
| if mod.data.get('vm'):
|
| - mvm_modules.append(mod)
|
| - else:
|
| - reg_modules.append(mod)
|
| + raise UnsupportedModuleError('MVM is not supported: %s' % m)
|
| + if mod.data.get('env') == 'flex':
|
| + raise UnsupportedModuleError('Flex is not supported yet: %s' % m)
|
| if mod.data.get('runtime') == 'go' and not os.environ.get('GOROOT'):
|
| - raise BadEnvironmentConfig('GOROOT must be set when deploying Go app')
|
| + raise BadEnvironmentError('GOROOT must be set when deploying Go app')
|
| + mods.append(mod)
|
| except KeyError as e:
|
| raise ValueError('Unknown module: %s' % e)
|
| - if reg_modules:
|
| - # Always make 'default' the first module to be uploaded.
|
| - reg_modules.sort(key=lambda x: '' if x == 'default' else x)
|
| - self.run_appcfg(
|
| - ['update'] + [m.path for m in reg_modules] + ['--version', version])
|
| - # Go modules have to be deployed one at a time, based on docs.
|
| - for m in mvm_modules:
|
| - self.deploy_mvm_module(m, version)
|
| + # Always make 'default' the first module to be uploaded.
|
| + mods.sort(key=lambda x: '' if x == 'default' else x)
|
| + self.run_appcfg(
|
| + ['update'] + [m.path for m in mods] + ['--version', version])
|
|
|
| def update_indexes(self):
|
| """Deploys new index.yaml."""
|
| @@ -458,27 +469,6 @@ class Application(object):
|
| return -1
|
| return sorted(actual_versions, key=extract_version_num)
|
|
|
| - def deploy_mvm_module(self, mod, version):
|
| - """Uses gcloud to upload MVM module using remote docker build.
|
| -
|
| - Assumes 'gcloud' and 'aedeploy' are in PATH.
|
| - """
|
| - # 'aedeploy' requires cwd to be set to module path.
|
| - check_tool_in_path('gcloud')
|
| - cmd = [
|
| - 'gcloud', 'app', 'deploy', os.path.basename(mod.path),
|
| - '--project', self.app_id,
|
| - '--version', version,
|
| - '--docker-build', 'remote',
|
| - '--no-promote', '--force',
|
| - ]
|
| - if self._verbose:
|
| - cmd.extend(['--verbosity', 'debug'])
|
| - if mod.data.get('runtime') == 'go':
|
| - check_tool_in_path('aedeploy')
|
| - cmd = ['aedeploy'] + cmd
|
| - self.run_cmd(cmd, cwd=os.path.dirname(mod.path))
|
| -
|
| def get_actives(self, modules=None):
|
| """Returns active version(s)."""
|
| args = [
|
| @@ -506,18 +496,6 @@ class Application(object):
|
| ]
|
|
|
|
|
| -def check_tool_in_path(tool):
|
| - """Raises BadEnvironmentConfig error if no such executable in PATH."""
|
| - for path in os.environ['PATH'].split(os.pathsep):
|
| - exe_file = os.path.join(path, tool)
|
| - if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):
|
| - return
|
| - msg = 'Can\'t find "%s" in PATH.' % tool
|
| - if tool in KNOWN_TOOLS:
|
| - msg += ' ' + KNOWN_TOOLS[tool]
|
| - raise BadEnvironmentConfig(msg)
|
| -
|
| -
|
| def setup_env(app_dir, app_id, version, module_id, remote_api=False):
|
| """Setups os.environ so GAE code works."""
|
| # GCS library behaves differently when running under remote_api. It uses
|
| @@ -578,7 +556,7 @@ def process_sdk_options(parser, options, app_dir):
|
|
|
| try:
|
| runtime = get_app_runtime(find_app_yamls(app_dir))
|
| - except (BadEnvironmentConfig, ValueError) as exc:
|
| + except (Error, ValueError) as exc:
|
| parser.error(str(exc))
|
|
|
| sdk_path = options.sdk_path or find_gae_sdk(RUNTIME_TO_SDK[runtime], app_dir)
|
| @@ -589,7 +567,7 @@ def process_sdk_options(parser, options, app_dir):
|
|
|
| try:
|
| return Application(app_dir, options.app_id, options.verbose)
|
| - except (BadEnvironmentConfig, ValueError) as e:
|
| + except (Error, ValueError) as e:
|
| parser.error(str(e))
|
|
|
|
|
| @@ -636,5 +614,5 @@ def setup_gae_env():
|
| """Sets up App Engine Python test environment."""
|
| sdk_path = find_gae_sdk(PYTHON_GAE_SDK)
|
| if not sdk_path:
|
| - raise BadEnvironmentConfig('Couldn\'t find GAE SDK.')
|
| + raise BadEnvironmentError('Couldn\'t find GAE SDK.')
|
| setup_gae_sdk(sdk_path)
|
|
|