SolarWinds
The SolarWinds integration interacts with the SWIS API to allow you to fetch alerts and events. It also provides commands to retrieve lists of alerts and events.
Network Security · SolarWinds
Details
| ID | SolarWinds |
|---|---|
| Provider | SolarWinds Corporation |
| Category | Network Security |
| From Version | 5.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
The SolarWinds integration interacts with the SWIS API to allow users to fetch alerts and events. It also provides commands to retrieve lists of alerts and events.
This integration was integrated and tested with version 3.0.0 of SolarWinds Information Service (SWIS API).
SolarWinds Help
The SolarWinds integration requires installation of SolarWinds Orion Platform which consolidates the full suite of monitoring capabilities into one platform. The following products used in this integration are managed under Orion
- Network Performance Manager
- Netflow Traffic Analyzer
- Network Configuration Manager
- IP Address Manager
- Log Analyzer
- Server and Application Monitor
How to install SolarWinds Orion Platform
Follow this link to view a comprehensive guide on how to install Orion and the managed products.
Configure SolarWinds in Cortex
| Parameter | Description | Required |
|---|---|---|
| Name or IP address of the Orion server | Name or IP address of the Orion server you want to connect to. Do not specify the port number. Examples: myorigin.mydomain.local, 12.153.24.2 | True |
| Port | Port number with which to connect to the Orion server. Default is 17774. | False |
| Username of the account | Admin can create users such as guests from the Orion platform and provide access permission according to need. | True |
| Password | True | |
| Type of incident to be fetched | Note: “Type of incident to be fetched” and “Incident type” should be the same to fetch similar types of incidents. | False |
| Maximum number of incidents per fetch | The maximum limit is 1000. | False |
| First fetch time interval | Date or relative timestamp to start fetching incidents from. For Alert, incidents will be fetched based on triggered date. For Event, the incidents will be fetched based on event time. (Formats accepted: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ, etc.). | False |
| Severity levels | Fetch list of alerts as per the severity level. If not specified, it fetches all the incidents. Note: Severity level is only available for alerts. |
False |
| Object Types | Filter alerts based on the type of property to monitor. If not specified, it will fetch all types of alerts. To list additional object types, use the query ‘SELECT DISTINCT ObjectType FROM Orion.AlertConfigurations’ in the swis-query command. | False |
| Event Types | Filter events based on the type. If not specified, it will fetch all types of events. To list additional event types, use the query ‘SELECT Name FROM Orion.EventTypes’ in the swis-query command. | False |
| Use system proxy settings | False | |
| Trust any certificate (not secure) | False | |
| Incident type | False | |
| Fetch incidents | False |
Commands
You can execute these commands from the 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.
swis-event-list
Retrieves a list of events on the filter values provided in the command arguments.
Base Command
swis-event-list
Input
| Argument Name | Description | Required |
|---|---|---|
| acknowledged | Whether to retrieve events that are acknowledged. If true, then retrieves all acknowledged events. Possible values: true and false. |
Optional |
| event_id | A comma-separated ist of event IDs. Note: event_id supports integer values (int64). |
Optional |
| event_type | A comma-separated list of event types. For example: Warning, Informational, Node Up, etc. | Optional |
| node | To retrieve events of specific nodes. | Optional |
| sort_key | Key by which the response will be sorted. For example: EventID, EventTime, Message, TimeStamp, EventTypeName, Node, etc. Default is EventID. |
Optional |
| sort_order | Order by which the response will be sorted. Possible values: ascending and descending. Default is ascending. | Optional |
| page | The page number from which retrieve events. By default, the per-page limit is 50 events. You can change this value in the limit argument. Default is 0. | Optional |
| limit | The maximum number of records to be retrieved. Note: The maximum value supported by the limit is maxValue int32. Default is 50. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SolarWinds.Event.EventID | Number | Event ID of the triggered event. |
| SolarWinds.Event.EventTime | Date | Timestamp at which the event is triggered. |
| SolarWinds.Event.NetworkNode | Number | Network node ID of a triggered event. |
| SolarWinds.Event.Node | String | Network node of a triggered event. |
| SolarWinds.Event.NetObjectID | Number | NetObject ID of a triggered event. |
| SolarWinds.Event.NetObjectValue | String | NetObject value of a triggered event. |
| SolarWinds.Event.EngineID | Number | Engine ID of a triggered event. |
| SolarWinds.Event.EventType | Number | Type of a triggered event. |
| SolarWinds.Event.EventTypeName | String | Name of the type of a triggered event. |
| SolarWinds.Event.Message | String | Message of a triggered event. |
| SolarWinds.Event.Acknowledged | Boolean | Whether the event is acknowledged. |
| SolarWinds.Event.NetObjectType | String | NetObject type of a triggered event. |
| SolarWinds.Event.Timestamp | String | Last modified time of an event. It is a counter that SQL server automatically increments when the event is updated. |
| SolarWinds.Event.DisplayName | String | Display name of an event. |
| SolarWinds.Event.Description | String | Description of an event. |
| SolarWinds.Event.InstanceType | String | Instance type of an event. |
| SolarWinds.Event.Uri | String | URI of a triggered event. |
| SolarWinds.Event.InstanceSiteID | Number | ID of an instance site of which event is triggered. |
Command Example
!swis-event-list sort_key="EventID" sort_order="Ascending" page="1" limit="2"
Context Example
{
"SolarWinds": {
"Event": [
{
"Acknowledged": false,
"EngineID": 1,
"EventID": 3,
"EventTime": "2021-03-31T12:02:05.6830000",
"EventType": 315,
"EventTypeName": "Notification Reset",
"InstanceSiteId": 0,
"InstanceType": "Orion.Events",
"Message": "Resetting unknown traffic notification events.",
"NetObjectID": 0,
"NetObjectType": "N",
"NetworkNode": 1,
"Node": "WIN-MV956AU5BSN",
"TimeStamp": [
"0",
"0",
"0",
"0",
"0",
"0",
"7",
"211"
],
"Uri": "swis://WIN-MV956AU5BSN./Orion/Orion.Events/EventID=3"
},
{
"Acknowledged": false,
"EngineID": 1,
"EventID": 4,
"EventTime": "2021-03-31T12:02:08.5570000",
"EventType": 300,
"EventTypeName": "The NetFlow Receiver Service Started",
"InstanceSiteId": 0,
"InstanceType": "Orion.Events",
"Message": "The NetFlow Receiver Service [WIN-MV956AU5BSN] started - listening on port(s) [2055]",
"NetObjectID": 0,
"NetObjectType": "N",
"NetworkNode": 1,
"Node": "WIN-MV956AU5BSN",
"TimeStamp": [
"0",
"0",
"0",
"0",
"0",
"0",
"7",
"212"
],
"Uri": "swis://WIN-MV956AU5BSN./Orion/Orion.Events/EventID=4"
}
]
}
}
Human Readable Output
Events
ID Message Type Node Acknowledged Triggered At 3 Resetting unknown traffic notification events. Notification Reset WIN-MV956AU5BSN false 31/03/2021 12:02 PM 4 The NetFlow Receiver Service [WIN-MV956AU5BSN] started - listening on port(s) [2055] The NetFlow Receiver Service Started WIN-MV956AU5BSN false 31/03/2021 12:02 PM
swis-alert-list
Retrieves a list of alerts based on the filter values provided in the command arguments.
Base Command
swis-alert-list
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | A comma-separated list of alert IDs. Note: alert_id supports integer values (int64). |
Optional |
| type | A comma-separated list of the type of property to monitor. For example: Node, IPAM.IPRequests, Orion.DiscoveryLogs, APM: Component, IPAM Networks, Orion.NodesForecastCapacity, APM: Application, Orion.VolumesForecastCapacity, Orion.NodesForecastCapacity, etc. | Optional |
| severity | A comma-separated list of severity levels. Possible values: Information, Warning, Critical, Serious, and Notice. |
Optional |
| sort_key | Key by which the response will be sorted. For example: AlertID, AlertActiveID, AlertObjectID, TriggeredDateTime, TriggeredMessage, AcknowledgedDateTime, EngineID, Name, ObjectType, etc. Default is AlertActiveID. |
Optional |
| sort_order | Order by which the response will be sorted. Possible values: ascending and descending. Default is ascending. | Optional |
| page | The page number from which to retrieve alerts. By default, the per-page limit is 50 alerts. You can change change this value in the limit argument. Default is 0. | Optional |
| limit | The number of records to be retrieved. Note: The maximum value supported by the limit is maxValue int32. Default is 50. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SolarWinds.Alert.AlertActiveID | Number | Active ID of the triggered alert. |
| SolarWinds.Alert.AlertObjectID | Number | Object ID of the triggered alert. |
| SolarWinds.Alert.Acknowledged | Boolean | Whether the alert is acknowledged. |
| SolarWinds.Alert.AcknowledgedBy | String | Name of the person who acknowledged the alert. |
| SolarWinds.Alert.AcknowledgedDateTime | Date | Timestamp when the alert was acknowledged. |
| SolarWinds.Alert.AcknowledgedNote | String | Acknowledge note of the alert. |
| SolarWinds.Alert.TriggeredDateTime | Date | Timestamp when the alert was triggered. |
| SolarWinds.Alert.TriggeredMessage | String | Message of the triggered alert. |
| SolarWinds.Alert.NumberOfNotes | Number | Number of notes of the alert. |
| SolarWinds.Alert.LastExecutedEscalationLevel | Number | Last executed escalation level for the alert. |
| SolarWinds.Alert.DisplayName | String | Display name of the alert. |
| SolarWinds.Alert.AlertDescription | String | Description of the alert. |
| SolarWinds.Alert.InstanceType | String | Type of instance of the alert. |
| SolarWinds.Alert.Uri | String | URI of the alert. |
| SolarWinds.Alert.InstanceSiteId | Number | Site ID of the instance. |
| SolarWinds.Alert.AlertID | Number | ID of the alert. |
| SolarWinds.Alert.EntityUri | String | URI for the object that triggered the alert. |
| SolarWinds.Alert.EntityType | String | Type of the object that triggered the alert. |
| SolarWinds.Alert.EntityCaption | String | The display name for the triggering object. |
| SolarWinds.Alert.EntityDetailsUrl | String | Relative URL for the details view for the triggering object. |
| SolarWinds.Alert.EntityNetObjectId | String | NetObject ID of the entity. |
| SolarWinds.Alert.RelatedNodeUri | String | URI of the related node |
| SolarWinds.Alert.RelatedNodeId | Number | ID of the related node. |
| SolarWinds.Alert.RelatedNodeDetailsUrl | String | URL which contains node details. |
| SolarWinds.Alert.RelatedNodeCaption | String | Caption of the related node. |
| SolarWinds.Alert.RealEntityUri | String | URI of the real entity. |
| SolarWinds.Alert.RealEntityType | String | Type of the real entity. |
| SolarWinds.Alert.TriggeredCount | Number | Number of times the alert was triggered. |
| SolarWinds.Alert.LastTriggeredDateTime | Date | Timestamp when the alert was last triggered. |
| SolarWinds.Alert.Context | String | Context of the alert. |
| SolarWinds.Alert.AlertNote | String | Note of the alert. |
| SolarWinds.Alert.AlertMessage | String | Message of the alert. |
| SolarWinds.Alert.AlertRefID | String | Unique identifier of the alert. |
| SolarWinds.Alert.Name | String | Name of the alert. |
| SolarWinds.Alert.ConfigurationDescription | String | Configuration description of the alert. |
| SolarWinds.Alert.ObjectType | String | Object type of the alert. |
| SolarWinds.Alert.Enabled | Boolean | Whether the alert is enabled. |
| SolarWinds.Alert.Frequency | Number | Frequency of the alert. |
| SolarWinds.Alert.Trigger | String | Condition due to which the alert was triggered. |
| SolarWinds.Alert.Reset | String | Reset condition for the alert. When the condition is met, the alert is removed from active alerts. |
| SolarWinds.Alert.Severity | Number | Severity of the alert. |
| SolarWinds.Alert.NotifyEnabled | Boolean | Whether it is notified enabled. |
| SolarWinds.Alert.NotificationSettings | String | Settings of the notifications for the alerts. |
| SolarWinds.Alert.LastEdit | Date | Timestamp when the alert was last edited. |
| SolarWinds.Alert.CreatedBy | String | Name of the person who created the alert. |
| SolarWinds.Alert.Category | String | Category of the alert. |
| SolarWinds.Alert.Canned | Boolean | Whether the alert is canned. |
| SolarWinds.Alert.ResponsibleTeam | String | Team that is responsible for the alert. |
Command Example
!swis-alert-list sort_key="AlertActiveID" sort_order="ascending" page="1" limit="2"
Context Example
{
"SolarWinds": {
"Alert": [
{
"AlertActiveID": 4,
"AlertID": 91,
"AlertMessage": "${N=SwisEntity;M=FirstName} ${N=SwisEntity;M=LastName} has requested ${N=SwisEntity;M=RequestAddressCount} IP address(es) at ${N=SwisEntity;M=RequestDate}\n Contact details: ${N=SwisEntity;M=Phone}, ${N=SwisEntity;M=Email}\n Comments: ${N=SwisEntity;M=Comment}",
"AlertObjectID": 3,
"AlertRefID": "227c01da-1e64-44f2-807b-e6c7d2898ae5",
"Canned": true,
"ConfigurationDescription": "This alert writes to the event log when Request IP Address is created.",
"Context": "null",
"Enabled": true,
"EntityCaption": "IP Request (test test)",
"EntityNetObjectId": "IPAMIPREQ:2",
"EntityType": "IPAM.IPRequests",
"EntityUri": "swis://WIN-MV956AU5BSN./Orion/IPAM.IPRequests/IPRequestId=2",
"Frequency": 60,
"InstanceSiteId": 0,
"InstanceType": "Orion.AlertActive",
"LastEdit": "2021-03-31T12:03:59.7466667Z",
"LastTriggeredDateTime": "2021-04-06T12:53:07.9200000Z",
"Name": "IP Address Request",
"NotifyEnabled": true,
"ObjectType": "IPAM.IPRequests",
"RealEntityType": "IPAM.IPRequests",
"RealEntityUri": "swis://WIN-MV956AU5BSN./Orion/IPAM.IPRequests/IPRequestId=2",
"Reset": "<ArrayOfAlertConditionShelve xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><AlertConditionShelve><AndThenTimeInterval i:nil=\"true\"/><ChainType>ResetCustom</ChainType><ConditionTypeID>Core.Dynamic</ConditionTypeID><Configuration><AlertConditionDynamic xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Alerting.Plugins.Conditions.Dynamic\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><ExprTree xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"><a:Child><a:Expr><a:Child><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Field</a:NodeType><a:Value>IPAM.IPRequests|State</a:Value></a:Expr><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Constant</a:NodeType><a:Value>3</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>=</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>AND</a:Value></ExprTree><Scope i:nil=\"true\" xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"/><TimeWindow i:nil=\"true\"/></AlertConditionDynamic></Configuration><ConjunctionOperator>None</ConjunctionOperator><IsInvertedMinCountThreshold>false</IsInvertedMinCountThreshold><NetObjectsMinCountThreshold i:nil=\"true\"/><ObjectType>IPAM IP Requests</ObjectType><SustainTime i:nil=\"true\"/></AlertConditionShelve></ArrayOfAlertConditionShelve>",
"Severity": 2,
"Trigger": "<ArrayOfAlertConditionShelve xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><AlertConditionShelve><AndThenTimeInterval i:nil=\"true\"/><ChainType>Trigger</ChainType><ConditionTypeID>Core.Dynamic</ConditionTypeID><Configuration><AlertConditionDynamic xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Alerting.Plugins.Conditions.Dynamic\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><ExprTree xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"><a:Child><a:Expr><a:Child><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Field</a:NodeType><a:Value>IPAM.IPRequests|State</a:Value></a:Expr><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Constant</a:NodeType><a:Value>2</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>=</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>AND</a:Value></ExprTree><Scope i:nil=\"true\" xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"/><TimeWindow i:nil=\"true\"/></AlertConditionDynamic></Configuration><ConjunctionOperator>None</ConjunctionOperator><IsInvertedMinCountThreshold>false</IsInvertedMinCountThreshold><NetObjectsMinCountThreshold i:nil=\"true\"/><ObjectType>IPAM IP Requests</ObjectType><SustainTime i:nil=\"true\"/></AlertConditionShelve></ArrayOfAlertConditionShelve>",
"TriggeredCount": 2,
"TriggeredDateTime": "2021-04-06T12:53:07.2300000Z",
"TriggeredMessage": "test test has requested 1 IP address(es) at 4/6/2021 4:54:24 PM\n Contact details: , dummy@dummy.com\n Comments: ",
"Uri": "swis://WIN-MV956AU5BSN./Orion/Orion.AlertActive/AlertActiveID=4,AlertObjectID=3"
},
{
"AlertActiveID": 7,
"AlertID": 112,
"AlertMessage": "Network Discovery Failed",
"AlertObjectID": 5,
"AlertRefID": "eac27bca-77f7-40a5-a359-4069e96bc88e",
"Canned": true,
"ConfigurationDescription": "This alert will send an email if Network Discovery fails.",
"Context": "{\"PropertiesValues\":{\".DiscoveryLogID\":5,\".FinishedTimeStamp\":\"2021-04-06T14:44:14Z\",\".ProfileID\":2,\".AutoImport\":true,\".Result\":3,\".ResultDescription\":\"Import Failed\",\".BatchID\":\"20881db3-ed49-4f4e-8356-e50f0ac5e4e0\",\".ErrorMessage\":\"Unknown Error\",\".InstanceType\":\"Orion.DiscoveryLogs\",\"Uri\":\"swis://WIN-MV956AU5BSN./Orion/Orion.DiscoveryLogs/DiscoveryLogID=5\",\"DisplayName\":\"Import Failed\"}}",
"Enabled": true,
"EntityCaption": "Import Failed",
"EntityNetObjectId": ":",
"EntityType": "Orion.DiscoveryLogs",
"EntityUri": "swis://WIN-MV956AU5BSN./Orion/Orion.DiscoveryLogs/DiscoveryLogID=5",
"Frequency": 60,
"InstanceSiteId": 0,
"InstanceType": "Orion.AlertActive",
"LastEdit": "2021-03-31T12:04:06.0133333Z",
"LastTriggeredDateTime": "2021-04-06T15:01:22.3570000Z",
"Name": "Network Discovery Failed",
"NotificationSettings": "<AlertNotificationSetting xmlns=\"http://schemas.solarwinds.com/2008/Core\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Enabled>true</Enabled><NetObjectType>Orion.DiscoveryLogs</NetObjectType><Severity>Informational</Severity><Subject>Network Discovery Failed</Subject><_properties xmlns:a=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\"/></AlertNotificationSetting>",
"NotifyEnabled": true,
"ObjectType": "Orion.DiscoveryLogs",
"RealEntityType": "Orion.DiscoveryLogs",
"RealEntityUri": "swis://WIN-MV956AU5BSN./Orion/Orion.DiscoveryLogs/DiscoveryLogID=5",
"Reset": "<ArrayOfAlertConditionShelve xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><AlertConditionShelve><AndThenTimeInterval i:nil=\"true\"/><ChainType>ResetWhenTriggered</ChainType><ConditionTypeID i:nil=\"true\"/><Configuration i:nil=\"true\"/><ConjunctionOperator>None</ConjunctionOperator><IsInvertedMinCountThreshold>false</IsInvertedMinCountThreshold><NetObjectsMinCountThreshold i:nil=\"true\"/><ObjectType i:nil=\"true\"/><SustainTime i:nil=\"true\"/></AlertConditionShelve></ArrayOfAlertConditionShelve>",
"Severity": 0,
"Trigger": "<ArrayOfAlertConditionShelve xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><AlertConditionShelve><AndThenTimeInterval i:nil=\"true\"/><ChainType>Trigger</ChainType><ConditionTypeID>Core.Dynamic</ConditionTypeID><Configuration><AlertConditionDynamic xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Alerting.Plugins.Conditions.Dynamic\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><ExprTree xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"><a:Child><a:Expr><a:Child><a:Expr><a:Child/><a:NodeType>Constant</a:NodeType><a:Value>True</a:Value></a:Expr><a:Expr><a:Child/><a:NodeType>Constant</a:NodeType><a:Value>0</a:Value></a:Expr><a:Expr><a:Child/><a:NodeType>Constant</a:NodeType><a:Value>0</a:Value></a:Expr><a:Expr><a:Child><a:Expr><a:Child><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Field</a:NodeType><a:Value>Orion.DiscoveryLogs|Result</a:Value></a:Expr><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Constant</a:NodeType><a:Value>1</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>=</a:Value></a:Expr><a:Expr><a:Child><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Field</a:NodeType><a:Value>Orion.DiscoveryLogs|Result</a:Value></a:Expr><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Constant</a:NodeType><a:Value>3</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>=</a:Value></a:Expr><a:Expr><a:Child><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Field</a:NodeType><a:Value>Orion.DiscoveryLogs|Result</a:Value></a:Expr><a:Expr><a:Child i:nil=\"true\"/><a:NodeType>Constant</a:NodeType><a:Value>4</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>=</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>OR</a:Value></a:Expr></a:Child><a:NodeType>Event</a:NodeType><a:Value>[createEvent].Orion.DiscoveryLogs|event</a:Value></a:Expr></a:Child><a:NodeType>Operator</a:NodeType><a:Value>AND</a:Value></ExprTree><Scope i:nil=\"true\" xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"/><TimeWindow i:nil=\"true\"/></AlertConditionDynamic></Configuration><ConjunctionOperator>None</ConjunctionOperator><IsInvertedMinCountThreshold>false</IsInvertedMinCountThreshold><NetObjectsMinCountThreshold i:nil=\"true\"/><ObjectType>Orion.DiscoveryLogs</ObjectType><SustainTime i:nil=\"true\"/></AlertConditionShelve></ArrayOfAlertConditionShelve>",
"TriggeredCount": 1,
"TriggeredDateTime": "2021-04-06T15:01:22.2170000Z",
"TriggeredMessage": "Network Discovery Failed",
"Uri": "swis://WIN-MV956AU5BSN./Orion/Orion.AlertActive/AlertActiveID=7,AlertObjectID=5"
}
]
}
}
Human Readable Output
Alerts
Active Alert ID Alert Name Triggered Message Entity Caption Triggered At Severity Type Configuration Description 4 IP Address Request test test has requested 1 IP address(es) at 4/6/2021 4:54:24 PM
Contact details: , dummy@dummy.com
Comments:IP Request (test test) 06/04/2021 12:53 PM CRITICAL IPAM.IPRequests This alert writes to the event log when Request IP Address is created. 7 Network Discovery Failed Network Discovery Failed Import Failed 06/04/2021 03:01 PM INFORMATION Orion.DiscoveryLogs This alert will send an email if Network Discovery fails.
swis-query
Executes a query request.
Click here to navigate to the guidelines to generate a query. SolarWinds Information Service schema can be found here.
Base Command
swis-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | The SWQL query to be executed. | Required |
Context Output
There is no context output for this command.
Command Example
!swis-query query="SELECT AlertActiveID, TriggeredDateTime FROM Orion.AlertActive ORDER BY AlertActiveID DESC WITH ROWS 1 To 3"
Context Example
{
"SolarWinds": {
"Query": [
{
"AlertActiveID": 18543,
"TriggeredDateTime": "2021-04-20T06:39:32.4330000Z"
},
{
"AlertActiveID": 18542,
"TriggeredDateTime": "2021-04-19T18:45:11.7730000Z"
},
{
"AlertActiveID": 18541,
"TriggeredDateTime": "2021-04-19T18:44:10.5730000Z"
}
]
}
}
Human Readable Output
Query Result
Alert Active ID Triggered Date Time 18543 2021-04-20T06:39:32.4330000Z 18542 2021-04-19T18:45:11.7730000Z 18541 2021-04-19T18:44:10.5730000Z
Configuration parameters
server— Name or IP address of the Orion server (required)port— Portcredentials— Username of the account (required)fetch_type— Type of incident to be fetchedmax_fetch— Maximum number of incidents per fetchfirst_fetch— First fetch time intervalseverities— Severity levelsobject_types— Object Typesevent_types— Event Typesproxy— Use system proxy settingsinsecure— Trust any certificate (not secure)incidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalisFetch— Fetch incidents
Commands (3)
-
swis-alert-listRetrieves a list of alerts based on the filter values provided in the command arguments.
-
swis-event-listRetrieves a list of events according to filter values provided in the command arguments.
-
swis-queryExecutes a query request. Note: The guidelines for composing the query can be found in the 'View Integration Documentation' linked in the integration tips.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import urllib3 from CommonServerUserPython import * # noqa from collections.abc import Callable # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # ISO8601 format with UTC, default in XSOAR READABLE_DATE_FORMAT = "%d/%m/%Y %I:%M %p" BASE_URL = "https://{}:{}/SolarWinds/InformationService/v3/Json" DEFAULT_FETCH_TYPE = "Alert" DEFAULT_MAX_FETCH = "15" DEFAULT_FIRST_FETCH = "3 days" HTTP_ERRORS = { 400: "Bad request: an error occurred while fetching the data. {}", 401: "Authentication error: please provide valid username and password.", 403: "Forbidden: please provide valid username and password.", 404: "Resource not found: invalid endpoint was called.", 500: "Internal server error: please try again after some time.", } ERR_MSG = { "PAGE": "Invalid value for page argument. Value should be a positive number.", "LIMIT": "Invalid value for limit argument. Value should be a positive number.", "SORT_ORDER": "Invalid value for sort_order argument. Value should be ascending or descending only.", "ACKNOWLEDGED": "Invalid value for acknowledged argument. Value should be true or false only.", "NO_RECORDS_FOUND": "No {} were found for the given argument(s).", "SEVERITIES_ERROR": "Invalid value for severity argument. Value should be in {}.", "ID_ERROR": "Invalid value for {} argument. Value should be a positive integer.", "INVALID_MAX_FETCH": "Argument 'Maximum number of incidents per fetch' should be a positive integer between 1 and 1000.", "INVALID_FIRST_FETCH": "Argument 'First fetch time interval' should be a valid date or relative timestamp such as " "'2 days', '2 months', 'yyyy-mm-dd', 'yyyy-mm-ddTHH:MM:SSZ'", "REQUIRED_ARGUMENT": "Invalid argument value. 'query' is a required argument.", } SEVERITIES_MAP = ["INFORMATION", "WARNING", "CRITICAL", "SERIOUS", "NOTICE"] URL_SUFFIX = {"QUERY": "Query"} FETCH_TYPES = ["Alert", "Event"] QUERY_PARAM = { "LIST_ALERTS": "SELECT A.AlertActiveID, A.AlertObjectID, A.Acknowledged, A.AcknowledgedBy," "A.AcknowledgedDateTime, A.AcknowledgedNote, A.TriggeredDateTime, A.TriggeredMessage," "A.NumberOfNotes, A.LastExecutedEscalationLevel, A.DisplayName, A.Description as AlertDescription," "A.InstanceType, A.Uri, A.InstanceSiteId, B.AlertID, B.EntityUri, B.EntityType, B.EntityCaption," "B.EntityDetailsUrl, B.EntityNetObjectId, B.RelatedNodeUri, B.RelatedNodeId," "B.RelatedNodeDetailsUrl, B.RelatedNodeCaption, B.RealEntityUri, B.RealEntityType," "B.TriggeredCount, B.LastTriggeredDateTime, B.Context, B.AlertNote, C.AlertMessage, C.AlertRefID," "C.Name, C.Description as ConfigurationDescription, C.ObjectType, C.Enabled, C.Frequency," "C.Trigger, C.Reset, C.Severity, C.NotifyEnabled, C.NotificationSettings, C.LastEdit, C.CreatedBy," "C.Category, C.Canned FROM Orion.AlertActive AS A " "INNER JOIN Orion.AlertObjects AS B ON A.AlertObjectID = B.AlertObjectID " "INNER JOIN Orion.AlertConfigurations AS C ON B.AlertID = C.AlertID " "INNER JOIN Orion.AlertConfigurationsCustomProperties AS D ON C.AlertID = D.AlertID", "GET_EVENTS": "SELECT E.EventID, E.EventTime, E.NetworkNode, N.Caption as Node, E.NetObjectID, E.NetObjectValue, " "E.EngineID, E.EventType, ET.Name as EventTypeName, E.Message, E.Acknowledged, E.NetObjectType, " "E.TimeStamp, E.DisplayName, E.Description, E.InstanceType, E.Uri, E.InstanceSiteId " "FROM Orion.Events AS E INNER JOIN Orion.EventTypes AS ET ON E.EventType = ET.EventType " "LEFT JOIN Orion.Nodes as N ON E.NetworkNode = N.NodeID", "FETCH_ALERTS": "SELECT TOP {} A.AlertActiveID, A.AlertObjectID, A.Acknowledged, A.AcknowledgedBy," "A.AcknowledgedDateTime, A.AcknowledgedNote, A.TriggeredDateTime, A.TriggeredMessage," "A.NumberOfNotes, A.LastExecutedEscalationLevel, A.DisplayName," "A.Description as AlertDescription, A.InstanceType, A.Uri, A.InstanceSiteId," "B.AlertID, B.EntityUri, B.EntityType, B.EntityCaption, B.EntityDetailsUrl, " "B.EntityNetObjectId, B.RelatedNodeUri, B.RelatedNodeId, B.RelatedNodeDetailsUrl, " "B.RelatedNodeCaption, B.RealEntityUri, B.RealEntityType, B.TriggeredCount, " "B.LastTriggeredDateTime, " "B.Context, B.AlertNote, C.AlertMessage, C.AlertRefID, C.Name, " "C.Description as ConfigurationDescription, C.ObjectType, C.Enabled, C.Frequency, " "C.Trigger, C.Reset, C.Severity, C.NotifyEnabled, C.NotificationSettings, C.LastEdit," "C.CreatedBy, C.Category, C.Canned " "FROM Orion.AlertActive AS A " "INNER JOIN Orion.AlertObjects AS B ON A.AlertObjectID = B.AlertObjectID " "INNER JOIN Orion.AlertConfigurations AS C ON B.AlertID = C.AlertID " "INNER JOIN Orion.AlertConfigurationsCustomProperties AS D ON C.AlertID = D.AlertID", "FETCH_EVENTS": "SELECT TOP {} A.EventID, A.EventTime, A.NetworkNode, A.NetObjectID, A.NetObjectValue," "A.EngineID, A.EventType, A.Message, A.Acknowledged, A.NetObjectType, A.TimeStamp," "A.DisplayName, A.Description, A.InstanceType, A.Uri,A.InstanceSiteId, B.Name " "FROM Orion.Events as A " "INNER JOIN Orion.EventTypes as B ON A.EventType=B.EventType", } """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API""" def __init__(self, server, port, verify, proxy, credentials): auth = (credentials.get("identifier"), credentials.get("password")) super().__init__(BASE_URL.format(server, port), verify=verify, proxy=proxy, auth=auth) def http_request(self, method: str, url_suffix: str, params: dict = None, json_data: dict = None, **kwargs): """ The wrapper for BaseClient's http_request method. :param method: the HTTP method. Valid values 'GET', 'POST' etc. :param url_suffix: the suffix to the endpoint. :param params: URL parameters to specify the query. :param json_data: The dictionary to send in a request. :return: json response from the endpoint. """ response = self._http_request( method, url_suffix, params=params, json_data=json_data, resp_type="response", ok_codes=[200, *list(HTTP_ERRORS.keys())], raise_on_status=False, **kwargs, ) if response.status_code == 400 and response.json() and response.json().get("Message"): raise DemistoException(HTTP_ERRORS[response.status_code].format("Message:" + response.json().get("Message"))) elif response.status_code in list(HTTP_ERRORS.keys()): raise DemistoException(HTTP_ERRORS[response.status_code]) return response.json() """ HELPER FUNCTIONS """ def validate_fetch_incidents_parameters(params: dict) -> dict: """ Validate fetch incidents params, throw ValueError on non-compliant arguments :param params: dictionary of parameters to be tested for fetch_incidents :rtype: ``dict`` return: dictionary containing valid parameters """ max_fetch = arg_to_number(params.get("max_fetch", DEFAULT_MAX_FETCH)) if (max_fetch is None) or (not 0 < max_fetch <= 1000): raise ValueError(ERR_MSG["INVALID_MAX_FETCH"]) params["max_fetch"] = max_fetch first_fetch = params.get("first_fetch") first_fetch_time = arg_to_datetime(first_fetch, required=True, settings={"TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True}) if first_fetch_time is None: raise ValueError(ERR_MSG["INVALID_FIRST_FETCH"]) params["first_fetch"] = first_fetch_time severities = params.get("severities", []) for severity in severities: if severity.upper() not in SEVERITIES_MAP: raise ValueError(ERR_MSG["SEVERITIES_ERROR"].format(SEVERITIES_MAP)) params["severities"] = severities return params def prepare_query_for_fetch_alerts(last_run: dict, params: dict) -> str: """ Return the query to fetch alerts :param last_run: last run returned by function demisto.getLastRun. :param params: arguments for fetch-incident. :rtype: ``str`` :return: query string to send in request """ max_fetch = params.get("max_fetch") first_fetch = params.get("first_fetch") severities = params.get("severities") object_types = params.get("object_types") query = QUERY_PARAM["FETCH_ALERTS"].format(max_fetch) # The AlertActiveIDs are unique for alerts, so it is used instead of timestamp for the last run for simplicity if last_run.get("alert_active_id"): query += f" WHERE A.AlertActiveID>{last_run.get('alert_active_id')}" else: query += f" WHERE A.TriggeredDateTime>='{first_fetch.strftime(DATE_FORMAT)}'" # type: ignore if severities: query += " AND (" + " OR ".join([f"C.Severity={SEVERITIES_MAP.index(severity)}" for severity in severities]) + ")" if object_types: query += " AND (" + " OR ".join([f"C.ObjectType='{object_type}'" for object_type in object_types]) + ")" query += " ORDER BY A.AlertActiveID" return query def prepare_query_for_fetch_events(last_run: dict, params: dict) -> str: """ Returns the query to fetch events :param last_run: last run returned by function demisto.getLastRun. :param params: arguments for fetch-incident. :rtype: ``str`` :return: query string to send in request """ max_fetch = params.get("max_fetch") first_fetch = params.get("first_fetch") event_types = params.get("event_types") query = QUERY_PARAM["FETCH_EVENTS"].format(max_fetch) # The EventIDs are unique for events, so it is used instead of timestamp for the last run for simplicity if last_run.get("event_id"): query += f" WHERE A.EventID>{last_run.get('event_id')}" else: query += f" WHERE A.EventTime>='{first_fetch.strftime(DATE_FORMAT)}'" # type: ignore if event_types: query += " AND (" + " OR ".join([f"B.Name='{event_type}'" for event_type in event_types]) + ")" query += " ORDER BY A.EventID" return query def validate_common_arguments(args: dict) -> dict: """ Validates common arguments and raises errors for invalid values. :type args: ``dict`` :param args: Arguments to validate :rtype: `dict`` :return: Returns a dictionary containing validated arguments :raises ValueError: Raises value errors for invalid values """ page = arg_to_number(args.get("page", 0)) if (page is None) or (page < 0): raise ValueError(ERR_MSG["PAGE"]) args["page"] = page limit = arg_to_number(args.get("limit", 50)) if (limit is None) or (limit < 1): raise ValueError(ERR_MSG["LIMIT"]) args["limit"] = limit sort_order = args.get("sort_order", "ascending") if sort_order and sort_order.lower() not in ["ascending", "descending"]: raise ValueError(ERR_MSG["SORT_ORDER"]) return args def validate_and_prepare_query_for_list_alerts(args) -> str: """ Validates and prepare arguments for alerts-list command and returns query. :param args: Arguments to validate and prepare query from :rtype: ``str`` :return: Query prepared by provided arguments """ args = validate_common_arguments(args) alert_types = argToList(args.get("type", ""), ",") severities = argToList(args.get("severity", "").upper(), ",") page = args.get("page", 0) limit = args.get("limit", 50) alert_ids = argToList(args.get("alert_id", []), ",") alert_ids = list(map(arg_to_number, alert_ids)) if None in alert_ids: raise ValueError(ERR_MSG["ID_ERROR"].format("alert_id")) sort_key = args.get("sort_key", "AlertActiveID") sort_order = "DESC" if args.get("sort_order", "ascending").lower() == "descending" else "ASC" filter_query = QUERY_PARAM["LIST_ALERTS"] # flag for where clause has been added where_flag = False if alert_types: filter_query += " WHERE ( " + " OR ".join([f"ObjectType = '{alert_type}'" for alert_type in alert_types]) + " )" where_flag = True if not set(severities).issubset(SEVERITIES_MAP): raise ValueError(ERR_MSG["SEVERITIES_ERROR"].format(SEVERITIES_MAP)) if severities: filter_query += " AND " if where_flag else " WHERE " filter_query += "( " + " OR ".join([f"Severity = {SEVERITIES_MAP.index(severity)}" for severity in severities]) + " )" where_flag = True if alert_ids: filter_query += " AND " if where_flag else " WHERE " filter_query += "( " + " OR ".join([f"AlertActiveID = {alert_id}" for alert_id in alert_ids]) + " )" filter_query += f" ORDER BY {sort_key} {sort_order} WITH ROWS {(page * limit) + 1} TO {(page + 1) * limit}" return filter_query def validate_and_prepare_query_for_event_list(args: dict) -> str: """ Validates and prepare arguments for event-list command and returns query. :param args: Arguments to validate and prepare query from :rtype: ``str`` :return: Query prepared by provided arguments """ args = validate_common_arguments(args) acknowledged = args.get("acknowledged", "") event_type = argToList(args.get("event_type", []), ",") node = argToList(args.get("node", []), ",") event_ids = argToList(args.get("event_id", []), ",") event_ids = list(map(arg_to_number, event_ids)) if None in event_ids: raise ValueError(ERR_MSG["ID_ERROR"].format("event_id")) args["event_id"] = event_ids page = args.get("page", 0) limit = args.get("limit", 50) sort_key = args.get("sort_key", "EventID") sort_order = "DESC" if args.get("sort_order", "ascending").lower() == "descending" else "ASC" query = QUERY_PARAM["GET_EVENTS"] where_added = False if acknowledged and acknowledged.lower() not in ["true", "false"]: raise ValueError(ERR_MSG["ACKNOWLEDGED"]) elif acknowledged: query += f" WHERE Acknowledged = {acknowledged}" where_added = True if event_type: query += " AND " if where_added else " WHERE " query += "( " + " OR ".join([f"EventTypeName = '{event_type_name}'" for event_type_name in event_type]) + " )" where_added = True if node: query += " AND " if where_added else " WHERE " query += "( " + " OR ".join([f"Node = '{node_name}'" for node_name in node]) + " )" if event_ids: query += " AND " if where_added else " WHERE " query += "( " + " OR ".join([f"EventID = {event_id}" for event_id in event_ids]) + " )" query += f" ORDER BY {sort_key} {sort_order} WITH ROWS {(page * limit) + 1} TO {(page + 1) * limit}" return query def convert_events_outputs_to_hr(outputs: list) -> str: """ Converts list of outputs received from response to human readable output. :param outputs: List outputs received from response :rtype: ``str`` :return: Human readable output generated from tableToMarkDown """ if len(outputs) == 0: return ERR_MSG["NO_RECORDS_FOUND"].format("event(s)") readable_outputs = [] for output in outputs: event_time = arg_to_datetime(output.get("EventTime")) readable_outputs.append( { "Event ID": output.get("EventID"), "Message": output.get("Message"), "Type": output.get("EventTypeName"), "Node": output.get("Node", ""), "Acknowledged": output.get("Acknowledged"), "Triggered At": event_time.strftime(READABLE_DATE_FORMAT), # type: ignore } ) return tableToMarkdown( "Events", readable_outputs, ["Event ID", "Message", "Type", "Node", "Acknowledged", "Triggered At"], removeNull=True ) def convert_alerts_outputs_to_hr(outputs: list) -> str: """ Converts list of outputs received from response to human readable output. :param outputs: List outputs received from response :rtype: ``str`` :return: Human readable output generated from tableToMarkDown """ if len(outputs) == 0: return ERR_MSG["NO_RECORDS_FOUND"].format("alert(s)") readable_outputs = [] for output in outputs: alert_time = arg_to_datetime(output.get("TriggeredDateTime", "")) readable_outputs.append( { "Active Alert ID": output.get("AlertActiveID"), "Alert Name": output.get("Name"), "Type": output.get("ObjectType"), "Triggered Message": output.get("TriggeredMessage"), "Configuration Description": output.get("ConfigurationDescription", ""), "Acknowledged": output.get("Acknowledged", ""), "Entity Caption": output.get("EntityCaption", ""), "Severity": SEVERITIES_MAP[output.get("Severity")], "Triggered At": alert_time.strftime(READABLE_DATE_FORMAT), # type: ignore } ) return tableToMarkdown( "Alerts", readable_outputs, [ "Active Alert ID", "Alert Name", "Triggered Message", "Entity Caption", "Triggered At", "Acknowledged", "Severity", "Type", "Configuration Description", ], removeNull=True, ) """ COMMAND FUNCTIONS """ def test_module(client: Client, params: dict) -> str: """Tests API connectivity and authentication' Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Raises exceptions if something goes wrong. :param client: client to use :param params: configuration parameters. :return: 'ok' if test passed, anything else will fail the test. """ client.http_request("GET", URL_SUFFIX["QUERY"], params={"query": "SELECT TOP 0 NodeID FROM Orion.Nodes"}) is_fetch = params.get("isFetch") if is_fetch: fetch_incidents(client, {}, params, True) return "ok" @logger def swis_alert_list_command(client: Client, args: dict) -> CommandResults: """ Retrieves a list of alerts based on the filter values provided in the command arguments. :param client: Client to make endpoint calls :param args: Arguments provided by the user :rtype: ``CommandResults`` :return: Response received from api in the form of CommandResults object """ filtered_query = validate_and_prepare_query_for_list_alerts(args) response = client.http_request(method="GET", url_suffix=URL_SUFFIX["QUERY"], params={"query": filtered_query}) outputs = createContext(response.get("results", []), removeNull=True) readable_outputs = convert_alerts_outputs_to_hr(outputs) return CommandResults( outputs_prefix="SolarWinds.Alert", outputs=outputs, readable_output=readable_outputs, raw_response=response, outputs_key_field="AlertActiveID", ) @logger def fetch_incidents(client: Client, last_run: dict, params: dict, is_test=False) -> tuple[dict, list]: """Fetches incidents from Solarwinds API. :param client: client to use :param last_run: last run returned by function demisto.getLastRun :param params: arguments for fetch-incident. :param is_test: whether this is a test function call or not. :rtype: ``Tuple`` :return: tuple of dictionary of next run and list of fetched incidents """ fetch_type = params.get("fetch_type", DEFAULT_FETCH_TYPE) if not fetch_type: fetch_type = DEFAULT_FETCH_TYPE params = validate_fetch_incidents_parameters(params) if fetch_type == DEFAULT_FETCH_TYPE: query = prepare_query_for_fetch_alerts(last_run, params) else: query = prepare_query_for_fetch_events(last_run, params) demisto.info(f"[SolarWinds]: Query: {query}") results = client.http_request("POST", URL_SUFFIX["QUERY"], json_data={"query": query}).get("results") next_run = last_run incidents = [] for result in results: occurred = result["TriggeredDateTime"] if fetch_type == "Alert" else result["EventTime"] incidents.append( { "name": result["Name"], "occurred": arg_to_datetime(occurred).strftime(DATE_FORMAT), # type: ignore "rawJSON": json.dumps(result), } ) if results: if fetch_type == DEFAULT_FETCH_TYPE: next_run["alert_active_id"] = results[-1]["AlertActiveID"] else: next_run["event_id"] = results[-1]["EventID"] if is_test: return {}, [] return next_run, incidents @logger def swis_event_list_command(client: Client, args: dict) -> CommandResults: """ Retrieves list of triggered events on the filter values provided in the command arguments. :param client: Client to make endpoint calls :param args: Arguments provided by the user :rtype: ``CommandResults`` :return: Response received from api in the form of CommandResults object """ query = validate_and_prepare_query_for_event_list(args) response = client.http_request(method="GET", url_suffix=URL_SUFFIX["QUERY"], params={"query": query}) outputs = createContext(response.get("results", []), removeNull=True) readable_outputs = convert_events_outputs_to_hr(outputs) return CommandResults( outputs_prefix="SolarWinds.Event", outputs=outputs, readable_output=readable_outputs, raw_response=response, outputs_key_field="EventID", ) def convert_query_output_to_hr(outputs) -> str: """ Converts raw response received from the api to human readable response. :type outputs: ``list`` :param outputs: raw response received from api :rtype: ``str`` :return: Markdown that can be shown in War room. """ if len(outputs) == 0: return ERR_MSG["NO_RECORDS_FOUND"].format("record(s)") readable_output = [] for response_list in outputs: response_list = {pascalToSpace(k): v for k, v in response_list.items()} readable_output.append(response_list) return tableToMarkdown("Query Result", readable_output, removeNull=True) def swis_query_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Executes a SWQL query entered by user :type client: ``Client`` :param client: client to use :type args: ``dict`` :param args: arguments provided by the user :rtype: ``CommandResults`` :return: response received from api """ query = args.get("query") if not query: raise ValueError(ERR_MSG["REQUIRED_ARGUMENT"]) response = client.http_request(method="GET", url_suffix=URL_SUFFIX["QUERY"], params={"query": query}) outputs = createContext(response.get("results", []), removeNull=True) readable_response = convert_query_output_to_hr(outputs) return CommandResults( outputs_prefix="SolarWinds.Query", outputs=outputs, readable_output=readable_response, raw_response=response ) """ MAIN FUNCTION """ def main() -> None: """Main function, parses params and runs command functions""" commands: dict[str, Callable] = { "swis-alert-list": swis_alert_list_command, "swis-event-list": swis_event_list_command, "swis-query": swis_query_command, } command = demisto.command() demisto.debug(f"Command being called is {command}") try: params = demisto.params() args = demisto.args() server = params["server"] port = params["port"] credentials = params.get("credentials", {}) verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) client = Client(server=server, port=port, credentials=credentials, verify=verify_certificate, proxy=proxy) for key, value in args.items(): if isinstance(value, str): args[key] = value.strip() remove_nulls_from_dictionary(args) if command == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client, params) return_results(result) elif command in commands: return_results(commands[command](client, args)) elif command == "fetch-incidents": last_run = demisto.getLastRun() next_run, incidents = fetch_incidents(client, last_run, params) demisto.incidents(incidents) demisto.setLastRun(next_run) # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{str(e)}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()