1# -------------------------------------------------------------------------------------------- 2# Copyright (c) Microsoft Corporation. All rights reserved. 3# Licensed under the MIT License. See License.txt in the project root for license information. 4# -------------------------------------------------------------------------------------------- 5 6import os 7import sys 8import zipfile 9import requests 10 11from azure.cli.command_modules.botservice.constants import CSHARP, JAVASCRIPT 12from knack.util import CLIError 13 14 15class BotPublishPrep: 16 17 @staticmethod 18 def create_upload_zip(logger, code_dir, include_node_modules=True): 19 file_excludes = ['upload.zip', 'db.lock', '.env'] 20 folder_excludes = ['packages', 'bin', 'obj'] 21 22 logger.info('Creating upload zip file, code directory %s.', code_dir) 23 24 if not include_node_modules: 25 26 logger.info('Adding node_modules to folders to exclude from zip file.') 27 folder_excludes.append('node_modules') 28 29 zip_filepath = os.path.abspath('upload.zip') 30 logger.info('Compressing bot source into %s.', zip_filepath) 31 32 save_cwd = os.getcwd() 33 os.chdir(code_dir) 34 try: 35 with zipfile.ZipFile(zip_filepath, 'w', 36 compression=zipfile.ZIP_DEFLATED) as zf: 37 path = os.path.normpath(os.curdir) 38 for dirpath, dirnames, filenames in os.walk(os.curdir, topdown=True): 39 for item in folder_excludes: 40 if item in dirnames: 41 dirnames.remove(item) 42 for name in sorted(dirnames): 43 path = os.path.normpath(os.path.join(dirpath, name)) 44 zf.write(path, path) 45 for name in filenames: 46 if name in file_excludes: 47 continue 48 path = os.path.normpath(os.path.join(dirpath, name)) 49 if os.path.isfile(path): 50 zf.write(path, path) 51 finally: 52 os.chdir(save_cwd) 53 return zip_filepath 54 55 @staticmethod 56 def prepare_publish_v4(logger, code_dir, proj_file, iis_info): 57 if iis_info['lang'] == JAVASCRIPT and iis_info['has_web_config'] and iis_info['has_iisnode_yml']: 58 logger.info("Necessary files for a Node.js deployment on IIS have been detected in the bot's folder. Not " 59 "retrieving required files from Azure.") 60 return 61 62 save_cwd = os.getcwd() 63 os.chdir(code_dir) 64 65 logger.info('Preparing Bot Builder SDK v4 bot for publish, with code directory %s and project file %s.', 66 code_dir, proj_file or '') 67 68 try: 69 if iis_info['lang'] == CSHARP: 70 logger.info('Detected bot language C#, Bot Builder version v4.') 71 72 if proj_file is None: 73 raise CLIError('Expected --proj-file-path argument provided with the full path to the ' 74 'bot csproj file for csharp bot with Bot Builder SDK v4 project.') 75 with open('.deployment', 'w') as f: 76 f.write('[config]\n') 77 proj_file = proj_file.lower() 78 proj_file = proj_file if proj_file.endswith('.csproj') else proj_file + '.csproj' 79 f.write('SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore {0}\n' 80 .format(BotPublishPrep.__find_proj(proj_file))) 81 82 elif iis_info['lang'] == JAVASCRIPT: 83 logger.info('Detected bot language JavaScript, Bot Builder version v4. Fetching necessary deployment ' 84 'files for deploying on IIS.') 85 86 # Retrieve iisnode.yml and web.config and hold in memory, then extract required missing files. 87 node_iis_zip = BotPublishPrep.__retrieve_node_v4_publish_zip() 88 BotPublishPrep.__extract_specific_file_from_zip(logger, 89 node_iis_zip, 90 iis_info['has_web_config'], 91 iis_info['has_iisnode_yml']) 92 93 finally: 94 os.chdir(save_cwd) 95 96 @staticmethod 97 def __retrieve_node_v4_publish_zip(): 98 """Retrieves required IIS Node.js v4 BotBuilder SDK deployment files from Azure. 99 :return: zipfile.ZipFile instance 100 """ 101 response = requests.get('https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip') 102 if sys.version_info.major >= 3: 103 import io 104 return zipfile.ZipFile(io.BytesIO(response.content)) 105 # If Python version is 2.X, use StringIO instead. 106 import StringIO # pylint: disable=import-error 107 return zipfile.ZipFile(StringIO.StringIO(response.content)) 108 109 @staticmethod 110 def __extract_specific_file_from_zip(logger, zip_file, web_config_exists, iisnode_yml_exists): 111 if not web_config_exists and not iisnode_yml_exists: 112 zip_file.extractall() 113 logger.info('"web.config" and "iisnode.yml" created in %s.' % os.getcwd()) 114 elif not web_config_exists: 115 with open('web.config', 'wb') as w: 116 w.write(zip_file.read('web.config')) 117 logger.info('"web.config" created in %s.' % os.getcwd()) 118 elif iisnode_yml_exists: 119 with open('iisnode.yml', 'wb') as i: 120 i.write(zip_file.read('iisnode.yml')) 121 logger.info('"iisnode.yml" created in %s.' % os.getcwd()) 122 123 @staticmethod 124 def __find_proj(proj_file): 125 for root, _, files in os.walk(os.curdir): 126 for file_name in files: 127 if proj_file == file_name.lower(): 128 return os.path.relpath(os.path.join(root, file_name)) 129 raise CLIError('project file not found. Please pass a valid --proj-file-path.') 130