Esempio n. 1
0
def test_expand_environment_variables_in_connection():
    conn_str = "{}://cluster('clustername').database('dbname')".format(TEST_URI_SCHEMA_NAME)
    os.environ['KQL_DATABASE'] = conn_str
    assert Parser.parse("$KQL_DATABASE {}".format(query1), empty_config) == \
            {'connection': conn_str,
            'kql': query1,
            'options': default_options}
Esempio n. 2
0
def test_parse_kusto_socket_connection_dsn():
    conn_str = "{}://username($USERNAME).password($PASSWORD).cluster('clustername').database('dbname')".format(TEST_URI_SCHEMA_NAME)
    result_conn_str = "{}://username('michael').password('michael123').cluster('clustername').database('dbname')".format(TEST_URI_SCHEMA_NAME)
    assert Parser.parse("{0} {1}".format(conn_str, query1), empty_config) == \
            {'connection': result_conn_str,
            'kql': query1,
            'options': default_options}
Esempio n. 3
0
def test_parse_kql_only():
    parsed = Parser.parse(query1, empty_config)
    print(parsed)
    assert parsed == \
           {'connection': "",
            'kql': query1,
            'options': default_options}
Esempio n. 4
0
def test_parse_kusto_socket_connection_with_credentials():
    conn_str = "{}://username('username').password('password').cluster('clustername').database('dbname')".format(
        TEST_URI_SCHEMA_NAME)
    assert Parser.parse("{0} {1}".format(conn_str, query1), empty_config, TEST_ENGINE, {}) == \
           {'connection': conn_str,
            'kql': query1,
            'options': default_options}
Esempio n. 5
0
def test_parse_kusto_socket_connection_with_env_credentials():
    conn_str = "{}://username($USERNAME).password($PASSWORD).cluster('clustername').database('dbname')".format(
        TEST_URI_SCHEMA_NAME)
    result_conn_str = "{}://username('michael').password('michael123').cluster('clustername').database('dbname')".format(
        TEST_URI_SCHEMA_NAME)
    os.environ['USERNAME'] = "******"
    os.environ['PASSWORD'] = "******"
    assert Parser.parse("{0} {1}".format(conn_str, query1), empty_config, TEST_ENGINE, {}) == \
           {'connection': result_conn_str,
            'kql': query1,
            'options': default_options}
