Exemple #1
0
    def __enable_zip_deploy(self, zip_file_path):
        """Pushes local bot's source code in zip format to Kudu for deployment.

        This method deploys the zipped bot source code via Kudu's zipdeploy API. This API does not run any build
        processes such as `npm install`, `dotnet restore`, `dotnet publish`, etc.

        :param zip_file_path: string
        :return: Dictionary with results of the latest deployment
        """
        if not self.__initialized:
            self.__initialize()

        zip_url = self.__scm_url + '/api/zipdeploy'
        headers = self.__get_application_octet_stream_headers()
        self.__empty_wwwroot_folder()
        with open(os.path.realpath(os.path.expanduser(zip_file_path)), 'rb') as fs:
            zip_content = fs.read()
            self.__logger.info('Source code read, uploading to Kudu.')
            r = requests.post(zip_url, data=zip_content, headers=headers)
            if r.status_code != 200:
                raise CLIError("Zip deployment {} failed with status code '{}' and reason '{}'".format(
                    zip_url, r.status_code, r.text))
            self.__logger.info('Source code successfully uploaded.')

        self.__logger.info('Retrieving the latest deployment info.')
        # On successful deployment navigate to the app, display the latest deployment JSON response.
        response = requests.get(self.__scm_url + '/api/deployments/latest', headers=self.__auth_headers)
        HttpResponseValidator.check_response_status(response)
        return response.json()
Exemple #2
0
    def __enable_zip_deploy(self, zip_file_path):
        """Pushes local bot's source code in zip format to Kudu for deployment.

        This method deploys the zipped bot source code via Kudu's zipdeploy API. This API does not run any build
        processes such as `npm install`, `dotnet restore`, `dotnet publish`, etc.

        :param zip_file_path: string
        :return: Dictionary with results of the latest deployment
        """
        if not self.__initialized:
            self.__initialize()

        zip_url = self.__scm_url + '/api/zipdeploy'
        headers = self.__get_application_octet_stream_headers()
        self.__empty_wwwroot_folder()
        with open(os.path.realpath(os.path.expanduser(zip_file_path)),
                  'rb') as fs:
            zip_content = fs.read()
            self.__logger.info('Source code read, uploading to Kudu.')
            r = requests.post(zip_url, data=zip_content, headers=headers)
            if r.status_code != 200:
                raise CLIError(
                    "Zip deployment {} failed with status code '{}' and reason '{}'"
                    .format(zip_url, r.status_code, r.text))
            self.__logger.info('Source code successfully uploaded.')

        self.__logger.info('Retrieving the latest deployment info.')
        # On successful deployment navigate to the app, display the latest deployment JSON response.
        response = requests.get(self.__scm_url + '/api/deployments/latest',
                                headers=self.__auth_headers)
        HttpResponseValidator.check_response_status(response)
        return response.json()
