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

IDSolarWinds
ProviderSolarWinds Corporation
CategoryNetwork Security
From Version5.5.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix 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>&lt;AlertConditionDynamic xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Alerting.Plugins.Conditions.Dynamic\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"&gt;&lt;ExprTree xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Field&lt;/a:NodeType&gt;&lt;a:Value&gt;IPAM.IPRequests|State&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;3&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;=&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;AND&lt;/a:Value&gt;&lt;/ExprTree&gt;&lt;Scope i:nil=\"true\" xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"/&gt;&lt;TimeWindow i:nil=\"true\"/&gt;&lt;/AlertConditionDynamic&gt;</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>&lt;AlertConditionDynamic xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Alerting.Plugins.Conditions.Dynamic\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"&gt;&lt;ExprTree xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Field&lt;/a:NodeType&gt;&lt;a:Value&gt;IPAM.IPRequests|State&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;2&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;=&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;AND&lt;/a:Value&gt;&lt;/ExprTree&gt;&lt;Scope i:nil=\"true\" xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"/&gt;&lt;TimeWindow i:nil=\"true\"/&gt;&lt;/AlertConditionDynamic&gt;</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>&lt;AlertConditionDynamic xmlns=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Alerting.Plugins.Conditions.Dynamic\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"&gt;&lt;ExprTree xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;True&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;0&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;0&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Field&lt;/a:NodeType&gt;&lt;a:Value&gt;Orion.DiscoveryLogs|Result&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;1&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;=&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Field&lt;/a:NodeType&gt;&lt;a:Value&gt;Orion.DiscoveryLogs|Result&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;3&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;=&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Field&lt;/a:NodeType&gt;&lt;a:Value&gt;Orion.DiscoveryLogs|Result&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;a:Expr&gt;&lt;a:Child i:nil=\"true\"/&gt;&lt;a:NodeType&gt;Constant&lt;/a:NodeType&gt;&lt;a:Value&gt;4&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;=&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;OR&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Event&lt;/a:NodeType&gt;&lt;a:Value&gt;[createEvent].Orion.DiscoveryLogs|event&lt;/a:Value&gt;&lt;/a:Expr&gt;&lt;/a:Child&gt;&lt;a:NodeType&gt;Operator&lt;/a:NodeType&gt;&lt;a:Value&gt;AND&lt;/a:Value&gt;&lt;/ExprTree&gt;&lt;Scope i:nil=\"true\" xmlns:a=\"http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Alerting\"/&gt;&lt;TimeWindow i:nil=\"true\"/&gt;&lt;/AlertConditionDynamic&gt;</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 — Port
  • credentials — Username of the account (required)
  • fetch_type — Type of incident to be fetched
  • max_fetch — Maximum number of incidents per fetch
  • first_fetch — First fetch time interval
  • severities — Severity levels
  • object_types — Object Types
  • event_types — Event Types
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • isFetch — Fetch incidents

Commands (3)

  • swis-alert-list

    Retrieves a list of alerts based on the filter values provided in the command arguments.

  • swis-event-list

    Retrieves a list of events according to filter values provided in the command arguments.

  • swis-query

    Executes 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()