Esempio n. 6
0
    def _parse_common_connection_str(self, conn_str: str, current,
                                     uri_schema_name, mandatory_key: str,
                                     alt_names: list,
                                     valid_keys_combinations: list,
                                     user_ns: dict):
        # parse schema part
        parts = conn_str.split("://", 1)
        if len(parts) != 2 or parts[0] not in alt_names:
            raise KqlEngineError(
                'invalid connection string, must be prefixed by a valid "<uri schema name>://"'
            )

        rest = conn_str[len(parts[0]) + 3:].strip()

        # get key/values in connection string
        parsed_conn_kv = Parser.parse_and_get_kv_string(rest, user_ns)

        # In case certificate_pem_file was specified instead of certificate.
        pem_file_name = parsed_conn_kv.get(ConnStrKeys.CERTIFICATE_PEM_FILE)
        if pem_file_name is not None:
            with open(pem_file_name, "r") as pem_file:
                parsed_conn_kv[ConnStrKeys.CERTIFICATE] = pem_file.read()
                del parsed_conn_kv[ConnStrKeys.CERTIFICATE_PEM_FILE]

        matched_keys_set = set(parsed_conn_kv.keys())

        # check for unknown keys
        all_keys = set(itertools.chain(*valid_keys_combinations))
        unknonw_keys_set = matched_keys_set.difference(all_keys)
        if len(unknonw_keys_set) > 0:
            raise KqlEngineError(
                "invalid connection string, detected unknown keys: {0}.".
                format(unknonw_keys_set))

        # check that mandatory key in matched set
        if mandatory_key not in matched_keys_set:
            raise KqlEngineError(
                "invalid connection strin, mandatory key {0} is missing.".
                format(mandatory_key))

        # find a valid combination for the set
        valid_combinations = [
            c for c in valid_keys_combinations if matched_keys_set.issubset(c)
        ]
        # in case of ambiguity, assume it is based on current connection, resolve by copying missing values from current
        if len(valid_combinations) > 1:
            if current is not None:
                for k, v in current._parsed_conn.items():
                    if k not in matched_keys_set and k not in self._NOT_INHERITABLE_KEYS:
                        parsed_conn_kv[k] = v
                        matched_keys_set.add(k)
                for k in self._CREDENTIAL_KEYS.intersection(matched_keys_set):
                    if parsed_conn_kv[k] != current._parsed_conn.get(k):
                        raise KqlEngineError(
                            "invalid connection string, not a valid keys set, missing keys."
                        )
        valid_combinations = [
            c for c in valid_combinations if matched_keys_set.issubset(c)
        ]

        # only one combination can be accepted
        if len(valid_combinations) == 0:
            raise KqlEngineError(
                "invalid connection string, not a valid keys set, missing keys."
            )

        conn_keys_list = None
        # if still too many choose the shortest
        if len(valid_combinations) > 1:
            for c in valid_combinations:
                if len(c) == 3:
                    conn_keys_list = c
        else:
            conn_keys_list = valid_combinations[0]

        if conn_keys_list is None:
            raise KqlEngineError(
                "invalid connection string, not a valid keys set, missing keys."
            )

        conn_keys_set = set(conn_keys_list)

        # in case inheritable fields are missing inherit from current if exist
        inherit_keys_set = self._INHERITABLE_KEYS.intersection(
            conn_keys_set).difference(matched_keys_set)
        if len(inherit_keys_set) > 1:
            if current is not None:
                for k in inherit_keys_set:
                    v = current._parsed_conn.get(k)
                    if v is not None:
                        parsed_conn_kv[k] = v
                        matched_keys_set.add(k)

        # make sure that all required keys are in set
        secret_key_set = self._SECRET_KEYS.intersection(conn_keys_set)
        missing_set = conn_keys_set.difference(matched_keys_set).difference(
            secret_key_set).difference(self._OPTIONAL_KEYS)
        if len(missing_set) > 0:
            raise KqlEngineError(
                "invalid connection string, missing {0}.".format(missing_set))
        # special case although tenant in _OPTIONAL_KEYS
        if parsed_conn_kv.get(
                ConnStrKeys.TENANT
        ) is None and ConnStrKeys.CLIENTID in conn_keys_set:
            raise KqlEngineError(
                "invalid connection string, missing tenant key/value.")

        # make sure that all required keys are with proper value
        for key in matched_keys_set:  # .difference(secret_key_set).difference(self._SHOULD_BE_NULL_KEYS):
            if key in self._SHOULD_BE_NULL_KEYS:
                if parsed_conn_kv[key] != "":
                    raise KqlEngineError(
                        "invalid connection string, key {0} must be empty.".
                        format(key))
            elif key not in self._SECRET_KEYS:
                if parsed_conn_kv[key] == "<{0}>".format(
                        key) or parsed_conn_kv[key] == "":
                    raise KqlEngineError(
                        "invalid connection string, key {0} cannot be empty or set to <{1}>."
                        .format(key, key))

        # in case secret is missing, get it from user
        if len(secret_key_set) == 1:
            s = secret_key_set.pop()
            if s not in matched_keys_set or parsed_conn_kv[
                    s] == "<{0}>".format(s):
                parsed_conn_kv[s] = getpass.getpass(
                    prompt="please enter {0}: ".format(s))
                matched_keys_set.add(s)

        # set attribuets
        self.cluster_name = parsed_conn_kv.get(
            ConnStrKeys.CLUSTER) or uri_schema_name
        self.database_name = parsed_conn_kv.get(mandatory_key)
        self.alias = parsed_conn_kv.get(ConnStrKeys.ALIAS)
        bind_url = []
        for key in conn_keys_list:
            if key not in self._EXCLUDE_FROM_URL_KEYS:
                bind_url.append("{0}('{1}')".format(key,
                                                    parsed_conn_kv.get(key)))
        self.bind_url = "{0}://".format(uri_schema_name) + ".".join(bind_url)
        return parsed_conn_kv
Esempio n. 7
0
def test_parse_kusto_socket_connection():
    conn_str = "{}://cluster('clustername').database('dbname')".format(TEST_URI_SCHEMA_NAME)
    assert Parser.parse("{0} {1}".format(conn_str, query1), empty_config) == \
           {'connection': conn_str,
            'kql': query1,
            'options': default_options}