Exemple #3
0
    def publish(self, zip_file_path, timeout, keep_node_modules,
                detected_language):
        """Publishes zipped bot source code to Kudu.

        Performs the following steps:
        1. Empties the `site/clirepo/` folder on Kudu
        2. Pushes the code to `site/clirepo/`
        3. Deploys the code via the zipdeploy API. (https://github.com/projectkudu/kudu/wiki/REST-API#zip-deployment)
        4. Gets the results of the latest Kudu deployment

        :param zip_file_path:
        :param timeout:
        :param keep_node_modules:
        :param detected_language:
        :return: Dictionary with results of the latest deployment
        """
        if not self.__initialized:
            self.__initialize()
        self.__empty_source_folder()

        headers = self.__get_application_octet_stream_headers()
        with open(zip_file_path, 'rb') as fs:
            zip_content = fs.read()
            response = requests.put(self.__scm_url + '/api/zip/site/clirepo',
                                    headers=headers,
                                    data=zip_content)
            HttpResponseValidator.check_response_status(response)

        return self.__enable_zip_deploy(zip_file_path, timeout,
                                        keep_node_modules, detected_language)
    def publish(self, zip_file_path, timeout, keep_node_modules, detected_language):
        """Publishes zipped bot source code to Kudu.

        Performs the following steps:
        1. Empties the `site/clirepo/` folder on Kudu
        2. Pushes the code to `site/clirepo/`
        3. Deploys the code via the zipdeploy API. (https://github.com/projectkudu/kudu/wiki/REST-API#zip-deployment)
        4. Gets the results of the latest Kudu deployment

        :param zip_file_path:
        :param timeout:
        :param keep_node_modules:
        :param detected_language:
        :return: Dictionary with results of the latest deployment
        """
        if not self.__initialized:
            self.__initialize()
        self.__empty_source_folder()

        headers = self.__get_application_octet_stream_headers()
        with open(zip_file_path, 'rb') as fs:
            zip_content = fs.read()
            response = requests.put(self.__scm_url + '/api/zip/site/clirepo',
                                    headers=headers,
                                    data=zip_content)
            HttpResponseValidator.check_response_status(response)

        return self.__enable_zip_deploy(zip_file_path, timeout, keep_node_modules, detected_language)
 def __empty_wwwroot_folder_except_for_node_modules(self):
     """Empty site/wwwroot/ folder but retain node_modules folder.
     :return:
     """
     self.__logger.info('Removing all files and folders from "site/wwwroot/" except for node_modules.')
     payload = {
         'command': '(for /D %i in (.\\*) do if not %~nxi == node_modules rmdir /s/q %i) && (for %i in (.\\*) '
                    'del %i)',
         'dir': r'site\wwwroot'
     }
     headers = self.__get_application_json_headers()
     response = requests.post(self.__scm_url + '/api/command', data=json.dumps(payload), headers=headers)
     HttpResponseValidator.check_response_status(response)
     self.__logger.info('All files and folders successfully removed from "site/wwwroot/" except for node_modules.')
    def __empty_source_folder(self):
        """Remove the `clirepo/` folder from Kudu.

        This method is called from KuduClient.publish() in preparation for uploading the user's local source code.
        After removing the folder from Kudu, the method performs another request to recreate the `clirepo/` folder.
        :return:
        """
        # The `clirepo/` folder contains the zipped up source code
        payload = {
            'command': 'rm -rf clirepo && mkdir clirepo',
            'dir': r'site'
        }
        headers = self.__get_application_json_headers()
        response = requests.post(self.__scm_url + '/api/command', data=json.dumps(payload), headers=headers)
        HttpResponseValidator.check_response_status(response)
    def __empty_wwwroot_folder(self):
        """Empty the site/wwwroot/ folder from Kudu.

        Empties the site/wwwroot/ folder by removing the entire directory, and then recreating it. Called when
        publishing a bot to Kudu.
        """
        self.__logger.info('Emptying the "site/wwwroot/" folder on Kudu in preparation for publishing.')
        payload = {
            'command': 'rm -rf wwwroot && mkdir wwwroot',
            'dir': r'site'
        }
        headers = self.__get_application_json_headers()
        response = requests.post(self.__scm_url + '/api/command', data=json.dumps(payload), headers=headers)
        HttpResponseValidator.check_response_status(response)
        self.__logger.info('"site/wwwroot/" successfully emptied.')
Exemple #8
0
    def install_node_dependencies(self):
        """Installs Node.js dependencies at `site/wwwroot/` for Node.js bots.

        This method is only called when the detected bot is a Node.js bot.

        :return: Dictionary with results of the HTTP Kudu request
        """
        if not self.__initialized:
            self.__initialize()

        payload = {'command': 'npm install', 'dir': r'site\wwwroot'}
        response = requests.post(self.__scm_url + '/api/command',
                                 data=json.dumps(payload),
                                 headers=self.__get_application_json_headers())
        HttpResponseValidator.check_response_status(response)
        return response.json()
