Exemplo n.º 1
0
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
Exemplo n.º 2
0
    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
        }
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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}
Exemplo n.º 5
0
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}
Exemplo n.º 6
0
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
Exemplo n.º 7
0
    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
        }
Exemplo n.º 8
0
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
Exemplo n.º 9
0
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