syndicate/core/build/runtime/nodejs.py (160 lines of code) (raw):
"""
Copyright 2018 EPAM Systems, 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.
"""
import concurrent
import glob
import json
import os
import shutil
import threading
from concurrent.futures.thread import ThreadPoolExecutor
from pathlib import Path
from syndicate.commons.log_helper import get_logger
from syndicate.core.build.helper import build_py_package_name, zip_dir
from syndicate.core.conf.processor import path_resolver
from syndicate.core.constants import (LAMBDA_CONFIG_FILE_NAME,
NODE_REQ_FILE_NAME,
LAMBDA_LAYER_CONFIG_FILE_NAME,
NODE_LAMBDA_LAYER_PATH, DEFAULT_SEP,
LOCAL_REQ_FILE_NAME)
from syndicate.core.groups import RUNTIME_NODEJS
from syndicate.core.helper import (build_path, unpack_kwargs,
execute_command_by_path, without_zip_ext,
zip_ext)
from syndicate.core.project_state.project_state import BUILD_MAPPINGS
from syndicate.core.resources.helper import validate_params
_LOG = get_logger(__name__)
_JS_EXT = "*.js"
DEPENDENCIES_FOLDER = 'node_modules'
def _copy_js_files(search_path, destination_path):
files = glob.iglob(build_path(search_path, _JS_EXT))
for js_file in files:
if os.path.isfile(js_file):
shutil.copy2(js_file, destination_path)
def assemble_node_lambdas(project_path, bundles_dir, **kwargs):
from syndicate.core import CONFIG
project_abs_path = Path(CONFIG.project_path, project_path)
_LOG.info(f'Going to package lambdas starting by path {project_abs_path}')
executor = ThreadPoolExecutor(max_workers=5)
futures = []
for root, sub_dirs, files in os.walk(project_abs_path):
for item in files:
if item.endswith(LAMBDA_CONFIG_FILE_NAME):
_LOG.info(f'Going to build artifact in: {root}')
arg = {
'item': item,
'root': root,
'target_folder': bundles_dir
}
futures.append(executor.submit(_build_node_artifact, arg))
elif item.endswith(LAMBDA_LAYER_CONFIG_FILE_NAME):
_LOG.info(f'Going to build lambda layer in `{root}`')
arg = {
'layer_root': root,
'target_folder': bundles_dir
}
futures.append(executor.submit(build_node_lambda_layer, arg))
for future in concurrent.futures.as_completed(futures):
if future.result():
_LOG.info(future.result())
@unpack_kwargs
def _build_node_artifact(item, root, target_folder):
_check_npm_is_installed()
_LOG.debug(f'Building artifact in {target_folder}')
lambda_config_dict = json.load(open(build_path(root, item)))
_LOG.debug(f'Root path: {root}')
req_params = ['lambda_path', 'name', 'version']
validate_params(root, lambda_config_dict, req_params)
lambda_name = lambda_config_dict['name']
lambda_version = lambda_config_dict['version']
artifact_name = lambda_name + '-' + lambda_version
package_name = build_py_package_name(lambda_name, lambda_version)
artifact_path = str(Path(target_folder, artifact_name))
shutil.copytree(root, str(Path(artifact_path, 'lambdas', lambda_name)))
install_requirements(root, target_folder, artifact_path, package_name)
@unpack_kwargs
def build_node_lambda_layer(layer_root: str, target_folder: str):
with open(Path(layer_root, LAMBDA_LAYER_CONFIG_FILE_NAME), 'r') as file:
layer_config = json.load(file)
validate_params(layer_root, layer_config, ['name', 'deployment_package'])
artifact_name = without_zip_ext(layer_config['deployment_package'])
package_name = zip_ext(layer_config['deployment_package'])
artifact_path = str(Path(target_folder, artifact_name))
modules_path = str(Path(artifact_path, DEPENDENCIES_FOLDER))
shutil.copytree(layer_root, modules_path)
install_requirements(layer_root, target_folder, artifact_path,
package_name, is_layer=True)
def install_requirements(root: str, target_folder: str, artifact_path: str,
package_name: str, is_layer=False):
"""
artifact_path: str - Absolute archive path
root: str - lambda folder (src/lambdas/{$lambda_name})
"""
_LOG.info(f'Artifacts path: {artifact_path}')
os.makedirs(artifact_path, exist_ok=True)
if not os.path.exists(artifact_path):
os.makedirs(artifact_path)
_LOG.debug('Folders are created')
# getting file content
req_path = Path(root, NODE_REQ_FILE_NAME)
try:
if os.path.exists(req_path):
command = 'npm install'
# this command creates 'node_modules' folder in lambda folder
execute_command_by_path(command=command, path=root)
_LOG.debug('3-rd party dependencies were installed successfully')
try:
shutil.copytree(Path(root, DEPENDENCIES_FOLDER),
Path(artifact_path, DEPENDENCIES_FOLDER),
dirs_exist_ok=True)
except FileNotFoundError:
_LOG.info('No dependencies folder - nothing to copy.')
except Exception as e:
_LOG.exception(f'Error occurred while lambda files coping: {e}')
# install local requirements
local_requirements_path = Path(root, LOCAL_REQ_FILE_NAME)
if os.path.exists(local_requirements_path):
_LOG.info('Going to install local dependencies')
_copy_local_req(artifact_path, local_requirements_path)
_LOG.info('Local dependencies were installed successfully')
if is_layer:
zip_dir(artifact_path,
str(build_path(target_folder, package_name)),
NODE_LAMBDA_LAYER_PATH)
else:
zip_dir(artifact_path,
build_path(target_folder, package_name))
lock = threading.RLock()
lock.acquire()
try:
# remove unused folder/files
node_modules_path = os.path.join(root, DEPENDENCIES_FOLDER)
if os.path.exists(node_modules_path):
shutil.rmtree(node_modules_path)
# todo Investigate deleting package_lock file
# shutil.rmtree(os.path.join(root, 'package_lock.json'))
shutil.rmtree(artifact_path)
except FileNotFoundError as e:
_LOG.exception(f'Error occurred while temp files removing: {e}')
finally:
lock.release()
return f'Lambda package {package_name} was created successfully'
except Exception as e:
_msg = 'Error occurred during the lambda layer deployment package ' \
'assembling'
_LOG.exception(f'{_msg}: {e}')
return _msg
def _check_npm_is_installed():
import subprocess
result = subprocess.call('npm -v', shell=True)
if result:
raise AssertionError(
'NPM is not installed. There is no ability to build '
'NodeJS bundle. Please, install npm and retry to build bundle.')
def _copy_local_req(artifact_path, local_req_path):
from syndicate.core import CONFIG
with open(local_req_path) as f:
local_req_list = f.readlines()
local_req_list = [path_resolver(r.strip()) for r in local_req_list]
_LOG.info(f'Installing local dependencies: {local_req_list}')
# copy folders
for lrp in local_req_list:
_LOG.info(f'Processing local dependency: {lrp}')
shutil.copytree(str(Path(CONFIG.project_path,
BUILD_MAPPINGS[RUNTIME_NODEJS], lrp)),
str(Path(artifact_path, lrp)))
_LOG.debug('Dependency was copied successfully')
folders = [r for r in lrp.split(DEFAULT_SEP) if r]
# process folder from root project
folders.insert(0, '')
for folder in folders:
src_path = Path(CONFIG.project_path,
BUILD_MAPPINGS[RUNTIME_NODEJS], folder)
dst_path = Path(artifact_path, folder)
_copy_js_files(str(src_path), str(dst_path))
_LOG.debug('JavaScript files from packages were copied successfully')