Exemple #9
0
    def download_bot_zip(self, file_save_path, folder_path):
        """Download bot's source code from Kudu.

        This method looks for the zipped source code in the site/clirepo/ folder on Kudu. If the code is not there, the
        contents of site/wwwroot are zipped and then downloaded.

        :param file_save_path: string
        :param folder_path: string
        :return: None
        """
        if not self.__initialized:
            self.__initialize()

        headers = self.__get_application_octet_stream_headers()
        # Download source code in zip format from Kudu
        response = requests.get(self.__scm_url + '/api/zip/site/clirepo/',
                                headers=headers)
        # If the status_code is not 200, the source code was not successfully retrieved.
        # Run the prepareSrc.cmd to zip up the code and prepare it for download.
        if response.status_code != 200:
            # try getting the bot from wwwroot instead
            payload = {
                'command':
                'PostDeployScripts\\prepareSrc.cmd {0}'.format(
                    self.__password),
                'dir':
                r'site\wwwroot'
            }
            prepareSrc_response = requests.post(
                self.__scm_url + '/api/command',
                data=json.dumps(payload),
                headers=self.__get_application_json_headers())
            HttpResponseValidator.check_response_status(prepareSrc_response)

            # Overwrite previous "response" with bot-src.zip.
            response = requests.get(self.__scm_url +
                                    '/api/vfs/site/bot-src.zip',
                                    headers=headers)
            HttpResponseValidator.check_response_status(response)

        download_path = os.path.join(file_save_path, 'download.zip')
        with open(os.path.join(file_save_path, 'download.zip'), 'wb') as f:
            f.write(response.content)
        zip_ref = zipfile.ZipFile(download_path)
        zip_ref.extractall(folder_path)
        zip_ref.close()
        os.remove(download_path)
Exemple #10
0
    def install_node_dependencies(self):
        """Installs Node.js dependencies at `site/wwwroot/` for Node.js bots.

        This method is only called when the detected bot is a Node.js bot.

        :return: Dictionary with results of the HTTP Kudu request
        """
        if not self.__initialized:
            self.__initialize()

        payload = {'command': 'npm install', 'dir': r'site\wwwroot'}
        # npm install can take a very long time to complete. By default, Azure's load balancer will terminate
        # connections after 230 seconds of no inbound or outbound packets. This timeout is not configurable.
        try:
            response = requests.post(
                self.__scm_url + '/api/command',
                data=json.dumps(payload),
                headers=self.__get_application_json_headers())
            HttpResponseValidator.check_response_status(response)
        except CLIError as e:
            if response.status_code == 500 and 'The request timed out.' in response.text:
                self.__logger.warning(
                    'npm install is taking longer than expected and did not finish within the '
                    'Azure-specified timeout of 230 seconds.')
                self.__logger.warning(
                    'The installation is likely still in progress. This is a known issue, please wait'
                    ' a short while before messaging your bot. You can also visit Kudu to manually '
                    'install the npm dependencies. (https://github.com/projectkudu/kudu/wiki)'
                )
                self.__logger.warning('Your Kudu website for this bot is: %s' %
                                      self.__scm_url)
                self.__logger.warning(
                    '\nYou can also use `--keep-node-modules` in your `az bot publish` command to '
                    'not `npm install` the dependencies for the bot on Kudu.')
                subscription_id = get_subscription_id(self.__cmd.cli_ctx)
                self.__logger.warning(
                    'Alternatively, you can configure your Application Settings for the App Service '
                    'to build during zipdeploy by using the following command:\n  az webapp config '
                    'appsettings set -n %s -g %s --subscription %s --settings '
                    'SCM_DO_BUILD_DURING_DEPLOYMENT=true' %
                    (self.bot_site_name, self.__resource_group_name,
                     subscription_id))

            else:
                raise e

        return response.json()
Exemple #11
0
    def __empty_source_folder(self):
        """Remove the `clirepo/` folder from Kudu.

        This method is called from KuduClient.publish() in preparation for uploading the user's local source code.
        After removing the folder from Kudu, the method performs another request to recreate the `clirepo/` folder.
        :return:
        """
        # The `clirepo/` folder contains the zipped up source code
        payload = {
            'command': 'rm -rf clirepo && mkdir clirepo',
            'dir': r'site'
        }
        headers = self.__get_application_json_headers()
        response = requests.post(self.__scm_url + '/api/command',
                                 data=json.dumps(payload),
                                 headers=headers)
        HttpResponseValidator.check_response_status(response)
