File: //snap/google-cloud-cli/current/lib/googlecloudsdk/api_lib/app/runtimes/fingerprinter.py
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Package containing fingerprinting for all runtimes.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from gae_ext_runtime import ext_runtime
from googlecloudsdk.api_lib.app import ext_runtime_adapter
from googlecloudsdk.api_lib.app.runtimes import python
from googlecloudsdk.api_lib.app.runtimes import python_compat
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
RUNTIMES = [
# Note that ordering of runtimes here is very important and changes to the
# relative positions need to be tested carefully.
# Custom comes first, if we've got a Dockerfile this is a custom runtime.
ext_runtime_adapter.CoreRuntimeLoader('custom', 'Custom',
['custom']),
# Go's position is relatively flexible due to its orthogonal nature.
ext_runtime_adapter.CoreRuntimeLoader('go', 'Go', ['go', 'custom']),
ext_runtime_adapter.CoreRuntimeLoader('ruby', 'Ruby', ['ruby', 'custom']),
ext_runtime_adapter.CoreRuntimeLoader('nodejs', 'Node.js',
['nodejs', 'custom']),
ext_runtime_adapter.CoreRuntimeLoader('java', 'Java',
['java', 'java7', 'custom']),
python_compat,
# Python and PHP are last because they match if any .py or .php file is
# present.
ext_runtime_adapter.CoreRuntimeLoader('python', 'Python',
['python', 'custom']),
ext_runtime_adapter.CoreRuntimeLoader('php', 'PHP', ['php', 'custom']),
]
class UnidentifiedDirectoryError(exceptions.Error):
"""Raised when GenerateConfigs() can't identify the directory."""
def __init__(self, path):
"""Constructor.
Args:
path: (basestring) Directory we failed to identify.
"""
super(UnidentifiedDirectoryError, self).__init__(
'Unrecognized directory type: [{0}]'.format(path))
self.path = path
class ExtRuntimeError(exceptions.Error):
"""ext_runtime.Error errors are converted to this."""
class ConflictingConfigError(exceptions.Error):
"""Property in app.yaml conflicts with params passed to fingerprinter."""
class AlterConfigFileError(exceptions.Error):
"""Error when attempting to update an existing config file (app.yaml)."""
def __init__(self, inner_exception):
super(AlterConfigFileError, self).__init__(
'Could not alter app.yaml due to an internal error:\n{0}\n'
'Please update app.yaml manually.'.format(inner_exception))
def IdentifyDirectory(path, params=None):
"""Try to identify the given directory.
As a side-effect, if there is a config file in 'params' with a runtime of
'custom', this sets params.custom to True.
Args:
path: (basestring) Root directory to identify.
params: (ext_runtime.Params or None) Parameters passed through to the
fingerprinters. Uses defaults if not provided.
Returns:
(ext_runtime.Configurator or None) Returns a module if we've identified
it, None if not.
"""
if not params:
params = ext_runtime.Params()
# Parameter runtime has precedence
if params.runtime:
specified_runtime = params.runtime
elif params.appinfo:
specified_runtime = params.appinfo.GetEffectiveRuntime()
else:
specified_runtime = None
if specified_runtime == 'custom':
params.custom = True
for runtime in RUNTIMES:
# If we have an app.yaml, don't fingerprint for any runtimes that don't
# allow the runtime name it specifies.
if (specified_runtime and runtime.ALLOWED_RUNTIME_NAMES and
specified_runtime not in runtime.ALLOWED_RUNTIME_NAMES):
log.info('Not checking for [%s] because runtime is [%s]' %
(runtime.NAME, specified_runtime))
continue
try:
configurator = runtime.Fingerprint(path, params)
except ext_runtime.Error as ex:
raise ExtRuntimeError(ex.message)
if configurator:
return configurator
return None
def _GetModule(path, params=None, config_filename=None):
"""Helper function for generating configs.
Args:
path: (basestring) Root directory to identify.
params: (ext_runtime.Params or None) Parameters passed through to the
fingerprinters. Uses defaults if not provided.
config_filename: (str or None) Filename of the config file (app.yaml).
Raises:
UnidentifiedDirectoryError: No runtime module matched the directory.
ConflictingConfigError: Current app.yaml conflicts with other params.
Returns:
ext_runtime.Configurator, the configurator for the path
"""
if not params:
params = ext_runtime.Params()
config = params.appinfo
# An app.yaml exists, results in a lot more cases
if config and not params.deploy:
# Enforce --custom
if not params.custom:
raise ConflictingConfigError(
'Configuration file already exists. This command generates an '
'app.yaml configured to run an application on Google App Engine. '
'To create the configuration files needed to run this '
'application with docker, try `gcloud preview app gen-config '
'--custom`.')
# Check that current config is for MVM
if not config.IsVm():
raise ConflictingConfigError(
'gen-config is only supported for App Engine Flexible. Please '
'use "vm: true" in your app.yaml if you would like to use App Engine '
'Flexible to run your application.')
# Check for conflicting --runtime and runtime in app.yaml
if (config.GetEffectiveRuntime() != 'custom' and params.runtime is not None
and params.runtime != config.GetEffectiveRuntime()):
raise ConflictingConfigError(
'[{0}] contains "runtime: {1}" which conficts with '
'--runtime={2}.'.format(config_filename, config.GetEffectiveRuntime(),
params.runtime))
module = IdentifyDirectory(path, params)
if not module:
raise UnidentifiedDirectoryError(path)
return module
def GenerateConfigs(path, params=None, config_filename=None):
"""Identify runtime and generate config files for a directory.
If a runtime can be identified for the given directory, calls the runtime's
GenerateConfigs method, which writes configs to the directory.
Args:
path: (basestring) Root directory to identify.
params: (ext_runtime.Params or None) Parameters passed through to the
fingerprinters. Uses defaults if not provided.
config_filename: (str or None) Filename of the config file (app.yaml).
Raises:
ExtRuntimeError: if there was an error generating configs
Returns:
(bool): True if files were written
"""
module = _GetModule(path, params=params, config_filename=config_filename)
try:
return module.GenerateConfigs()
except ext_runtime.Error as ex:
raise ExtRuntimeError(ex.message)
def GenerateConfigData(path, params=None, config_filename=None):
"""Identify runtime and generate contents of config files for a directory.
If a runtime can be identified for the given directory, calls the runtime's
GenerateConfigData method, which generates the contents of config files.
Args:
path: (basestring) Root directory to identify.
params: (ext_runtime.Params or None) Parameters passed through to the
fingerprinters. Uses defaults if not provided.
config_filename: (str or None) Filename of the config file (app.yaml).
Raises:
ExtRuntimeError: if there was an error generating configs
Returns:
[ext_runtime.GeneratedFile] generated config files.
"""
module = _GetModule(path, params=params, config_filename=config_filename)
try:
return module.GenerateConfigData()
except ext_runtime.Error as ex:
raise ExtRuntimeError(ex.message)