3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Manages a Go VM Runtime process running inside of a docker container.
27 from google
.appengine
.tools
.devappserver2
import go_application
28 from google
.appengine
.tools
.devappserver2
import instance
29 from google
.appengine
.tools
.devappserver2
import vm_runtime_proxy
32 VM_SERVICE_PORT
= 8181
33 # TODO: Remove this when classic Go SDK is gone.
34 DEFAULT_DOCKER_FILE
= """FROM google/appengine-go
36 RUN apt-get install --no-install-recommends -y -q \
37 curl build-essential git mercurial bzr
38 RUN mkdir /goroot && curl https://storage.googleapis.com/golang/go1.2.2.linux-amd64.tar.gz | tar xvzf - -C /goroot --strip-components=1
43 ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
45 # TODO: Remove next line once google/appengine-go image updates.
49 RUN /bin/bash /app/_ah/build.sh
52 # Where to look for go-app-builder, which is needed for copying
53 # into the Docker image for building the Go App Engine app.
54 # There is no need to add '.exe' here because it is always a Linux executable.
55 _GO_APP_BUILDER
= os
.path
.join(
56 go_application
.GOROOT
, 'pkg', 'tool', 'docker-gab')
59 class GoVMRuntimeProxy(instance
.RuntimeProxy
):
60 """Manages a Go VM Runtime process running inside of a docker container.
62 The Go VM Runtime forwards all requests to the Go application instance.
65 def __init__(self
, docker_client
, runtime_config_getter
,
66 module_configuration
):
67 """Initializer for VMRuntimeProxy.
70 docker_client: docker.Client object to communicate with Docker daemon.
71 runtime_config_getter: A function that can be called without arguments
72 and returns the runtime_config_pb2.Config containing the configuration
74 module_configuration: An application_configuration.ModuleConfiguration
75 instance respresenting the configuration of the module that owns the
78 super(GoVMRuntimeProxy
, self
).__init
__()
79 self
._runtime
_config
_getter
= runtime_config_getter
80 self
._module
_configuration
= module_configuration
83 VM_SERVICE_PORT
: None,
85 self
._vm
_runtime
_proxy
= vm_runtime_proxy
.VMRuntimeProxy(
86 docker_client
=docker_client
,
87 runtime_config_getter
=runtime_config_getter
,
88 module_configuration
=module_configuration
,
89 port_bindings
=port_bindings
)
91 def handle(self
, environ
, start_response
, url_map
, match
, request_id
,
93 """Handle request to Go runtime.
95 Serves this request by forwarding to the Go application instance via
99 environ: An environ dict for the request as defined in PEP-333.
100 start_response: A function with semantics defined in PEP-333.
101 url_map: An appinfo.URLMap instance containing the configuration for the
102 handler matching this request.
103 match: A re.MatchObject containing the result of the matched URL pattern.
104 request_id: A unique string id associated with the request.
105 request_type: The type of the request. See instance.*_REQUEST module
109 A sequence of strings containing the body of the HTTP response.
112 it
= self
._vm
_runtime
_proxy
.handle(environ
, start_response
, url_map
,
113 match
, request_id
, request_type
)
118 logging
.info('Starting Go VM Deployment process')
121 application_dir
= os
.path
.abspath(
122 self
._module
_configuration
.application_root
)
124 with
TempDir('go_deployment_dir') as dst_deployment_dir
:
125 build_go_docker_image_source(
126 application_dir
, dst_deployment_dir
, _GO_APP_BUILDER
,
127 self
._module
_configuration
.nobuild_files
,
128 self
._module
_configuration
.skip_files
)
130 self
._vm
_runtime
_proxy
.start(dockerfile_dir
=dst_deployment_dir
)
133 'GoVM vmservice available at http://127.0.0.1:%s/ !',
134 self
._vm
_runtime
_proxy
.PortBinding(VM_SERVICE_PORT
))
136 except Exception as e
:
137 logging
.info('Go VM Deployment process failed: %s', str(e
))
141 self
._vm
_runtime
_proxy
.quit()
144 def _write_dockerfile(dst_dir
):
145 """Writes Dockerfile to named directory if one does not exist.
148 dst_dir: string name of destination directory.
150 dst_dockerfile
= os
.path
.join(dst_dir
, 'Dockerfile')
151 if not os
.path
.exists(dst_dockerfile
):
152 with
open(dst_dockerfile
, 'w') as fd
:
153 fd
.write(DEFAULT_DOCKER_FILE
)
156 class TempDir(object):
157 """Creates a temporary directory."""
159 def __init__(self
, prefix
=''):
160 self
._temp
_dir
= None
161 self
._prefix
= prefix
164 self
._temp
_dir
= tempfile
.mkdtemp(self
._prefix
)
165 return self
._temp
_dir
167 def __exit__(self
, *_
):
168 shutil
.rmtree(self
._temp
_dir
, ignore_errors
=True)
171 def _copytree(src
, dst
, skip_files
, symlinks
=False):
172 """Copies src tree to dst (except those matching skip_files).
175 src: string name of source directory to copy from.
176 dst: string name of destination directory to copy to.
177 skip_files: RegexStr of files to skip from appinfo.py.
178 symlinks: optional bool determines if symbolic links are followed.
180 # Ignore files that match the skip_files RegexStr.
181 # TODO: skip_files expects the full path relative to the app root, so
182 # this may need fixing.
183 def ignored_files(unused_dir
, filenames
):
184 return [filename
for filename
in filenames
if skip_files
.match(filename
)]
186 for item
in os
.listdir(src
):
187 s
= os
.path
.join(src
, item
)
188 if skip_files
.match(item
):
189 logging
.info('skipping file %s', s
)
191 d
= os
.path
.join(dst
, item
)
193 shutil
.copytree(s
, d
, symlinks
, ignore
=ignored_files
)
198 def build_go_docker_image_source(
199 application_dir
, dst_deployment_dir
, go_app_builder
,
200 nobuild_files
, skip_files
):
201 """Builds the Docker image source in preparation for building.
204 copy the application to dst_deployment_dir (follow symlinks)
205 copy used parts of $GOPATH to dst_deployment_dir
206 copy or create a Dockerfile in dst_deployment_dir
209 application_dir: string pathname of application directory.
210 dst_deployment_dir: string pathname of temporary deployment directory.
211 go_app_builder: string pathname of docker-gab executable.
212 nobuild_files: regexp identifying which files to not build.
213 skip_files: regexp identifying which files to omit from app.
216 _copytree(application_dir
, dst_deployment_dir
, skip_files
)
217 except shutil
.Error
as e
:
218 logging
.error('Error copying tree: %s', e
)
219 for src
, unused_dst
, unused_error
in e
.args
[0]:
220 if os
.path
.islink(src
):
221 linkto
= os
.readlink(src
)
222 if not os
.path
.exists(linkto
):
223 logging
.error('Dangling symlink in Go project. '
224 'Path %s links to %s', src
, os
.readlink(src
))
227 logging
.error('Failed to copy dir: %s', e
.strerror
)
230 extras
= go_application
.get_app_extras_for_vm(
231 application_dir
, nobuild_files
, skip_files
)
232 for dest
, src
in extras
:
234 dest
= os
.path
.join(dst_deployment_dir
, dest
)
235 dirname
= os
.path
.dirname(dest
)
236 if not os
.path
.exists(dirname
):
238 shutil
.copy(src
, dest
)
240 logging
.error('Failed to copy %s to %s', src
, dest
)
243 # Make the _ah subdirectory for the app engine tools.
244 ah_dir
= os
.path
.join(dst_deployment_dir
, '_ah')
248 logging
.error('Failed to create %s: %s', ah_dir
, e
.strerror
)
253 gab_dest
= os
.path
.join(ah_dir
, 'gab')
254 shutil
.copy(go_app_builder
, gab_dest
)
256 logging
.error('Failed to copy %s to %s', go_app_builder
, gab_dest
)
259 # Write build script.
265 '-goroot', '/goroot',
266 '-nobuild_files', '^' + str(nobuild_files
),
268 '-binary_name', '_ah_exe',
269 '-work_dir', '/tmp/work',
272 gab_args
.extend(go_application
.list_go_files(
273 application_dir
, nobuild_files
, skip_files
))
274 gab_args
.extend([x
[0] for x
in extras
])
275 dst_build
= os
.path
.join(ah_dir
, 'build.sh')
279 'mkdir -p /tmp/work',
280 'chmod a+x /app/_ah/gab',
281 # Without this line, Windows errors "text file busy".
282 'shasum /app/_ah/gab',
284 'mv /tmp/work/_ah_exe /app/_ah/exe',
288 with
open(dst_build
, 'wb') as fd
:
289 fd
.write('\n'.join(lines
) + '\n')
290 os
.chmod(dst_build
, 0777)
292 # TODO: Remove this when classic Go SDK is gone.
293 # Write default Dockerfile if none found.
294 _write_dockerfile(dst_deployment_dir
)
295 # Also write the default Dockerfile if not found in the app dir.
296 _write_dockerfile(application_dir
)