Details
| ID | Snowflake |
|---|---|
| Provider | Snowflake |
| Category | Database |
| From Version | 5.0.0 |
| Docker Image | demisto/snowflake:1.0.0.10238860 |
| Supported Modules | Agentix XSIAM |
README
Use the Snowflake integration to query and update your Snowflake database.
Configure Snowflake on Cortex XSOAR
Several parameters are explained in greater detail in the Detailed Instructions section.
- Navigate to Settings > Integrations > Servers & Services.
- Search for Snowflake.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Username
- Account - See Detailed Description section.
- Region (only if you are not US West)
- Authenticator - See Detailed Description section.
- Default warehouse to use
- Default database to use
- Default schema to use
- Default role to use
- Use system proxy settings
- Trust server certificate (insecure)
- Client ID
- Client Secret
- OAuth Token URL
- OAuth Scope
- Fetch incidents
- Fetch query to retrieve new incidents. This field is mandatory when ‘Fetches incidents’ is set to true.
- First fetch timestamp ( , e.g., 12 hours, 7 days)
- The name of the field/column that contains the datetime object or timestamp for the data being fetched (case sensitive). This field is mandatory when ‘Fetches incidents’ is set to true.
- The name of the field/column in the fetched data from which the name for the Cortex XSOAR incident will be assigned (case sensitive)
- The maximum number of rows to be returned by a fetch
- Incident type
- Click Test to validate the URLs, token, and connection.
Detailed Instructions
Additional information for configuring the integration instance.
Integration Parameters
-
Account
The name of the Snowflake account to connect to without the domain name: snowflakecomputing.com. For example, mycompany.snowflakecomputing.com, enter “mycompany”. For more information, see the Snowflake Computing documentation. -
Authenticator
(Optional) Use this parameter to log in to your Snowflake account using Okta. For the ‘Username’ parameter, enter your ‘<okta_login_name>’. For the ‘Password’ parameter, enter your ‘<okta_password>’. The value entered here should be ‘https://<okta_account_name>.okta.com/’ where all the values between the less than and greater than symbols are replaced with the actual information specific to your Okta account. -
Credentials
To use Key Pair authentication, follow these instructions:- Follow steps 1-4 in the instructions detailed in the Snowflake Computing documentation.
- Follow the instructions under the section titled Configure Cortex XSOAR Credentials at this link.
- Use the credentials you configured. Refer to the two images at the bottom of the section titled Configure an External Credentials Vault.
-
Authentication via External OAuth
To configure External OAuth authentication, please consult the following setup guidelines: Snowflake External OAuth Overview.When using External OAuth, fill in the OAuth Client ID, OAuth Client Secret, OAuth Token URL, and optionally the OAuth Scope parameters. The Username field should still be set to the Snowflake service user that the IdP token maps to. The Password field can be left empty.
Prerequisites
- In the IdP (e.g., Okta):
- Create an API Services application (no user redirect, machine-to-machine).
- Note the Client ID and Client Secret.
- Create / use a Custom Authorization Server.
- Add a custom scope named
session:role:<SNOWFLAKE_ROLE>(e.g.,session:role:ANALYST).
- In Snowflake:
- Create an External OAuth Security Integration that trusts the IdP's issuer & JWKS URL and maps the JWT
subclaim to a Snowflake user. - Create / use a service user of
TYPE = SERVICE.
- Create an External OAuth Security Integration that trusts the IdP's issuer & JWKS URL and maps the JWT
- In the IdP (e.g., Okta):
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.
1. Run a query in Snowflake
Executes a SELECT query and retrieve the data.
Base Command
snowflake-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | The query to execute. | Required |
| warehouse | The warehouse to use for the query. If not specified, the default will be used. | Optional |
| database | The database to use for the query. If not specified, the default will be used. | Optional |
| schema | The schema to use for the query. If not specified, the default will be used. | Optional |
| role | The role to use for the query. If not specified, the default will be used. | Optional |
| limit | The number of rows to retrieve. | Optional |
| columns | A CSV list of columns to display in the specified order, for example: “Name, ID, Timestamp” | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Snowflake.Query | String | The query used to fetch results from the database. |
| Snowflake.Result | Unknown | Results from querying the database. |
| Snowflake.Database | String | The name of the database object. |
| Snowflake.Schema | String | The name of the schema object. |
Command Example
snowflake-query warehouse=demo_wh database=demo_db schema=public query="select * from test"
Context Example
{
"Snowflake": {
"Query": "select * from test",
"Schema": "public",
"Result": [
{
"TS": "2018-09-11 00:00:00.000000",
"ID": 1,
"NAME": "b"
},
{
"TS": "2018-10-12 00:00:00.000000",
"ID": 2,
"NAME": "kuku"
},
{
"TS": "2018-10-12 00:00:00.000000",
"ID": 3,
"NAME": "kiki"
},
{
"TS": "2018-10-12 00:00:00.000000",
"ID": 4,
"NAME": "kaka"
},
{
"TS": "2018-10-12 00:00:00.000000",
"ID": 5,
"NAME": "kuku"
},
{
"TS": "2019-03-26 11:14:18.574000",
"ID": 8,
"NAME": "blah"
},
{
"TS": "2019-03-26 11:16:16.773000",
"ID": 8,
"NAME": "new"
},
{
"TS": "2019-03-26 11:30:42.479000",
"ID": 9,
"NAME": "nBw4QhFcGJ"
},
{
"TS": "2019-03-14 00:00:00.000000",
"ID": 10,
"NAME": "UPDATing"
},
{
"TS": "2019-03-19 00:00:00.000000",
"ID": 11,
"NAME": "TESTING IT OUT again"
},
{
"TS": "2019-03-28 05:32:13.355000",
"ID": 13,
"NAME": "New Alert"
},
{
"TS": "2019-03-28 06:09:26.153000",
"ID": 14,
"NAME": "SHOULD FETCH THIS NEW"
},
{
"TS": "2019-03-28 08:46:50.311000",
"ID": 15,
"NAME": "Perth"
},
{
"TS": "2019-03-28 06:19:06.271000",
"ID": 16,
"NAME": "Edinburgh"
},
{
"TS": "2019-03-28 06:19:14.059000",
"ID": 17,
"NAME": "York"
},
{
"TS": "2019-03-28 06:20:27.126000",
"ID": 18,
"NAME": "Persimmon"
},
{
"TS": "2019-03-28 06:28:31.001000",
"ID": 19,
"NAME": "Langdon"
},
{
"TS": "2019-03-28 11:53:41.416000",
"ID": 20,
"NAME": "London"
}
],
"Database": "demo_db"
}
}
Human Readable Output
select * from test
| ID | NAME | TS |
|---|---|---|
| 1 | b | 2018-09-11 00:00:00.000000 |
| 2 | kuku | 2018-10-12 00:00:00.000000 |
| 3 | kiki | 2018-10-12 00:00:00.000000 |
| 4 | kaka | 2018-10-12 00:00:00.000000 |
| 5 | kuku | 2018-10-12 00:00:00.000000 |
| 8 | blah | 2019-03-26 11:14:18.574000 |
| 8 | new | 2019-03-26 11:16:16.773000 |
| 9 | nBw4QhFcGJ | 2019-03-26 11:30:42.479000 |
| 10 | UPDATing | 2019-03-14 00:00:00.000000 |
| 11 | TESTING IT OUT again | 2019-03-19 00:00:00.000000 |
| 13 | New Alert | 2019-03-28 05:32:13.355000 |
| 14 | SHOULD FETCH THIS NEW | 2019-03-28 06:09:26.153000 |
| 15 | Perth | 2019-03-28 08:46:50.311000 |
| 16 | Edinburgh | 2019-03-28 06:19:06.271000 |
| 17 | York | 2019-03-28 06:19:14.059000 |
| 18 | Persimmon | 2019-03-28 06:20:27.126000 |
| 19 | Langdon | 2019-03-28 06:28:31.001000 |
| 20 | London | 2019-03-28 11:53:41.416000 |
2. Make a DML change in the database
Makes a DML change in the database.
Base Command
snowflake-update
Input
| Argument Name | Description | Required |
|---|---|---|
| db_operation | The command to execute. | Required |
| warehouse | The warehouse to use for the query. If not specified, the default will be used. | Optional |
| database | The database to use for the query. If not specified, the default will be used. | Optional |
| schema | The schema to use for the query. If not specified, the default will be used. | Optional |
| role | The role to use for the query. If not specified, the default will be used. | Optional |
Context Output
There is no context output for this command.
Command Example
snowflake-update warehouse=demo_wh database=demo_db schema=public db_operation="update test set NAME='Persimmon' where ID=18"
Human Readable Output
Operation executed successfully.
Configuration parameters
account— Account - See Detailed Description section. (required)credentials— Usernameregion— Region (only if you are not US West)authenticator— Authenticator - See Detailed Description section.warehouse— Default warehouse to use (required)database— Default database to use (required)schema— Default schema to userole— Default role to useproxy— Use system proxy settingsinsecure— Trust any certificate (not secure)oauth_client_id— Client IDoauth_client_secret—oauth_token_url— OAuth Token URLoauth_scope— OAuth ScopeisFetch— Fetch incidentsfetch_query— Fetch query to retrieve new incidents. This field is mandatory when 'Fetches incidents' is set to true.fetch_time— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)datetime_column— The name of the field/column that contains the datetime object or timestamp for the data being fetched (case sensitive). This field is mandatory when 'Fetches incidents' is set to true.incident_name_column— The name of the field/column in the fetched data from which the name for the demisto incident will be assigned (case sensitive)limit— The maximum number of rows to be returned by a fetchincidentType— Incident typeincidentFetchInterval— Incidents Fetch Interval
Commands (2)
-
snowflake-queryExecutes a SELECT query and retrieve the data.
-
snowflake-updateMakes a DML change in the database.
import demistomock as demisto # pylint: disable=W9005 from CommonServerPython import * # pylint: disable=W9011 from CommonServerUserPython import * """IMPORTS""" from datetime import date, datetime, timedelta from datetime import time as dttime from decimal import Decimal import snowflake.connector # pylint: disable=E0401,E0611 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization """GLOBAL VARS""" PARAMS = demisto.params() # pylint: disable=W9016 CREDENTIALS = PARAMS.get("credentials", {}) USER = CREDENTIALS.get("identifier") PASSWORD = CREDENTIALS.get("password") CERTIFICATE = CREDENTIALS.get("credentials", {}).get("sshkey", "").encode() CERT_PASSWORD = CREDENTIALS.get("credentials", {}).get("password") CERT_PASSWORD = CERT_PASSWORD.encode() if CERT_PASSWORD else None ACCOUNT = PARAMS.get("account") AUTHENTICATOR = PARAMS.get("authenticator") REGION = PARAMS.get("region") WAREHOUSE = PARAMS.get("warehouse") DATABASE = PARAMS.get("database") SCHEMA = PARAMS.get("schema") ROLE = PARAMS.get("role") INSECURE = PARAMS.get("insecure", False) OAUTH_CLIENT_ID = PARAMS.get("oauth_client_id") OAUTH_CLIENT_SECRET = PARAMS.get("oauth_client_secret", {}).get("password") OAUTH_TOKEN_URL = PARAMS.get("oauth_token_url") OAUTH_SCOPE_RAW = PARAMS.get("oauth_scope", "") def parse_oauth_scope(raw_scope: str) -> str | None: """Convert a comma-separated scope string to space-separated as required by the Snowflake API. Args: raw_scope: Comma-separated scope string (e.g. "scope1,scope2,scope3"). Returns: Space-separated scope string, or None if the input is empty. """ if not raw_scope: return None result = " ".join(scope.strip() for scope in raw_scope.split(",") if scope.strip()) return result or None OAUTH_SCOPE = parse_oauth_scope(OAUTH_SCOPE_RAW) # How much time before the first fetch to retrieve incidents IS_FETCH = PARAMS.get("isFetch") FETCH_TIME = PARAMS.get("fetch_time") FETCH_QUERY = PARAMS.get("fetch_query") DATETIME_COLUMN = PARAMS.get("datetime_column") INCIDENT_NAME_COLUMN = PARAMS.get("incident_name_column") MAX_ROWS = int(PARAMS.get("limit")) if PARAMS.get("limit") else 10000 TYPE_CODE_TO_DATATYPE = { 0: "number/int", 1: "real", 2: "varchar/string", 3: "date", 4: "timestamp", 5: "variant", 6: "timestamp_ltz", 7: "timestamp_tz", 8: "timestamp_tz", 9: "object", 10: "array", 11: "binary", 12: "time", 13: "boolean", } DT_NEEDS_CHECKING = {"date", "timestamp", "timestamp_ltz", "timestamp_tz", "time"} """SETUP""" if IS_FETCH and not (FETCH_QUERY and DATETIME_COLUMN): err_msg = "When fetching is enabled there are two additional parameters that are required;" err_msg += " The fetch query that determines what data to fetch and the name of the column" err_msg += " in the fetched data that contains a datetime object or timestamp." raise Exception(err_msg) """HELPER FUNCTIONS""" def convert_datetime_to_string(v): # pylint: disable=W9014 """ Parses date, time, timedelta, or datetime object into string parameter: (datetime/date/time/timedelta) v The datetime/date/time/timedelta object to convert returns: Formatted string of the object """ if isinstance(v, datetime): formatted_datetime = v.strftime("%Y-%m-%d %H:%M:%S.%f %z").strip() if "." in formatted_datetime: # convert the time format to 2 digits after the decimal point to co-p with server formats. split_datetime = formatted_datetime.split(".") fixed_datetime = split_datetime[0] + "." + split_datetime[1][:2] else: fixed_datetime = formatted_datetime return fixed_datetime.strip() elif isinstance(v, date): return v.strftime("%Y-%m-%d").strip() elif isinstance(v, dttime): return v.strftime("%H:%M:%S.%f").strip() return v def error_message_from_snowflake_error(e): # pylint: disable=W9014 """ Return formatted error message from contents of a Snowflake error parameter: (snowflake.connector.errors.Error) e The Snowflake error object returns: Formatted error message """ err_msg = f"Snowflake DB error code: {e.errno}\n" err_msg += f"ANSI-compliant SQL State code: {e.sqlstate}\n" err_msg += f"Snowflake query ID: {e.sfqid}\n" err_msg += "Error message: {}" if e.errno == 606: first_sentence = e.raw_msg[: e.raw_msg.find(".") + 1] err_msg = err_msg.format(first_sentence) err_msg += " Specify an active warehouse in the command " err_msg += "arguments or in the integration parameters." elif e.errno == 2003: err_msg = err_msg.format(e.raw_msg) err_msg += " A possible explanation is that the values you entered" err_msg += " for the 'warehouse' and 'database' were incorrect." else: err_msg = err_msg.format(e.raw_msg) return err_msg def set_provided(params, key, val1, val2=None): # pylint: disable=W9014 """ If value is provided, set it in the dict """ if val1: params[key] = val1 elif val2: params[key] = val2 def process_table_row(row, checks): # pylint: disable=W9014 """ Check row data and reformat if necessary The 'checks' parameter contains the names of fields that have the potential to cause issues when they will be json decoded. This function checks the values of the fields flagged in the 'checks' parameter and formats the contents to a json friendly type if necessary. parameter: (dict) row The data (table row) that needs to be processed parameter: (dict[str, list]) checks Dictionary where the key is a string indicative of the type (or bucket of types) that needs reformatting and the values are a list of column names whose data is of that type returns: Reformatted Row """ for column_name, val in row.items(): if column_name in checks.get("isDecimal", []): # Then check the value and reformat it if necessary if isinstance(val, Decimal): row[column_name] = str(val) elif column_name in checks.get("isDT", []): # Then reformat it if necessary row[column_name] = convert_datetime_to_string(val) return row def format_to_json_serializable(column_descriptions, results): # pylint: disable=W9014 """ Screen and reformat any data in 'results' argument that is not json serializable, and return 'results'. 'results' can be a table of data (a list of rows) or a single row. parameter: (list) column_descriptions The metadata that describes data for each column in the 'results' parameter parameter: (list/dict) results What was returned by the cursor object's execute or fetch operation returns: Reformatted 'results' """ name = 0 type_code = 1 checks: dict = {} # Screen by type_code for col in column_descriptions: # if col[type_code] == 0: if TYPE_CODE_TO_DATATYPE.get(col[type_code]) == "number/int": # Then need to check that column's data to see if its data type is Decimal checks.setdefault("isDecimal", []).append(col[name]) # elif col[type_code] in {3, 4, 6, 7, 8, 12}: elif TYPE_CODE_TO_DATATYPE.get(col[type_code]) in DT_NEEDS_CHECKING: # Then need to check that column's data to see if its data type is date, time, timedelta or datetime checks.setdefault("isDT", []).append(col[name]) # if 'results' is a list then it is a data table (list of rows) and need to process each row # in the table, otherwise if 'results' is a dict then it a single table row # Check candidates and reformat if necessary if isinstance(results, dict): results = process_table_row(results, checks) else: # if 'results' isn't a dict, assume it's a list for i, row in enumerate(results): results[i] = process_table_row(row, checks) return results def get_connection_params(args): # pylint: disable=W9014 """ Construct and return the connection parameters parameter: (dict) args The command arguments of the command function calling this helper function returns: Snowflake connection params """ params: dict = {} # Validate that at least one authentication method is configured has_oauth = any([OAUTH_CLIENT_ID, OAUTH_TOKEN_URL, OAUTH_CLIENT_SECRET, OAUTH_SCOPE]) has_certificate = bool(CERTIFICATE) has_password = bool(PASSWORD) if not any([has_oauth, has_certificate, has_password]): raise ValueError( "No authentication method configured. Please provide one of: " "Credentials (username/password), Certificate (key pair), or OAuth parameters." ) if not USER: raise ValueError("Username is required for all authentication methods (username/password, key pair, and OAuth).") set_provided(params, "user", USER) # user value is required for all authentication methods set_provided(params, "account", ACCOUNT) set_provided(params, "region", REGION) set_provided(params, "insecure_mode", INSECURE) set_provided(params, "warehouse", args.get("warehouse"), WAREHOUSE) set_provided(params, "database", args.get("database"), DATABASE) set_provided(params, "schema", args.get("schema"), SCHEMA) set_provided(params, "role", args.get("role"), ROLE) if has_oauth: if not all([OAUTH_CLIENT_ID, OAUTH_TOKEN_URL, OAUTH_CLIENT_SECRET]): raise ValueError("OAuth Client ID, Client Secret, and Token URL must all be provided for OAuth authentication.") # ── OAuth (External OAuth, Client Credentials grant) ── # The snowflake-connector-python natively supports the client credentials flow. # Override the authenticator with the OAuth-specific value. set_provided(params, "authenticator", "OAUTH_CLIENT_CREDENTIALS") set_provided(params, "oauth_client_id", OAUTH_CLIENT_ID) set_provided(params, "oauth_client_secret", OAUTH_CLIENT_SECRET) set_provided(params, "oauth_token_request_url", OAUTH_TOKEN_URL) set_provided(params, "oauth_scope", OAUTH_SCOPE) elif has_certificate: set_provided(params, "authenticator", AUTHENTICATOR) # ── Key Pair authentication ── p_key = serialization.load_pem_private_key(CERTIFICATE, password=CERT_PASSWORD, backend=default_backend()) pkb = p_key.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) params["private_key"] = pkb else: # Username + Password set_provided(params, "authenticator", AUTHENTICATOR) set_provided(params, "password", PASSWORD) return params def row_to_incident(column_descriptions, row): # pylint: disable=W9014 """ Create incident from data returned by queried database in fetch_incidents parameter: (list) column_descriptions The metadata that describes the values for each column in the 'data' parameter parameter: (dict) row The row of data where each cell's key in the row is the name of the column to which it belongs returns: Incident Object """ incident = {} occurred = row.get(DATETIME_COLUMN) timestamp = None if occurred: if isinstance(occurred, dttime | timedelta): err_msg = "The datetime field specified in the integration parameters must " err_msg += 'contain values of type "datetime" or "date".' raise Exception(err_msg) timestamp = occurred.timestamp() * 1000 else: err_msg = "Nothing found when trying to fetch the datetime field specified in" err_msg += " the integration parameters. Please check that the name was correct." err_msg += " If the field name was correct, verify that the returned value for" err_msg += " the specified field is not NULL for ALL of the rows to be fetched." raise Exception(err_msg) # Incident Title if INCIDENT_NAME_COLUMN: name = row.get(INCIDENT_NAME_COLUMN) else: name = "Snowflake Incident -- " name += convert_datetime_to_string(occurred) + "- " + str(datetime.now().timestamp()) incident["name"] = name incident["occurred"] = timestamp_to_datestring(timestamp) # Incident occurrence time as timestamp - the datetime field specified in the integration parameters incident["timestamp"] = timestamp # The raw response for the row (reformatted to be json serializable) returned by the db query reformatted_row = format_to_json_serializable(column_descriptions, row) incident["rawJSON"] = json.dumps(reformatted_row) return incident """MAIN FUNCTIONS / API CALLS""" def test_module(): """ Test the validity of the integration instance parameters by trying to create a connection returns: An 'ok' message if valid, otherwise an error message """ params = get_connection_params({}) with snowflake.connector.connect(**params): # pylint: disable=E1101 demisto.results("ok") # pylint: disable=W9008 def fetch_incidents(): """ Fetch events from this integration and return them as Demisto incidents returns: Demisto incidents """ # demisto.getLastRun() will returns an obj with the previous run in it. last_run = demisto.getLastRun() # Get the last fetch time and data if it exists last_fetch = last_run.get("last_fetched_data_timestamp") last_fetched_data = last_run.get("last_fetched_data") # Handle first time fetch, fetch incidents retroactively if not last_fetch: last_fetch, _ = parse_date_range(FETCH_TIME, to_timestamp=True) args = {"limit": MAX_ROWS, "query": FETCH_QUERY} column_descriptions, data = snowflake_query(args) data.sort(key=lambda k: k[DATETIME_COLUMN]) # convert the data/events to demisto incidents incidents = [] for row in data: incident = row_to_incident(column_descriptions, row) incident_timestamp = incident.get("timestamp") # Update last run and add incident if the incident is newer than last fetch if incident_timestamp and incident_timestamp >= last_fetch: last_fetch = incident_timestamp if incident.get("rawJSON") != last_fetched_data: last_fetched_data = incident.get("rawJSON") del incident["timestamp"] incidents.append(incident) this_run = {"last_fetched_data": last_fetched_data, "last_fetched_data_timestamp": last_fetch} demisto.setLastRun(this_run) demisto.incidents(incidents) def snowflake_query(args): # pylint: disable=W9014 params = get_connection_params(args) query = args.get("query") limit = args.get("limit", "100") try: limit = int(limit) except ValueError: raise ValueError("The value for limit must be an integer.") if limit > MAX_ROWS: limit = MAX_ROWS with snowflake.connector.connect(**params) as connection, connection.cursor(snowflake.connector.DictCursor) as cur: # pylint: disable=E1101 cur.execute(query) results = cur.fetchmany(limit) if results: return cur.description, results else: return [], [] def snowflake_query_command(): args = demisto.args() # pylint: disable=W9017 query = args.get("query") db = args.get("database") if args.get("database") else DATABASE schema = args.get("schema") if args.get("schema") else SCHEMA col_descriptions, results = snowflake_query(args) if not results: demisto.results("No data found matching the query") # pylint: disable=W9008 else: results = format_to_json_serializable(col_descriptions, results) entry_context = {"Database": db, "Schema": schema, "Query": query, "Result": results} columns = argToList(args.get("columns")) human_readable = tableToMarkdown(query, results, columns, removeNull=True) demisto_transform = "Snowflake(val.Query && val.Query === obj.Query" demisto_transform += " && val.Database && val.Database === obj.Database" demisto_transform += " && val.Schema && val.Schema === obj.Schema)" outputs = {demisto_transform: entry_context} return_outputs(outputs=outputs, readable_output=human_readable, raw_response=results) # pylint: disable=W9009 def snowflake_update_command(): args = demisto.args() # pylint: disable=W9017 db_operation = args.get("db_operation") params = get_connection_params(args) with snowflake.connector.connect(**params) as connection, connection.cursor() as cursor: # pylint: disable=E1101 cursor.execute(db_operation) demisto.results("Operation executed successfully.") # pylint: disable=W9008 """COMMAND SWITCHBOARD""" commands = { "test-module": test_module, "fetch-incidents": fetch_incidents, "snowflake-query": snowflake_query_command, "snowflake-update": snowflake_update_command, } """EXECUTION""" def main() -> None: error = "" try: handle_proxy() if demisto.command() in commands: commands[demisto.command()]() except snowflake.connector.errors.Error as e: # pylint: disable=E1101 error = error_message_from_snowflake_error(e) except Exception as e: if IS_FETCH: raise e else: if isinstance(e, snowflake.connector.errors.Error): # pylint: disable=E1101 error = error_message_from_snowflake_error(e) else: error = str(e) finally: if error: return_error(error) if __name__ in ("__main__", "__builtin__", "builtins"): main()