Exemple #12
0
def __install_node_dependencies(kudu_client):
    """Installs Node.js dependencies at `site/wwwroot/` for Node.js bots.

    This method is only called when the detected bot is a Node.js bot.

    :return: Dictionary with results of the HTTP Kudu request
    """
    if not kudu_client._KuduClient__initialized:  # pylint:disable=protected-access
        kudu_client._KuduClient__initialize()  # pylint:disable=protected-access

    payload = {
        'command': 'npm install',
        'dir': r'site\wwwroot'
    }
    response = requests.post(kudu_client._KuduClient__scm_url + '/api/command', data=json.dumps(payload),  # pylint:disable=protected-access
                             headers=kudu_client._KuduClient__get_application_json_headers())   # pylint:disable=protected-access
    HttpResponseValidator.check_response_status(response)
    return response.json()
Exemple #13
0
    def install_node_dependencies(self):
        """Installs Node.js dependencies at `site/wwwroot/` for Node.js bots.

        This method is only called when the detected bot is a Node.js bot.

        :return: Dictionary with results of the HTTP Kudu request
        """
        if not self.__initialized:
            self.__initialize()

        payload = {
            'command': 'npm install',
            'dir': r'site\wwwroot'
        }
        response = requests.post(self.__scm_url + '/api/command', data=json.dumps(payload),
                                 headers=self.__get_application_json_headers())
        HttpResponseValidator.check_response_status(response)
        return response.json()
Exemple #14
0
    def get_bot_file(self, bot_file):
        """Retrieve the .bot file from Kudu.

        :param bot_file:
        :return:
        """
        if not self.__initialized:
            self.__initialize()

        if bot_file.startswith('./') or bot_file.startswith('.\\'):
            bot_file = bot_file[2:]
        # Format backslashes to forward slashes and URL escape
        bot_file = quote(bot_file.replace('\\', '/'))
        request_url = self.__scm_url + '/api/vfs/site/wwwroot/' + bot_file
        self.__logger.info('Attempting to retrieve .bot file content from %s' % request_url)
        response = requests.get(request_url, headers=self.__get_application_octet_stream_headers())
        HttpResponseValidator.check_response_status(response)
        self.__logger.info('Bot file successfully retrieved from Kudu.')
        return json.loads(response.text)
Exemple #15
0
    def __empty_wwwroot_folder(self):
        """Empty the site/wwwroot/ folder from Kudu.

        Empties the site/wwwroot/ folder by removing the entire directory, and then recreating it. Called when
        publishing a bot to Kudu.
        """
        self.__logger.info(
            'Emptying the "site/wwwroot/" folder on Kudu in preparation for publishing.'
        )
        payload = {
            'command': 'rm -rf wwwroot && mkdir wwwroot',
            'dir': r'site'
        }
        headers = self.__get_application_json_headers()
        response = requests.post(self.__scm_url + '/api/command',
                                 data=json.dumps(payload),
                                 headers=headers)
        HttpResponseValidator.check_response_status(response)
        self.__logger.info('"site/wwwroot/" successfully emptied.')
Exemple #16
0
    def download_bot_zip(self, file_save_path, folder_path):
        """Download bot's source code from Kudu.

        This method looks for the zipped source code in the site/clirepo/ folder on Kudu. If the code is not there, the
        contents of site/wwwroot are zipped and then downloaded.

        :param file_save_path: string
        :param folder_path: string
        :return: None
        """
        if not self.__initialized:
            self.__initialize()

        headers = self.__get_application_octet_stream_headers()
        # Download source code in zip format from Kudu
        response = requests.get(self.__scm_url + '/api/zip/site/clirepo/', headers=headers)
        # If the status_code is not 200, the source code was not successfully retrieved.
        # Run the prepareSrc.cmd to zip up the code and prepare it for download.
        if response.status_code != 200:
            # try getting the bot from wwwroot instead
            payload = {
                'command': 'PostDeployScripts\\prepareSrc.cmd {0}'.format(self.__password),
                'dir': r'site\wwwroot'
            }
            prepareSrc_response = requests.post(self.__scm_url + '/api/command',
                                                data=json.dumps(payload),
                                                headers=self.__get_application_json_headers())
            HttpResponseValidator.check_response_status(prepareSrc_response)

            # Overwrite previous "response" with bot-src.zip.
            response = requests.get(self.__scm_url + '/api/vfs/site/bot-src.zip',
                                    headers=headers)
            HttpResponseValidator.check_response_status(response)

        download_path = os.path.join(file_save_path, 'download.zip')
        with open(os.path.join(file_save_path, 'download.zip'), 'wb') as f:
            f.write(response.content)
        zip_ref = zipfile.ZipFile(download_path)
        zip_ref.extractall(folder_path)
        zip_ref.close()
        os.remove(download_path)
