""" 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 import ALL_COMPLETED from concurrent.futures.thread import ThreadPoolExecutor from distutils import dir_util 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, REQ_FILE_NAME, LOCAL_REQ_FILE_NAME, DEFAULT_SEP) from syndicate.core.helper import (build_path, unpack_kwargs, execute_command, prettify_json) from syndicate.core.resources.helper import validate_params _LOG = get_logger('python_runtime_assembler') _PY_EXT = "*.py" def assemble_python_lambdas(project_path, bundles_dir): from syndicate.core import CONFIG project_base_folder = os.path.basename(os.path.normpath(project_path)) project_abs_path = build_path(CONFIG.project_path, project_path) _LOG.info('Going to process python project by path: {0}'.format( 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('Going to build artifact in: {0}'.format(root)) arg = { 'item': item, 'project_base_folder': project_base_folder, 'project_path': project_path, 'root': root, 'target_folder': bundles_dir } futures.append(executor.submit(_build_python_artifact, arg)) concurrent.futures.wait(futures, return_when=ALL_COMPLETED) executor.shutdown() _LOG.info('Python project was processed successfully') @unpack_kwargs def _build_python_artifact(item, project_base_folder, project_path, root, target_folder): from syndicate.core import CONFIG _LOG.debug('Building artifact in {0}'.format(target_folder)) lambda_config_dict = json.load(open(build_path(root, item))) req_params = ['lambda_path', 'name', 'version'] validate_params(root, lambda_config_dict, req_params) lambda_path = path_resolver(lambda_config_dict['lambda_path']) lambda_name = lambda_config_dict['name'] lambda_version = lambda_config_dict['version'] artifact_name = lambda_name + '-' + lambda_version # create folder to store artifacts artifact_path = build_path(target_folder, artifact_name) _LOG.debug('Artifacts path: {0}'.format(artifact_path)) os.makedirs(artifact_path) _LOG.debug('Folders are created') # install requirements.txt content # getting file content req_path = build_path(root, REQ_FILE_NAME) if os.path.exists(req_path): _LOG.debug('Going to install 3-rd party dependencies') with open(req_path) as f: req_list = f.readlines() req_list = [path_resolver(r.strip()) for r in req_list] _LOG.debug(str(req_list)) # install dependencies for lib in req_list: command = 'pip install {0} -t {1}'.format(lib, artifact_path) execute_command(command=command) _LOG.debug('3-rd party dependencies were installed successfully') # install local requirements local_req_path = build_path(root, LOCAL_REQ_FILE_NAME) if os.path.exists(local_req_path): _LOG.debug('Going to install local dependencies') _install_local_req(artifact_path, local_req_path, project_base_folder, project_path) _LOG.debug('Local dependencies were installed successfully') src_path = build_path(CONFIG.project_path, project_path, lambda_path) _copy_py_files(src_path, artifact_path) package_name = build_py_package_name(lambda_name, lambda_version) zip_dir(artifact_path, build_path(target_folder, package_name)) # remove unused folder lock = threading.RLock() lock.acquire() try: shutil.rmtree(artifact_path) finally: lock.release() _LOG.info('Package {0} was created successfully'.format(package_name)) def _install_local_req(artifact_path, local_req_path, project_base_folder, project_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('Local dependencies: {0}'.format(prettify_json(local_req_list))) # copy folders for lrp in local_req_list: _LOG.info('Processing dependency: {0}'.format(lrp)) folder_path = build_path(artifact_path, project_base_folder, lrp) if not os.path.exists(folder_path): os.makedirs(folder_path) dir_util.copy_tree(build_path(CONFIG.project_path, project_path, lrp), folder_path) _LOG.debug('Dependency was copied successfully') folders = [r for r in lrp.split(DEFAULT_SEP) if r] # process folder from root python project folders.insert(0, '') i = 0 temp_path = '' while i < len(folders): temp_path += DEFAULT_SEP + folders[i] src_path = build_path(CONFIG.project_path, project_path, temp_path) dst_path = build_path(artifact_path, project_base_folder, temp_path) _copy_py_files(src_path, dst_path) i += 1 _LOG.debug('Python files from packages were copied successfully') def _copy_py_files(search_path, destination_path): files = glob.iglob(build_path(search_path, _PY_EXT)) for py_file in files: if os.path.isfile(py_file): shutil.copy2(py_file, destination_path)