Esempio n. 8
0
def test_parse_with_kql():
    assert Parser.parse("dbname@clustername {}".format(query1),
                 empty_config) == \
           {'connection': "dbname@clustername",
            'kql': query1,
            'options': default_options}
Esempio n. 9
0
def test_parse_no_kql():
    assert Parser.parse("dbname@clustername", empty_config) == \
           {'connection': "dbname@clustername",
            'kql': '',
            'options': default_options}
Esempio n. 10
0
    def execute(self, line, cell="", local_ns={}):
        """Query Kusto or ApplicationInsights using kusto query language (kql). Repository specified by a connect string.

        Magic Syntax::

            %%kql <connection-string>
            <KQL statement>
            # Note: establish connection and query.

            %%kql <established-connection-reference>
            <KQL statemnt>
            # Note: query using an established connection.

            %%kql
            <KQL statement>
            # Note: query using current established connection.

            %kql <KQL statment>
            # Note: single line query using current established connection.

            %kql <connection-string>
            # Note: established connection only.


        Connection string Syntax::

            kusto://username('<username>).password(<password>).cluster(<cluster>).database(<database>')

            appinsights://appid(<appid>).appkey(<appkey>)

            loganalytics://workspace(<workspaceid>).appkey(<appkey>)

            %<connectionStringVariable>%
            # Note: connection string is taken from the environment variable.

            [<sectionName>]
            # Note: connection string is built from the dsn file settings, section <sectionName>. 
            #       The dsn filename value is taken from configuartion value Kqlmagic.dsn_filename.

            # Note: if password or appkey component is missing, user will be prompted.
            # Note: connection string doesn't have to include all components, see examples below.
            # Note: substring of the form $name or ${name} in windows also %name%, are replaced by environment variables if exist.


        Examples::

            %%kql kusto://username('myName').password('myPassword').cluster('myCluster').database('myDatabase')
            <KQL statement>
            # Note: establish connection to kusto and submit query.

            %%kql myDatabase@myCluster
            <KQL statement>
            # Note: submit query using using an established kusto connection to myDatabase database at cluster myCluster.

            %%kql appinsights://appid('myAppid').appkey('myAppkey')
            <KQL statement>
            # Note: establish connection to ApplicationInsights and submit query.

            %%kql myAppid@appinsights
            <KQL statement>
            # Note: submit query using established ApplicationInsights connection to myAppid.

            %%kql loganalytics://workspace('myWorkspaceid').appkey('myAppkey')
            <KQL statement>
            # Note: establish connection to LogAnalytics and submit query.

            %%kql myWorkspaceid@loganalytics
            <KQL statement>
            # Note: submit query using established LogAnalytics connection to myWorkspaceid.

            %%kql
            <KQL statement>
            # Note: submit query using current established connection.

            %kql <KQL statement>
            # Note: submit single line query using current established connection.

            %%kql kusto://cluster('myCluster').database('myDatabase')
            <KQL statement>
            # Note: establish connection to kusto using current username and password to form the full connection string and submit query.

            %%kql kusto://database('myDatabase')
            <KQL statement>
            # Note: establish connection to kusto using current username, password and cluster to form the full connection string and submit query.

            %kql kusto://username('myName').password('myPassword').cluster('myCluster')
            # Note: set current (default) username, passsword and cluster to kusto.

            %kql kusto://username('myName').password('myPassword')
            # Note: set current (default) username and password to kusto.

            %kql kusto://cluster('myCluster')
            # Note set current (default) cluster to kusto.
        """

        set_logger(Logger(None, create_log_context()))

        # save globals and locals so they can be referenced in bind vars
        user_ns = self.shell.user_ns.copy()
        user_ns.update(local_ns)

        logger().debug("To Parsed: \n\rline: {}\n\rcell:\n\r{}".format(
            line, cell))
        try:
            parsed = None
            parsed_queries = Parser.parse("%s\n%s" % (line, cell), self)
            logger().debug("Parsed: {}".format(parsed_queries))
            result = None
            for parsed in parsed_queries:
                result = self.execute_query(parsed, user_ns)
            return result
        except Exception as e:
            if parsed:
                if parsed["options"].get("short_errors", self.short_errors):
                    Display.showDangerMessage(str(e))
                    return None
            elif self.short_errors:
                Display.showDangerMessage(str(e))
                return None
            raise