Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ If you need help setting up a custom integration, you can create an [issue](http
- [Scale Computing](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/scale-computing/)
- [Snipe-IT](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snipe-it/)
- [Snow License Manager](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snow-license-manager/)
- [Solarwinds Information Service](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/solarwinds-information-service/)
- [Stairwell](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/stairwell/)
- [Tailscale](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/tailscale/)
- [Tanium](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/tanium/)
Expand Down
10 changes: 8 additions & 2 deletions docs/integrations.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"lastUpdated": "2026-01-15T14:44:35.262088Z",
"totalIntegrations": 30,
"lastUpdated": "2026-01-15T15:30:47.444549Z",
"totalIntegrations": 31,
"integrationDetails": [
{
"name": "Moysle",
Expand Down Expand Up @@ -176,6 +176,12 @@
"readme": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snipe-it/README.md",
"integration": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snipe-it/snipeit.star"
},
{
"name": "Solarwinds Information Service",
"type": "inbound",
"readme": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/solarwinds-information-service/README.md",
"integration": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/solarwinds-information-service/custom-integration-swis.star"
},
{
"name": "Proxmox",
"type": "inbound",
Expand Down
70 changes: 70 additions & 0 deletions solarwinds-information-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Custom Integration: SolarWinds Orion - SolarWinds Information Service (SWIS)

## Getting Started

- Clone this repository

```
git clone https://github.com/runZeroInc/runzero-custom-integrations.git
```

## runZero requirements

- Superuser access to the [Custom Integrations configuration](https://console.runzero.com/custom-integrations) in runZero

## SolarWinds SWIS requirements

**SWIS Instance URL** - The domain or IP of the Snow Atlas web server and the port number e.g. "https://my.solarwinds.instance:17774" (defined within the starlark script as `SWIS_BASE_URL`)

**username** - account username for Solarwinds access (configured in Credentials section of runZero)

**password** - account password (configured in Credentials section of runZero)

## SolarWinds SWIS API Docs

- [SWIS Cortex.Orion.Node Schema Reference](https://solarwinds.github.io/OrionSDK/schema/Cortex.Orion.Node.html)

- [Orion SDK SWIS Docs](https://github.com/solarwinds/OrionSDK/wiki/About-SWIS)

## Steps

### Solarwinds configuration

1. Determine the proper Solarwinds URL and port:
- Common Solarwinds ports are 17774 (default since v2024) and 17778 (the default in v2023 and prior).
- Assign the URL to `SWIS_BASE_URL` within the starlark script
- Determine proper username and password credentials for access. These will be configured in the Custom Integration credentials section within the runZero console.

### runZero configuration

1. (Make any neccessary changes to the script to align with your environment.
- Modify API calls as needed to filter assets
>- Determine the proper SWQL query needed to return the data set to import to runZero
>- Add this query to the integration script in the 'params' variable within the 'get_assets' function.
- Modify attribute mapping based on the data returned by the SWQL query as needed
>- The integration script outlines some common example attributes that could be brought in from Solarwinds but the attributes that are actually retrieved will be determined by the SWQL query passed in the API call params
>- Modify the asset attributes and custom attributes to match the data provided by the SWQL query following the pattern outlined in the script
>- For a list of "core" attributes that runZero maps, reference the Custom SDK documentation [here](https://runzeroinc.github.io/runzero-sdk-py/autoapi/runzero/types/_data_models_gen/index.html#runzero.types._data_models_gen.ImportAsset). All other attributes provided by Solarwinds should be mapped within 'Custom Attributes'
2. [Create the Credential for the Custom Integration](https://console.runzero.com/credentials)
- Select the type **Custom Integration Script Secrets**
- Both **access_key** and **access_secret** are required
- **access_key** corresponds to the username to access Solarwinds
- **access_secret** corresponds to the password to access Solarwinds
3. [Create the Custom Integration](https://console.runzero.com/custom-integrations/new)
- Add a Name (e.g. solarwinds) and Icon
- Toggle **Enable custom integration script** to input your finalized script
- Click **Validate** to ensure it has valide syntax
- Click **Save** to create the Custom Integration
4. [Create the Custom Integration task](https://console.runzero.com/ingest/custom/)
- Select the Credential and Custom Integration created in steps 2 and 3
- Update the task schedule to recur at the desired timeframes
- Select the Explorer you'd like the Custom Integration to run from
- Click **Save** to start the task


### What's next?

- You will see the task initilize on the [tasks](https://console.runzero.com/tasks) page like other integration tasks
- The task will update the existing assets with the data pulled from the Custom Integration source
- The task will create new assets for when there are no existing assets that meet merge criteria (hostname, MAC, IP, etc)
- You can search for assets enriched by this custom integration with the runZero search `custom_integration:<INSERT_NAME_HERE>`
1 change: 1 addition & 0 deletions solarwinds-information-service/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "Solarwinds Information Service", "type": "inbound" }
109 changes: 109 additions & 0 deletions solarwinds-information-service/custom-integration-swis.star
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
load('runzero.types', 'ImportAsset', 'NetworkInterface')
load('base64', base64_encode='encode', base64_decode='decode')
load('http', http_get='get', http_post='post', 'url_encode')
load('json', json_encode='encode', json_decode='decode')
load('net', 'ip_address')
load('uuid', 'new_uuid')

SWIS_BASE_URL = 'https://localhost:17774'
RUNZERO_REDIRECT = 'https://console.runzero.com/'

def build_assets(assets):
assets_import = []
for asset in assets:
asset_id = str(asset.get('NodeId', str(new_uuid)))
hostname = asset.get('Fqdn', '')
os = asset.get('OsVersion', '')
vendor = asset.get('Vendor', '')

# create the network interfaces
interfaces = []
addresses = asset.get('IpAddress', [])
interface = build_network_interface(ips=[addresses], mac=None)
interfaces.append(interface)

# Retrieve and map custom attributes
cpu_util = str(asset.get('CpuPercentUtilization', ''))
discovery_profile_id = str(asset.get('DiscoveryProfileId', ''))
mem_util_perc = str(asset.get('PercentMemoryUsed', ''))
mem_util = str(asset.get('MemoryUsed', ''))
pollers = asset.get('Pollers', '')
response_time = str(asset.get('ResponseTime', ''))
snmp_port = str(asset.get('SnmpPort', ''))
snmp_version = str(asset.get('SnmpVersion', ''))
status = asset.get('Status', '')
sys_object_id = asset.get('SysObjectId', '')
uptime = str(asset.get('Uptime', ''))

custom_attributes = {
'percentCpuUtilization': cpu_util,
'discoveryProfileId': discovery_profile_id,
'percentMemoryUtilization': mem_util_perc,
'memoryUtilized': mem_util,
'pollers': pollers,
'responseTime': response_time,
'snmp.port': snmp_port,
'snmp.version': snmp_version,
'status': status,
'sysObjectId': sys_object_id,
'uptime': uptime
}

# Build assets for import
assets_import.append(
ImportAsset(
id=asset_id,
hostnames=[hostname],
os=os,
manufacturer=vendor,
networkInterfaces=interfaces,
customAttributes=custom_attributes
)
)
return assets_import

def build_network_interface(ips, mac):
ip4s = []
ip6s = []
for ip in ips[:99]:
ip_addr = ip_address(ip)
if ip_addr.version == 4:
ip4s.append(ip_addr)
elif ip_addr.version == 6:
ip6s.append(ip_addr)
else:
continue
if not mac:
return NetworkInterface(ipv4Addresses=ip4s, ipv6Addresses=ip6s)
else:
return NetworkInterface(macAddress=mac, ipv4Addresses=ip4s, ipv6Addresses=ip6s)

def get_assets(creds):

url = SWIS_BASE_URL + 'SolarWinds/InformationService/v3/Json/Query?'
headers = {'Accept': 'application/json',
'Authorization': 'Basic ' + creds}
# Populate the SWQL query to return desired assets and attributes in the params query value e.g.
# params = {'query': 'SELECT N.NodeID, N.OsVersion, N.Fqdn, N.Vendor, N.IPAddress, N.CpuPercentUtilization, N.DiscoveryProfileId, N.PercentMemoryUsed, N.MemoryUsed, N.Pollers, N.responseTime, N.snmp.port, N.snmp.version, N.status, N.sysObjectId, N.Uptime FROM Orion.Nodes'}
params = {'query': ''}
response = http_get(url, headers=headers, params=params)
if response.status_code != 200:
print('failed to retrieve assets', 'status code: ' + str(response.status_code))
data = json_decode(response.body)
assets_all.extend(data)

return assets_all

def main(*args, **kwargs):
username = kwargs['access_key']
password = kwargs['access_secret']
b64_creds = base64_encode(username + ":" + password)
assets = get_assets(b64_creds)

# Format asset list for import into runZero
import_assets = build_assets(assets)
if not import_assets:
print('no assets')
return None

return import_assets