def __init__(self, config_paths, app_id=None): """Initializer for ApplicationConfiguration. Args: config_paths: A list of strings containing the paths to yaml files, or to directories containing them. app_id: A string that is the application id, or None if the application id from the yaml or xml file should be used. Raises: InvalidAppConfigError: On invalid configuration. """ self.modules = [] self.dispatch = None # It's really easy to add a test case that passes in a string rather than # a list of strings, so guard against that. assert not isinstance(config_paths, basestring) config_paths = self._config_files_from_paths(config_paths) for config_path in config_paths: # TODO: add support for backends.xml and dispatch.xml here if (config_path.endswith('backends.yaml') or config_path.endswith('backends.yml')): # TODO: Reuse the ModuleConfiguration created for the app.yaml # instead of creating another one for the same file. app_yaml = config_path.replace('backends.y', 'app.y') self.modules.extend( BackendsConfiguration(app_yaml, config_path, app_id).get_backend_configurations()) elif (config_path.endswith('dispatch.yaml') or config_path.endswith('dispatch.yml')): if self.dispatch: raise errors.InvalidAppConfigError( 'Multiple dispatch.yaml files specified') self.dispatch = DispatchConfiguration(config_path) else: module_configuration = ModuleConfiguration(config_path, app_id) self.modules.append(module_configuration) application_ids = set(module.application for module in self.modules) if len(application_ids) > 1: raise errors.InvalidAppConfigError( 'More than one application ID found: %s' % ', '.join(sorted(application_ids))) self._app_id = application_ids.pop() module_names = set() for module in self.modules: if module.module_name in module_names: raise errors.InvalidAppConfigError('Duplicate module: %s' % module.module_name) module_names.add(module.module_name) if self.dispatch: if appinfo.DEFAULT_MODULE not in module_names: raise errors.InvalidAppConfigError( 'A default module must be specified.') missing_modules = ( set(module_name for _, module_name in self.dispatch.dispatch) - module_names) if missing_modules: raise errors.InvalidAppConfigError( 'Modules %s specified in dispatch.yaml are not defined by a yaml ' 'file.' % sorted(missing_modules))
def _files_in_dir_matching(dir_path, names): abs_names = [os.path.join(dir_path, name) for name in names] files = [f for f in abs_names if os.path.exists(f)] if len(files) > 1: raise errors.InvalidAppConfigError('Directory "%s" contains %s' % (dir_path, ' and '.join(names))) return files
def __init__(self, root_path, url_map, app_info_default_expiration=None): """Initializer for StaticDirHandler. Args: root_path: A string containing the full path of the directory containing the application's app.yaml file. url_map: An appinfo.URLMap instance containing the configuration for this handler. app_info_default_expiration: A string containing the value of the default_expiration value from app info yaml. None if no default_expiration is set. """ url = url_map.url # Take a url pattern like "/css" and transform it into a match pattern like # "/css/(?P<file>.*)$" if url[-1] != '/': url += '/' try: url_pattern = re.compile('%s(?P<file>.*)$' % url) except re.error as e: raise errors.InvalidAppConfigError( 'invalid url %r in static_dir handler: %s' % (url, e)) super(StaticDirHandler, self).__init__(root_path, url_map, url_pattern, app_info_default_expiration)
def __init__(self, root_path, url_map): """Initializer for StaticFilesHandler. Args: root_path: A string containing the full path of the directory containing the application's app.yaml file. url_map: An appinfo.URLMap instance containing the configuration for this handler. """ try: url_pattern = re.compile('%s$' % url_map.url) except re.error, e: raise errors.InvalidAppConfigError( 'invalid url %r in static_files handler: %s' % (url_map.url, e))
def __init__(self, root_path, url_map): """Initializer for StaticDirHandler. Args: root_path: A string containing the full path of the directory containing the application's app.yaml file. url_map: An appinfo.URLMap instance containing the configuration for this handler. """ url = url_map.url # Take a url pattern like "/css" and transform it into a match pattern like # "/css/(?P<file>.*)$" if url[-1] != '/': url += '/' try: url_pattern = re.compile('%s(?P<file>.*)$' % url) except re.error, e: raise errors.InvalidAppConfigError( 'invalid url %r in static_dir handler: %s' % (url, e))
def _files_in_dir_matching(dir_path, names): """Return a single-element list containing an absolute path to a file. The method accepts a list of filenames. If multiple are found, an error is raised. If only one match is found, the full path to this file is returned. Args: dir_path: A string base directory for searching for filenames. names: A list of string relative file names to seek within dir_path. Raises: InvalidAppConfigError: If the xml files are not found. Returns: A single-element list containing a full path to a file. """ abs_names = [os.path.join(dir_path, name) for name in names] files = [f for f in abs_names if os.path.exists(f)] if len(files) > 1: raise errors.InvalidAppConfigError('Directory "%s" contains %s' % (dir_path, ' and '.join(names))) return files
def __init__(self, root_path, url_map, app_info_default_expiration=None): """Initializer for StaticFilesHandler. Args: root_path: A string containing the full path of the directory containing the application's app.yaml file. url_map: An appinfo.URLMap instance containing the configuration for this handler. app_info_default_expiration: A string containing the value of the default_expiration value from app info yaml. None if no default_expiration is set. """ try: url_pattern = re.compile('%s$' % url_map.url) except re.error as e: raise errors.InvalidAppConfigError( 'invalid url %r in static_files handler: %s' % (url_map.url, e)) super(StaticFilesHandler, self).__init__(root_path, url_map, url_pattern, app_info_default_expiration)
def __init__(self, config_path, app_id=None, runtime=None, env_variables=None): """Initializer for ModuleConfiguration. Args: config_path: A string containing the full path of the yaml or xml file containing the configuration for this module. app_id: A string that is the application id, or None if the application id from the yaml or xml file should be used. runtime: A string that is the runtime to use, or None if the runtime from the yaml or xml file should be used. env_variables: A dictionary that is the environment variables passed by flags. Raises: errors.DockerfileError: Raised if a user supplied a Dockerfile and a non-custom runtime. errors.InvalidAppConfigError: Raised if a user select python vanilla runtime. """ self._config_path = config_path self._forced_app_id = app_id root = os.path.dirname(config_path) self._is_java = os.path.normpath(config_path).endswith( os.sep + 'WEB-INF' + os.sep + 'appengine-web.xml') if self._is_java: # We assume Java's XML-based config files only if config_path is # something like /foo/bar/WEB-INF/appengine-web.xml. In this case, # the application root is /foo/bar. Other apps, configured with YAML, # have something like /foo/bar/app.yaml, with application root /foo/bar. root = os.path.dirname(root) self._application_root = os.path.realpath(root) self._last_failure_message = None self._app_info_external, files_to_check = self._parse_configuration( self._config_path) # This if-statement is necessary because of following corner case # appinfo.EnvironmentVariables.Merge({}, None) returns None if env_variables: merged_env_variables = appinfo.EnvironmentVariables.Merge( self._app_info_external.env_variables, env_variables) self._app_info_external.env_variables = merged_env_variables self._mtimes = self._get_mtimes(files_to_check) self._application = '%s~%s' % (self.partition, self.application_external_name) self._api_version = self._app_info_external.api_version self._module_name = self._app_info_external.module self._version = self._app_info_external.version self._threadsafe = self._app_info_external.threadsafe self._basic_scaling_config = self._app_info_external.basic_scaling self._manual_scaling_config = self._app_info_external.manual_scaling self._automatic_scaling_config = self._app_info_external.automatic_scaling self._runtime = runtime or self._app_info_external.runtime self._effective_runtime = self._app_info_external.GetEffectiveRuntime() if self._runtime == 'python-compat': logging.warn( 'The python-compat runtime is deprecated, please consider upgrading ' 'your application to use the Flexible runtime. See ' 'https://cloud.google.com/appengine/docs/flexible/python/upgrading ' 'for more details.') elif self._runtime == 'vm': logging.warn( 'The Managed VMs runtime is deprecated, please consider migrating ' 'your application to use the Flexible runtime. See ' 'https://cloud.google.com/appengine/docs/flexible/python/migrating ' 'for more details.') dockerfile_dir = os.path.dirname(self._config_path) dockerfile = os.path.join(dockerfile_dir, 'Dockerfile') if self._effective_runtime != 'custom' and os.path.exists(dockerfile): raise errors.DockerfileError( 'When there is a Dockerfile in the current directory, the only ' 'supported runtime is runtime: custom. Please switch to runtime: ' 'custom. The devappserver does not actually use your Dockerfile, so ' 'please use either the --runtime flag to specify the runtime you ' 'want or use the --custom_entrypoint flag to describe how to start ' 'your application.') if self._runtime == 'python': logging.warning( 'The "python" runtime specified in "%s" is not supported - the ' '"python27" runtime will be used instead. A description of the ' 'differences between the two can be found here:\n' 'https://developers.google.com/appengine/docs/python/python25/diff27', self._config_path) self._minor_version_id = ''.join( random.choice(string.digits) for _ in range(18)) self._forwarded_ports = {} if self.runtime == 'vm': # Avoid using python-vanilla with dev_appserver if 'python' == self._effective_runtime: raise errors.InvalidAppConfigError( 'Under dev_appserver, ' 'runtime:python is not supported ' 'for Flexible environment.') # Java uses an api_version of 1.0 where everyone else uses just 1. # That doesn't matter much elsewhere, but it does pain us with VMs # because they recognize api_version 1 not 1.0. # TODO: sort out this situation better, probably by changing # Java to use 1 like everyone else. if self._api_version == '1.0': self._api_version = '1' vm_settings = self._app_info_external.vm_settings ports = None if vm_settings: ports = vm_settings.get('forwarded_ports') if not ports: if (self._app_info_external.network and self._app_info_external.network.forwarded_ports): # Depending on the YAML formatting, these may be strings or ints. # Force them to be strings. ports = ','.join( str(p) for p in self._app_info_external.network.forwarded_ports) if ports: logging.debug('setting forwarded ports %s', ports) pm = port_manager.PortManager() pm.Add(ports, 'forwarded') self._forwarded_ports = pm.GetAllMappedPorts()['tcp'] self._translate_configuration_files() # vm_health_check is deprecated but it still needs to be taken into account # if it is populated. if self._app_info_external.health_check is not None: health_check = self._app_info_external.health_check else: health_check = self._app_info_external.vm_health_check self._health_check = _set_health_check_defaults(health_check) # Configure the _is_{typeof}_scaling, _instance_class, and _memory_limit # attributes. self._is_manual_scaling = None self._is_basic_scaling = None self._is_automatic_scaling = None self._instance_class = self._app_info_external.instance_class if self._manual_scaling_config or self._runtime == 'vm': # TODO: Remove this 'or' when we support auto-scaled VMs. self._is_manual_scaling = True self._instance_class = ( self._instance_class or constants.DEFAULT_MANUAL_SCALING_INSTANCE_CLASS) elif self._basic_scaling_config: self._is_basic_scaling = True self._instance_class = ( self._instance_class or constants.DEFAULT_BASIC_SCALING_INSTANCE_CLASS) else: self._is_automatic_scaling = True self._instance_class = ( self._instance_class or constants.DEFAULT_AUTO_SCALING_INSTANCE_CLASS) self._memory_limit = constants.INSTANCE_CLASS_MEMORY_LIMIT.get( self._instance_class)
def __init__(self, yaml_paths): """Initializer for ApplicationConfiguration. Args: yaml_paths: A list of strings containing the paths to yaml files. """ self.modules = [] self.dispatch = None if len(yaml_paths) == 1 and os.path.isdir(yaml_paths[0]): directory_path = yaml_paths[0] for app_yaml_path in [os.path.join(directory_path, 'app.yaml'), os.path.join(directory_path, 'app.yml')]: if os.path.exists(app_yaml_path): yaml_paths = [app_yaml_path] break else: raise errors.AppConfigNotFoundError( 'no app.yaml file at %r' % directory_path) for backends_yaml_path in [os.path.join(directory_path, 'backends.yaml'), os.path.join(directory_path, 'backends.yml')]: if os.path.exists(backends_yaml_path): yaml_paths.append(backends_yaml_path) break for yaml_path in yaml_paths: if os.path.isdir(yaml_path): raise errors.InvalidAppConfigError( '"%s" is a directory and a yaml configuration file is required' % yaml_path) elif (yaml_path.endswith('backends.yaml') or yaml_path.endswith('backends.yml')): # TODO: Reuse the ModuleConfiguration created for the app.yaml # instead of creating another one for the same file. self.modules.extend( BackendsConfiguration(yaml_path.replace('backends.y', 'app.y'), yaml_path).get_backend_configurations()) elif (yaml_path.endswith('dispatch.yaml') or yaml_path.endswith('dispatch.yml')): if self.dispatch: raise errors.InvalidAppConfigError( 'Multiple dispatch.yaml files specified') self.dispatch = DispatchConfiguration(yaml_path) else: module_configuration = ModuleConfiguration(yaml_path) self.modules.append(module_configuration) application_ids = set(module.application for module in self.modules) if len(application_ids) > 1: raise errors.InvalidAppConfigError( 'More than one application ID found: %s' % ', '.join(sorted(application_ids))) self._app_id = application_ids.pop() module_names = set() for module in self.modules: if module.module_name in module_names: raise errors.InvalidAppConfigError('Duplicate module: %s' % module.module_name) module_names.add(module.module_name) if self.dispatch: if appinfo.DEFAULT_MODULE not in module_names: raise errors.InvalidAppConfigError( 'A default module must be specified.') missing_modules = ( set(module_name for _, module_name in self.dispatch.dispatch) - module_names) if missing_modules: raise errors.InvalidAppConfigError( 'Modules %s specified in dispatch.yaml are not defined by a yaml ' 'file.' % sorted(missing_modules))