Exemple #17
0
    def install_node_dependencies(self):
        """Installs Node.js dependencies at `site/wwwroot/` for Node.js bots.

        This method is only called when the detected bot is a Node.js bot.

        :return: Dictionary with results of the HTTP Kudu request
        """
        if not self.__initialized:
            self.__initialize()

        payload = {
            'command': 'npm install',
            'dir': r'site\wwwroot'
        }
        # npm install can take a very long time to complete. By default, Azure's load balancer will terminate
        # connections after 230 seconds of no inbound or outbound packets. This timeout is not configurable.
        try:
            response = requests.post(self.__scm_url + '/api/command', data=json.dumps(payload),
                                     headers=self.__get_application_json_headers())
            HttpResponseValidator.check_response_status(response)
        except CLIError as e:
            if response.status_code == 500 and 'The request timed out.' in response.text:
                self.__logger.warning('npm install is taking longer than expected and did not finish within the '
                                      'Azure-specified timeout of 230 seconds.')
                self.__logger.warning('The installation is likely still in progress. This is a known issue, please wait'
                                      ' a short while before messaging your bot. You can also visit Kudu to manually '
                                      'install the npm dependencies. (https://github.com/projectkudu/kudu/wiki)')
                self.__logger.warning('Your Kudu website for this bot is: %s' % self.__scm_url)
                self.__logger.warning('\nYou can also use `--keep-node-modules` in your `az bot publish` command to '
                                      'not `npm install` the dependencies for the bot on Kudu.')
                subscription_id = get_subscription_id(self.__cmd.cli_ctx)
                self.__logger.warning('Alternatively, you can configure your Application Settings for the App Service '
                                      'to build during zipdeploy by using the following command:\n  az webapp config '
                                      'appsettings set -n %s -g %s --subscription %s --settings '
                                      'SCM_DO_BUILD_DURING_DEPLOYMENT=true' %
                                      (self.bot_site_name, self.__resource_group_name, subscription_id))

            else:
                raise e

        return response.json()
Exemple #18
0
    def get_bot_file(self, bot_file):
        """Retrieve the .bot file from Kudu.

        :param bot_file:
        :return:
        """
        if not self.__initialized:
            self.__initialize()

        if bot_file.startswith('./') or bot_file.startswith('.\\'):
            bot_file = bot_file[2:]
        # Format backslashes to forward slashes and URL escape
        bot_file = quote(bot_file.replace('\\', '/'))
        request_url = self.__scm_url + '/api/vfs/site/wwwroot/' + bot_file
        self.__logger.info('Attempting to retrieve .bot file content from %s' %
                           request_url)
        response = requests.get(
            request_url, headers=self.__get_application_octet_stream_headers())
        HttpResponseValidator.check_response_status(response)
        self.__logger.info('Bot file successfully retrieved from Kudu.')
        return json.loads(response.text)
Exemple #19
0
 def __empty_wwwroot_folder_except_for_node_modules(self):
     """Empty site/wwwroot/ folder but retain node_modules folder.
     :return:
     """
     self.__logger.info(
         'Removing all files and folders from "site/wwwroot/" except for node_modules.'
     )
     payload = {
         'command':
         '(for /D %i in (.\\*) do if not %~nxi == node_modules rmdir /s/q %i) && (for %i in (.\\*) '
         'del %i)',
         'dir':
         r'site\wwwroot'
     }
     headers = self.__get_application_json_headers()
     response = requests.post(self.__scm_url + '/api/command',
                              data=json.dumps(payload),
                              headers=headers)
     HttpResponseValidator.check_response_status(response)
     self.__logger.info(
         'All files and folders successfully removed from "site/wwwroot/" except for node_modules.'
     )