blob: 991570fd79bb5f5e472f6529749ada843983ab30 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# 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.
#
"""Manages a Dart VM Runtime process running inside of a docker container.
"""
import logging
import os
import shutil
import subprocess
import tempfile
import google
from google.appengine.tools.devappserver2 import instance
from google.appengine.tools.devappserver2 import vm_runtime_proxy
DEBUG_PORT = 5858
VM_SERVICE_PORT = 8181
DEV_MODE = 'dev'
DEPLOY_MODE = 'deploy'
class DartVMRuntimeProxy(instance.RuntimeProxy):
"""Manages a Dart VM Runtime process running inside of a docker container.
"""
def __init__(self, docker_client, runtime_config_getter,
module_configuration,
default_port=8080, port_bindings=None):
"""Initializer for VMRuntimeProxy.
Args:
docker_client: docker.Client object to communicate with Docker daemon.
runtime_config_getter: A function that can be called without arguments
and returns the runtime_config_pb2.Config containing the configuration
for the runtime.
module_configuration: An application_configuration.ModuleConfiguration
instance respresenting the configuration of the module that owns the
runtime.
default_port: int, main port inside of the container that instance is
listening on.
port_bindings: dict, Additional port bindings from the container.
"""
super(DartVMRuntimeProxy, self).__init__()
self._runtime_config_getter = runtime_config_getter
self._module_configuration = module_configuration
if not port_bindings:
port_bindings = {
VM_SERVICE_PORT: None,
}
# Get the Dart configuration.
runtime_config = self._runtime_config_getter()
dart_config = runtime_config.vm_config.dart_config
# Find the 'pub' executable to use.
if dart_config.dart_sdk:
self._pub = os.path.join(dart_config.dart_sdk, 'bin', 'pub')
else:
self._pub = 'pub'
# Get 'pub serve' and mode configuration.
self._pub_serve_host = dart_config.dart_pub_serve_host
self._pub_serve_port = dart_config.dart_pub_serve_port
self._mode = dart_config.dart_dev_mode or DEV_MODE
additional_environment = None
if self._use_pub_serve:
pub_serve = 'http://%s:%s' % (self._pub_serve_host, self._pub_serve_port)
additional_environment = {'DART_PUB_SERVE': pub_serve}
self._vm_runtime_proxy = vm_runtime_proxy.VMRuntimeProxy(
docker_client=docker_client,
runtime_config_getter=runtime_config_getter,
module_configuration=module_configuration,
default_port=default_port,
port_bindings=port_bindings,
additional_environment=additional_environment)
def handle(self, environ, start_response, url_map, match, request_id,
request_type):
"""Handle request to Dart runtime.
Serves this request by first forwarding it to 'pub serve' if
configured to be used. If 'pub serve' cannot be contacted or
does not have the resource (status 404) the request is forwarded
to the Dart application instance via HttpProxy.
Args:
environ: An environ dict for the request as defined in PEP-333.
start_response: A function with semantics defined in PEP-333.
url_map: An appinfo.URLMap instance containing the configuration for the
handler matching this request.
match: A re.MatchObject containing the result of the matched URL pattern.
request_id: A unique string id associated with the request.
request_type: The type of the request. See instance.*_REQUEST module
constants.
Returns:
Generator of sequence of strings containing the body of the HTTP response.
"""
return self._vm_runtime_proxy.handle(environ, start_response, url_map,
match, request_id, request_type)
def start(self):
logging.info('Starting Dart VM Deployment process')
try:
application_dir = os.path.abspath(
self._module_configuration.application_root)
# - copy the application to a new temporary directy (follow symlinks)
# - copy the dockerfiles/dart/Dockerfile into the directory
# - build & deploy the docker container
with TempDir('dart_deployment_dir') as temp_dir:
dst_application_dir = os.path.join(temp_dir, 'app')
try:
shutil.copytree(application_dir, dst_application_dir)
except Exception as e:
for src, unused_dst, unused_error in e.args[0]:
if os.path.islink(src):
linkto = os.readlink(src)
if not os.path.exists(linkto):
logging.error('Dangling symlink in Dart project. Path ' + src +
' links to ' + os.readlink(src))
raise
dst_build_dir = os.path.join(dst_application_dir, 'build')
if self._is_deployment_mode:
# Run 'pub build' to generate assets from web/ directory if necessary.
web_dir = os.path.join(application_dir, 'web')
if os.path.exists(web_dir):
subprocess.check_call(' '.join([self._pub, 'build', '--mode=debug',
'web', '-o', dst_build_dir]),
cwd=application_dir, shell=True)
self._vm_runtime_proxy.start(dockerfile_dir=dst_application_dir)
logging.info(
'To access Dart VM observatory for module {module} '
'open http://{host}:{port}'.format(
module=self._module_configuration.module_name,
host=self._vm_runtime_proxy.ContainerHost(),
port=self._vm_runtime_proxy.PortBinding(VM_SERVICE_PORT)))
except Exception as e:
logging.info('Dart VM Deployment process failed: %s', str(e))
raise
def quit(self):
self._vm_runtime_proxy.quit()
@property
def _use_pub_serve(self):
return self._is_development_mode and self._pub_serve_port
@property
def _is_development_mode(self):
return self._mode == DEV_MODE
@property
def _is_deployment_mode(self):
return self._mode == DEPLOY_MODE
class TempDir(object):
def __init__(self, prefix=''):
self._temp_dir = None
self._prefix = prefix
def __enter__(self):
self._temp_dir = tempfile.mkdtemp(self._prefix)
return self._temp_dir
def __exit__(self, *_):
shutil.rmtree(self._temp_dir, ignore_errors=True)