def publish_app( cmd, client, resource_group_name, resource_name, code_dir=None, proj_file_path=None, version='v4', # pylint:disable=too-many-statements keep_node_modules=None, timeout=None): if version == 'v4': logger.warning( 'DEPRECATION WARNING: `az bot publish` is deprecated for v4 bots. We recommend using `az webapp`' ' to deploy your bot to Azure. For more information on how to deploy a v4 bot, see ' 'https://aka.ms/deploy-your-bot.') # Get the bot information and ensure it's not only a registration bot. bot = client.bots.get(resource_group_name=resource_group_name, resource_name=resource_name) if bot.kind == 'bot': raise CLIError( 'Bot kind is \'bot\', meaning it is a registration bot. ' 'Source publish is not supported for registration only bots.') # If the user does not pass in a path to the local bot project, get the current working directory. if not code_dir: code_dir = os.getcwd() logger.info( 'Parameter --code-dir not provided, defaulting to current working directory, %s. ' 'For more information, run \'az bot publish -h\'', code_dir) code_dir = code_dir.strip() if not os.path.isdir(code_dir): raise CLIError( 'The path %s is not a valid directory. ' 'Please supply a valid directory path containing your source code.' % code_dir) # If local IIS Node.js files exist, this means two things: # 1. We may not need to download the necessary web.config and iisnode.yml files to deploy a Node.js bot on IIS. # 2. We shouldn't delete their local web.config and issnode.yml files (if they exist). iis_publish_info = { 'lang': CSHARP if not os.path.exists(os.path.join(code_dir, 'package.json')) else JAVASCRIPT, 'has_web_config': True if os.path.exists(os.path.join(code_dir, 'web.config')) else False, 'has_iisnode_yml': True if os.path.exists(os.path.join(code_dir, 'iisnode.yml')) else False } # Ensure that the directory contains appropriate post deploy scripts folder if 'PostDeployScripts' not in os.listdir(code_dir): if version == 'v4': logger.info( 'Detected SDK version v4. Running prepare publish in code directory %s and for project file %s' # pylint:disable=logging-not-lazy % (code_dir, proj_file_path)) # Automatically run prepare-publish in case of v4. BotPublishPrep.prepare_publish_v4(logger, code_dir, proj_file_path, iis_publish_info) else: logger.info( 'Detected SDK version v3. PostDeploymentScripts folder not found in directory provided: %s', code_dir) raise CLIError( 'Publish directory provided is uses Bot Builder SDK V3, and as a legacy bot needs to be ' 'prepared for deployment. Please run prepare-publish. For more information, run \'az bot ' 'prepare-publish -h\'.') zip_filepath = BotPublishPrep.create_upload_zip(logger, code_dir, include_node_modules=False) logger.info('Zip file path created, at %s.', zip_filepath) kudu_client = KuduClient(cmd, resource_group_name, resource_name, bot, logger) output = kudu_client.publish(zip_filepath, timeout, keep_node_modules, iis_publish_info['lang']) logger.info( 'Bot source published. Preparing bot application to run the new source.' ) os.remove('upload.zip') # If the bot is a Node.js bot and did not initially have web.config, delete web.config and iisnode.yml. if iis_publish_info['lang'] == JAVASCRIPT: if not iis_publish_info['has_web_config'] and os.path.exists( os.path.join(code_dir, 'web.config')): os.remove(os.path.join(code_dir, 'web.config')) if not iis_publish_info['has_iisnode_yml'] and os.path.exists( os.path.join(code_dir, 'iisnode.yml')): os.remove(os.path.join(code_dir, 'iisnode.yml')) if not iis_publish_info['has_iisnode_yml'] and not iis_publish_info[ 'has_web_config']: logger.info( "web.config and iisnode.yml for Node.js bot were fetched from Azure for deployment using IIS. " "These files have now been removed from %s." "To see the two files used for your deployment, either visit your bot's Kudu site or download " "the files from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip", code_dir) elif not iis_publish_info['has_iisnode_yml']: logger.info( "iisnode.yml for Node.js bot was fetched from Azure for deployment using IIS. To see this file " "that was used for your deployment, either visit your bot's Kudu site or download the file " "from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip" ) elif not iis_publish_info['has_web_config']: logger.info( "web.config for Node.js bot was fetched from Azure for deployment using IIS. To see this file " "that was used for your deployment, either visit your bot's Kudu site or download the file " "from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip" ) if os.path.exists(os.path.join('.', 'package.json')) and not keep_node_modules: logger.info( 'Detected language javascript. Installing node dependencies in remote bot.' ) kudu_client.install_node_dependencies() if output.get('active'): logger.info('Deployment successful!') if not output.get('active'): scm_url = output.get('url') deployment_id = output.get('id') # Instead of replacing "latest", which would could be in the bot name, we replace "deployments/latest" deployment_url = scm_url.replace('deployments/latest', 'deployments/%s' % deployment_id) logger.error( 'Deployment failed. To find out more information about this deployment, please visit %s.', deployment_url) return output
def create_bot_json( cmd, client, resource_group_name, resource_name, logger, app_password=None, # pylint:disable=too-many-locals raw_bot_properties=None, password_only=True): """ :param cmd: :param client: :param resource_group_name: :param resource_name: :param logger: :param app_password: :param raw_bot_properties: :return: Dictionary """ if not raw_bot_properties: raw_bot_properties = client.bots.get( resource_group_name=resource_group_name, resource_name=resource_name) # Initialize names bot_file and secret to capture botFilePath and botFileSecret values from the application's # settings. bot_file = None bot_file_secret = None profile = Profile(cli_ctx=cmd.cli_ctx) if not app_password: site_name = WebAppOperations.get_bot_site_name( raw_bot_properties.properties.endpoint) app_settings = WebAppOperations.get_app_settings( cmd=cmd, resource_group_name=resource_group_name, name=site_name) app_password_values = [ item['value'] for item in app_settings if item['name'] == 'MicrosoftAppPassword' ] app_password = app_password_values[ 0] if app_password_values else None if not app_password: bot_file_values = [ item['value'] for item in app_settings if item['name'] == 'botFilePath' ] bot_file = bot_file_values[0] if bot_file_values else None bot_file_secret_values = [ item['value'] for item in app_settings if item['name'] == 'botFileSecret' ] bot_file_secret = bot_file_secret_values[ 0] if bot_file_secret_values else None if not bot_file and not app_password: bot_site_name = WebAppOperations.get_bot_site_name( raw_bot_properties.properties.endpoint) scm_url = WebAppOperations.get_scm_url(cmd, resource_group_name, bot_site_name, None) # TODO: Reevaluate "Public-or-Gov" Azure logic. is_public_azure = ( 'azurewebsites.net' in raw_bot_properties.properties.endpoint or '.net' in raw_bot_properties.properties.endpoint or '.com' in raw_bot_properties.properties.endpoint) host = 'https://portal.azure.com/' if is_public_azure else 'https://portal.azure.us/' subscription_id = get_subscription_id(cmd.cli_ctx) tenant_id = profile.get_subscription( subscription=client.config.subscription_id)['tenantId'] settings_url = host + '#@{}/resource/subscriptions/{}/resourceGroups/{}/providers/Microsoft.BotService/botServices/{}/app_settings'.format(tenant_id, subscription_id, resource_group_name, resource_name) # pylint: disable=line-too-long logger.warning( '"MicrosoftAppPassword" and "botFilePath" not found in application settings' ) logger.warning( 'To see your bot\'s application settings, visit %s' % settings_url) logger.warning( 'To visit your deployed bot\'s code on Azure, visit Kudu for your bot at %s' % scm_url) elif not app_password and bot_file: # We have the information we need to obtain the MSA App app password via bot file data from Kudu. kudu_client = KuduClient(cmd, resource_group_name, resource_name, raw_bot_properties, logger) bot_file_data = kudu_client.get_bot_file(bot_file) app_password = BotJsonFormatter.__decrypt_bot_file( bot_file_data, bot_file_secret, logger, password_only) return { 'type': 'abs', 'id': raw_bot_properties.name, 'name': raw_bot_properties.properties.display_name, 'appId': raw_bot_properties.properties.msa_app_id, 'appPassword': app_password, 'endpoint': raw_bot_properties.properties.endpoint, 'resourceGroup': str(resource_group_name), 'tenantId': profile.get_subscription( subscription=client.config.subscription_id)['tenantId'], 'subscriptionId': client.config.subscription_id, 'serviceName': resource_name }
def publish_app(cmd, client, resource_group_name, resource_name, code_dir=None, proj_name=None, version='v3'): """Publish local bot code to Azure. This method is directly called via "bot publish" :param cmd: :param client: :param resource_group_name: :param resource_name: :param code_dir: :param proj_name: :param version: :return: """ # Get the bot information and ensure it's not only a registration bot. bot = client.bots.get( resource_group_name=resource_group_name, resource_name=resource_name ) if bot.kind == 'bot': raise CLIError('Bot kind is \'bot\', meaning it is a registration bot. ' 'Source publish is not supported for registration only bots.') # If the user does not pass in a path to the local bot project, get the current working directory. if not code_dir: code_dir = os.getcwd() logger.info('Parameter --code-dir not provided, defaulting to current working directory, %s. ' 'For more information, run \'az bot publish -h\'', code_dir) code_dir = code_dir.strip() if not os.path.isdir(code_dir): raise CLIError('The path %s is not a valid directory. ' 'Please supply a valid directory path containing your source code.' % code_dir) # Ensure that the directory contains appropriate post deploy scripts folder if 'PostDeployScripts' not in os.listdir(code_dir): if version == 'v4': logger.info('Detected SDK version v4. Running prepare publish in code directory %s and for project file %s' # pylint:disable=logging-not-lazy % (code_dir, proj_name)) # Automatically run prepare-publish in case of v4. BotPublishPrep.prepare_publish_v4(logger, code_dir, proj_name) else: logger.info('Detected SDK version v3. PostDeploymentScripts folder not found in directory provided: %s', code_dir) raise CLIError('Publish directory provided is uses Bot Builder SDK V3, and as a legacy bot needs to be ' 'prepared for deployment. Please run prepare-publish. For more information, run \'az bot ' 'prepare-publish -h\'.') logger.info('Creating upload zip file.') zip_filepath = BotPublishPrep.create_upload_zip(logger, code_dir, include_node_modules=False) logger.info('Zip file path created, at %s.', zip_filepath) kudu_client = KuduClient(cmd, resource_group_name, resource_name, bot, logger) output = kudu_client.publish(zip_filepath) logger.info('Bot source published. Preparing bot application to run the new source.') os.remove('upload.zip') if os.path.exists(os.path.join('.', 'package.json')): logger.info('Detected language javascript. Installing node dependencies in remote bot.') kudu_client.install_node_dependencies() logger.info('Bot publish completed successfully.') return output
def download_app(cmd, client, resource_group_name, resource_name, file_save_path=None): # pylint: disable=too-many-statements, too-many-locals, too-many-branches logger.info('Retrieving bot information from Azure.') # Get the bot and ensure it's not a registration only bot bot = client.bots.get(resource_group_name=resource_group_name, resource_name=resource_name) logger.info('Bot information retrieved successfully from Azure.') if bot.kind == 'bot': raise CLIError( 'Source download is not supported for registration only bots') if not file_save_path: file_save_path = os.getcwd() logger.info( 'Parameter --save-path not provided, defaulting to current working directory, %s. ' 'For more information, run \'az bot download -h\'', file_save_path) file_save_path = file_save_path.strip() if not os.path.isdir(file_save_path): raise CLIError('Path name not valid') # TODO: Verify that the behavior for download and publish is the same in regards to where the files are downloaded # TODO: to and uploaded from. folder_path = os.path.join(file_save_path, resource_name) logger.info('Bot source will be downloaded to %s.', folder_path) if os.path.exists(folder_path): raise CLIError( 'The path {0} already exists. ' 'Please delete this folder or specify an alternate path'.format( folder_path)) logger.info('Attempting to preemptively create directory %s', folder_path) os.mkdir(folder_path) logger.info('Creating Kudu client to download bot source.') kudu_client = KuduClient(cmd, resource_group_name, resource_name, bot, logger) logger.info( 'Downloading bot source. This operation may take seconds or minutes depending on the size of ' 'your bot source and the download speed of your internet connection.') kudu_client.download_bot_zip(file_save_path, folder_path) logger.info('Bot source download successful. Preparing bot project.') # TODO: Examine cases where PostDeployScripts, deploy.cmd, etc. do not exist. if (os.path.exists( os.path.join(folder_path, 'PostDeployScripts', 'deploy.cmd.template')) and os.path.exists(os.path.join(folder_path, 'deploy.cmd'))): logger.info( 'Post deployment scripts and deploy.cmd found in source under folder %s. Copying deploy.cmd.' ) shutil.copyfile( os.path.join(folder_path, 'deploy.cmd'), os.path.join(folder_path, 'PostDeployScripts', 'deploy.cmd.template')) # If the bot source contains a .bot file # TODO: If there is only one bot file, that is the bot file. # TODO: If there are more than one bot file, the user must disambiguate before continuing. # TODO: Show error and suggest passsing --bot-file-name bot_file_path = os.path.join(folder_path, '{0}.bot'.format(resource_name)) if os.path.exists(bot_file_path): logger.info('Detected bot file %s.', bot_file_path) app_settings = WebAppOperations.get_app_settings( cmd=cmd, resource_group_name=resource_group_name, name=kudu_client.bot_site_name) bot_secret = [ item['value'] for item in app_settings if item['name'] == 'botFileSecret' ] # Write a .env file bot_env = { 'botFileSecret': bot_secret[0], 'botFilePath': '{0}.bot'.format(resource_name), 'NODE_ENV': 'development' } # If javascript, write .env file content to .env file if os.path.exists(os.path.join(folder_path, 'package.json')): logger.info( 'Detected runtime as Node.js. Package.json present at %s. Creating .env file in that ' 'folder.', folder_path) with open(os.path.join(folder_path, '.env'), 'w') as f: for key, value in bot_env.items(): f.write('{0}={1}\n'.format(key, value)) # If C#, write .env file content to appsettings.json else: app_settings_path = os.path.join(folder_path, 'appsettings.json') logger.info( 'Detected language as CSharp. Loading app settings from %s.', app_settings_path) existing = None if not os.path.exists(app_settings_path): logger.info( 'App settings not found at %s, defaulting app settings to {}.', app_settings_path) existing = '{}' else: with open(app_settings_path, 'r') as f: existing = json.load(f) with open(os.path.join(app_settings_path), 'w+') as f: for key, value in bot_env.items(): existing[key] = value f.write(json.dumps(existing)) # TODO: Optimize this logic and document. If there is not bot secret, add bot_env download path. Why? # TODO: Consider just returning downloadPath as a string rather than this object. There seem to be no # TODO: usages of the other properties such as botFileSecret if not bot_secret: logger.info('Bot secret not found. Setting download path to %s', folder_path) bot_env['downloadPath'] = folder_path return bot_env else: __prepare_configuration_file(cmd, resource_group_name, kudu_client, folder_path) logger.info('Bot download completed successfully.') return {'downloadPath': folder_path}
def download_app(cmd, client, resource_group_name, resource_name, file_save_path=None): # pylint: disable=too-many-statements, too-many-locals """Download the bot's source code. This method is directly called via "bot download" :param cmd: :param client: :param resource_group_name: :param resource_name: :param file_save_path: :return: """ logger.info('Retrieving bot information from Azure.') # Get the bot and ensure it's not a registration only bot bot = client.bots.get( resource_group_name=resource_group_name, resource_name=resource_name ) logger.info('Bot information retrieved successfully from Azure.') if bot.kind == 'bot': raise CLIError('Source download is not supported for registration only bots') if not file_save_path: file_save_path = os.getcwd() logger.info('Parameter --save-path not provided, defaulting to current working directory, %s. ' 'For more information, run \'az bot download -h\'', file_save_path) file_save_path = file_save_path.strip() if not os.path.isdir(file_save_path): raise CLIError('Path name not valid') # TODO: Verify that the behavior for download and publish is the same in regards to where the files are downloaded # TODO: to and uploaded from. folder_path = os.path.join(file_save_path, resource_name) logger.info('Bot source will be downloaded to %s.', folder_path) if os.path.exists(folder_path): raise CLIError('The path {0} already exists. ' 'Please delete this folder or specify an alternate path'.format(folder_path)) logger.info('Attempting to preemptively create directory %s', folder_path) os.mkdir(folder_path) logger.info('Creating Kudu client to download bot source.') kudu_client = KuduClient(cmd, resource_group_name, resource_name, bot, logger) logger.info('Downloading bot source. This operation may take seconds or minutes depending on the size of ' 'your bot source and the download speed of your internet connection.') kudu_client.download_bot_zip(file_save_path, folder_path) logger.info('Bot source download successful. Preparing bot project.') # TODO: Examine cases where PostDeployScripts, deploy.cmd, etc. do not exist. if (os.path.exists(os.path.join(folder_path, 'PostDeployScripts', 'deploy.cmd.template')) and os.path.exists(os.path.join(folder_path, 'deploy.cmd'))): logger.info('Post deployment scripts and deploy.cmd found in source under folder %s. Copying deploy.cmd.') shutil.copyfile(os.path.join(folder_path, 'deploy.cmd'), os.path.join(folder_path, 'PostDeployScripts', 'deploy.cmd.template')) # If the bot source contains a .bot file # TODO: If there is only one bot file, that is the bot file. # TODO: If there are more than one bot file, the user must disambiguate before continuing. # TODO: Show error and suggest passsing --bot-file-name bot_file_path = os.path.join(folder_path, '{0}.bot'.format(resource_name)) if os.path.exists(bot_file_path): logger.info('Detected bot file %s.', bot_file_path) app_settings = WebAppOperations.get_app_settings( cmd=cmd, resource_group_name=resource_group_name, name=kudu_client.bot_site_name ) bot_secret = [item['value'] for item in app_settings if item['name'] == 'botFileSecret'] # Write a .env file bot_env = { 'botFileSecret': bot_secret[0], 'botFilePath': '{0}.bot'.format(resource_name), 'NODE_ENV': 'development' } # If javascript, write .env file content to .env file if os.path.exists(os.path.join(folder_path, 'package.json')): logger.info('Detected language as javascript. Package.json present at %s. Creating .env file in that ' 'folder.', folder_path) with open(os.path.join(folder_path, '.env'), 'w') as f: for key, value in bot_env.items(): f.write('{0}={1}\n'.format(key, value)) # If C#, write .env file content to appsettings.json else: app_settings_path = os.path.join(folder_path, 'appsettings.json') logger.info('Detected language as CSharp. Loading app settings from %s.', app_settings_path) existing = None if not os.path.exists(app_settings_path): logger.info('App settings not found at %s, defaulting app settings to {}.', app_settings_path) existing = '{}' else: with open(app_settings_path, 'r') as f: existing = json.load(f) with open(os.path.join(app_settings_path), 'w+') as f: for key, value in bot_env.items(): existing[key] = value f.write(json.dumps(existing)) # TODO: Optimize this logic and document. If there is not bot secret, add bot_env download path. Why? # TODO: Consider just returning downloadPath as a string rather than this object. There seem to be no # TODO: usages of the other properties such as botFileSecret if not bot_secret: logger.info('Bot secret not found. Setting download path to %s', folder_path) bot_env['downloadPath'] = folder_path return bot_env logger.info('Bot download completed successfully.') return {'downloadPath': folder_path}
def publish_app(cmd, client, resource_group_name, resource_name, code_dir=None, proj_name=None, version='v3'): """Publish local bot code to Azure. This method is directly called via "bot publish" :param cmd: :param client: :param resource_group_name: :param resource_name: :param code_dir: :param proj_name: :param version: :return: """ if version == 'v3': return publish_appv3(cmd, client, resource_group_name, resource_name, code_dir) # Get the bot information and ensure it's not only a registration bot. bot = client.bots.get( resource_group_name=resource_group_name, resource_name=resource_name ) if bot.kind == 'bot': raise CLIError('Bot kind is \'bot\', meaning it is a registration bot. ' 'Source publish is not supported for registration only bots.') # If the user does not pass in a path to the local bot project, get the current working directory. if not code_dir: code_dir = os.getcwd() logger.info('Parameter --code-dir not provided, defaulting to current working directory, %s. ' 'For more information, run \'az bot publish -h\'', code_dir) if not os.path.isdir(code_dir): raise CLIError('The path %s is not a valid directory. ' 'Please supply a valid directory path containing your source code.' % code_dir) # Ensure that the directory contains appropriate post deploy scripts folder if 'PostDeployScripts' not in os.listdir(code_dir): BotPublishPrep.prepare_publish_v4(logger, code_dir, proj_name) logger.info('Creating upload zip file.') zip_filepath = BotPublishPrep.create_upload_zip(logger, code_dir, include_node_modules=False) logger.info('Zip file path created, at %s.', zip_filepath) kudu_client = KuduClient(cmd, resource_group_name, resource_name, bot) output = kudu_client.publish(zip_filepath) logger.info('Bot source published. Preparing bot application to run the new source.') os.remove('upload.zip') if os.path.exists(os.path.join('.', 'package.json')): logger.info('Detected language javascript. Installing node dependencies in remote bot.') __install_node_dependencies(kudu_client) if output.get('active'): logger.info('Deployment successful!') if not output.get('active'): scm_url = output.get('url') deployment_id = output.get('id') # Instead of replacing "latest", which would could be in the bot name, we replace "deployments/latest" deployment_url = scm_url.replace('deployments/latest', 'deployments/%s' % deployment_id) logger.error('Deployment failed. To find out more information about this deployment, please visit %s.' % deployment_url) return output
def create_bot_json(cmd, client, resource_group_name, resource_name, logger, app_password=None, # pylint:disable=too-many-locals raw_bot_properties=None, password_only=True): """ :param cmd: :param client: :param resource_group_name: :param resource_name: :param logger: :param app_password: :param raw_bot_properties: :return: Dictionary """ if not raw_bot_properties: raw_bot_properties = client.bots.get( resource_group_name=resource_group_name, resource_name=resource_name ) # Initialize names bot_file and secret to capture botFilePath and botFileSecret values from the application's # settings. bot_file = None bot_file_secret = None profile = Profile(cli_ctx=cmd.cli_ctx) if not app_password: site_name = WebAppOperations.get_bot_site_name(raw_bot_properties.properties.endpoint) app_settings = WebAppOperations.get_app_settings( cmd=cmd, resource_group_name=resource_group_name, name=site_name ) app_password_values = [item['value'] for item in app_settings if item['name'] == 'MicrosoftAppPassword'] app_password = app_password_values[0] if app_password_values else None if not app_password: bot_file_values = [item['value'] for item in app_settings if item['name'] == 'botFilePath'] bot_file = bot_file_values[0] if bot_file_values else None bot_file_secret_values = [item['value'] for item in app_settings if item['name'] == 'botFileSecret'] bot_file_secret = bot_file_secret_values[0] if bot_file_secret_values else None if not bot_file and not app_password: bot_site_name = WebAppOperations.get_bot_site_name(raw_bot_properties.properties.endpoint) scm_url = WebAppOperations.get_scm_url(cmd, resource_group_name, bot_site_name, None) # TODO: Reevaluate "Public-or-Gov" Azure logic. is_public_azure = ('azurewebsites.net' in raw_bot_properties.properties.endpoint or '.net' in raw_bot_properties.properties.endpoint or '.com' in raw_bot_properties.properties.endpoint) host = 'https://portal.azure.com/' if is_public_azure else 'https://portal.azure.us/' subscription_id = get_subscription_id(cmd.cli_ctx) tenant_id = profile.get_subscription(subscription=client.config.subscription_id)['tenantId'] settings_url = host + '#@{}/resource/subscriptions/{}/resourceGroups/{}/providers/Microsoft.BotService/botServices/{}/app_settings'.format(tenant_id, subscription_id, resource_group_name, resource_name) # pylint: disable=line-too-long logger.warning('"MicrosoftAppPassword" and "botFilePath" not found in application settings') logger.warning('To see your bot\'s application settings, visit %s' % settings_url) logger.warning('To visit your deployed bot\'s code on Azure, visit Kudu for your bot at %s' % scm_url) elif not app_password and bot_file: # We have the information we need to obtain the MSA App app password via bot file data from Kudu. kudu_client = KuduClient(cmd, resource_group_name, resource_name, raw_bot_properties, logger) bot_file_data = kudu_client.get_bot_file(bot_file) app_password = BotJsonFormatter.__decrypt_bot_file(bot_file_data, bot_file_secret, logger, password_only) return { 'type': 'abs', 'id': raw_bot_properties.name, 'name': raw_bot_properties.properties.display_name, 'appId': raw_bot_properties.properties.msa_app_id, 'appPassword': app_password, 'endpoint': raw_bot_properties.properties.endpoint, 'resourceGroup': str(resource_group_name), 'tenantId': profile.get_subscription(subscription=client.config.subscription_id)['tenantId'], 'subscriptionId': client.config.subscription_id, 'serviceName': resource_name }
def publish_app( cmd, client, resource_group_name, resource_name, code_dir=None, proj_file_path=None, version='v3', # pylint:disable=too-many-statements keep_node_modules=None, timeout=None): """Publish local bot code to Azure. This method is directly called via "bot publish" :param cmd: :param client: :param resource_group_name: :param resource_name: :param code_dir: :param proj_file_path: :param version: :param keep_node_modules: :param timeout: :return: """ # Get the bot information and ensure it's not only a registration bot. bot = client.bots.get(resource_group_name=resource_group_name, resource_name=resource_name) if bot.kind == 'bot': raise CLIError( 'Bot kind is \'bot\', meaning it is a registration bot. ' 'Source publish is not supported for registration only bots.') # If the user does not pass in a path to the local bot project, get the current working directory. if not code_dir: code_dir = os.getcwd() logger.info( 'Parameter --code-dir not provided, defaulting to current working directory, %s. ' 'For more information, run \'az bot publish -h\'', code_dir) code_dir = code_dir.strip() if not os.path.isdir(code_dir): raise CLIError( 'The path %s is not a valid directory. ' 'Please supply a valid directory path containing your source code.' % code_dir) # If local IIS Node.js files exist, this means two things: # 1. We may not need to download the necessary web.config and iisnode.yml files to deploy a Node.js bot on IIS. # 2. We shouldn't delete their local web.config and issnode.yml files (if they exist). iis_publish_info = { 'lang': 'Csharp' if not os.path.exists(os.path.join(code_dir, 'package.json')) else 'Node', 'has_web_config': True if os.path.exists(os.path.join(code_dir, 'web.config')) else False, 'has_iisnode_yml': True if os.path.exists(os.path.join(code_dir, 'iisnode.yml')) else False } # Ensure that the directory contains appropriate post deploy scripts folder if 'PostDeployScripts' not in os.listdir(code_dir): if version == 'v4': logger.info( 'Detected SDK version v4. Running prepare publish in code directory %s and for project file %s' # pylint:disable=logging-not-lazy % (code_dir, proj_file_path)) # Automatically run prepare-publish in case of v4. BotPublishPrep.prepare_publish_v4(logger, code_dir, proj_file_path, iis_publish_info) else: logger.info( 'Detected SDK version v3. PostDeploymentScripts folder not found in directory provided: %s', code_dir) raise CLIError( 'Publish directory provided is uses Bot Builder SDK V3, and as a legacy bot needs to be ' 'prepared for deployment. Please run prepare-publish. For more information, run \'az bot ' 'prepare-publish -h\'.') zip_filepath = BotPublishPrep.create_upload_zip(logger, code_dir, include_node_modules=False) logger.info('Zip file path created, at %s.', zip_filepath) kudu_client = KuduClient(cmd, resource_group_name, resource_name, bot, logger) app_settings = WebAppOperations.get_app_settings( cmd=cmd, resource_group_name=resource_group_name, name=kudu_client.bot_site_name) scm_do_build = [ item['value'] for item in app_settings if item['name'] == 'SCM_DO_BUILD_DURING_DEPLOYMENT' ] if scm_do_build and scm_do_build[0] == 'true': logger.info( 'Detected SCM_DO_BUILD_DURING_DEPLOYMENT with value of "true" in App Service\'s Application ' 'Settings. Build will commence during deployment. For more information, see ' 'https://github.com/projectkudu/kudu/wiki/Deploying-from-a-zip-file' ) else: logger.warning( 'Didn\'t detect SCM_DO_BUILD_DURING_DEPLOYMENT or its value was "false" in App Service\'s ' 'Application Settings. Build may not commence during deployment. To learn how to trigger a build' ' when deploying to your App Service, see ' 'https://github.com/projectkudu/kudu/wiki/Deploying-from-a-zip-file' ) subscription_id = get_subscription_id(cmd.cli_ctx) logger.warning( 'To change the Application Setting via az cli, use the following command:\naz webapp config ' # pylint:disable=logging-not-lazy 'appsettings set -n %s -g %s --subscription %s --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true' % (kudu_client.bot_site_name, resource_group_name, subscription_id)) output = kudu_client.publish(zip_filepath, timeout, keep_node_modules, iis_publish_info['lang']) logger.info( 'Bot source published. Preparing bot application to run the new source.' ) os.remove('upload.zip') # If the bot is a Node.js bot and did not initially have web.config, delete web.config and iisnode.yml. if iis_publish_info['lang'] == 'Node': if not iis_publish_info['has_web_config'] and os.path.exists( os.path.join(code_dir, 'web.config')): os.remove(os.path.join(code_dir, 'web.config')) if not iis_publish_info['has_iisnode_yml'] and os.path.exists( os.path.join(code_dir, 'iisnode.yml')): os.remove(os.path.join(code_dir, 'iisnode.yml')) if not iis_publish_info['has_iisnode_yml'] and not iis_publish_info[ 'has_web_config']: logger.info( "web.config and iisnode.yml for Node.js bot were fetched from Azure for deployment using IIS. " "These files have now been removed from %s." "To see the two files used for your deployment, either visit your bot's Kudu site or download " "the files from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip", code_dir) elif not iis_publish_info['has_iisnode_yml']: logger.info( "iisnode.yml for Node.js bot was fetched from Azure for deployment using IIS. To see this file " "that was used for your deployment, either visit your bot's Kudu site or download the file " "from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip" ) elif not iis_publish_info['has_web_config']: logger.info( "web.config for Node.js bot was fetched from Azure for deployment using IIS. To see this file " "that was used for your deployment, either visit your bot's Kudu site or download the file " "from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip" ) if os.path.exists(os.path.join('.', 'package.json')) and not keep_node_modules: logger.info( 'Detected language javascript. Installing node dependencies in remote bot.' ) kudu_client.install_node_dependencies() if output.get('active'): logger.info('Deployment successful!') if not output.get('active'): scm_url = output.get('url') deployment_id = output.get('id') # Instead of replacing "latest", which would could be in the bot name, we replace "deployments/latest" deployment_url = scm_url.replace('deployments/latest', 'deployments/%s' % deployment_id) logger.error( 'Deployment failed. To find out more information about this deployment, please visit %s.', deployment_url) return output
def publish_app(cmd, client, resource_group_name, resource_name, code_dir=None, proj_file_path=None, version='v3', # pylint:disable=too-many-statements keep_node_modules=None, timeout=None): """Publish local bot code to Azure. This method is directly called via "bot publish" :param cmd: :param client: :param resource_group_name: :param resource_name: :param code_dir: :param proj_file_path: :param version: :param keep_node_modules: :param timeout: :return: """ # Get the bot information and ensure it's not only a registration bot. bot = client.bots.get( resource_group_name=resource_group_name, resource_name=resource_name ) if bot.kind == 'bot': raise CLIError('Bot kind is \'bot\', meaning it is a registration bot. ' 'Source publish is not supported for registration only bots.') # If the user does not pass in a path to the local bot project, get the current working directory. if not code_dir: code_dir = os.getcwd() logger.info('Parameter --code-dir not provided, defaulting to current working directory, %s. ' 'For more information, run \'az bot publish -h\'', code_dir) code_dir = code_dir.strip() if not os.path.isdir(code_dir): raise CLIError('The path %s is not a valid directory. ' 'Please supply a valid directory path containing your source code.' % code_dir) # If local IIS Node.js files exist, this means two things: # 1. We may not need to download the necessary web.config and iisnode.yml files to deploy a Node.js bot on IIS. # 2. We shouldn't delete their local web.config and issnode.yml files (if they exist). iis_publish_info = { 'lang': 'Csharp' if not os.path.exists(os.path.join(code_dir, 'package.json')) else 'Node', 'has_web_config': True if os.path.exists(os.path.join(code_dir, 'web.config')) else False, 'has_iisnode_yml': True if os.path.exists(os.path.join(code_dir, 'iisnode.yml')) else False } # Ensure that the directory contains appropriate post deploy scripts folder if 'PostDeployScripts' not in os.listdir(code_dir): if version == 'v4': logger.info('Detected SDK version v4. Running prepare publish in code directory %s and for project file %s' # pylint:disable=logging-not-lazy % (code_dir, proj_file_path)) # Automatically run prepare-publish in case of v4. BotPublishPrep.prepare_publish_v4(logger, code_dir, proj_file_path, iis_publish_info) else: logger.info('Detected SDK version v3. PostDeploymentScripts folder not found in directory provided: %s', code_dir) raise CLIError('Publish directory provided is uses Bot Builder SDK V3, and as a legacy bot needs to be ' 'prepared for deployment. Please run prepare-publish. For more information, run \'az bot ' 'prepare-publish -h\'.') zip_filepath = BotPublishPrep.create_upload_zip(logger, code_dir, include_node_modules=False) logger.info('Zip file path created, at %s.', zip_filepath) kudu_client = KuduClient(cmd, resource_group_name, resource_name, bot, logger) app_settings = WebAppOperations.get_app_settings( cmd=cmd, resource_group_name=resource_group_name, name=kudu_client.bot_site_name ) scm_do_build = [item['value'] for item in app_settings if item['name'] == 'SCM_DO_BUILD_DURING_DEPLOYMENT'] if scm_do_build and scm_do_build[0] == 'true': logger.info('Detected SCM_DO_BUILD_DURING_DEPLOYMENT with value of "true" in App Service\'s Application ' 'Settings. Build will commence during deployment. For more information, see ' 'https://github.com/projectkudu/kudu/wiki/Deploying-from-a-zip-file') else: logger.warning('Didn\'t detect SCM_DO_BUILD_DURING_DEPLOYMENT or its value was "false" in App Service\'s ' 'Application Settings. Build may not commence during deployment. To learn how to trigger a build' ' when deploying to your App Service, see ' 'https://github.com/projectkudu/kudu/wiki/Deploying-from-a-zip-file') subscription_id = get_subscription_id(cmd.cli_ctx) logger.warning('To change the Application Setting via az cli, use the following command:\naz webapp config ' # pylint:disable=logging-not-lazy 'appsettings set -n %s -g %s --subscription %s --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true' % (kudu_client.bot_site_name, resource_group_name, subscription_id)) output = kudu_client.publish(zip_filepath, timeout, keep_node_modules, iis_publish_info['lang']) logger.info('Bot source published. Preparing bot application to run the new source.') os.remove('upload.zip') # If the bot is a Node.js bot and did not initially have web.config, delete web.config and iisnode.yml. if iis_publish_info['lang'] == 'Node': if not iis_publish_info['has_web_config'] and os.path.exists(os.path.join(code_dir, 'web.config')): os.remove(os.path.join(code_dir, 'web.config')) if not iis_publish_info['has_iisnode_yml'] and os.path.exists(os.path.join(code_dir, 'iisnode.yml')): os.remove(os.path.join(code_dir, 'iisnode.yml')) if not iis_publish_info['has_iisnode_yml'] and not iis_publish_info['has_web_config']: logger.info("web.config and iisnode.yml for Node.js bot were fetched from Azure for deployment using IIS. " "These files have now been removed from %s." "To see the two files used for your deployment, either visit your bot's Kudu site or download " "the files from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip", code_dir) elif not iis_publish_info['has_iisnode_yml']: logger.info("iisnode.yml for Node.js bot was fetched from Azure for deployment using IIS. To see this file " "that was used for your deployment, either visit your bot's Kudu site or download the file " "from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip") elif not iis_publish_info['has_web_config']: logger.info("web.config for Node.js bot was fetched from Azure for deployment using IIS. To see this file " "that was used for your deployment, either visit your bot's Kudu site or download the file " "from https://icscratch.blob.core.windows.net/bot-packages/node_v4_publish.zip") if os.path.exists(os.path.join('.', 'package.json')) and not keep_node_modules: logger.info('Detected language javascript. Installing node dependencies in remote bot.') kudu_client.install_node_dependencies() if output.get('active'): logger.info('Deployment successful!') if not output.get('active'): scm_url = output.get('url') deployment_id = output.get('id') # Instead of replacing "latest", which would could be in the bot name, we replace "deployments/latest" deployment_url = scm_url.replace('deployments/latest', 'deployments/%s' % deployment_id) logger.error('Deployment failed. To find out more information about this deployment, please visit %s.', deployment_url) return output