From 196c780dadcc77f1ea50830102e5f62786681439 Mon Sep 17 00:00:00 2001 From: TechnoSavage Date: Wed, 14 Jan 2026 11:30:32 -0500 Subject: [PATCH 1/5] initial commit --- solarwinds-information-service/README.md | 70 +++++++++++++ solarwinds-information-service/config.json | 1 + solarwinds-information-service/swis.star | 109 +++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 solarwinds-information-service/README.md create mode 100644 solarwinds-information-service/config.json create mode 100644 solarwinds-information-service/swis.star diff --git a/solarwinds-information-service/README.md b/solarwinds-information-service/README.md new file mode 100644 index 0000000..1c28797 --- /dev/null +++ b/solarwinds-information-service/README.md @@ -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:` \ No newline at end of file diff --git a/solarwinds-information-service/config.json b/solarwinds-information-service/config.json new file mode 100644 index 0000000..2ca3b3b --- /dev/null +++ b/solarwinds-information-service/config.json @@ -0,0 +1 @@ +{ "name": "Orion SOlarwinds Information Service", "type": "inbound" } \ No newline at end of file diff --git a/solarwinds-information-service/swis.star b/solarwinds-information-service/swis.star new file mode 100644 index 0000000..c36b7ff --- /dev/null +++ b/solarwinds-information-service/swis.star @@ -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 \ No newline at end of file From 0967155f2f90064e4bde3d28ef129b09ceb190cf Mon Sep 17 00:00:00 2001 From: TechnoSavage Date: Wed, 14 Jan 2026 11:30:58 -0500 Subject: [PATCH 2/5] initial commit --- solarwinds-information-service/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solarwinds-information-service/config.json b/solarwinds-information-service/config.json index 2ca3b3b..301ec59 100644 --- a/solarwinds-information-service/config.json +++ b/solarwinds-information-service/config.json @@ -1 +1 @@ -{ "name": "Orion SOlarwinds Information Service", "type": "inbound" } \ No newline at end of file +{ "name": "Solarwinds Information Service", "type": "inbound" } \ No newline at end of file From 5d42cabc6b792518a455fd1a2c0ac37750037819 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Jan 2026 16:32:56 +0000 Subject: [PATCH 3/5] Auto: update integrations JSON and README --- README.md | 1 + docs/integrations.json | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d1f1f5a..ae08c91 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/docs/integrations.json b/docs/integrations.json index abab71b..2fd993d 100644 --- a/docs/integrations.json +++ b/docs/integrations.json @@ -1,6 +1,6 @@ { - "lastUpdated": "2025-12-15T22:06:49.938502Z", - "totalIntegrations": 30, + "lastUpdated": "2026-01-14T16:32:56.084487Z", + "totalIntegrations": 31, "integrationDetails": [ { "name": "Moysle", @@ -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/swis.star" + }, { "name": "Proxmox", "type": "inbound", From 3cb0e1c60579870000d575fc9c7d142a83139095 Mon Sep 17 00:00:00 2001 From: Tyler Diderich <15862572+tdiderich@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:29:56 -0600 Subject: [PATCH 4/5] Rename swis.star to custom-integration-swis.star --- .../{swis.star => custom-integration-swis.star} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename solarwinds-information-service/{swis.star => custom-integration-swis.star} (99%) diff --git a/solarwinds-information-service/swis.star b/solarwinds-information-service/custom-integration-swis.star similarity index 99% rename from solarwinds-information-service/swis.star rename to solarwinds-information-service/custom-integration-swis.star index c36b7ff..d3014e1 100644 --- a/solarwinds-information-service/swis.star +++ b/solarwinds-information-service/custom-integration-swis.star @@ -106,4 +106,4 @@ def main(*args, **kwargs): print('no assets') return None - return import_assets \ No newline at end of file + return import_assets From d3221ab89b099632ba650231e9284b5114f79302 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 15 Jan 2026 15:30:47 +0000 Subject: [PATCH 5/5] Auto: update integrations JSON and README --- docs/integrations.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations.json b/docs/integrations.json index 2fd993d..1ee5640 100644 --- a/docs/integrations.json +++ b/docs/integrations.json @@ -1,5 +1,5 @@ { - "lastUpdated": "2026-01-14T16:32:56.084487Z", + "lastUpdated": "2026-01-15T15:30:47.444549Z", "totalIntegrations": 31, "integrationDetails": [ { @@ -180,7 +180,7 @@ "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/swis.star" + "integration": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/solarwinds-information-service/custom-integration-swis.star" }, { "name": "Proxmox",