ftl/node/layer_builder.py (123 lines of code) (raw):

# Copyright 2017 Google Inc. 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. """This package implements the Node package layer builder.""" import logging import os import json from ftl.common import constants from ftl.common import ftl_util from ftl.common import ftl_error from ftl.common import single_layer_image from ftl.common import tar_to_dockerimage class LayerBuilder(single_layer_image.CacheableLayerBuilder): def __init__(self, ctx=None, descriptor_files=None, pkg_descriptor=None, directory=None, destination_path=constants.DEFAULT_DESTINATION_PATH, should_use_yarn=None, cache_key_version=None, cache=None): super(LayerBuilder, self).__init__() self._ctx = ctx self._descriptor_files = descriptor_files self._pkg_descriptor = pkg_descriptor self._directory = directory self._destination_path = destination_path self._should_use_yarn = should_use_yarn self._cache_key_version = cache_key_version self._cache = cache def GetCacheKeyRaw(self): all_descriptor_contents = ftl_util.all_descriptor_contents( self._descriptor_files, self._ctx) cache_key = '%s %s' % (all_descriptor_contents, self._destination_path) return "%s %s" % (cache_key, self._cache_key_version) def BuildLayer(self): """Override.""" cached_img = None is_gcp_build = False if self._ctx and self._ctx.Contains(constants.PACKAGE_JSON): is_gcp_build = ftl_util.is_gcp_build( json.loads(self._ctx.GetFile(constants.PACKAGE_JSON))) if is_gcp_build: env = {"NODE_ENV": "development"} if self._should_use_yarn: ftl_util.gcp_build(self._directory, 'yarn', 'run', env_map=env) self._cleanup_build_layer() else: ftl_util.gcp_build(self._directory, 'npm', 'run-script', env_map=env) self._cleanup_build_layer() key = self.GetCacheKey() if self._cache: with ftl_util.Timing('checking_cached_packages_json_layer'): cached_img = self._cache.Get(key) self._log_cache_result(False if cached_img is None else True, key) if cached_img: self.SetImage(cached_img) else: with ftl_util.Timing('building_packages_json_layer'): self._build_layer() self._cleanup_build_layer() if self._cache: with ftl_util.Timing('uploading_packages_json_layer'): self._cache.Set(key, self.GetImage()) def _build_layer(self): if self._should_use_yarn: blob, u_blob = self._gen_yarn_install_tar(self._directory) else: blob, u_blob = self._gen_npm_install_tar(self._directory) self._img = tar_to_dockerimage.FromFSImage([blob], [u_blob], ftl_util.generate_overrides( False)) def _cleanup_build_layer(self): if self._directory: modules_dir = os.path.join(self._directory, "node_modules") rm_cmd = ['rm', '-rf', modules_dir] ftl_util.run_command('rm_node_modules', rm_cmd) def _gen_yarn_install_tar(self, app_dir): yarn_install_cmd = ['yarn', 'install', '--production'] ftl_util.run_command( 'yarn_install', yarn_install_cmd, cmd_cwd=app_dir, err_type=ftl_error.FTLErrors.USER()) module_destination = os.path.join(self._destination_path, 'node_modules') modules_dir = os.path.join(self._directory, "node_modules") return ftl_util.zip_dir_to_layer_sha(modules_dir, module_destination) def _gen_npm_install_tar(self, app_dir): npm_install_cmd = ['npm', 'install', '--production'] npm_output = ftl_util.run_command( 'npm_install', npm_install_cmd, cmd_cwd=app_dir, err_type=ftl_error.FTLErrors.USER()) module_destination = os.path.join(self._destination_path, 'node_modules') modules_dir = os.path.join(self._directory, "node_modules") if not os.path.isdir(modules_dir) or os.listdir(modules_dir) == []: if "Invalid name" in npm_output: raise ftl_error.UserError("%s\n%s" % (npm_output, "0")) return ftl_util.zip_dir_to_layer_sha(modules_dir, module_destination) def _log_cache_result(self, hit, key): if self._pkg_descriptor: if hit: cache_str = constants.PHASE_2_CACHE_HIT else: cache_str = constants.PHASE_2_CACHE_MISS logging.info( cache_str.format( key_version=constants.CACHE_KEY_VERSION, language='NODE', package_name=self._pkg_descriptor[0], package_version=self._pkg_descriptor[1], key=key)) else: if hit: cache_str = constants.PHASE_1_CACHE_HIT else: cache_str = constants.PHASE_1_CACHE_MISS logging.info( cache_str.format( key_version=constants.CACHE_KEY_VERSION, language='NODE', key=key))