├── meta ├── runtime.yml ├── ee-bindep.txt ├── ee-requirements.txt └── execution-environment.yml ├── requirements.txt ├── roles └── orion_node │ ├── vars │ └── main.yml │ ├── meta │ └── main.yml │ ├── defaults │ └── main.yml │ ├── README.md │ └── tasks │ └── main.yml ├── releases ├── jeisenbath-solarwinds-3.0.0.tar.gz ├── jeisenbath-solarwinds-3.0.1.tar.gz └── jeisenbath-solarwinds-3.1.0.tar.gz ├── .gitignore ├── tests └── integration │ ├── targets │ ├── orion_volume │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── linux.yml │ ├── orion_volume_info │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── linux.yml │ ├── orion_credential_set │ │ └── tasks │ │ │ ├── main.yml │ │ │ ├── snmpv3.yml │ │ │ └── assign.yml │ ├── orion_node_module │ │ └── tasks │ │ │ ├── main.yml │ │ │ ├── snmp_node.yml │ │ │ ├── snmpv3_node.yml │ │ │ └── icmp_node.yml │ ├── orion_node_info │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── info.yml │ ├── orion_custom_property │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── node_custom_property.yml │ ├── orion_node_application │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── node_application.yml │ ├── orion_node_ncm │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── ncm.yml │ ├── orion_node_poller │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── poller.yml │ ├── orion_node_poller_info │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── info.yml │ ├── orion_node_custom_poller │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── custom_poller.yml │ ├── orion_node_hardware_health │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── hardware_health.yml │ ├── orion_node_interface │ │ └── tasks │ │ │ ├── main.yml │ │ │ ├── discover.yml │ │ │ └── defined.yml │ └── orion_node_interface_info │ │ └── tasks │ │ ├── main.yml │ │ └── info.yml │ └── integration_config_example.yml ├── galaxy.yml ├── changelogs ├── config.yaml ├── .plugin-cache.yaml └── changelog.yaml ├── plugins ├── doc_fragments │ ├── orion_node_options.py │ └── orion_auth_options.py ├── module_utils │ └── credential.py └── modules │ ├── orion_node_info.py │ ├── orion_node_poller_info.py │ ├── orion_update_node.py │ ├── orion_volume_info.py │ ├── orion_node_interface_info.py │ ├── orion_query.py │ ├── orion_node_custom_poller.py │ ├── orion_custom_property.py │ ├── orion_node_poller.py │ ├── orion_node_application.py │ ├── orion_node_hardware_health.py │ ├── orion_node_ncm.py │ ├── orion_volume.py │ ├── orion_node_interface.py │ └── orion_credential_set.py ├── playbooks ├── update_node_to_snmpv3.yml └── orion_add_node.yml ├── CONTRIBUTING.md ├── README.md ├── .github └── workflows │ └── ansible-test.yml └── CHANGELOG.rst /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | requires_ansible: ">=2.15" 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | orionsdk>=0.3.0 2 | python-dateutil 3 | requests 4 | -------------------------------------------------------------------------------- /roles/orion_node/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for orion_node 3 | -------------------------------------------------------------------------------- /releases/jeisenbath-solarwinds-3.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeisenbath/ansible-collection-solarwinds-orion/HEAD/releases/jeisenbath-solarwinds-3.0.0.tar.gz -------------------------------------------------------------------------------- /releases/jeisenbath-solarwinds-3.0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeisenbath/ansible-collection-solarwinds-orion/HEAD/releases/jeisenbath-solarwinds-3.0.1.tar.gz -------------------------------------------------------------------------------- /releases/jeisenbath-solarwinds-3.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeisenbath/ansible-collection-solarwinds-orion/HEAD/releases/jeisenbath-solarwinds-3.1.0.tar.gz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ansible/ 2 | args.json 3 | tests/output 4 | tests/integration/integration_config.yml 5 | tests/integration/inventory 6 | plugins/doc_fragments/__pycache__/ 7 | plugins/modules/__pycache__/ 8 | .vscode/ -------------------------------------------------------------------------------- /tests/integration/targets/orion_volume/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_volume module 3 | - name: Include tests 4 | ansible.builtin.include_tasks: "{{ item }}" 5 | loop: 6 | - linux.yml 7 | ... 8 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_volume_info/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_volume_info module 3 | - name: Include tests 4 | ansible.builtin.include_tasks: "{{ item }}" 5 | loop: 6 | - linux.yml 7 | ... 8 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_credential_set/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration test for orion_credential_set module 3 | - name: Include tests 4 | ansible.builtin.include_tasks: "{{ item }}" 5 | loop: 6 | - snmpv3.yml 7 | - assign.yml 8 | ... 9 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_module/tasks/main.yml: -------------------------------------------------------------------------------- 1 | # Integration tests for orion_node module 2 | 3 | - name: Include tests 4 | ansible.builtin.include_tasks: "{{ item }}" 5 | loop: 6 | - icmp_node.yml 7 | - snmp_node.yml 8 | - snmpv3_node.yml 9 | ... 10 | -------------------------------------------------------------------------------- /roles/orion_node/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | role_name: orion_node 3 | namespace: jeisenbath.solarwinds 4 | author: Josh Eisenbath (@jeisenbath) 5 | description: Add a node to Solarwinds Orion 6 | license: GPL-3.0-or-later 7 | min_ansible_version: '2.9' 8 | galaxy_tags: 9 | - monitoring 10 | dependencies: [] 11 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | namespace: jeisenbath 3 | name: solarwinds 4 | version: 3.1.0 5 | readme: README.md 6 | authors: 7 | - Josh Eisenbath (@jeisenbath) 8 | description: Modules and plugins for managing nodes with Solarwinds Orion 9 | repository: https://github.com/jeisenbath/ansible-collection-solarwinds-orion 10 | homepage: https://github.com/jeisenbath/ansible-collection-solarwinds-orion 11 | issues: https://github.com/jeisenbath/ansible-collection-solarwinds-orion 12 | tags: 13 | - monitoring 14 | - solarwinds 15 | - orion 16 | dependencies: {} 17 | license: 18 | - GPL-3.0-or-later 19 | build_ignore: 20 | - releases 21 | -------------------------------------------------------------------------------- /roles/orion_node/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for orion_node 3 | orion_node_solarwinds_server: "{{ solarwinds_server }}" 4 | orion_node_solarwinds_username: "{{ solarwinds_username }}" 5 | orion_node_solarwinds_password: "{{ solarwinds_password }}" 6 | orion_node_caption_name: "{{ ansible_facts.nodename }}" 7 | orion_node_ip_address: "{{ ansible_facts.default_ipv4.address }}" 8 | orion_node_polling_method: ICMP 9 | orion_node_snmp_pollers: 10 | - name: N.Cpu.SNMP.HrProcessorLoad 11 | enabled: true 12 | - name: N.Memory.SNMP.NetSnmpReal 13 | enabled: true 14 | orion_node_discover_interfaces: false 15 | orion_node_ncm: false 16 | -------------------------------------------------------------------------------- /meta/ee-bindep.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Ansible Project 2 | # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | 6 | # List your controller-side system package requirements here if exist 7 | # to support easy execution environments building for your collection users 8 | # https://docs.ansible.com/ansible/devel/getting_started_ee/index.html 9 | # See https://ansible.readthedocs.io/projects/builder/en/latest/collection_metadata/#how-to-verify-collection-level-metadata 10 | # to learn how to test your configuration. 11 | # Do not delete this file even if the collection has no dependencies! -------------------------------------------------------------------------------- /meta/ee-requirements.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Ansible Project 2 | # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | 6 | # List your controller-side Python package requirements here if exist 7 | # to support easy execution environments building for your collection users 8 | # https://docs.ansible.com/ansible/devel/getting_started_ee/index.html 9 | # See https://ansible.readthedocs.io/projects/builder/en/latest/collection_metadata/#how-to-verify-collection-level-metadata 10 | # to learn how to test your configuration. 11 | # Do not delete this file even if the collection has no dependencies! 12 | orionsdk>=0.3.0 13 | python-dateutil 14 | requests -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_info/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_info module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: ICMP 12 | delegate_to: localhost 13 | 14 | - name: Include tests 15 | ansible.builtin.include_tasks: "{{ item }}" 16 | loop: 17 | - info.yml 18 | 19 | - name: Remove test node 20 | orion_node: 21 | hostname: "{{ orion_test_solarwinds_server }}" 22 | username: "{{ orion_test_solarwinds_username }}" 23 | password: "{{ orion_test_solarwinds_password }}" 24 | state: absent 25 | name: "{{ orion_test_node_name }}" 26 | delegate_to: localhost 27 | ... 28 | -------------------------------------------------------------------------------- /changelogs/config.yaml: -------------------------------------------------------------------------------- 1 | changelog_filename_template: ../CHANGELOG.rst 2 | changelog_filename_version_depth: 0 3 | changes_file: changelog.yaml 4 | changes_format: combined 5 | ignore_other_fragment_extensions: true 6 | keep_fragments: false 7 | mention_ancestor: true 8 | new_plugins_after_name: removed_features 9 | notesdir: fragments 10 | prelude_section_name: release_summary 11 | prelude_section_title: Release Summary 12 | sanitize_changelog: true 13 | sections: 14 | - - major_changes 15 | - Major Changes 16 | - - minor_changes 17 | - Minor Changes 18 | - - breaking_changes 19 | - Breaking Changes / Porting Guide 20 | - - deprecated_features 21 | - Deprecated Features 22 | - - removed_features 23 | - Removed Features (previously deprecated) 24 | - - security_fixes 25 | - Security Fixes 26 | - - bugfixes 27 | - Bugfixes 28 | - - known_issues 29 | - Known Issues 30 | title: Solarwinds.Orion 31 | trivial_section_name: trivial 32 | use_fqcn: true 33 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_custom_property/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_custom_property module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: ICMP 12 | delegate_to: localhost 13 | 14 | - name: Include tests 15 | ansible.builtin.include_tasks: "{{ item }}" 16 | loop: 17 | - node_custom_property.yml 18 | 19 | - name: Remove test node 20 | orion_node: 21 | hostname: "{{ orion_test_solarwinds_server }}" 22 | username: "{{ orion_test_solarwinds_username }}" 23 | password: "{{ orion_test_solarwinds_password }}" 24 | state: absent 25 | name: "{{ orion_test_node_name }}" 26 | delegate_to: localhost 27 | ... 28 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_application/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_application module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: ICMP 12 | delegate_to: localhost 13 | 14 | - name: Include tests 15 | ansible.builtin.include_tasks: "{{ item }}" 16 | loop: 17 | - node_application.yml 18 | 19 | - name: Remove test node 20 | orion_node: 21 | hostname: "{{ orion_test_solarwinds_server }}" 22 | username: "{{ orion_test_solarwinds_username }}" 23 | password: "{{ orion_test_solarwinds_password }}" 24 | state: absent 25 | name: "{{ orion_test_node_name }}" 26 | delegate_to: localhost 27 | ... 28 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_ncm/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_ncm module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: SNMP 12 | snmp_version: 2 13 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 14 | delegate_to: localhost 15 | 16 | - name: Include tests 17 | ansible.builtin.include_tasks: "{{ item }}" 18 | loop: 19 | - ncm.yml 20 | 21 | - name: Remove test node 22 | orion_node: 23 | hostname: "{{ orion_test_solarwinds_server }}" 24 | username: "{{ orion_test_solarwinds_username }}" 25 | password: "{{ orion_test_solarwinds_password }}" 26 | state: absent 27 | name: "{{ orion_test_node_name }}" 28 | delegate_to: localhost 29 | ... 30 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_poller/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_poller module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: SNMP 12 | snmp_version: 2 13 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 14 | delegate_to: localhost 15 | 16 | - name: Include tests 17 | ansible.builtin.include_tasks: "{{ item }}" 18 | loop: 19 | - poller.yml 20 | 21 | - name: Remove test node 22 | orion_node: 23 | hostname: "{{ orion_test_solarwinds_server }}" 24 | username: "{{ orion_test_solarwinds_username }}" 25 | password: "{{ orion_test_solarwinds_password }}" 26 | state: absent 27 | name: "{{ orion_test_node_name }}" 28 | delegate_to: localhost 29 | ... 30 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_poller_info/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_poller_info module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: SNMP 12 | snmp_version: 2 13 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 14 | delegate_to: localhost 15 | 16 | - name: Include tests 17 | ansible.builtin.include_tasks: "{{ item }}" 18 | loop: 19 | - info.yml 20 | 21 | - name: Remove test node 22 | orion_node: 23 | hostname: "{{ orion_test_solarwinds_server }}" 24 | username: "{{ orion_test_solarwinds_username }}" 25 | password: "{{ orion_test_solarwinds_password }}" 26 | state: absent 27 | name: "{{ orion_test_node_name }}" 28 | delegate_to: localhost 29 | ... 30 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_custom_poller/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_custom_poller module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: SNMP 12 | snmp_version: 2 13 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 14 | delegate_to: localhost 15 | 16 | - name: Include tests 17 | ansible.builtin.include_tasks: "{{ item }}" 18 | loop: 19 | - custom_poller.yml 20 | 21 | - name: Remove test node 22 | orion_node: 23 | hostname: "{{ orion_test_solarwinds_server }}" 24 | username: "{{ orion_test_solarwinds_username }}" 25 | password: "{{ orion_test_solarwinds_password }}" 26 | state: absent 27 | name: "{{ orion_test_node_name }}" 28 | delegate_to: localhost 29 | ... 30 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_hardware_health/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_hardware_health module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: SNMP 12 | snmp_version: 2 13 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 14 | delegate_to: localhost 15 | 16 | - name: Include tests 17 | ansible.builtin.include_tasks: "{{ item }}" 18 | loop: 19 | - hardware_health.yml 20 | 21 | - name: Remove test node 22 | orion_node: 23 | hostname: "{{ orion_test_solarwinds_server }}" 24 | username: "{{ orion_test_solarwinds_username }}" 25 | password: "{{ orion_test_solarwinds_password }}" 26 | state: absent 27 | name: "{{ orion_test_node_name }}" 28 | delegate_to: localhost 29 | ... 30 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_interface/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_interface module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: SNMP 12 | snmp_version: 2 13 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 14 | delegate_to: localhost 15 | 16 | - name: Include tests 17 | ansible.builtin.include_tasks: "{{ item }}" 18 | loop: 19 | - discover.yml 20 | - defined.yml 21 | 22 | - name: Remove test node 23 | orion_node: 24 | hostname: "{{ orion_test_solarwinds_server }}" 25 | username: "{{ orion_test_solarwinds_username }}" 26 | password: "{{ orion_test_solarwinds_password }}" 27 | state: absent 28 | name: "{{ orion_test_node_name }}" 29 | delegate_to: localhost 30 | ... 31 | -------------------------------------------------------------------------------- /plugins/doc_fragments/orion_node_options.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright: (c) 2023, Josh M. Eisenbath 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | 8 | __metaclass__ = type 9 | 10 | 11 | class ModuleDocFragment(object): 12 | 13 | DOCUMENTATION = r""" 14 | options: 15 | node_id: 16 | description: 17 | - Node ID of the node. 18 | - One of I(ip_address), I(node_id), or I(name) is required. 19 | required: false 20 | type: str 21 | name: 22 | description: 23 | - Name of the node. 24 | - One of I(ip_address), I(node_id), or I(name) is required. 25 | required: false 26 | type: str 27 | aliases: [ 'caption' ] 28 | ip_address: 29 | description: 30 | - IP Address of the node. 31 | - One of I(ip_address), I(node_id), or I(name) is required. 32 | required: false 33 | type: str 34 | """ 35 | -------------------------------------------------------------------------------- /tests/integration/integration_config_example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | orion_test_solarwinds_server: "{{ solarwinds_server }}" 3 | orion_test_solarwinds_username: "{{ solarwinds_username }}" 4 | orion_test_solarwinds_password: "{{ solarwinds_password }}" 5 | orion_test_node_name: "{{ node_name }}" 6 | orion_test_node_ip_address: "{{ node_ip_address }}" 7 | orion_test_node_ro_community_string: "{{ node_ro_community_string }}" 8 | orion_test_custom_property_name: "{{ custom_property_name }}" 9 | orion_test_custom_property_value: "{{ custom_property_value }}" 10 | orion_test_custom_property_value_update: "{{ custom_property_value_update }}" 11 | orion_test_node_application: "{{ node_application }}" 12 | orion_test_node_custom_poller: "{{ node_custom_poller }}" 13 | orion_test_node_hardware_health_polling_method: "{{ hardware_health_polling_method }}" 14 | orion_test_node_ncm_profile: "{{ ncm_profile }}" 15 | orion_test_node_ncm_profile_update: "{{ ncm_profile_update }}" 16 | orion_test_node_snmpv3_credential_set: "{{ snmpv3_credentail_set }}" 17 | orion_test_node_snmpv3_username: "{{ snmpv3_username }}" 18 | orion_test_node_snmpv3_auth_key: "{{ snmpv3_auth_key }}" 19 | orion_test_node_snmpv3_priv_key: "{{ snmpv3_priv_key }}" 20 | -------------------------------------------------------------------------------- /meta/execution-environment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Copyright (c) Ansible Project 3 | # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | # List your controller-side system package and/or Python package requirements 7 | # if exist in the files specified below to support easy execution environments building for your collection users 8 | # https://docs.ansible.com/ansible/devel/getting_started_ee/index.html 9 | # See https://ansible.readthedocs.io/projects/builder/en/latest/collection_metadata/#how-to-verify-collection-level-metadata 10 | # to learn how to test your configuration. 11 | 12 | # Do not delete these files even if the collection has no dependencies! 13 | # If you delete them, ansible-builder will use bindep.txt and requirements.txt 14 | # from the collection root by default. Especially requirements.txt in the root is often 15 | # used for test/build/development requirements for the collection, that are 16 | # not needed for end-users to use the collection. Ansible-builder installing these 17 | # would increase the EE size and increase the chance of dependency collisions. 18 | version: 1 19 | dependencies: 20 | python: meta/ee-requirements.txt 21 | system: meta/ee-bindep.txt 22 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_interface_info/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Integration tests for orion_node_interface_info module 3 | - name: Create test node 4 | orion_node: 5 | hostname: "{{ orion_test_solarwinds_server }}" 6 | username: "{{ orion_test_solarwinds_username }}" 7 | password: "{{ orion_test_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_test_node_name }}" 10 | ip_address: "{{ orion_test_node_ip_address }}" 11 | polling_method: SNMP 12 | snmp_version: 2 13 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 14 | delegate_to: localhost 15 | 16 | - name: Add interfaces to test node 17 | orion_node_interface: 18 | hostname: "{{ orion_test_solarwinds_server }}" 19 | username: "{{ orion_test_solarwinds_username }}" 20 | password: "{{ orion_test_solarwinds_password }}" 21 | state: present 22 | name: "{{ orion_test_node_name }}" 23 | delegate_to: localhost 24 | 25 | - name: Include tests 26 | ansible.builtin.include_tasks: "{{ item }}" 27 | loop: 28 | - info.yml 29 | 30 | - name: Remove test node 31 | orion_node: 32 | hostname: "{{ orion_test_solarwinds_server }}" 33 | username: "{{ orion_test_solarwinds_username }}" 34 | password: "{{ orion_test_solarwinds_password }}" 35 | state: absent 36 | name: "{{ orion_test_node_name }}" 37 | delegate_to: localhost 38 | ... 39 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_info/tasks/info.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get info (check mode) 3 | orion_node_info: 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | delegate_to: localhost 9 | check_mode: true 10 | register: info_01 11 | 12 | - name: Get info 13 | orion_node_info: 14 | hostname: "{{ orion_test_solarwinds_server }}" 15 | username: "{{ orion_test_solarwinds_username }}" 16 | password: "{{ orion_test_solarwinds_password }}" 17 | name: "{{ orion_test_node_name }}" 18 | delegate_to: localhost 19 | register: info_02 20 | 21 | - name: Info asserts 22 | ansible.builtin.assert: 23 | that: 24 | - not info_01.changed 25 | - not info_02.changed 26 | - info_02.orion_node is defined 27 | - info_02.orion_node.caption == orion_test_node_name 28 | - info_02.orion_node.ipaddress == orion_test_node_ip_address 29 | - info_02.orion_node.lastsystemuptimepollutc is defined 30 | - info_02.orion_node.netobjectid is defined 31 | - info_02.orion_node.nodeid is defined 32 | - info_02.orion_node.objectsubtype is defined 33 | - info_02.orion_node.status is defined 34 | - info_02.orion_node.statusdescription is defined 35 | - info_02.orion_node.unmanaged is defined 36 | - info_02.orion_node.unmanagefrom is defined 37 | - info_02.orion_node.unmanageuntil is defined 38 | - info_02.orion_node.uri is defined 39 | ... 40 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_credential_set/tasks/snmpv3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create SNMPv3 Credential Set (check) 3 | jeisenbath.solarwinds.orion_credential_set: &create_credential 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | type: snmpv3 9 | credential_name: "{{ orion_test_node_snmpv3_credential_set }}" 10 | snmpv3: 11 | username: "{{ orion_test_node_snmpv3_username }}" 12 | auth_key: "{{ orion_test_node_snmpv3_auth_key }}" 13 | priv_key: "{{ orion_test_node_snmpv3_priv_key }}" 14 | delegate_to: localhost 15 | check_mode: true 16 | register: create_snmpv3_01 17 | 18 | - name: Create SNMPv3 Credential Set 19 | jeisenbath.solarwinds.orion_credential_set: 20 | <<: *create_credential 21 | delegate_to: localhost 22 | register: create_snmpv3_02 23 | 24 | - name: Create SNMPv3 Credential Set (check) (idempotence) 25 | jeisenbath.solarwinds.orion_credential_set: 26 | <<: *create_credential 27 | delegate_to: localhost 28 | check_mode: true 29 | register: create_snmpv3_03 30 | 31 | - name: Create SNMPv3 Credential Set (idempotence) 32 | jeisenbath.solarwinds.orion_credential_set: 33 | <<: *create_credential 34 | delegate_to: localhost 35 | register: create_snmpv3_04 36 | 37 | - name: Asserts 38 | ansible.builtin.assert: 39 | that: 40 | - create_snmpv3_01.changed 41 | - create_snmpv3_02.changed 42 | - create_snmpv3_02.credential is defined 43 | - not create_snmpv3_03.changed 44 | - not create_snmpv3_04.changed 45 | ... 46 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_poller_info/tasks/info.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get info (check mode) 3 | orion_node_poller_info: 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | delegate_to: localhost 9 | check_mode: true 10 | register: info_01 11 | 12 | - name: Get info 13 | orion_node_poller_info: 14 | hostname: "{{ orion_test_solarwinds_server }}" 15 | username: "{{ orion_test_solarwinds_username }}" 16 | password: "{{ orion_test_solarwinds_password }}" 17 | name: "{{ orion_test_node_name }}" 18 | delegate_to: localhost 19 | register: info_02 20 | 21 | - name: Info asserts 22 | ansible.builtin.assert: 23 | that: 24 | - not info_01.changed 25 | - not info_02.changed 26 | - info_02.orion_node is defined 27 | - info_02.orion_node.caption == orion_test_node_name 28 | - info_02.orion_node.ipaddress == orion_test_node_ip_address 29 | - info_02.orion_node.lastsystemuptimepollutc is defined 30 | - info_02.orion_node.netobjectid is defined 31 | - info_02.orion_node.nodeid is defined 32 | - info_02.orion_node.objectsubtype is defined 33 | - info_02.orion_node.status is defined 34 | - info_02.orion_node.statusdescription is defined 35 | - info_02.orion_node.unmanaged is defined 36 | - info_02.orion_node.unmanagefrom is defined 37 | - info_02.orion_node.unmanageuntil is defined 38 | - info_02.orion_node.uri is defined 39 | - info_02.pollers is defined 40 | - info_02.pollers[0].Enabled is defined 41 | - info_02.pollers[0].PollerType is defined 42 | ... 43 | -------------------------------------------------------------------------------- /plugins/doc_fragments/orion_auth_options.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright: (c) 2023, Josh M. Eisenbath 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | 8 | __metaclass__ = type 9 | 10 | 11 | class ModuleDocFragment(object): 12 | 13 | DOCUMENTATION = r""" 14 | options: 15 | hostname: 16 | description: 17 | - Name of Orion host running SWIS service. 18 | - If not defined in the task, the value of E(SOLARWINDS_SERVER) will be used instead. 19 | required: false 20 | type: str 21 | username: 22 | description: 23 | - Orion Username. 24 | - Active Directory users must use DOMAIN\\username format. 25 | - If not defined in the task, the value of E(SOLARWINDS_USERNAME) will be used instead. 26 | required: false 27 | type: str 28 | password: 29 | description: 30 | - Password for Orion user. 31 | - If not defined in the task, the value of E(SOLARWINDS_PASSWORD) will be used instead. 32 | required: false 33 | type: str 34 | port: 35 | description: 36 | - Port to connect to the Solarwinds Information Service API. 37 | - Solarwinds ver. 2024.1.0 and on has a new API on port 17774, with legacy supported on 17778. 38 | - This argument was introduced in orionsdk 0.4.0 to support connecting to either API. 39 | - If using an older version of Solarwinds with orionsdk 0.4.0, define this as port 17778. 40 | required: false 41 | default: '17774' 42 | type: str 43 | verify: 44 | description: 45 | - Verify SSL Certificate for Solarwinds Information Service API. 46 | - Requires orionsdk >= 0.4.0 47 | required: false 48 | default: false 49 | type: bool 50 | """ 51 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_interface_info/tasks/info.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get info (check mode) 3 | orion_node_interface_info: 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | delegate_to: localhost 9 | check_mode: true 10 | register: info_01 11 | 12 | - name: Get info 13 | orion_node_interface_info: 14 | hostname: "{{ orion_test_solarwinds_server }}" 15 | username: "{{ orion_test_solarwinds_username }}" 16 | password: "{{ orion_test_solarwinds_password }}" 17 | name: "{{ orion_test_node_name }}" 18 | delegate_to: localhost 19 | register: info_02 20 | 21 | - name: Info asserts 22 | ansible.builtin.assert: 23 | that: 24 | - not info_01.changed 25 | - not info_02.changed 26 | - info_02.orion_node is defined 27 | - info_02.orion_node.caption == orion_test_node_name 28 | - info_02.orion_node.ipaddress == orion_test_node_ip_address 29 | - info_02.orion_node.lastsystemuptimepollutc is defined 30 | - info_02.orion_node.netobjectid is defined 31 | - info_02.orion_node.nodeid is defined 32 | - info_02.orion_node.objectsubtype is defined 33 | - info_02.orion_node.status is defined 34 | - info_02.orion_node.statusdescription is defined 35 | - info_02.orion_node.unmanaged is defined 36 | - info_02.orion_node.unmanagefrom is defined 37 | - info_02.orion_node.unmanageuntil is defined 38 | - info_02.orion_node.uri is defined 39 | - info_02.interfaces is defined 40 | - info_02.interfaces[0].Name is defined 41 | - info_02.interfaces[0].InterfaceID is defined 42 | - info_02.interfaces[0].AdminStatus is defined 43 | - info_02.interfaces[0].OperStatus is defined 44 | - info_02.interfaces[0].Speed is defined 45 | - info_02.interfaces[0].Type is defined 46 | - info_02.interfaces[0].Status is defined 47 | - info_02.interfaces[0].StatusDescription is defined 48 | ... 49 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_credential_set/tasks/assign.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create test node 3 | orion_node: 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | ip_address: "{{ orion_test_node_ip_address }}" 10 | polling_method: ICMP 11 | delegate_to: localhost 12 | 13 | - name: Assign Credential (check) 14 | jeisenbath.solarwinds.orion_credential_set: &assign_credential 15 | hostname: "{{ orion_test_solarwinds_server }}" 16 | username: "{{ orion_test_solarwinds_username }}" 17 | password: "{{ orion_test_solarwinds_password }}" 18 | state: assigned 19 | type: snmpv3 20 | snmpv3: 21 | username: "{{ orion_test_node_snmpv3_username }}" 22 | auth_key: "{{ orion_test_node_snmpv3_auth_key }}" 23 | priv_key: "{{ orion_test_node_snmpv3_priv_key }}" 24 | credential_name: "{{ orion_test_node_snmpv3_credential_set }}" 25 | ip_address: "{{ orion_test_node_ip_address }}" 26 | delegate_to: localhost 27 | check_mode: true 28 | register: assign_snmpv3_01 29 | 30 | - name: Assign Credential 31 | jeisenbath.solarwinds.orion_credential_set: 32 | <<: *assign_credential 33 | delegate_to: localhost 34 | register: assign_snmpv3_02 35 | 36 | - name: Assign Credential (check) (idempotence) 37 | jeisenbath.solarwinds.orion_credential_set: 38 | <<: *assign_credential 39 | delegate_to: localhost 40 | check_mode: true 41 | register: assign_snmpv3_03 42 | 43 | - name: Assign Credential (idempotence) 44 | jeisenbath.solarwinds.orion_credential_set: 45 | <<: *assign_credential 46 | delegate_to: localhost 47 | register: assign_snmpv3_04 48 | 49 | - name: Assign Credential asserts 50 | ansible.builtin.assert: 51 | that: 52 | - assign_snmpv3_01.changed 53 | - assign_snmpv3_02.changed 54 | - assign_snmpv3_02.orion_node is defined 55 | - assign_snmpv3_02.credentials is defined 56 | - not assign_snmpv3_03.changed 57 | - not assign_snmpv3_04.changed 58 | 59 | - name: Remove test node 60 | orion_node: 61 | hostname: "{{ orion_test_solarwinds_server }}" 62 | username: "{{ orion_test_solarwinds_username }}" 63 | password: "{{ orion_test_solarwinds_password }}" 64 | state: absent 65 | name: "{{ orion_test_node_name }}" 66 | delegate_to: localhost 67 | ... 68 | -------------------------------------------------------------------------------- /playbooks/update_node_to_snmpv3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update a host from ICMP or SNMPv2 polling to SNMPv3 3 | hosts: "{{ target | default('localhost') }}" 4 | 5 | tasks: 6 | - name: Update polling method to SNMPv3 7 | jeisenbath.solarwinds.orion_update_node: 8 | hostname: "{{ solarwinds_server }}" 9 | username: "{{ solarwinds_username }}" 10 | password: "{{ solarwinds_password }}" 11 | name: "{{ node_name }}" 12 | properties: 13 | ObjectSubType: SNMP 14 | SNMPVersion: 3 15 | delegate_to: localhost 16 | 17 | - name: Create SNMPv3 Credential Set 18 | jeisenbath.solarwinds.orion_credential_set: 19 | hostname: "{{ solarwinds_server }}" 20 | username: "{{ solarwinds_username }}" 21 | password: "{{ solarwinds_password }}" 22 | state: present 23 | type: snmpv3 24 | credential_name: "{{ snmpv3_credential_set_name }}" 25 | snmpv3: 26 | username: "{{ snmpv3_username }}" 27 | auth_key: "{{ snmpv3_auth_key }}" 28 | priv_key: "{{ snmpv3_priv_key }}" 29 | delegate_to: localhost 30 | 31 | - name: Assign Credential Set to node 32 | jeisenbath.solarwinds.orion_credential_set: 33 | hostname: "{{ solarwinds_server }}" 34 | username: "{{ solarwinds_username }}" 35 | password: "{{ solarwinds_password }}" 36 | state: assigned 37 | type: snmpv3 38 | snmpv3: 39 | username: "{{ snmpv3_username }}" 40 | auth_key: "{{ snmpv3_auth_key }}" 41 | priv_key: "{{ snmpv3_priv_key }}" 42 | credential_name: "{{ snmpv3_credential_set_name }}" 43 | caption: "{{ node_name }}" 44 | delegate_to: localhost 45 | 46 | - name: Add SNMP pollers to node 47 | jeisenbath.solarwinds.orion_node_poller: 48 | hostname: "{{ solarwinds_server }}" 49 | username: "{{ solarwinds_username }}" 50 | password: "{{ solarwinds_password }}" 51 | name: "{{ node_name }}" 52 | state: present 53 | poller: "{{ item.name }}" 54 | enabled: "{{ item.enabled }}" 55 | loop: 56 | - name: N.Status.ICMP.Native 57 | enabled: 'True' 58 | - name: N.ResponseTime.ICMP.Native 59 | enabled: 'True' 60 | - name: N.Details.SNMP.Generic 61 | enabled: 'True' 62 | - name: N.Uptime.SNMP.Generic 63 | enabled: 'True' 64 | delegate_to: localhost 65 | ... 66 | -------------------------------------------------------------------------------- /playbooks/orion_add_node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add Linux host as node to Solarwinds 3 | hosts: "{{ target }}" 4 | gather_facts: true # if you don't want to use ansible facts, make sure to then override default caption and IP variables 5 | 6 | # Be sure to check out the role README for a full list of variables 7 | roles: 8 | - role: jeisenbath.solarwinds.orion_node 9 | solarwinds_server: 127.0.0.1 10 | solarwinds_username: admin 11 | solarwinds_password: changeme2345 12 | orion_node_polling_method: SNMP 13 | orion_node_snmp_version: 2 14 | orion_node_ro_community_string: community 15 | orion_node_discover_interfaces: true 16 | orion_node_volumes: 17 | - / 18 | - /home 19 | - /var/log 20 | # These are the basic pollers for a Linux node 21 | # If you have other pollers enabled, such as Topology Layer 3, you can find a list by adding a node manually, 22 | # then run the following query against the orion database: 23 | 24 | # SELECT n.Caption, p.PollerType, p.Enabled 25 | # from Orion.Nodes n 26 | # left join Orion.Pollers as p on p.NetObjectID = n.NodeId 27 | # where n.Caption = 'your_node_caption_here' 28 | orion_node_snmp_pollers: 29 | - name: N.LoadAverage.SNMP.Linux 30 | enabled: true 31 | - name: N.Cpu.SNMP.HrProcessorLoad 32 | enabled: true 33 | - name: N.Memory.SNMP.NetSnmpReal 34 | enabled: true 35 | # The same can be done for custom pollers, if you have any. 36 | # This query can get a list of Custom Pollers assigned to a node: 37 | 38 | # SELECT n.Caption, c.CustomPollerName 39 | # from Orion.Nodes n 40 | # left join Orion.NPM.CustomPollerAssignment as c on c.NodeID = n.NodeID 41 | # where n.Caption = 'your_node_caption_here' 42 | 43 | # You can also get a full list of custom pollers available with this query: 44 | # SELECT GroupName, UniqueName 45 | # FROM Orion.NPM.CustomPollers 46 | # Order By GroupName 47 | 48 | tasks: 49 | - name: Validate SNMP is polling after node add 50 | jeisenbath.solarwinds.orion_node_info: 51 | hostname: "{{ solarwinds_server }}" 52 | username: "{{ solarwinds_user }}" 53 | password: "{{ solarwinds_pass }}" 54 | name: "{{ orion_node_caption }}" 55 | delegate_to: localhost 56 | register: orion_node_info 57 | until: orion_node_info.orion_node.lastsystemuptimepollutc 58 | retries: 6 59 | delay: 5 60 | ... 61 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_custom_poller/tasks/custom_poller.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add poller to node (check) 3 | orion_node_custom_poller: &add_poller 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | custom_poller: "{{ orion_test_node_custom_poller }}" 9 | state: present 10 | delegate_to: localhost 11 | check_mode: true 12 | register: add_poller_01 13 | 14 | - name: Add poller to node 15 | orion_node_custom_poller: 16 | <<: *add_poller 17 | delegate_to: localhost 18 | register: add_poller_02 19 | 20 | - name: Add poller to node (check) (idempotence) 21 | orion_node_custom_poller: 22 | <<: *add_poller 23 | delegate_to: localhost 24 | check_mode: true 25 | register: add_poller_03 26 | 27 | - name: Add poller to node (idempotence) 28 | orion_node_custom_poller: 29 | <<: *add_poller 30 | delegate_to: localhost 31 | register: add_poller_04 32 | 33 | - name: Add poller asserts 34 | ansible.builtin.assert: 35 | that: 36 | - add_poller_01.changed 37 | - add_poller_02.changed 38 | - not add_poller_03.changed 39 | - not add_poller_04.changed 40 | - add_poller_02.orion_node is defined 41 | 42 | - name: Remove poller from node (check) 43 | orion_node_custom_poller: &remove_poller 44 | hostname: "{{ orion_test_solarwinds_server }}" 45 | username: "{{ orion_test_solarwinds_username }}" 46 | password: "{{ orion_test_solarwinds_password }}" 47 | name: "{{ orion_test_node_name }}" 48 | custom_poller: "{{ orion_test_node_custom_poller }}" 49 | state: absent 50 | delegate_to: localhost 51 | check_mode: true 52 | register: remove_poller_01 53 | 54 | - name: Remove poller from node 55 | orion_node_custom_poller: 56 | <<: *remove_poller 57 | delegate_to: localhost 58 | register: remove_poller_02 59 | 60 | - name: Remove poller from node (check) (idempotence) 61 | orion_node_custom_poller: 62 | <<: *remove_poller 63 | delegate_to: localhost 64 | check_mode: true 65 | register: remove_poller_03 66 | 67 | - name: Remove poller from node (idempotence) 68 | orion_node_custom_poller: 69 | <<: *remove_poller 70 | delegate_to: localhost 71 | register: remove_poller_04 72 | 73 | - name: Remove poller from node asserts 74 | ansible.builtin.assert: 75 | that: 76 | - remove_poller_01.changed 77 | - remove_poller_02.changed 78 | - not remove_poller_03.changed 79 | - not remove_poller_04.changed 80 | ... 81 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_application/tasks/node_application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add APM template to node (check) 3 | orion_node_application: &add_application 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | application_template_name: "{{ orion_test_node_application }}" 9 | state: present 10 | delegate_to: localhost 11 | check_mode: true 12 | register: add_app_01 13 | 14 | - name: Add APM template to node 15 | orion_node_application: 16 | <<: *add_application 17 | delegate_to: localhost 18 | register: add_app_02 19 | 20 | - name: Add APM template to node (check) (idempotence) 21 | orion_node_application: 22 | <<: *add_application 23 | delegate_to: localhost 24 | check_mode: true 25 | register: add_app_03 26 | 27 | - name: Add APM template to node (idempotence) 28 | orion_node_application: 29 | <<: *add_application 30 | delegate_to: localhost 31 | register: add_app_04 32 | 33 | - name: Add APM template asserts 34 | ansible.builtin.assert: 35 | that: 36 | - add_app_01.changed 37 | - add_app_02.changed 38 | - not add_app_03.changed 39 | - not add_app_04.changed 40 | - add_app_02.orion_node is defined 41 | 42 | - name: Remove APM template from node (check) 43 | orion_node_application: &remove_application 44 | hostname: "{{ orion_test_solarwinds_server }}" 45 | username: "{{ orion_test_solarwinds_username }}" 46 | password: "{{ orion_test_solarwinds_password }}" 47 | name: "{{ orion_test_node_name }}" 48 | application_template_name: "{{ orion_test_node_application }}" 49 | state: absent 50 | delegate_to: localhost 51 | check_mode: true 52 | register: remove_app_01 53 | 54 | - name: Remove APM template from node 55 | orion_node_application: 56 | <<: *remove_application 57 | delegate_to: localhost 58 | register: remove_app_02 59 | 60 | - name: Remove APM template from node 61 | orion_node_application: 62 | <<: *remove_application 63 | delegate_to: localhost 64 | check_mode: true 65 | register: remove_app_03 66 | 67 | - name: Remove APM template from node 68 | orion_node_application: 69 | <<: *remove_application 70 | delegate_to: localhost 71 | register: remove_app_04 72 | 73 | - name: Remove APM template asserts 74 | ansible.builtin.assert: 75 | that: 76 | - remove_app_01.changed 77 | - remove_app_02.changed 78 | - not remove_app_03.changed 79 | - not remove_app_04.changed 80 | ... 81 | -------------------------------------------------------------------------------- /roles/orion_node/README.md: -------------------------------------------------------------------------------- 1 | orion_node 2 | ========= 3 | 4 | Adds an node to be monitored in Solarwinds. 5 | 6 | Requirements 7 | ------------ 8 | 9 | jeisenbath.solarwinds collection 10 | 11 | Role Variables 12 | -------------- 13 | 14 | defaults/main.yml 15 | ```yaml 16 | orion_node_solarwinds_server: "{{ solarwinds_server }}" 17 | orion_node_solarwinds_username: "{{ solarwinds_username }}" 18 | orion_node_solarwinds_password: "{{ solarwinds_password }}" 19 | orion_node_caption_name: "{{ ansible_facts.nodename }}" 20 | orion_node_ip_address: "{{ ansible_facts.default_ipv4.address }}" 21 | orion_node_polling_method: ICMP 22 | orion_node_snmp_pollers: 23 | - name: N.Cpu.SNMP.HrProcessorLoad 24 | enabled: true 25 | - name: N.Memory.SNMP.NetSnmpReal 26 | enabled: true 27 | orion_node_discover_interfaces: false 28 | orion_node_ncm: false 29 | orion_node_snmp_port: 161 30 | orion_node_snmp_allow_64: true 31 | ``` 32 | 33 | Variables required depending on the values of defaults 34 | ```yaml 35 | # required when orion_node_polling method is SNMP, set which version of SNMP (choices: 2, 3) 36 | orion_node_snmp_version: 37 | # required when SNMP version is 2 38 | orion_node_ro_community_string: 39 | # required when SNMP version is 3 40 | orion_node_snmpv3_credential_set: 41 | orion_node_snmpv3_username: 42 | orion_node_snmpv3_auth_key: 43 | orion_node_snmpv3_priv_key: 44 | 45 | ``` 46 | 47 | Optional variables, define these if you need to configure 48 | ```yaml 49 | orion_node_custom_pollers: list, additional custom UnDP pollers 50 | orion_node_interfaces: list, interfaces to monitor 51 | orion_node_volumes: list, volumes to monitor 52 | orion_node_applications: list, APM templates to add to node 53 | orion_node_custom_properties: list, elements are dicts (name, value), custom property names and values to set 54 | orion_node_hardware_health_poller: string, Name of the Hardware Health poller to enable on node 55 | orion_node_ncm_profile_name: string, Name of NCM profile if managing node in NCM 56 | ``` 57 | 58 | 59 | Example Playbook 60 | ---------------- 61 | 62 | ```yaml 63 | 64 | - name: Add servers to Solarwinds as SNMPv2 nodes 65 | hosts: servers 66 | gather_facts: true 67 | vars: 68 | solarwinds_server: 127.0.0.1 69 | solarwinds_username: admin 70 | solarwinds_password: changeme2345 71 | roles: 72 | - role: jeisenbath.solarwinds.orion_node 73 | orion_node_polling_method: SNMP 74 | orion_node_snmp_version: 2 75 | orion_node_ro_community_string: community 76 | orion_node_discover_interfaces: true 77 | 78 | ``` 79 | 80 | License 81 | ------- 82 | 83 | GPL-3.0-or-later 84 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_hardware_health/tasks/hardware_health.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add hardware health poller to node (check) 3 | orion_node_hardware_health: &add_hh 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | polling_method: "{{ orion_test_node_hardware_health_polling_method }}" 9 | state: present 10 | delegate_to: localhost 11 | check_mode: true 12 | register: add_hh_01 13 | 14 | - name: Add hardware health poller to node 15 | orion_node_hardware_health: 16 | <<: *add_hh 17 | delegate_to: localhost 18 | register: add_hh_02 19 | 20 | - name: Add hardware health poller to node (check) (idempotence) 21 | orion_node_hardware_health: 22 | <<: *add_hh 23 | delegate_to: localhost 24 | check_mode: true 25 | register: add_hh_03 26 | 27 | - name: Add hardware health poller to node (idempotence) 28 | orion_node_hardware_health: 29 | <<: *add_hh 30 | delegate_to: localhost 31 | register: add_hh_04 32 | 33 | - name: Add poller asserts 34 | ansible.builtin.assert: 35 | that: 36 | - add_hh_01.changed 37 | - add_hh_02.changed 38 | - not add_hh_03.changed 39 | - not add_hh_04.changed 40 | - add_hh_02.orion_node is defined 41 | 42 | - name: Remove hardware health poller from node (check) 43 | orion_node_hardware_health: &remove_hh 44 | hostname: "{{ orion_test_solarwinds_server }}" 45 | username: "{{ orion_test_solarwinds_username }}" 46 | password: "{{ orion_test_solarwinds_password }}" 47 | name: "{{ orion_test_node_name }}" 48 | polling_method: "{{ orion_test_node_hardware_health_polling_method }}" 49 | state: absent 50 | delegate_to: localhost 51 | check_mode: true 52 | register: remove_hh_01 53 | 54 | - name: Remove hardware health poller from node 55 | orion_node_hardware_health: 56 | <<: *remove_hh 57 | delegate_to: localhost 58 | register: remove_hh_02 59 | 60 | - name: Remove hardware health poller from node (check) (idempotence) 61 | orion_node_hardware_health: 62 | <<: *remove_hh 63 | delegate_to: localhost 64 | check_mode: true 65 | register: remove_hh_03 66 | 67 | - name: Remove hardware health poller from node (idempotence) 68 | orion_node_hardware_health: 69 | <<: *remove_hh 70 | delegate_to: localhost 71 | register: remove_hh_04 72 | 73 | - name: Remove poller from node asserts 74 | ansible.builtin.assert: 75 | that: 76 | - remove_hh_01.changed 77 | - remove_hh_02.changed 78 | - not remove_hh_03.changed 79 | - not remove_hh_04.changed 80 | ... 81 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_volume_info/tasks/linux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create test node 3 | orion_node: 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | ip_address: "{{ orion_test_node_ip_address }}" 10 | polling_method: SNMP 11 | snmp_version: 2 12 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 13 | delegate_to: localhost 14 | 15 | - name: Create volume on test node 16 | orion_volume: 17 | hostname: "{{ orion_test_solarwinds_server }}" 18 | username: "{{ orion_test_solarwinds_username }}" 19 | password: "{{ orion_test_solarwinds_password }}" 20 | name: "{{ orion_test_node_name }}" 21 | volume: 22 | name: '/' 23 | state: present 24 | delegate_to: localhost 25 | 26 | - name: Get volume info (check) 27 | orion_volume_info: &volume_info 28 | hostname: "{{ orion_test_solarwinds_server }}" 29 | username: "{{ orion_test_solarwinds_username }}" 30 | password: "{{ orion_test_solarwinds_password }}" 31 | name: "{{ orion_test_node_name }}" 32 | volume: 33 | name: '/' 34 | delegate_to: localhost 35 | check_mode: true 36 | register: info_01 37 | 38 | - name: Get volume info 39 | orion_volume_info: 40 | <<: *volume_info 41 | delegate_to: localhost 42 | register: info_02 43 | 44 | - name: Info asserts 45 | ansible.builtin.assert: 46 | that: 47 | - not info_01.changed 48 | - not info_02.changed 49 | - info_02.orion_node is defined 50 | - info_02.orion_node.caption == orion_test_node_name 51 | - info_02.orion_node.ipaddress == orion_test_node_ip_address 52 | - info_02.orion_volume.caption == '/' 53 | - info_02.orion_volume.displayname is defined 54 | - info_02.orion_volume.volumeindex is defined 55 | - info_02.orion_volume.status is defined 56 | - info_02.orion_volume.type is defined 57 | - info_02.orion_volume.pollinterval is defined 58 | - info_02.orion_volume.statcollection is defined 59 | - info_02.orion_volume.rediscoveryinterval is defined 60 | - info_02.orion_volume.volumedescription is defined 61 | - info_02.orion_volume.icon is defined 62 | - info_02.orion_volume.uri is defined 63 | 64 | - name: Remove test node 65 | orion_node: 66 | hostname: "{{ orion_test_solarwinds_server }}" 67 | username: "{{ orion_test_solarwinds_username }}" 68 | password: "{{ orion_test_solarwinds_password }}" 69 | state: absent 70 | name: "{{ orion_test_node_name }}" 71 | delegate_to: localhost 72 | ... 73 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_module/tasks/snmp_node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: (SNMP v2) Create node (check mode) 3 | orion_node: &add_snmp_node 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | ip_address: "{{ orion_test_node_ip_address }}" 10 | polling_method: SNMP 11 | snmp_version: 2 12 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 13 | delegate_to: localhost 14 | check_mode: true 15 | register: snmp_add_01 16 | 17 | - name: (SNMP v2) Create node 18 | orion_node: 19 | <<: *add_snmp_node 20 | delegate_to: localhost 21 | register: snmp_add_02 22 | 23 | - name: (SNMP v2) Create node (check) (idempotence) 24 | orion_node: 25 | <<: *add_snmp_node 26 | delegate_to: localhost 27 | check_mode: true 28 | register: snmp_add_03 29 | 30 | - name: (SNMP v2) Create node (idempotence) 31 | orion_node: 32 | <<: *add_snmp_node 33 | delegate_to: localhost 34 | register: snmp_add_04 35 | 36 | - name: (SNMP v2) Create node asserts 37 | ansible.builtin.assert: 38 | that: 39 | - snmp_add_01.changed 40 | - snmp_add_02.changed 41 | - snmp_add_02.orion_node.caption == orion_test_node_name 42 | - snmp_add_02.orion_node.ipaddress == orion_test_node_ip_address 43 | - snmp_add_02.orion_node.objectsubtype == "SNMP" 44 | - not snmp_add_03.changed 45 | - not snmp_add_04.changed 46 | 47 | - name: (SNMP v2) Delete node (check) 48 | orion_node: &remove_snmp_node 49 | hostname: "{{ orion_test_solarwinds_server }}" 50 | username: "{{ orion_test_solarwinds_username }}" 51 | password: "{{ orion_test_solarwinds_password }}" 52 | state: absent 53 | name: "{{ orion_test_node_name }}" 54 | delegate_to: localhost 55 | check_mode: true 56 | register: snmp_delete_01 57 | 58 | - name: (SNMP v2) Delete node 59 | orion_node: 60 | <<: *remove_snmp_node 61 | delegate_to: localhost 62 | register: snmp_delete_02 63 | 64 | - name: (SNMP v2) Delete node (check) (idempotence) 65 | orion_node: 66 | <<: *remove_snmp_node 67 | delegate_to: localhost 68 | check_mode: true 69 | register: snmp_delete_03 70 | 71 | - name: (SNMP v2) Delete node (idempotence) 72 | orion_node: 73 | <<: *remove_snmp_node 74 | delegate_to: localhost 75 | register: snmp_delete_04 76 | 77 | - name: (SNMP v2) Delete node asserts 78 | ansible.builtin.assert: 79 | that: 80 | - snmp_delete_01.changed 81 | - snmp_delete_02.changed 82 | - not snmp_delete_03.changed 83 | - not snmp_delete_04.changed 84 | ... 85 | -------------------------------------------------------------------------------- /plugins/module_utils/credential.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4 | 5 | from __future__ import absolute_import, division, print_function 6 | __metaclass__ = type 7 | 8 | def get_credentials(orion, name): 9 | credential = {} 10 | query = f""" 11 | SELECT ID, Name, CredentialType 12 | FROM Orion.Credential 13 | WHERE Name = '{name}' 14 | """ 15 | results = orion.swis.query(query) 16 | if results['results']: 17 | credential['ID'] = results['results'][0]['ID'] 18 | credential['Name'] = results['results'][0]['Name'] 19 | credential['CredentialType'] = results['results'][0]['CredentialType'] 20 | return credential 21 | 22 | def create_snmpv3_credentials(orion, name, snmp3_props: dict, context: str = '', owner: str = 'Orion'): 23 | result = orion.swis.invoke( 24 | 'Orion.Credential', 'CreateSNMPv3Credentials', name, snmp3_props['username'], context, 25 | snmp3_props['auth_method'], snmp3_props['auth_key'], snmp3_props['auth_key_is_pwd'], 26 | snmp3_props['priv_method'], snmp3_props['priv_key'], snmp3_props['priv_key_is_pwd'], 27 | owner) 28 | return result 29 | 30 | def create_username_password_credentials(orion, name, wmiProps: dict, owner): 31 | result = orion.swis.invoke('Orion.Credential', 'CreateUsernamePasswordCredentials', name, wmiProps['username'], wmiProps['password'], owner) 32 | return result 33 | 34 | def validate_snmp3_credentials(orion, node, properties, port: int = 161): 35 | snmp3_credentials = { 36 | 'UserName': properties['username'], 37 | 'Context': '', 38 | 'PrivacyType': properties['priv_method'], 39 | 'PrivacyPassword': properties['priv_key'], 40 | 'PrivacyKeyIsPassword': properties['priv_key_is_pwd'], 41 | 'AuthenticationPassword': properties['auth_key'], 42 | 'AuthenticationType': properties['auth_method'], 43 | 'AuthenticationKeyIsPassword': properties['auth_key_is_pwd'], 44 | } 45 | result = orion.swis.invoke( 46 | 'Orion.Discovery', 47 | 'ValidateCredentials', 48 | node['ipaddress'], 49 | port, 50 | 'SolarWinds.Orion.Core.Models.Credentials.SnmpCredentialsV3', 51 | snmp3_credentials, 52 | node['engineid'], 53 | ) 54 | return result 55 | 56 | def assign_credentials_to_node(orion, node, credentialSet, nodeSetting): 57 | properties = { 58 | 'NodeID': node['nodeid'], 59 | 'SettingName': nodeSetting, 60 | 'SettingValue': credentialSet['ID'] 61 | } 62 | result = orion.swis.create( 63 | 'Orion.NodeSettings', 64 | **properties 65 | ) 66 | return result 67 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_module/tasks/snmpv3_node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: (SNMP v3) Create node (check mode) 3 | orion_node: &add_snmp_node 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | ip_address: "{{ orion_test_node_ip_address }}" 10 | polling_method: SNMP 11 | snmp_version: 3 12 | snmpv3_credential_set: "{{ orion_test_node_snmpv3_credential_set }}" 13 | snmpv3_username: "{{ orion_test_node_snmpv3_username }}" 14 | snmpv3_auth_key: "{{ orion_test_node_snmpv3_auth_key }}" 15 | snmpv3_priv_key: "{{ orion_test_node_snmpv3_priv_key }}" 16 | delegate_to: localhost 17 | check_mode: true 18 | register: snmp_add_01 19 | 20 | - name: (SNMP v3) Create node 21 | orion_node: 22 | <<: *add_snmp_node 23 | delegate_to: localhost 24 | register: snmp_add_02 25 | 26 | - name: (SNMP v3) Create node (check) (idempotence) 27 | orion_node: 28 | <<: *add_snmp_node 29 | delegate_to: localhost 30 | check_mode: true 31 | register: snmp_add_03 32 | 33 | - name: (SNMP v3) Create node (idempotence) 34 | orion_node: 35 | <<: *add_snmp_node 36 | delegate_to: localhost 37 | register: snmp_add_04 38 | 39 | - name: (SNMP v3) Create node asserts 40 | ansible.builtin.assert: 41 | that: 42 | - snmp_add_01.changed 43 | - snmp_add_02.changed 44 | - snmp_add_02.orion_node.caption == orion_test_node_name 45 | - snmp_add_02.orion_node.ipaddress == orion_test_node_ip_address 46 | - snmp_add_02.orion_node.objectsubtype == "SNMP" 47 | - not snmp_add_03.changed 48 | - not snmp_add_04.changed 49 | 50 | - name: (SNMP v3) Delete node (check) 51 | orion_node: &remove_snmp_node 52 | hostname: "{{ orion_test_solarwinds_server }}" 53 | username: "{{ orion_test_solarwinds_username }}" 54 | password: "{{ orion_test_solarwinds_password }}" 55 | state: absent 56 | name: "{{ orion_test_node_name }}" 57 | delegate_to: localhost 58 | check_mode: true 59 | register: snmp_delete_01 60 | 61 | - name: (SNMP v3) Delete node 62 | orion_node: 63 | <<: *remove_snmp_node 64 | delegate_to: localhost 65 | register: snmp_delete_02 66 | 67 | - name: (SNMP v3) Delete node (check) (idempotence) 68 | orion_node: 69 | <<: *remove_snmp_node 70 | delegate_to: localhost 71 | check_mode: true 72 | register: snmp_delete_03 73 | 74 | - name: (SNMP v3) Delete node (idempotence) 75 | orion_node: 76 | <<: *remove_snmp_node 77 | delegate_to: localhost 78 | register: snmp_delete_04 79 | 80 | - name: (SNMP v3) Delete node asserts 81 | ansible.builtin.assert: 82 | that: 83 | - snmp_delete_01.changed 84 | - snmp_delete_02.changed 85 | - not snmp_delete_03.changed 86 | - not snmp_delete_04.changed 87 | ... 88 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_ncm/tasks/ncm.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add node to NCM (check) 3 | orion_node_ncm: &add_ncm 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | profile_name: "{{ orion_test_node_ncm_profile }}" 9 | state: present 10 | delegate_to: localhost 11 | check_mode: true 12 | register: add_ncm_01 13 | 14 | - name: Add node to NCM 15 | orion_node_ncm: 16 | <<: *add_ncm 17 | delegate_to: localhost 18 | register: add_ncm_02 19 | 20 | - name: Add node to NCM (check) (idempotence) 21 | orion_node_ncm: 22 | <<: *add_ncm 23 | delegate_to: localhost 24 | check_mode: true 25 | register: add_ncm_03 26 | 27 | - name: Add node to NCM (idempotence) 28 | orion_node_ncm: 29 | <<: *add_ncm 30 | delegate_to: localhost 31 | register: add_ncm_04 32 | 33 | - name: Update NCM profile (check) 34 | orion_node_ncm: &update_ncm 35 | hostname: "{{ orion_test_solarwinds_server }}" 36 | username: "{{ orion_test_solarwinds_username }}" 37 | password: "{{ orion_test_solarwinds_password }}" 38 | name: "{{ orion_test_node_name }}" 39 | profile_name: "{{ orion_test_node_ncm_profile_update }}" 40 | state: present 41 | delegate_to: localhost 42 | check_mode: true 43 | register: update_ncm_01 44 | 45 | - name: Update NCM profile 46 | orion_node_ncm: 47 | <<: *update_ncm 48 | delegate_to: localhost 49 | register: update_ncm_02 50 | 51 | - name: Add node to NCM asserts 52 | ansible.builtin.assert: 53 | that: 54 | - add_ncm_01.changed 55 | - add_ncm_02.changed 56 | - not add_ncm_03.changed 57 | - not add_ncm_04.changed 58 | - add_ncm_02.orion_node is defined 59 | - update_ncm_01.changed 60 | - update_ncm_01.changed 61 | 62 | - name: Remove node from NCM (check) 63 | orion_node_ncm: &remove_ncm 64 | hostname: "{{ orion_test_solarwinds_server }}" 65 | username: "{{ orion_test_solarwinds_username }}" 66 | password: "{{ orion_test_solarwinds_password }}" 67 | name: "{{ orion_test_node_name }}" 68 | state: absent 69 | delegate_to: localhost 70 | check_mode: true 71 | register: remove_ncm_01 72 | 73 | - name: Remove node from NCM 74 | orion_node_ncm: 75 | <<: *remove_ncm 76 | delegate_to: localhost 77 | register: remove_ncm_02 78 | 79 | - name: Remove node from NCM (check) (idempotence) 80 | orion_node_ncm: 81 | <<: *remove_ncm 82 | delegate_to: localhost 83 | check_mode: true 84 | register: remove_ncm_03 85 | 86 | - name: Remove node from NCM (idempotence) 87 | orion_node_ncm: 88 | <<: *remove_ncm 89 | delegate_to: localhost 90 | register: remove_ncm_04 91 | 92 | - name: Remove node from NCM asserts 93 | ansible.builtin.assert: 94 | that: 95 | - remove_ncm_01.changed 96 | - remove_ncm_02.changed 97 | - not remove_ncm_03.changed 98 | - not remove_ncm_04.changed 99 | ... 100 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_interface/tasks/discover.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add discovered interfaces (check) 3 | orion_node_interface: &solarwinds_interface_add 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | delegate_to: localhost 10 | check_mode: true 11 | register: discover_if_add_01 12 | 13 | - name: Add discovered interfaces 14 | orion_node_interface: 15 | <<: *solarwinds_interface_add 16 | delegate_to: localhost 17 | register: discover_if_add_02 18 | 19 | - name: Add discovered interfaces (check) (idempotence) 20 | orion_node_interface: 21 | <<: *solarwinds_interface_add 22 | delegate_to: localhost 23 | check_mode: true 24 | register: discover_if_add_03 25 | 26 | - name: Add discovered interfaces (idempotence) 27 | orion_node_interface: 28 | <<: *solarwinds_interface_add 29 | delegate_to: localhost 30 | register: discover_if_add_04 31 | 32 | - name: Add discovered interfaces asserts 33 | ansible.builtin.assert: 34 | that: 35 | - discover_if_add_01.changed 36 | - discover_if_add_02.changed 37 | - not discover_if_add_03.changed 38 | - not discover_if_add_04.changed 39 | - discover_if_add_02.discovered is defined 40 | - discover_if_add_02.interfaces is defined 41 | - discover_if_add_02.interfaces[0].Caption is defined 42 | - discover_if_add_02.interfaces[0].InterfaceID is defined 43 | - discover_if_add_02.interfaces[0].Manageable is defined 44 | - discover_if_add_02.interfaces[0].ifAdminStatus is defined 45 | - discover_if_add_02.interfaces[0].ifIndex is defined 46 | - discover_if_add_02.interfaces[0].ifOperStatus is defined 47 | - discover_if_add_02.interfaces[0].ifSpeed is defined 48 | - discover_if_add_02.interfaces[0].ifSubType is defined 49 | - discover_if_add_02.interfaces[0].ifType is defined 50 | 51 | - name: Remove all interfaces (check) 52 | orion_node_interface: &solarwinds_interface_remove 53 | hostname: "{{ orion_test_solarwinds_server }}" 54 | username: "{{ orion_test_solarwinds_username }}" 55 | password: "{{ orion_test_solarwinds_password }}" 56 | state: absent 57 | name: "{{ orion_test_node_name }}" 58 | delegate_to: localhost 59 | check_mode: true 60 | register: discover_if_remove_01 61 | 62 | - name: Remove all interfaces 63 | orion_node_interface: 64 | <<: *solarwinds_interface_remove 65 | delegate_to: localhost 66 | register: discover_if_remove_02 67 | 68 | - name: Remove all interfaces (check) (idempotence) 69 | orion_node_interface: 70 | <<: *solarwinds_interface_remove 71 | delegate_to: localhost 72 | check_mode: true 73 | register: discover_if_remove_03 74 | 75 | - name: Remove all interfaces (idempotence) 76 | orion_node_interface: 77 | <<: *solarwinds_interface_remove 78 | delegate_to: localhost 79 | register: discover_if_remove_04 80 | 81 | - name: Remove discovered interfaces asserts 82 | ansible.builtin.assert: 83 | that: 84 | - discover_if_remove_01.changed 85 | - discover_if_remove_02.changed 86 | - not discover_if_remove_03.changed 87 | - not discover_if_remove_04.changed 88 | ... 89 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_interface/tasks/defined.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add single defined interface (check) 3 | orion_node_interface: &solarwinds_interface_add 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | interface: lo 10 | delegate_to: localhost 11 | check_mode: true 12 | register: defined_if_add_01 13 | 14 | - name: Add single defined interface 15 | orion_node_interface: 16 | <<: *solarwinds_interface_add 17 | delegate_to: localhost 18 | register: defined_if_add_02 19 | 20 | - name: Add single defined interface (check) (idempotence) 21 | orion_node_interface: 22 | <<: *solarwinds_interface_add 23 | delegate_to: localhost 24 | check_mode: true 25 | register: defined_if_add_03 26 | 27 | - name: Add single defined interface (idempotence) 28 | orion_node_interface: 29 | <<: *solarwinds_interface_add 30 | delegate_to: localhost 31 | register: defined_if_add_04 32 | 33 | - name: Add single defined interface asserts 34 | ansible.builtin.assert: 35 | that: 36 | - defined_if_add_01.changed 37 | - defined_if_add_02.changed 38 | - not defined_if_add_03.changed 39 | - not defined_if_add_04.changed 40 | - defined_if_add_02.discovered is defined 41 | - defined_if_add_02.interfaces is defined 42 | - defined_if_add_02.interfaces[0].Caption is defined 43 | - defined_if_add_02.interfaces[0].InterfaceID is defined 44 | - defined_if_add_02.interfaces[0].Manageable is defined 45 | - defined_if_add_02.interfaces[0].ifAdminStatus is defined 46 | - defined_if_add_02.interfaces[0].ifIndex is defined 47 | - defined_if_add_02.interfaces[0].ifOperStatus is defined 48 | - defined_if_add_02.interfaces[0].ifSpeed is defined 49 | - defined_if_add_02.interfaces[0].ifSubType is defined 50 | - defined_if_add_02.interfaces[0].ifType is defined 51 | 52 | - name: Remove single defined interface (check) 53 | orion_node_interface: &solarwinds_interface_remove 54 | hostname: "{{ orion_test_solarwinds_server }}" 55 | username: "{{ orion_test_solarwinds_username }}" 56 | password: "{{ orion_test_solarwinds_password }}" 57 | name: "{{ orion_test_node_name }}" 58 | interface: lo 59 | state: absent 60 | delegate_to: localhost 61 | check_mode: true 62 | register: defined_if_remove_01 63 | 64 | - name: Remove single defined interface 65 | orion_node_interface: 66 | <<: *solarwinds_interface_remove 67 | delegate_to: localhost 68 | register: defined_if_remove_02 69 | 70 | - name: Remove single defined interface (check) (idempotence) 71 | orion_node_interface: 72 | <<: *solarwinds_interface_remove 73 | delegate_to: localhost 74 | check_mode: true 75 | register: defined_if_remove_03 76 | 77 | - name: Remove single defined interface (idempotence) 78 | orion_node_interface: 79 | <<: *solarwinds_interface_remove 80 | delegate_to: localhost 81 | register: defined_if_remove_04 82 | 83 | - name: Remove single defined interface asserts 84 | ansible.builtin.assert: 85 | that: 86 | - defined_if_remove_01.changed 87 | - defined_if_remove_02.changed 88 | - not defined_if_remove_03.changed 89 | - not defined_if_remove_04.changed 90 | ... 91 | -------------------------------------------------------------------------------- /changelogs/.plugin-cache.yaml: -------------------------------------------------------------------------------- 1 | objects: 2 | role: {} 3 | plugins: 4 | become: {} 5 | cache: {} 6 | callback: {} 7 | cliconf: {} 8 | connection: {} 9 | httpapi: {} 10 | inventory: 11 | orion_nodes_inventory: 12 | description: Solarwinds Orion nodes inventory source 13 | name: orion_nodes_inventory 14 | version_added: null 15 | lookup: {} 16 | module: 17 | orion_credential_set: 18 | description: Manage Credential Sets for Solarwinds. 19 | name: orion_credential_set 20 | namespace: '' 21 | version_added: 3.1.0 22 | orion_custom_property: 23 | description: Manage custom properties on Node in Solarwinds Orion NPM 24 | name: orion_custom_property 25 | namespace: '' 26 | version_added: 1.0.0 27 | orion_node: 28 | description: Created/Removes/Edits Nodes in Solarwinds Orion NPM 29 | name: orion_node 30 | namespace: '' 31 | version_added: 1.0.0 32 | orion_node_application: 33 | description: Manages APM application templates assigned to nodes. 34 | name: orion_node_application 35 | namespace: '' 36 | version_added: 1.0.0 37 | orion_node_custom_poller: 38 | description: Creates/Removes custom pollers to a Node in Solarwinds Orion NPM 39 | name: orion_node_custom_poller 40 | namespace: '' 41 | version_added: 1.0.0 42 | orion_node_hardware_health: 43 | description: Manage hardware health polling on a node in Solarwinds Orion 44 | name: orion_node_hardware_health 45 | namespace: '' 46 | version_added: null 47 | orion_node_info: 48 | description: Gets info about a Node in Solarwinds Orion NPM 49 | name: orion_node_info 50 | namespace: '' 51 | version_added: 1.0.0 52 | orion_node_interface: 53 | description: Manage interfaces on Nodes in Solarwinds Orion NPM 54 | name: orion_node_interface 55 | namespace: '' 56 | version_added: 1.0.0 57 | orion_node_interface_info: 58 | description: Get info about interfaces on Nodes in Solarwinds Orion NPM 59 | name: orion_node_interface_info 60 | namespace: '' 61 | version_added: 1.0.0 62 | orion_node_ncm: 63 | description: Manages a node in Solarwinds NCM 64 | name: orion_node_ncm 65 | namespace: '' 66 | version_added: 1.3.0 67 | orion_node_poller: 68 | description: Manage Pollers on Nodes in Solarwinds Orion NPM 69 | name: orion_node_poller 70 | namespace: '' 71 | version_added: 1.0.0 72 | orion_node_poller_info: 73 | description: Gets info about pollers assigned to a Node in Solarwinds Orion 74 | NPM 75 | name: orion_node_poller_info 76 | namespace: '' 77 | version_added: 1.3.0 78 | orion_query: 79 | description: Queries the Solarwinds Orion database 80 | name: orion_query 81 | namespace: '' 82 | version_added: 1.3.0 83 | orion_update_node: 84 | description: Updates Node in Solarwinds Orion NPM 85 | name: orion_update_node 86 | namespace: '' 87 | version_added: 1.0.0 88 | orion_volume: 89 | description: Manage Volumes on Nodes in Solarwinds Orion NPM 90 | name: orion_volume 91 | namespace: '' 92 | version_added: 1.0.0 93 | orion_volume_info: 94 | description: Gets info about a Volume in Solarwinds Orion NPM 95 | name: orion_volume_info 96 | namespace: '' 97 | version_added: 1.0.0 98 | netconf: {} 99 | shell: {} 100 | strategy: {} 101 | vars: {} 102 | version: 3.1.0 103 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_node_info 13 | short_description: Gets info about a Node in Solarwinds Orion NPM 14 | description: 15 | - "Get info about a Node in Solarwinds Orion NPM." 16 | version_added: "1.0.0" 17 | author: "Josh M. Eisenbath (@jeisenbath)" 18 | extends_documentation_fragment: 19 | - jeisenbath.solarwinds.orion_auth_options 20 | - jeisenbath.solarwinds.orion_node_options 21 | requirements: 22 | - orionsdk 23 | - requests 24 | ''' 25 | 26 | EXAMPLES = r''' 27 | --- 28 | 29 | - name: Get info about a node by name 30 | jeisenbath.solarwinds.orion_node_info: 31 | hostname: "{{ solarwinds_server }}" 32 | username: "{{ solarwinds_user }}" 33 | password: "{{ solarwinds_pass }}" 34 | name: "{{ node_name }}" 35 | delegate_to: localhost 36 | 37 | ''' 38 | 39 | RETURN = r''' 40 | orion_node: 41 | description: Info about an orion node. 42 | returned: always 43 | type: dict 44 | sample: { 45 | "caption": "localhost", 46 | "ipaddress": "127.0.0.1", 47 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 48 | "netobjectid": "N:12345", 49 | "nodeid": "12345", 50 | "objectsubtype": "SNMP", 51 | "status": 1, 52 | "statusdescription": "Node status is Up.", 53 | "unmanaged": false, 54 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 55 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 56 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 57 | } 58 | ''' 59 | 60 | from datetime import datetime 61 | from ansible.module_utils.basic import AnsibleModule 62 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 63 | try: 64 | from dateutil import parser 65 | HAS_DATEUTIL = True 66 | except ImportError: 67 | HAS_DATEUTIL = False 68 | except Exception: 69 | raise Exception 70 | try: 71 | import requests 72 | HAS_REQUESTS = True 73 | requests.packages.urllib3.disable_warnings() 74 | except ImportError: 75 | HAS_REQUESTS = False 76 | except Exception: 77 | raise Exception 78 | 79 | 80 | def main(): 81 | argument_spec = orion_argument_spec() 82 | module = AnsibleModule( 83 | argument_spec, 84 | supports_check_mode=True, 85 | required_one_of=[('name', 'node_id', 'ip_address')], 86 | ) 87 | 88 | orion = OrionModule(module) 89 | changed = False 90 | 91 | node = orion.get_node() 92 | if not node: 93 | module.fail_json(skipped=True, msg='Node not found') 94 | 95 | # trigger poll if last poll is null or greater than 5 minutes ago 96 | object_subtype = node['objectsubtype'] 97 | if object_subtype == 'SNMP': 98 | last_poll = node['lastsystemuptimepollutc'] 99 | if not last_poll: 100 | orion.poll_now(node) 101 | node = orion.get_node() 102 | elif last_poll: 103 | time_since_poll = parser.parse(last_poll).replace(tzinfo=None) - datetime.utcnow() 104 | if time_since_poll.seconds > 300: 105 | orion.poll_now(node) 106 | node = orion.get_node() 107 | 108 | module.exit_json(changed=changed, orion_node=node) 109 | 110 | 111 | if __name__ == "__main__": 112 | main() 113 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_poller/tasks/poller.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add poller to node (check) 3 | orion_node_poller: &add_poller 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | poller: N.Topology_Layer3.SNMP.ipNetToMedia 9 | enabled: 'True' 10 | state: present 11 | delegate_to: localhost 12 | check_mode: true 13 | register: add_poller_01 14 | 15 | - name: Add poller to node 16 | orion_node_poller: 17 | <<: *add_poller 18 | delegate_to: localhost 19 | register: add_poller_02 20 | 21 | - name: Add poller to node (check) (idempotence) 22 | orion_node_poller: 23 | <<: *add_poller 24 | delegate_to: localhost 25 | check_mode: true 26 | register: add_poller_03 27 | 28 | - name: Add poller to node (idempotence) 29 | orion_node_poller: 30 | <<: *add_poller 31 | delegate_to: localhost 32 | register: add_poller_04 33 | 34 | - name: Disable poller on node (check) 35 | orion_node_poller: &disable_poller 36 | hostname: "{{ orion_test_solarwinds_server }}" 37 | username: "{{ orion_test_solarwinds_username }}" 38 | password: "{{ orion_test_solarwinds_password }}" 39 | name: "{{ orion_test_node_name }}" 40 | poller: N.Topology_Layer3.SNMP.ipNetToMedia 41 | enabled: 'False' 42 | state: present 43 | delegate_to: localhost 44 | check_mode: true 45 | register: disable_poller_01 46 | 47 | - name: Disable poller on node 48 | orion_node_poller: 49 | <<: *disable_poller 50 | delegate_to: localhost 51 | register: disable_poller_02 52 | 53 | - name: Disable poller on node (check) (idempotence) 54 | orion_node_poller: 55 | <<: *disable_poller 56 | delegate_to: localhost 57 | check_mode: true 58 | register: disable_poller_03 59 | 60 | - name: Disable poller on node (idempotence) 61 | orion_node_poller: 62 | <<: *disable_poller 63 | delegate_to: localhost 64 | register: disable_poller_04 65 | 66 | - name: Add poller asserts 67 | ansible.builtin.assert: 68 | that: 69 | - add_poller_01.changed 70 | - add_poller_02.changed 71 | - not add_poller_03.changed 72 | - not add_poller_04.changed 73 | - disable_poller_01.changed 74 | - disable_poller_02.changed 75 | - not disable_poller_03.changed 76 | - not disable_poller_04.changed 77 | - add_poller_02.orion_node.caption == orion_test_node_name 78 | - add_poller_02.orion_node.ipaddress == orion_test_node_ip_address 79 | 80 | - name: Remove poller from node (check) 81 | orion_node_poller: &remove_poller 82 | hostname: "{{ orion_test_solarwinds_server }}" 83 | username: "{{ orion_test_solarwinds_username }}" 84 | password: "{{ orion_test_solarwinds_password }}" 85 | name: "{{ orion_test_node_name }}" 86 | poller: N.Topology_Layer3.SNMP.ipNetToMedia 87 | state: absent 88 | delegate_to: localhost 89 | check_mode: true 90 | register: remove_poller_01 91 | 92 | - name: Remove poller from node 93 | orion_node_poller: 94 | <<: *remove_poller 95 | delegate_to: localhost 96 | register: remove_poller_02 97 | 98 | - name: Remove poller from node (check) (idempotence) 99 | orion_node_poller: 100 | <<: *remove_poller 101 | delegate_to: localhost 102 | check_mode: true 103 | register: remove_poller_03 104 | 105 | - name: Remove poller from node (idempotence) 106 | orion_node_poller: 107 | <<: *remove_poller 108 | delegate_to: localhost 109 | register: remove_poller_04 110 | 111 | - name: Remove poller from node asserts 112 | ansible.builtin.assert: 113 | that: 114 | - remove_poller_01.changed 115 | - remove_poller_02.changed 116 | - not remove_poller_03.changed 117 | - not remove_poller_04.changed 118 | ... 119 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_volume/tasks/linux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create test node 3 | orion_node: 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | ip_address: "{{ orion_test_node_ip_address }}" 10 | polling_method: SNMP 11 | snmp_version: 2 12 | ro_community_string: "{{ orion_test_node_ro_community_string }}" 13 | delegate_to: localhost 14 | 15 | - name: Create volume (check) 16 | orion_volume: &add_volume 17 | hostname: "{{ orion_test_solarwinds_server }}" 18 | username: "{{ orion_test_solarwinds_username }}" 19 | password: "{{ orion_test_solarwinds_password }}" 20 | name: "{{ orion_test_node_name }}" 21 | volume: 22 | name: '/' 23 | state: present 24 | delegate_to: localhost 25 | check_mode: true 26 | register: add_vol_01 27 | 28 | - name: Create volume 29 | orion_volume: 30 | <<: *add_volume 31 | delegate_to: localhost 32 | register: add_vol_02 33 | 34 | - name: Create volume (check) (idempotence) 35 | orion_volume: 36 | <<: *add_volume 37 | delegate_to: localhost 38 | check_mode: true 39 | register: add_vol_03 40 | 41 | - name: Create volume (idempotence) 42 | orion_volume: 43 | <<: *add_volume 44 | delegate_to: localhost 45 | register: add_vol_04 46 | 47 | - name: Create volume asserts 48 | ansible.builtin.assert: 49 | that: 50 | - add_vol_01.changed 51 | - add_vol_02.changed 52 | - not add_vol_03.changed 53 | - not add_vol_04.changed 54 | - add_vol_02.orion_node is defined 55 | - add_vol_02.orion_node.caption == orion_test_node_name 56 | - add_vol_02.orion_node.ipaddress == orion_test_node_ip_address 57 | - add_vol_02.orion_volume.caption == '/' 58 | - add_vol_02.orion_volume.displayname is defined 59 | - add_vol_02.orion_volume.volumeindex is defined 60 | - add_vol_02.orion_volume.status is defined 61 | - add_vol_02.orion_volume.type is defined 62 | - add_vol_02.orion_volume.pollinterval is defined 63 | - add_vol_02.orion_volume.statcollection is defined 64 | - add_vol_02.orion_volume.rediscoveryinterval is defined 65 | - add_vol_02.orion_volume.volumedescription is defined 66 | - add_vol_02.orion_volume.icon is defined 67 | - add_vol_02.orion_volume.uri is defined 68 | 69 | - name: Remove volume (check) 70 | orion_volume: &remove_volume 71 | hostname: "{{ orion_test_solarwinds_server }}" 72 | username: "{{ orion_test_solarwinds_username }}" 73 | password: "{{ orion_test_solarwinds_password }}" 74 | name: "{{ orion_test_node_name }}" 75 | volume: 76 | name: '/' 77 | state: absent 78 | delegate_to: localhost 79 | check_mode: true 80 | register: remove_vol_01 81 | 82 | - name: Remove volume 83 | orion_volume: 84 | <<: *remove_volume 85 | delegate_to: localhost 86 | register: remove_vol_02 87 | 88 | - name: Remove volume (check) (idempotence) 89 | orion_volume: 90 | <<: *remove_volume 91 | delegate_to: localhost 92 | check_mode: true 93 | register: remove_vol_03 94 | 95 | - name: Remove volume (idempotence) 96 | orion_volume: 97 | <<: *remove_volume 98 | delegate_to: localhost 99 | register: remove_vol_04 100 | 101 | - name: Remove volume asserts 102 | ansible.builtin.assert: 103 | that: 104 | - remove_vol_01.changed 105 | - remove_vol_02.changed 106 | - not remove_vol_03.changed 107 | - not remove_vol_04.changed 108 | 109 | - name: Remove test node 110 | orion_node: 111 | hostname: "{{ orion_test_solarwinds_server }}" 112 | username: "{{ orion_test_solarwinds_username }}" 113 | password: "{{ orion_test_solarwinds_password }}" 114 | state: absent 115 | name: "{{ orion_test_node_name }}" 116 | delegate_to: localhost 117 | ... 118 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_custom_property/tasks/node_custom_property.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set custom property on node (check) 3 | orion_custom_property: &add_property 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | name: "{{ orion_test_node_name }}" 8 | state: present 9 | property_name: "{{ orion_test_custom_property_name }}" 10 | property_value: "{{ orion_test_custom_property_value }}" 11 | delegate_to: localhost 12 | check_mode: true 13 | register: add_property_01 14 | 15 | - name: Set custom property on node 16 | orion_custom_property: 17 | <<: *add_property 18 | delegate_to: localhost 19 | register: add_property_02 20 | 21 | - name: Set custom property on node (check) (idempotence) 22 | orion_custom_property: 23 | <<: *add_property 24 | delegate_to: localhost 25 | check_mode: true 26 | register: add_property_03 27 | 28 | - name: Set custom property on node (idempotence) 29 | orion_custom_property: 30 | <<: *add_property 31 | delegate_to: localhost 32 | register: add_property_04 33 | 34 | - name: Update property value (check) 35 | orion_custom_property: &update_property 36 | hostname: "{{ orion_test_solarwinds_server }}" 37 | username: "{{ orion_test_solarwinds_username }}" 38 | password: "{{ orion_test_solarwinds_password }}" 39 | name: "{{ orion_test_node_name }}" 40 | state: present 41 | property_name: "{{ orion_test_custom_property_name }}" 42 | property_value: "{{ orion_test_custom_property_value_update }}" 43 | delegate_to: localhost 44 | check_mode: true 45 | register: update_property_01 46 | 47 | - name: Update property value 48 | orion_custom_property: 49 | <<: *update_property 50 | delegate_to: localhost 51 | register: update_property_02 52 | 53 | - name: Update property value (check) (idempotence) 54 | orion_custom_property: 55 | <<: *update_property 56 | delegate_to: localhost 57 | check_mode: true 58 | register: update_property_03 59 | 60 | - name: Update property value (idempotence) 61 | orion_custom_property: 62 | <<: *update_property 63 | delegate_to: localhost 64 | register: update_property_04 65 | 66 | - name: Create custom property asserts 67 | ansible.builtin.assert: 68 | that: 69 | - add_property_01.changed 70 | - add_property_02.changed 71 | - not add_property_03.changed 72 | - not add_property_04.changed 73 | - update_property_01.changed 74 | - update_property_02.changed 75 | - not update_property_03.changed 76 | - not update_property_04.changed 77 | - add_property_02.orion_node is defined 78 | 79 | - name: Unset custom property (check) 80 | orion_custom_property: &remove_property 81 | hostname: "{{ orion_test_solarwinds_server }}" 82 | username: "{{ orion_test_solarwinds_username }}" 83 | password: "{{ orion_test_solarwinds_password }}" 84 | name: "{{ orion_test_node_name }}" 85 | state: absent 86 | property_name: "{{ orion_test_custom_property_name }}" 87 | delegate_to: localhost 88 | check_mode: true 89 | register: remove_property_01 90 | 91 | - name: Unset custom property 92 | orion_custom_property: 93 | <<: *remove_property 94 | delegate_to: localhost 95 | register: remove_property_02 96 | 97 | - name: Unset custom property (check) (idempotence) 98 | orion_custom_property: 99 | <<: *remove_property 100 | delegate_to: localhost 101 | check_mode: true 102 | register: remove_property_03 103 | 104 | - name: Unset custom property (idempotence) 105 | orion_custom_property: 106 | <<: *remove_property 107 | delegate_to: localhost 108 | register: remove_property_04 109 | 110 | - name: Unset custom property asserts 111 | ansible.builtin.assert: 112 | that: 113 | - remove_property_01.changed 114 | - remove_property_02.changed 115 | - not remove_property_03.changed 116 | - not remove_property_04.changed 117 | ... 118 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_poller_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2024, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_node_poller_info 13 | short_description: Gets info about pollers assigned to a Node in Solarwinds Orion NPM 14 | description: 15 | - "Get the Poller Types assigned to a Node in Solarwinds Orion NPM, and if those pollers are enabled." 16 | version_added: "1.3.0" 17 | author: "Josh M. Eisenbath (@jeisenbath)" 18 | extends_documentation_fragment: 19 | - jeisenbath.solarwinds.orion_auth_options 20 | - jeisenbath.solarwinds.orion_node_options 21 | requirements: 22 | - orionsdk 23 | - requests 24 | ''' 25 | 26 | EXAMPLES = r''' 27 | --- 28 | 29 | - name: Get the pollers assigned to a node 30 | jeisenbath.solarwinds.orion_node_poller_info: 31 | hostname: "{{ solarwinds_server }}" 32 | username: "{{ solarwinds_user }}" 33 | password: "{{ solarwinds_pass }}" 34 | name: "{{ node_name }}" 35 | delegate_to: localhost 36 | register: poller_info 37 | 38 | - name: Loop through the pollers and show PollerType and Enabled 39 | ansible.builtin.debug: 40 | msg: "{{ item.PollerType }}: {{ item.Enabled }}" 41 | loop: "{{ poller_info.pollers }}" 42 | 43 | ''' 44 | 45 | RETURN = r''' 46 | orion_node: 47 | description: Info about an orion node. 48 | returned: always 49 | type: dict 50 | sample: { 51 | "caption": "localhost", 52 | "ipaddress": "127.0.0.1", 53 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 54 | "netobjectid": "N:12345", 55 | "nodeid": "12345", 56 | "objectsubtype": "SNMP", 57 | "status": 1, 58 | "statusdescription": "Node status is Up.", 59 | "unmanaged": false, 60 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 61 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 62 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 63 | } 64 | pollers: 65 | description: List of pollers assigned to the orion node. 66 | returned: always 67 | type: list 68 | elements: dict 69 | sample: [ 70 | { 71 | "Enabled": false, 72 | "PollerType": "N.ResponseTime.SNMP.Native" 73 | }, 74 | { 75 | "Enabled": false, 76 | "PollerType": "N.Status.SNMP.Native" 77 | }, 78 | { 79 | "Enabled": true, 80 | "PollerType": "N.LoadAverage.SNMP.Linux" 81 | }, 82 | { 83 | "Enabled": true, 84 | "PollerType": "N.Memory.SNMP.NetSnmpReal" 85 | } 86 | ] 87 | ''' 88 | 89 | from ansible.module_utils.basic import AnsibleModule 90 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 91 | try: 92 | import requests 93 | HAS_REQUESTS = True 94 | requests.packages.urllib3.disable_warnings() 95 | except ImportError: 96 | HAS_REQUESTS = False 97 | except Exception: 98 | raise Exception 99 | 100 | 101 | def main(): 102 | argument_spec = orion_argument_spec() 103 | module = AnsibleModule( 104 | argument_spec, 105 | supports_check_mode=True, 106 | required_one_of=[('name', 'node_id', 'ip_address')], 107 | ) 108 | 109 | orion = OrionModule(module) 110 | 111 | node = orion.get_node() 112 | if not node: 113 | module.fail_json(skipped=True, msg='Node not found') 114 | 115 | changed = False 116 | if node: 117 | query = """SELECT p.PollerType, p.Enabled 118 | from Orion.Nodes n left join Orion.Pollers as p on p.NetObjectID = n.NodeId 119 | where n.NodeId = '{0}'""".format(node['nodeid']) 120 | pollers = orion.swis_query(query) 121 | 122 | module.exit_json(changed=changed, orion_node=node, pollers=pollers) 123 | 124 | 125 | if __name__ == "__main__": 126 | main() 127 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidelines for Contributing to Collection 2 | 3 | ## Building your local development environment 4 | 5 | It is highly recommended to set up a python virtual environment for development. 6 | Use the [Ansible Release and Maintenance](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html) to determine the oldest supported python version matching the current minimum ansible version from meta/runtime.yml. 7 | 1. Install the ansible-core version matching the minimum ansible version from meta/runtime.yml. 8 | 2. Install other python package requirements from requirements.txt. 9 | 3. Create a fork of this repository, and clone it to your ansible collection path. 10 | - Checkout the devel- branch of the current major version. 11 | 12 | ``` 13 | $ mkdir -p ~/.ansible/collections/ansible_collections/solarwinds 14 | $ git clone *your_repo* ~/.ansible/collections/ansible_collections/solarwinds/orion 15 | $ cd ~/.ansible/collections/ansible_collections/solarwinds/orion 16 | $ git checkout devel-2.x 17 | ``` 18 | 19 | ### Testing your code 20 | 21 | 1. Change directory to the collection's root. 22 | 2. Run ansible-test sanity and correct any errors. 23 | 3. Prepare and run integration tests 24 | - Copying the test/integration/integration_config_example.yml into integration_config.yml 25 | - Update the values in integration_config.yml to match your testing environment 26 | - If your change includes any files from module_utils, run a full integration test with ansible-test integration. 27 | - If you are motifying a single module, or creating a new one, run ansible-test integration *target* to test only that specific resource. 28 | 29 | ## Pull requests 30 | 31 | ### Pre-requisites for new pull requests 32 | 33 | All pull requests must: 34 | - Be submitted to the devel branch corresponding to the current major version. 35 | - Pass an ansible-sanity test. 36 | - Not include changes to the changelog itself, this will be done by maintainer when merging upstream. 37 | - Not include changes to galaxy.yml version, nor a build of the collection. This will be done by the maintainer when merging upstream. 38 | 39 | #### Major changes 40 | 41 | Major changes include new modules and plugins, or new options for existing ones. 42 | Major change PRs must: 43 | - Include a changelog fragment with a major_changes section. 44 | - Include an integration test for new modules. 45 | - Include updates to integration test if including new options. 46 | - Only include one new feature per PR. 47 | 48 | Major change PRs should: 49 | - Have either a corresponding open issue or discussion. This is to help ensure some degree of discussion available to all. 50 | 51 | New modules should: 52 | - Import and use OrionModule from module_utils to manage API connection. 53 | - Import and use orion_argument_spec from module_utils, and use corresponding document fragment. 54 | - Include an integration test which tests for: 55 | - Check mode. 56 | - Present and absent for state based modules. 57 | - Updating options for state based modules when state=present. 58 | - All expected return values for info based modules. 59 | - Idempotence. 60 | 61 | #### Minor changes 62 | 63 | Minor changes include new roles or example playbooks, or documentation updates. 64 | Minor change PRs must: 65 | - Include a changelog fragment with a minor_changes section. 66 | 67 | Minor change PRs should: 68 | - Only create or update one feature per PR. 69 | 70 | #### Bugfixes 71 | 72 | Bugfix PRs must: 73 | - Include a changelog fragment with a bugfixes section. 74 | - Not include any new features. 75 | 76 | Bugfix PRs should: 77 | - Have a corresponding open issue. This is to help confirm the bug is reproducable and not simply an issue with environment. 78 | 79 | ## Reporting Issues 80 | 81 | General bug reporting guidelines 82 | - Check existing Issues for similar sounding bugs. 83 | - Review ansible-doc documentation and example playbooks when submitting questions. Incomplete, unclear, or inaccurate documentation is an issue! 84 | - Explain your expected behavior versus the actual behavior. 85 | - Include what version of ansible and of the collection you are using. If not using the current version, update and retest. 86 | - Submit a test scenario with the simplest parameters possible to recreate. 87 | -------------------------------------------------------------------------------- /plugins/modules/orion_update_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_update_node 13 | short_description: Updates Node in Solarwinds Orion NPM 14 | description: 15 | - Updates properties of a node, such as caption or IP Address. 16 | - Updating a node's caption, or polling method, can be used to retain historical data about the node. 17 | - For cases where that data isn't needed, it is recommended to use M(jeisenbath.solarwinds.orion_node) to re-add it. 18 | - Never use this to update a node_id. 19 | version_added: "1.0.0" 20 | author: "Josh M. Eisenbath (@jeisenbath)" 21 | options: 22 | properties: 23 | description: 24 | - Properties of the node that will be updated. 25 | required: False 26 | default: {} 27 | type: dict 28 | extends_documentation_fragment: 29 | - jeisenbath.solarwinds.orion_auth_options 30 | - jeisenbath.solarwinds.orion_node_options 31 | requirements: 32 | - orionsdk 33 | - requests 34 | ''' 35 | 36 | EXAMPLES = r''' 37 | --- 38 | 39 | - name: Update node caption 40 | jeisenbath.solarwinds.orion_update_node: 41 | hostname: "{{ solarwinds_server }}" 42 | username: "{{ solarwinds_user }}" 43 | password: "{{ solarwinds_pass }}" 44 | ip_address: "{{ node_ip_address }}" 45 | properties: 46 | Caption: "{{ new_node_caption }}" 47 | delegate_to: localhost 48 | 49 | - name: Update node to SNMPv2 polling 50 | jeisenbath.solarwinds.orion_update_node: 51 | hostname: "{{ solarwinds_server }}" 52 | username: "{{ solarwinds_user }}" 53 | password: "{{ solarwinds_pass }}" 54 | name: "{{ node_name }}" 55 | properties: 56 | ObjectSubType: SNMP 57 | SNMPVersion: 2 58 | Community: "{{ ro_community_string }}" 59 | delegate_to: localhost 60 | 61 | ''' 62 | 63 | RETURN = r''' 64 | orion_node: 65 | description: Info about an orion node. 66 | returned: always 67 | type: dict 68 | sample: { 69 | "caption": "localhost", 70 | "ipaddress": "127.0.0.1", 71 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 72 | "netobjectid": "N:12345", 73 | "nodeid": "12345", 74 | "objectsubtype": "SNMP", 75 | "status": 1, 76 | "statusdescription": "Node status is Up.", 77 | "unmanaged": false, 78 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 79 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 80 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 81 | } 82 | ''' 83 | 84 | from ansible.module_utils.basic import AnsibleModule 85 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 86 | try: 87 | import requests 88 | HAS_REQUESTS = True 89 | requests.packages.urllib3.disable_warnings() 90 | except ImportError: 91 | HAS_REQUESTS = False 92 | except Exception: 93 | raise Exception 94 | 95 | 96 | def main(): 97 | argument_spec = orion_argument_spec() 98 | argument_spec.update( 99 | properties=dict(required=False, default={}, type='dict') 100 | ) 101 | module = AnsibleModule( 102 | argument_spec, 103 | supports_check_mode=True, 104 | required_one_of=[('name', 'node_id', 'ip_address')], 105 | ) 106 | 107 | orion = OrionModule(module) 108 | 109 | node = orion.get_node() 110 | if not node: 111 | module.fail_json(skipped=True, msg='Node not found') 112 | 113 | changed = False 114 | 115 | try: 116 | if module.check_mode: 117 | changed = True 118 | else: 119 | orion.swis.update(node['uri'], **module.params['properties']) 120 | changed = True 121 | except Exception as OrionException: 122 | module.fail_json(msg='Failed to update {0}'.format(str(OrionException))) 123 | 124 | module.exit_json(changed=changed, orion_node=node) 125 | 126 | 127 | if __name__ == "__main__": 128 | main() 129 | -------------------------------------------------------------------------------- /plugins/modules/orion_volume_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_volume_info 13 | short_description: Gets info about a Volume in Solarwinds Orion NPM 14 | description: 15 | - Get info about a Volume in Solarwinds Orion NPM. 16 | version_added: "1.0.0" 17 | author: "Josh M. Eisenbath (@jeisenbath)" 18 | options: 19 | volume: 20 | description: 21 | - Attributes of the volume being managed. 22 | required: True 23 | type: dict 24 | suboptions: 25 | name: 26 | description: 27 | - Name of the volume. 28 | aliases: [ 'caption' ] 29 | type: str 30 | extends_documentation_fragment: 31 | - jeisenbath.solarwinds.orion_auth_options 32 | - jeisenbath.solarwinds.orion_node_options 33 | requirements: 34 | - orionsdk 35 | - requests 36 | ''' 37 | 38 | EXAMPLES = r''' 39 | --- 40 | 41 | - name: Get info about a volume 42 | jeisenbath.solarwinds.orion_volume_info: 43 | hostname: "{{ solarwinds_server }}" 44 | username: "{{ solarwinds_user }}" 45 | password: "{{ solarwinds_pass }}" 46 | name: "{{ node_name }}" 47 | volume: 48 | name: "{{ volume_name }}" 49 | delegate_to: localhost 50 | 51 | ''' 52 | 53 | RETURN = r''' 54 | orion_node: 55 | description: Info about an orion node. 56 | returned: always 57 | type: dict 58 | sample: { 59 | "caption": "localhost", 60 | "ipaddress": "127.0.0.1", 61 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 62 | "netobjectid": "N:12345", 63 | "nodeid": "12345", 64 | "objectsubtype": "SNMP", 65 | "status": 1, 66 | "statusdescription": "Node status is Up.", 67 | "unmanaged": false, 68 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 69 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 70 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 71 | } 72 | orion_volume: 73 | description: Info about an orion volume. 74 | returned: always 75 | type: dict 76 | sample: { 77 | "displayname": "/", 78 | "volumeindex": 0, 79 | "status": "0", 80 | "type": "Fixed Disk", 81 | "caption": "/", 82 | "pollinterval": "420", 83 | "statcollection": "15", 84 | "rediscoveryinterval": "60", 85 | "volumedescription": "/", 86 | "icon": "FixedDisk.gif", 87 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345/Volumes/VolumeID=67890" 88 | } 89 | ''' 90 | 91 | from ansible.module_utils.basic import AnsibleModule 92 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 93 | try: 94 | import requests 95 | HAS_REQUESTS = True 96 | requests.packages.urllib3.disable_warnings() 97 | except ImportError: 98 | HAS_REQUESTS = False 99 | except Exception: 100 | raise Exception 101 | 102 | 103 | def main(): 104 | argument_spec = orion_argument_spec() 105 | argument_spec.update( 106 | volume=dict( 107 | required=True, type='dict', 108 | options=dict( 109 | name=dict(type='str', aliases=['caption']), # TODO required by? 110 | ) 111 | ), 112 | ) 113 | module = AnsibleModule( 114 | argument_spec, 115 | supports_check_mode=True, 116 | required_one_of=[('name', 'node_id', 'ip_address')], 117 | ) 118 | 119 | orion = OrionModule(module) 120 | 121 | node = orion.get_node() 122 | if not node: 123 | module.exit_json(skipped=True, msg='Node not found') 124 | 125 | volume = orion.get_volume(node, module.params['volume']) 126 | if not volume: 127 | module.exit_json(skipped=True, msg="Volume not found") 128 | else: 129 | module.exit_json(changed=False, orion_node=node, orion_volume=volume) 130 | 131 | 132 | if __name__ == "__main__": 133 | main() 134 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_interface_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | __metaclass__ = type 8 | 9 | DOCUMENTATION = r''' 10 | --- 11 | module: orion_node_interface_info 12 | short_description: Get info about interfaces on Nodes in Solarwinds Orion NPM 13 | description: 14 | - Retrieve information about interfaces on a Node in Orion NPM that are currently being monitored. 15 | - Provides details such as interface name, status, and other relevant attributes. 16 | version_added: "1.0.0" 17 | author: "Andrew Bailey (@Andyjb8)" 18 | extends_documentation_fragment: 19 | - jeisenbath.solarwinds.orion_auth_options 20 | - jeisenbath.solarwinds.orion_node_options 21 | requirements: 22 | - orionsdk 23 | - requests 24 | ''' 25 | 26 | EXAMPLES = r''' 27 | --- 28 | 29 | - name: Get info about all interfaces on a node 30 | jeisenbath.solarwinds.orion_node_interface_info: 31 | hostname: "{{ solarwinds_server }}" 32 | username: "{{ solarwinds_user }}" 33 | password: "{{ solarwinds_pass }}" 34 | name: "{{ inventory_hostname }}" 35 | delegate_to: localhost 36 | 37 | ''' 38 | 39 | RETURN = r''' 40 | orion_node: 41 | description: Info about an Orion node. 42 | returned: always 43 | type: dict 44 | sample: { 45 | "caption": "localhost", 46 | "ipaddress": "127.0.0.1", 47 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 48 | "netobjectid": "N:12345", 49 | "nodeid": "12345", 50 | "objectsubtype": "SNMP", 51 | "status": 1, 52 | "statusdescription": "Node status is Up.", 53 | "unmanaged": false, 54 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 55 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 56 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 57 | } 58 | interfaces: 59 | description: List of interfaces currently monitored on the node. 60 | returned: always 61 | type: list 62 | elements: dict 63 | sample: [ 64 | { 65 | "Name": "eth0", 66 | "InterfaceID": 268420, 67 | "AdminStatus": 1, 68 | "OperStatus": 1, 69 | "Speed": 1000000000.0, 70 | "Type": 6, 71 | "Status": 1, 72 | "StatusDescription": "Up" 73 | }, 74 | { 75 | "Name": "eth1", 76 | "InterfaceID": 268421, 77 | "AdminStatus": 2, 78 | "OperStatus": 2, 79 | "Speed": 1000000000.0, 80 | "Type": 6, 81 | "Status": 0, 82 | "StatusDescription": "Unknown" 83 | } 84 | ] 85 | ''' 86 | 87 | from ansible.module_utils.basic import AnsibleModule 88 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 89 | try: 90 | import requests 91 | HAS_REQUESTS = True 92 | requests.packages.urllib3.disable_warnings() 93 | except ImportError: 94 | HAS_REQUESTS = False 95 | except Exception: 96 | raise Exception 97 | 98 | 99 | def main(): 100 | argument_spec = orion_argument_spec() 101 | module = AnsibleModule( 102 | argument_spec, 103 | supports_check_mode=True, 104 | required_one_of=[('name', 'node_id', 'ip_address')], 105 | ) 106 | 107 | orion = OrionModule(module) 108 | 109 | node = orion.get_node() 110 | if not node: 111 | module.fail_json(skipped=True, msg='Node not found') 112 | 113 | changed = False 114 | interfaces = [] 115 | try: 116 | interface_query = orion.swis.query( 117 | "SELECT Caption, Name, InterfaceID, AdminStatus, OperStatus, Speed, Type, Status, StatusDescription " 118 | "FROM Orion.NPM.Interfaces WHERE NodeID = '{0}'".format(node['nodeid']) 119 | ) 120 | interfaces = interface_query['results'] 121 | except Exception as e: 122 | module.fail_json(msg="Failed to retrieve interfaces: {0}".format(str(e))) 123 | 124 | module.exit_json(changed=changed, orion_node=node, interfaces=interfaces) 125 | 126 | 127 | if __name__ == "__main__": 128 | main() 129 | -------------------------------------------------------------------------------- /plugins/modules/orion_query.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2024, Josh M. Eisenbath 5 | # Copyright: (c) 2024, Andrew Bailey 6 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 | 8 | from __future__ import absolute_import, division, print_function 9 | __metaclass__ = type 10 | 11 | DOCUMENTATION = r''' 12 | --- 13 | module: orion_query 14 | short_description: Queries the Solarwinds Orion database 15 | description: 16 | - "Run a query to get info from the Solarwinds Orion database." 17 | - "Will return the query results as a json object, which can be registered and used with other modules." 18 | - "Optionally can also save results into a csv." 19 | version_added: "1.3.0" 20 | author: 21 | - "Josh M. Eisenbath (@jeisenbath)" 22 | - "Andrew Bailey (@andyjb8)" 23 | options: 24 | query: 25 | description: 26 | - SWQL query 27 | required: true 28 | type: str 29 | csv_path: 30 | description: 31 | - The path to save the output CSV file. 32 | required: false 33 | type: str 34 | extends_documentation_fragment: 35 | - jeisenbath.solarwinds.orion_auth_options 36 | requirements: 37 | - orionsdk 38 | - requests 39 | - csv 40 | ''' 41 | 42 | EXAMPLES = r''' 43 | --- 44 | 45 | - name: Run a query for the top 10 nodes in Orion.Nodes 46 | jeisenbath.solarwinds.orion_query: 47 | hostname: "{{ solarwinds_server }}" 48 | username: "{{ solarwinds_user }}" 49 | password: "{{ solarwinds_pass }}" 50 | query: | 51 | SELECT 52 | TOP 10 N.NodeID, N.Caption, N.IP_Address, N.MachineType, N.Vendor, N.StatusIcon, N.IOSVersion, N.IOSImage, CP.City, CP.Department 53 | FROM 54 | Orion.Nodes AS N 55 | LEFT JOIN Orion.NodesCustomProperties AS CP ON N.NodeID = CP.NodeID 56 | csv_path: ./results.csv 57 | delegate_to: localhost 58 | 59 | ''' 60 | 61 | RETURN = r''' 62 | results: 63 | description: Results of SWQL query. 64 | returned: always 65 | type: list 66 | sample: [ 67 | { 68 | "Caption": "localhost", 69 | "City": "Villa Straylight", 70 | "Department": null, 71 | "IOSImage": "", 72 | "IOSVersion": "", 73 | "IP_Address": "127.0.0.1", 74 | "MachineType": "net-snmp - Linux", 75 | "NodeID": 12345, 76 | "StatusIcon": "Up.gif", 77 | "Vendor": "net-snmp" 78 | } 79 | ] 80 | 81 | ''' 82 | 83 | import csv 84 | from ansible.module_utils.basic import AnsibleModule, env_fallback 85 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule 86 | try: 87 | import requests 88 | HAS_REQUESTS = True 89 | requests.packages.urllib3.disable_warnings() 90 | except ImportError: 91 | HAS_REQUESTS = False 92 | except Exception: 93 | raise Exception 94 | 95 | 96 | def write_to_csv(nodes, csv_file_path): 97 | headers = nodes[0].keys() if nodes else [] 98 | with open(csv_file_path, 'w', newline='') as file: 99 | writer = csv.DictWriter(file, fieldnames=headers) 100 | writer.writeheader() 101 | for node in nodes: 102 | writer.writerow(node) 103 | 104 | 105 | def main(): 106 | argument_spec = dict( 107 | hostname=dict(fallback=(env_fallback, ['SOLARWINDS_SERVER']), required=False), 108 | username=dict(fallback=(env_fallback, ['SOLARWINDS_USERNAME']), required=False, no_log=True), 109 | password=dict(fallback=(env_fallback, ['SOLARWINDS_PASSWORD']), required=False, no_log=True), 110 | port=dict(required=False, type='str', default='17774'), 111 | verify=dict(required=False, type='bool', default=False), 112 | query=dict(required=True, type='str'), 113 | csv_path=dict(required=False, type='str'), 114 | ) 115 | module = AnsibleModule( 116 | argument_spec, 117 | supports_check_mode=True, 118 | ) 119 | 120 | orion = OrionModule(module) 121 | 122 | results = orion.swis_query(module.params['query']) 123 | if module.params['csv_path']: 124 | write_to_csv(results, module.params['csv_path']) 125 | 126 | module.exit_json(changed=False, results=results) 127 | 128 | 129 | if __name__ == "__main__": 130 | main() 131 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_custom_poller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_node_custom_poller 13 | short_description: Creates/Removes custom pollers to a Node in Solarwinds Orion NPM 14 | description: 15 | - This module will add or remove a custom poller to a Node in Solarwinds Orion NPM. 16 | - The custom poller must already exist to be added to the node. 17 | - Note this module is different from M(jeisenbath.solarwinds.orion_node_poller), which manages standard Orion pollers. 18 | version_added: "1.0.0" 19 | author: "Josh M. Eisenbath (@jeisenbath)" 20 | options: 21 | state: 22 | description: 23 | - The desired state of the custom poller. 24 | required: True 25 | type: str 26 | choices: 27 | - present 28 | - absent 29 | custom_poller: 30 | description: 31 | - The name of the custom poller. 32 | required: True 33 | type: str 34 | extends_documentation_fragment: 35 | - jeisenbath.solarwinds.orion_auth_options 36 | - jeisenbath.solarwinds.orion_node_options 37 | requirements: 38 | - orionsdk 39 | - requests 40 | ''' 41 | 42 | EXAMPLES = r''' 43 | --- 44 | 45 | - name: Add custom pollers to node 46 | jeisenbath.solarwinds.orion_node_custom_poller: 47 | hostname: "{{ solarwinds_server }}" 48 | username: "{{ solarwinds_user }}" 49 | password: "{{ solarwinds_pass }}" 50 | name: "{{ node_name }}" 51 | state: present 52 | custom_poller: "{{ custom_poller_name }}" 53 | delegate_to: localhost 54 | 55 | ''' 56 | 57 | RETURN = r''' 58 | orion_node: 59 | description: Info about an orion node. 60 | returned: always 61 | type: dict 62 | sample: { 63 | "caption": "localhost", 64 | "ipaddress": "127.0.0.1", 65 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 66 | "netobjectid": "N:12345", 67 | "nodeid": "12345", 68 | "objectsubtype": "SNMP", 69 | "status": 1, 70 | "statusdescription": "Node status is Up.", 71 | "unmanaged": false, 72 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 73 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 74 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 75 | } 76 | ''' 77 | 78 | from ansible.module_utils.basic import AnsibleModule 79 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 80 | try: 81 | import requests 82 | HAS_REQUESTS = True 83 | requests.packages.urllib3.disable_warnings() 84 | except ImportError: 85 | HAS_REQUESTS = False 86 | except Exception: 87 | raise Exception 88 | 89 | 90 | def main(): 91 | argument_spec = orion_argument_spec() 92 | argument_spec.update( 93 | state=dict(required=True, choices=['present', 'absent']), 94 | custom_poller=dict(required=True, type='str') 95 | ) 96 | module = AnsibleModule( 97 | argument_spec, 98 | supports_check_mode=True, 99 | required_one_of=[('name', 'node_id', 'ip_address')], 100 | ) 101 | 102 | orion = OrionModule(module) 103 | 104 | node = orion.get_node() 105 | if not node: 106 | module.fail_json(skipped=True, msg='Node not found') 107 | 108 | changed = False 109 | if module.params['state'] == 'present': 110 | try: 111 | if not orion.get_custom_poller_id(module.params['custom_poller']): 112 | module.fail_json(msg='Custom poller {0} not found.'.format(module.params['custom_poller'])) 113 | except Exception as OrionException: 114 | module.fail_json(msg='Failed to query for custom poller: {0}'.format(str(OrionException))) 115 | 116 | try: 117 | if not orion.get_custom_poller_uri(node, module.params['custom_poller']): 118 | if not module.check_mode: 119 | orion.add_custom_poller(node, module.params['custom_poller']) 120 | changed = True 121 | except Exception as OrionException: 122 | module.fail_json(msg='Failed to create custom poller: {0}'.format(str(OrionException))) 123 | elif module.params['state'] == 'absent': 124 | try: 125 | if orion.get_custom_poller_uri(node, module.params['custom_poller']): 126 | if not module.check_mode: 127 | orion.remove_custom_poller(node, module.params['custom_poller']) 128 | changed = True 129 | except Exception as OrionException: 130 | module.fail_json(msg='Failed to remove custom poller: {0}'.format(str(OrionException))) 131 | 132 | module.exit_json(changed=changed, orion_node=node) 133 | 134 | 135 | if __name__ == "__main__": 136 | main() 137 | -------------------------------------------------------------------------------- /plugins/modules/orion_custom_property.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_custom_property 13 | short_description: Manage custom properties on Node in Solarwinds Orion NPM 14 | description: 15 | - Adds or removes a custom property on Node in Solarwinds Orion NPM. 16 | - This module requires the custom property to already exist in Solarwinds settings. 17 | version_added: "1.0.0" 18 | author: "Josh M. Eisenbath (@jeisenbath)" 19 | options: 20 | state: 21 | description: 22 | - Desired state of the custom property on the node. 23 | - When I(state=absent), sets value of property for the node to None. 24 | required: True 25 | type: str 26 | choices: 27 | - present 28 | - absent 29 | property_name: 30 | description: 31 | - Name of the custom property for the node. 32 | required: True 33 | type: str 34 | property_value: 35 | description: 36 | - Value to set for the custom property on the node. 37 | - Required if I(state=present). 38 | required: False 39 | type: str 40 | extends_documentation_fragment: 41 | - jeisenbath.solarwinds.orion_auth_options 42 | - jeisenbath.solarwinds.orion_node_options 43 | requirements: 44 | - orionsdk 45 | - requests 46 | ''' 47 | 48 | EXAMPLES = r''' 49 | --- 50 | 51 | - name: Set the custom property Timezone to EST for node 52 | jeisenbath.solarwinds.orion_custom_property: 53 | hostname: "{{ solarwinds_server }}" 54 | username: "{{ solarwinds_user }}" 55 | password: "{{ solarwinds_pass }}" 56 | name: "{{ node_name }}" 57 | state: present 58 | property_name: Timezone 59 | property_value: EST 60 | delegate_to: localhost 61 | 62 | ''' 63 | 64 | RETURN = r''' 65 | orion_node: 66 | description: Info about an orion node. 67 | returned: always 68 | type: dict 69 | sample: { 70 | "caption": "localhost", 71 | "ipaddress": "127.0.0.1", 72 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 73 | "netobjectid": "N:12345", 74 | "nodeid": "12345", 75 | "objectsubtype": "SNMP", 76 | "status": 1, 77 | "statusdescription": "Node status is Up.", 78 | "unmanaged": false, 79 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 80 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 81 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 82 | } 83 | ''' 84 | 85 | from ansible.module_utils.basic import AnsibleModule 86 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 87 | try: 88 | import requests 89 | HAS_REQUESTS = True 90 | requests.packages.urllib3.disable_warnings() 91 | except ImportError: 92 | HAS_REQUESTS = False 93 | except Exception: 94 | raise Exception 95 | 96 | 97 | def main(): 98 | argument_spec = orion_argument_spec() 99 | argument_spec.update( 100 | state=dict(required=True, choices=['present', 'absent']), 101 | property_name=dict(required=True, type='str'), 102 | property_value=dict(required=False, type='str') 103 | ) 104 | module = AnsibleModule( 105 | argument_spec, 106 | supports_check_mode=True, 107 | required_one_of=[('name', 'node_id', 'ip_address')], 108 | required_if=[ 109 | ('state', 'present', ['property_value']) 110 | ] 111 | ) 112 | 113 | orion = OrionModule(module) 114 | 115 | node = orion.get_node() 116 | if not node: 117 | module.fail_json(skipped=True, msg='Node not found') 118 | 119 | changed = False 120 | if module.params['state'] == 'present': 121 | try: 122 | prop_name, prop_value = orion.get_node_custom_property_value(node, module.params['property_name']) 123 | if prop_value != module.params['property_value']: 124 | if not module.check_mode: 125 | orion.add_custom_property(node, module.params['property_name'], module.params['property_value']) 126 | changed = True 127 | except Exception as OrionException: 128 | module.fail_json(msg='Failed to add custom properties: {0}'.format(OrionException)) 129 | elif module.params['state'] == 'absent': 130 | try: 131 | prop_name, prop_value = orion.get_node_custom_property_value(node, module.params['property_name']) 132 | if prop_value: 133 | if not module.check_mode: 134 | orion.add_custom_property(node, module.params['property_name'], None) 135 | changed = True 136 | except Exception as OrionException: 137 | module.fail_json(msg='Failed to remove custom property from node: {0}'.format(OrionException)) 138 | 139 | module.exit_json(changed=changed, orion_node=node) 140 | 141 | 142 | if __name__ == "__main__": 143 | main() 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solarwinds Collection for Ansible 2 | 3 | 4 | 5 | Collection for managing Nodes in Solarwinds Orion. 6 | 7 | ## Included Content 8 | 9 | 10 | ### Modules 11 | | Name | Description | 12 | |--------------------------------------------|-------------------------------------------------------------| 13 | | jeisenbath.solarwinds.orion_custom_property | Manage custom properties on Node. | 14 | | jeisenbath.solarwinds.orion_node | Creates, Removes, Manage, or Mute Node. | 15 | | jeisenbath.solarwinds.orion_node_application | Manages APM application templates assigned to Nodes. | 16 | | jeisenbath.solarwinds.orion_node_custom_poller | Creates/Removes custom pollers on a Node. | 17 | | jeisenbath.solarwinds.orion_node_hardware_health | Creates/Removes Hardware Health poller on a Node. | 18 | | jeisenbath.solarwinds.orion_node_info | Gets info about a Node. | 19 | | jeisenbath.solarwinds.orion_node_interface | Manage interfaces on Nodes. | 20 | | jeisenbath.solarwinds.orion_node_interface_info | Query info about interfaces on a Node. | 21 | | jeisenbath.solarwinds.orion_node_poller | Manage Pollers on Nodes. | 22 | | jeisenbath.solarwinds.orion_node_poller_info | Query info about pollers assigned to a Node. | 23 | | jeisenbath.solarwinds.orion_update_node | Updates Node properties. | 24 | | jeisenbath.solarwinds.orion_volume | Manage Volumes on Nodes. | 25 | | jeisenbath.solarwinds.orion_volume_info | Gets info about a Volume assigned to a Node. | 26 | | jeisenbath.solarwinds.orion_node_ncm | Adds or Removes an existing node to NCM. | 27 | | jeisenbath.solarwinds.orion_query | Run a SWQL query against the orion database. | 28 | | jeisenbath.solarwinds.orion_credential_set | Create and Assign SNMPv3, WMI Credential Sets | 29 | 30 | ### Plugins 31 | | Name | Description | 32 | |----------------------------------------|-----------------------------------------------| 33 | | jeisenbath.solarwinds.orion_nodes_inventory | Dynamic Inventory Plugin for Solarwinds Orion | 34 | 35 | ### Roles 36 | | Name | Description | 37 | |-----------------------------|--------------------------------| 38 | | jeisenbath.solarwinds.orion_node | Add a node to solarwinds orion | 39 | 40 | ## Tested with Ansible 41 | 42 | 43 | 2.9 44 | 2.12.2 45 | 2.13.3 46 | 2.14.2 47 | 48 | ## External requirements 49 | 50 | ```bash 51 | pip install -r requirements.txt 52 | ``` 53 | 54 | ### Installing the Collection from Ansible Galaxy 55 | 56 | Before using this collection, you need to install it with the Ansible Galaxy command-line tool: 57 | ```bash 58 | ansible-galaxy collection install jeisenbath.solarwinds 59 | ``` 60 | 61 | You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format: 62 | ```yaml 63 | --- 64 | collections: 65 | - name: jeisenbath.solarwinds 66 | ``` 67 | 68 | Note that if you install the collection from Ansible Galaxy, it will not be upgraded automatically when you upgrade the `ansible` package. To upgrade the collection to the latest available version, run the following command: 69 | ```bash 70 | ansible-galaxy collection install jeisenbath.solarwinds --upgrade 71 | ``` 72 | 73 | You can also install a specific version of the collection, for example, if you need to downgrade when something is broken in the latest version (please report an issue in this repository). Use the following syntax to install version `1.0.0`: 74 | 75 | ```bash 76 | ansible-galaxy collection install jeisenbath.solarwinds,v3.0.0 77 | ``` 78 | 79 | If you are using a version prior to 3.0.0 when the namespace changed from solarwinds.orion to jeisenbath.solarwinds, use this to force update from the stable-2.x branch. 80 | This branch will be maintained with bugfixes until 2026-01-01 81 | 82 | ```bash 83 | ansible-galaxy collection install git+https://github.com/jeisenbath/ansible-collection-solarwinds-orion.git,stable-2.x --force 84 | ``` 85 | 86 | See [Ansible Using collections](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html) for more details. 87 | 88 | ## Licensing 89 | 90 | 91 | 92 | GNU General Public License v3.0 or later. 93 | 94 | See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. 95 | 96 | ## Community Code of Conduct 97 | 98 | Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html#code-of-conduct). 99 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_poller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_node_poller 13 | short_description: Manage Pollers on Nodes in Solarwinds Orion NPM 14 | description: 15 | - Create/Remove Pollers on Nodes in Orion NPM 16 | - Note this module is different from M(jeisenbath.solarwinds.orion_node_custom_poller), which manages user created custom Orion pollers. 17 | version_added: "1.0.0" 18 | author: "Josh M. Eisenbath (@jeisenbath)" 19 | options: 20 | state: 21 | description: 22 | - The desired state of the poller. 23 | required: True 24 | type: str 25 | choices: 26 | - present 27 | - absent 28 | poller: 29 | description: 30 | - Name of the poller. 31 | required: True 32 | type: str 33 | enabled: 34 | description: 35 | - Set poller to enabled or disabled. 36 | type: bool 37 | default: True 38 | extends_documentation_fragment: 39 | - jeisenbath.solarwinds.orion_auth_options 40 | - jeisenbath.solarwinds.orion_node_options 41 | requirements: 42 | - orionsdk 43 | - requests 44 | ''' 45 | 46 | EXAMPLES = r''' 47 | --- 48 | - name: Add Linux SNMP pollers to node 49 | jeisenbath.solarwinds.orion_node_poller: 50 | hostname: "{{ solarwinds_server }}" 51 | username: "{{ solarwinds_user }}" 52 | password: "{{ solarwinds_pass }}" 53 | name: "{{ node_name }}" 54 | state: present 55 | poller: "{{ item.name }}" 56 | enabled: "{{ item.enabled }}" 57 | loop: 58 | - name: N.LoadAverage.SNMP.Linux 59 | enabled: True 60 | - name: N.Cpu.SNMP.HrProcessorLoad 61 | enabled: True 62 | - name: N.Memory.SNMP.NetSnmpReal 63 | enabled: True 64 | delegate_to: localhost 65 | 66 | - name: Disable Topology Layer 3 poller on node 67 | jeisenbath.solarwinds.orion_node_poller: 68 | hostname: "{{ solarwinds_server }}" 69 | username: "{{ solarwinds_user }}" 70 | password: "{{ solarwinds_pass }}" 71 | name: "{{ node_name }}" 72 | state: present 73 | poller: N.Topology_Layer3.SNMP.ipNetToMedia 74 | enabled: False 75 | delegate_to: localhost 76 | 77 | - name: Remove IPv6 Routing Table poller on node 78 | jeisenbath.solarwinds.orion_node_poller: 79 | hostname: "{{ solarwinds_server }}" 80 | username: "{{ solarwinds_user }}" 81 | password: "{{ solarwinds_pass }}" 82 | name: "{{ node_name }}" 83 | state: absent 84 | poller: N.Routing.SNMP.Ipv6RoutingTable 85 | delegate_to: localhost 86 | ''' 87 | 88 | RETURN = r''' 89 | orion_node: 90 | description: Info about an orion node. 91 | returned: always 92 | type: dict 93 | sample: { 94 | "caption": "localhost", 95 | "ipaddress": "127.0.0.1", 96 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 97 | "netobjectid": "N:12345", 98 | "nodeid": "12345", 99 | "objectsubtype": "SNMP", 100 | "status": 1, 101 | "statusdescription": "Node status is Up.", 102 | "unmanaged": false, 103 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 104 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 105 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 106 | } 107 | ''' 108 | 109 | from ansible.module_utils.basic import AnsibleModule 110 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 111 | try: 112 | import requests 113 | HAS_REQUESTS = True 114 | requests.packages.urllib3.disable_warnings() 115 | except ImportError: 116 | HAS_REQUESTS = False 117 | except Exception: 118 | raise Exception 119 | 120 | 121 | def main(): 122 | argument_spec = orion_argument_spec() 123 | argument_spec.update( 124 | state=dict(required=True, choices=['present', 'absent']), 125 | poller=dict(required=True, type='str'), 126 | enabled=dict(required=False, default=True, type='bool'), 127 | ) 128 | module = AnsibleModule( 129 | argument_spec, 130 | supports_check_mode=True, 131 | required_one_of=[('name', 'node_id', 'ip_address')], 132 | ) 133 | 134 | orion = OrionModule(module) 135 | 136 | node = orion.get_node() 137 | if not node: 138 | module.fail_json(skipped=True, msg='Node not found') 139 | 140 | changed = False 141 | if module.params['state'] == 'present': 142 | try: 143 | poller = orion.get_poller('N', str(node['nodeid']), module.params['poller']) 144 | if not poller or not poller['Enabled'] == module.params['enabled']: 145 | if not module.check_mode: 146 | orion.add_poller('N', str(node['nodeid']), module.params['poller'], module.params['enabled']) 147 | changed = True 148 | except Exception as OrionException: 149 | module.fail_json(msg='Failed to add poller: {0}'.format(str(OrionException))) 150 | 151 | elif module.params['state'] == 'absent': 152 | try: 153 | poller = orion.get_poller('N', str(node['nodeid']), module.params['poller']) 154 | if poller: 155 | if not module.check_mode: 156 | orion.remove_poller('N', str(node['nodeid']), module.params['poller']) 157 | changed = True 158 | except Exception as OrionException: 159 | module.fail_json(msg='Failed to remove poller: {0}'.format(str(OrionException))) 160 | 161 | module.exit_json(changed=changed, orion_node=node) 162 | 163 | 164 | if __name__ == "__main__": 165 | main() 166 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_application.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_node_application 13 | short_description: Manages APM application templates assigned to nodes. 14 | description: 15 | - Adds or removes an APM Appliction Template to Node in Solarwinds Orion NPM. 16 | - This module requires the Application Template to already exist in Solarwinds APM. 17 | - If the Application Template required credentials, they also need to already exist. 18 | version_added: "1.0.0" 19 | author: "Josh M. Eisenbath (@jeisenbath)" 20 | options: 21 | state: 22 | description: 23 | - Desired state of the application on the node. 24 | required: True 25 | type: str 26 | choices: 27 | - present 28 | - absent 29 | application_template_name: 30 | description: 31 | - Name of the application template. 32 | required: True 33 | type: str 34 | credential_name: 35 | description: 36 | - Name of the credentials to use for the application template. 37 | - If not passed, will Inherit credentials from template. 38 | required: False 39 | type: str 40 | skip_duplicates: 41 | description: 42 | - Option to allow duplicate APM Application Template assigned to node. 43 | required: False 44 | default: True 45 | type: bool 46 | extends_documentation_fragment: 47 | - jeisenbath.solarwinds.orion_auth_options 48 | - jeisenbath.solarwinds.orion_node_options 49 | requirements: 50 | - orionsdk 51 | - requests 52 | ''' 53 | 54 | EXAMPLES = r''' 55 | --- 56 | 57 | - name: Add APM Application Template to Node 58 | jeisenbath.solarwinds.orion_node_application: 59 | hostname: "{{ solarwinds_server }}" 60 | username: "{{ solarwinds_user }}" 61 | password: "{{ solarwinds_pass }}" 62 | name: "{{ node_name }}" 63 | state: present 64 | application_template_name: "{{ APM_application_name }}" 65 | delegate_to: localhost 66 | 67 | ''' 68 | 69 | RETURN = r''' 70 | orion_node: 71 | description: Info about an orion node. 72 | returned: always 73 | type: dict 74 | sample: { 75 | "caption": "localhost", 76 | "ipaddress": "127.0.0.1", 77 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 78 | "netobjectid": "N:12345", 79 | "nodeid": "12345", 80 | "objectsubtype": "SNMP", 81 | "status": 1, 82 | "statusdescription": "Node status is Up.", 83 | "unmanaged": false, 84 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 85 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 86 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 87 | } 88 | ''' 89 | 90 | from ansible.module_utils.basic import AnsibleModule 91 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 92 | try: 93 | import requests 94 | HAS_REQUESTS = True 95 | requests.packages.urllib3.disable_warnings() 96 | except ImportError: 97 | HAS_REQUESTS = False 98 | except Exception: 99 | raise Exception 100 | 101 | 102 | def main(): 103 | argument_spec = orion_argument_spec() 104 | argument_spec.update( 105 | state=dict(required=True, choices=['present', 'absent']), 106 | application_template_name=dict(required=True, type='str'), 107 | credential_name=dict(required=False, type='str'), 108 | skip_duplicates=dict(required=False, default=True, type='bool') 109 | ) 110 | module = AnsibleModule( 111 | argument_spec, 112 | supports_check_mode=True, 113 | required_one_of=[('name', 'node_id', 'ip_address')], 114 | ) 115 | 116 | orion = OrionModule(module) 117 | 118 | node = orion.get_node() 119 | if not node: 120 | module.fail_json(skipped=True, msg='Node not found') 121 | 122 | changed = False 123 | if module.params['state'] == 'present': 124 | try: 125 | application_template_id = orion.get_application_template_id(module.params['application_template_name']) 126 | 127 | credential_id = "-4" 128 | if module.params['credential_name']: 129 | try: 130 | credential_id = orion.get_apm_credential_id(module.params['credential_name']) 131 | except Exception as OrionException: 132 | module.fail_json(msg='Failed to query credential name: {0}'.format(OrionException)) 133 | 134 | application_id = orion.get_application_id(node, module.params['application_template_name']) 135 | if not application_id: 136 | if not module.check_mode: 137 | orion.add_application_template_to_node(node, application_template_id, credential_id, module.params['skip_duplicates']) 138 | changed = True 139 | except Exception as OrionException: 140 | module.fail_json(msg='Failed to add application to node: {0}'.format(OrionException)) 141 | 142 | elif module.params['state'] == 'absent': 143 | try: 144 | application_id = orion.get_application_id(node, module.params['application_template_name']) 145 | 146 | if application_id: 147 | if not module.check_mode: 148 | orion.remove_application_template_from_node(application_id) 149 | changed = True 150 | except Exception as OrionException: 151 | module.fail_json(msg='Failed to remove application from node: {0}'.format(OrionException)) 152 | 153 | module.exit_json(changed=changed, orion_node=node) 154 | 155 | 156 | if __name__ == "__main__": 157 | main() 158 | -------------------------------------------------------------------------------- /roles/orion_node/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for orion_node 3 | - name: Create node 4 | jeisenbath.solarwinds.orion_node: 5 | hostname: "{{ orion_node_solarwinds_server }}" 6 | username: "{{ orion_node_solarwinds_username }}" 7 | password: "{{ orion_node_solarwinds_password }}" 8 | state: present 9 | name: "{{ orion_node_caption_name }}" 10 | ip_address: "{{ orion_node_ip_address }}" 11 | polling_method: "{{ orion_node_polling_method }}" 12 | snmp_version: "{{ orion_node_snmp_version | default(omit) }}" 13 | ro_community_string: "{{ orion_node_ro_community_string | default(omit) }}" 14 | rw_community_string: "{{ orion_node_rw_community_string | default(omit) }}" 15 | snmpv3_credential_set: "{{ orion_node_snmpv3_credential_set | default(omit) }}" 16 | snmpv3_username: "{{ orion_node_snmpv3_username | default(omit) }}" 17 | snmpv3_auth_key: "{{ orion_node_snmpv3_auth_key | default(omit) }}" 18 | snmpv3_priv_key: "{{ orion_node_snmpv3_priv_key | default(omit) }}" 19 | snmp_port: "{{ orion_node_snmp_port | default(omit) }}" 20 | snmp_allow_64: "{{ orion_node_snmp_allow_64 | default(omit) }}" 21 | delegate_to: localhost 22 | 23 | - name: Add SNMP pollers to node if polling method is SNMP 24 | when: orion_node_polling_method == 'SNMP' 25 | jeisenbath.solarwinds.orion_node_poller: 26 | hostname: "{{ orion_node_solarwinds_server }}" 27 | username: "{{ orion_node_solarwinds_username }}" 28 | password: "{{ orion_node_solarwinds_password }}" 29 | state: present 30 | name: "{{ orion_node_caption_name }}" 31 | poller: "{{ item.name }}" 32 | enabled: "{{ item.enabled }}" 33 | loop: "{{ orion_node_snmp_pollers }}" 34 | delegate_to: localhost 35 | 36 | - name: Add Custom Pollers when orion_node_custom_pollers is defined 37 | when: orion_node_polling_method == 'SNMP' and orion_node_custom_pollers is defined 38 | jeisenbath.solarwinds.orion_node_custom_poller: 39 | hostname: "{{ orion_node_solarwinds_server }}" 40 | username: "{{ orion_node_solarwinds_username }}" 41 | password: "{{ orion_node_solarwinds_password }}" 42 | state: present 43 | name: "{{ orion_node_caption_name }}" 44 | custom_poller: "{{ item }}" 45 | loop: "{{ orion_node_custom_pollers }}" 46 | delegate_to: localhost 47 | 48 | - name: Add interfaces to node if polling method is SNMP and orion_node_interfaces list of interfaces is defined 49 | when: orion_node_polling_method == 'SNMP' and orion_node_interfaces is defined and not orion_node_discover_interfaces 50 | jeisenbath.solarwinds.orion_node_interface: 51 | hostname: "{{ orion_node_solarwinds_server }}" 52 | username: "{{ orion_node_solarwinds_username }}" 53 | password: "{{ orion_node_solarwinds_password }}" 54 | state: present 55 | name: "{{ orion_node_caption_name }}" 56 | interface: "{{ item }}" 57 | loop: "{{ orion_node_interfaces }}" 58 | delegate_to: localhost 59 | 60 | - name: Discover and add interfaces if orion_node_discover_interfaces is true 61 | when: orion_node_polling_method == 'SNMP' and orion_node_discover_interfaces 62 | jeisenbath.solarwinds.orion_node_interface: 63 | hostname: "{{ orion_node_solarwinds_server }}" 64 | username: "{{ orion_node_solarwinds_username }}" 65 | password: "{{ orion_node_solarwinds_password }}" 66 | state: present 67 | name: "{{ orion_node_caption_name }}" 68 | delegate_to: localhost 69 | 70 | - name: Add volumes to node if orion_node_volumes is defined and polling method is SNMP 71 | when: orion_node_polling_method == 'SNMP' and orion_node_volumes is defined 72 | jeisenbath.solarwinds.orion_volume: 73 | hostname: "{{ orion_node_solarwinds_server }}" 74 | username: "{{ orion_node_solarwinds_username }}" 75 | password: "{{ orion_node_solarwinds_password }}" 76 | state: present 77 | name: "{{ orion_node_caption_name }}" 78 | volume: 79 | name: "{{ item }}" 80 | loop: "{{ orion_node_volumes }}" 81 | delegate_to: localhost 82 | 83 | - name: Add APM Application template to node if orion_node_applications is defined 84 | when: orion_node_applications is defined 85 | jeisenbath.solarwinds.orion_node_application: 86 | hostname: "{{ orion_node_solarwinds_server }}" 87 | username: "{{ orion_node_solarwinds_username }}" 88 | password: "{{ orion_node_solarwinds_password }}" 89 | state: present 90 | name: "{{ orion_node_caption_name }}" 91 | application_template_name: "{{ item }}" 92 | loop: "{{ orion_node_applications }}" 93 | delegate_to: localhost 94 | 95 | - name: Add Custom Properties when orion_node_custom_properties is defined 96 | when: orion_node_custom_properties is defined 97 | jeisenbath.solarwinds.orion_custom_property: 98 | hostname: "{{ orion_node_solarwinds_server }}" 99 | username: "{{ orion_node_solarwinds_username }}" 100 | password: "{{ orion_node_solarwinds_password }}" 101 | state: present 102 | name: "{{ orion_node_caption_name }}" 103 | property_name: "{{ item.name }}" 104 | property_value: "{{ item.value }}" 105 | loop: "{{ orion_node_custom_properties }}" 106 | delegate_to: localhost 107 | 108 | - name: Add Hardware Health poller if orion_node_hardware_health_poller is defined 109 | when: orion_node_hardware_health_poller is defined 110 | jeisenbath.solarwinds.orion_node_hardware_health: 111 | hostname: "{{ orion_node_solarwinds_server }}" 112 | username: "{{ orion_node_solarwinds_username }}" 113 | password: "{{ orion_node_solarwinds_password }}" 114 | state: present 115 | name: "{{ orion_node_caption_name }}" 116 | polling_method: "{{ orion_node_hardware_health_poller }}" 117 | delegate_to: localhost 118 | 119 | - name: Add node to NCM if orion_node_ncm is true 120 | when: orion_node_ncm 121 | jeisenbath.solarwinds.orion_node_ncm: 122 | hostname: "{{ orion_node_solarwinds_server }}" 123 | username: "{{ orion_node_solarwinds_username }}" 124 | password: "{{ orion_node_solarwinds_password }}" 125 | state: present 126 | name: "{{ orion_node_caption_name }}" 127 | profile_name: "{{ orion_node_ncm_profile_name }}" 128 | ... 129 | -------------------------------------------------------------------------------- /.github/workflows/ansible-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # README FIRST 3 | # 1. Subscribe to https://github.com/ansible-collections/news-for-maintainers 4 | # (click the Watch button on the homepage > Custom > Issues) 5 | # and keep this matrix up to date in accordance to related announcements. 6 | # Timely add new ansible-core versions and consider dropping support 7 | # and testing against its EOL versions. 8 | # 2. If your collection repository is under the ansible-collections org, 9 | # please keep in mind that the number of GHA jobs is limited 10 | # and shared across all the collections in the org. 11 | # So, focusing on good test coverage of your collection, 12 | # please avoid testing against unnecessary entities such as 13 | # ansible-core EOL versions your collection does not support 14 | # or ansible-core versions that are not EOL yet but not supported by the collection. 15 | # 3. If you don't have unit or integration tests, remove corresponding sections. 16 | # 4. If your collection depends on other collections ensure they are installed, 17 | # add them to the "test-deps" input. 18 | # 5. For the comprehensive list of the inputs supported by the 19 | # ansible-community/ansible-test-gh-action GitHub Action, see 20 | # https://github.com/marketplace/actions/ansible-test. 21 | # 6. If you want to prevent merging PRs that do not pass all tests, 22 | # make sure to add the "check" job to your repository branch 23 | # protection once this workflow is added. 24 | # It is also possible to tweak which jobs are allowed to fail. See 25 | # https://github.com/marketplace/actions/alls-green#gotchas for more detail. 26 | # 7. If you need help please ask in #community:ansible.com on Matrix 27 | # or in bridged #ansible-community on the Libera.Chat IRC channel. 28 | # See https://docs.ansible.com/ansible/devel/community/communication.html 29 | # for details. 30 | # 8. If your collection is [going to get] included in the Ansible package, 31 | # it has to adhere to Python compatibility and CI testing requirements described in 32 | # https://docs.ansible.com/ansible/latest/community/collection_contributors/collection_requirements.html. 33 | 34 | name: CI 35 | on: 36 | # Run CI against all pushes (direct commits, also merged PRs), Pull Requests 37 | push: 38 | branches: 39 | - main 40 | - stable-* 41 | pull_request: 42 | # Run CI once per week (at 06:00 UTC) 43 | # This ensures that even if there haven't been commits that we are still 44 | # testing against latest version of ansible-test for each ansible-core 45 | # version 46 | schedule: 47 | - cron: '0 6 * * 0' 48 | 49 | concurrency: 50 | group: >- 51 | ${{ github.workflow }}-${{ 52 | github.event.pull_request.number || github.sha 53 | }} 54 | cancel-in-progress: true 55 | 56 | jobs: 57 | 58 | ### 59 | # Sanity tests (REQUIRED) 60 | # 61 | # https://docs.ansible.com/ansible/latest/dev_guide/testing_sanity.html 62 | 63 | sanity: 64 | name: Sanity (Ⓐ${{ matrix.ansible }}) 65 | strategy: 66 | matrix: 67 | ansible: 68 | # It's important that Sanity is tested against all stable-X.Y branches 69 | # Testing against `devel` may fail as new tests are added. 70 | # An alternative to `devel` is the `milestone` branch with 71 | # gets synchronized with `devel` every few weeks and therefore 72 | # tends to be a more stable target. Be aware that it is not updated 73 | # around creation of a new stable branch, this might cause a problem 74 | # that two different versions of ansible-test use the same sanity test 75 | # ignore.txt file. 76 | # Add new versions announced in 77 | # https://github.com/ansible-collections/news-for-maintainers in a timely manner, 78 | # consider dropping testing against EOL versions and versions you don't support. 79 | - stable-2.15 80 | - stable-2.16 81 | - stable-2.17 82 | - stable-2.18 83 | - devel 84 | # - milestone 85 | # Ansible-test on various stable branches does not yet work well with cgroups v2. 86 | # Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04 87 | # image for these stable branches. The list of branches where this is necessary will 88 | # shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28 89 | # for the latest list. 90 | runs-on: >- 91 | ${{ contains(fromJson( 92 | '["stable-2.9", "stable-2.10", "stable-2.11"]' 93 | ), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }} 94 | steps: 95 | # Run sanity tests inside a Docker container. 96 | # The docker container has all the pinned dependencies that are 97 | # required and all Python versions Ansible supports. 98 | - name: Perform sanity testing 99 | # See the documentation for the following GitHub action on 100 | # https://github.com/ansible-community/ansible-test-gh-action/blob/main/README.md 101 | uses: ansible-community/ansible-test-gh-action@release/v1 102 | with: 103 | ansible-core-version: ${{ matrix.ansible }} 104 | testing-type: sanity 105 | # OPTIONAL If your sanity tests require code 106 | # from other collections, install them like this 107 | # test-deps: >- 108 | # ansible.netcommon 109 | # ansible.utils 110 | # OPTIONAL If set to true, will test only against changed files, 111 | # which should improve CI performance. See limitations on 112 | # https://github.com/ansible-community/ansible-test-gh-action#pull-request-change-detection 113 | pull-request-change-detection: false 114 | 115 | check: # This job does nothing and is only used for the branch protection 116 | # or multi-stage CI jobs, like making sure that all tests pass before 117 | # a publishing job is started. 118 | if: always() 119 | 120 | needs: 121 | - sanity 122 | 123 | runs-on: ubuntu-latest 124 | 125 | steps: 126 | - name: Decide whether the needed jobs succeeded or failed 127 | uses: re-actors/alls-green@release/v1 128 | with: 129 | jobs: ${{ toJSON(needs) }} 130 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_hardware_health.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | __metaclass__ = type 8 | 9 | DOCUMENTATION = r''' 10 | --- 11 | module: orion_node_hardware_health 12 | short_description: Manage hardware health polling on a node in Solarwinds Orion 13 | description: 14 | - This module enables or disables hardware health polling on a node in Solarwinds Orion. 15 | author: "Andrew Bailey (@Andyjb8)" 16 | requirements: 17 | - orionsdk 18 | options: 19 | polling_method: 20 | description: 21 | - The polling method to be used for hardware health. 22 | - Required when I(state=present) 23 | required: False 24 | choices: 25 | - 'Unknown' 26 | - 'VMware' 27 | - 'SnmpDell' 28 | - 'SnmpHP' 29 | - 'SnmpIBM' 30 | - 'VMwareAPI' 31 | - 'WmiDell' 32 | - 'WmiHP' 33 | - 'WmiIBM' 34 | - 'SnmpCisco' 35 | - 'SnmpJuniper' 36 | - 'SnmpNPMHP' 37 | - 'SnmpF5' 38 | - 'SnmpDellPowerEdge' 39 | - 'SnmpDellPowerConnect' 40 | - 'SnmpDellBladeChassis' 41 | - 'SnmpHPBladeChassis' 42 | - 'Forwarded' 43 | - 'SnmpArista' 44 | type: str 45 | state: 46 | description: 47 | - Whether to enable (present) or disable (absent) hardware health polling. 48 | required: True 49 | choices: ['present', 'absent'] 50 | type: str 51 | extends_documentation_fragment: 52 | - jeisenbath.solarwinds.orion_auth_options 53 | - jeisenbath.solarwinds.orion_node_options 54 | ''' 55 | 56 | EXAMPLES = r''' 57 | --- 58 | - name: Enable hardware health polling on Cisco node 59 | jeisenbath.solarwinds.orion_node_hardware_health: 60 | hostname: "server" 61 | username: "admin" 62 | password: "pass" 63 | name: "{{ inventory_hostname }}" 64 | polling_method: SnmpCisco 65 | state: present 66 | delegate_to: localhost 67 | 68 | - name: Disable hardware health polling on Juniper node 69 | jeisenbath.solarwinds.orion_node_hardware_health: 70 | hostname: "server" 71 | username: "admin" 72 | password: "pass" 73 | name: "{{ inventory_hostname }}" 74 | state: absent 75 | delegate_to: localhost 76 | ''' 77 | 78 | RETURN = r''' 79 | orion_node: 80 | description: Info about an orion node. 81 | returned: always 82 | type: dict 83 | sample: { 84 | "caption": "localhost", 85 | "ipaddress": "127.0.0.1", 86 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 87 | "netobjectid": "N:12345", 88 | "nodeid": "12345", 89 | "objectsubtype": "SNMP", 90 | "status": 1, 91 | "statusdescription": "Node status is Up.", 92 | "unmanaged": false, 93 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 94 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 95 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 96 | } 97 | ''' 98 | 99 | from ansible.module_utils.basic import AnsibleModule 100 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 101 | try: 102 | import requests 103 | HAS_REQUESTS = True 104 | requests.packages.urllib3.disable_warnings() 105 | except ImportError: 106 | HAS_REQUESTS = False 107 | except Exception: 108 | raise Exception 109 | 110 | 111 | # Mapping of polling method names to their corresponding IDs 112 | POLLING_METHOD_MAP = { 113 | 'Unknown': 0, 114 | 'VMware': 1, 115 | 'SnmpDell': 2, 116 | 'SnmpHP': 3, 117 | 'SnmpIBM': 4, 118 | 'VMwareAPI': 5, 119 | 'WmiDell': 6, 120 | 'WmiHP': 7, 121 | 'WmiIBM': 8, 122 | 'SnmpCisco': 9, 123 | 'SnmpJuniper': 10, 124 | 'SnmpNPMHP': 11, 125 | 'SnmpF5': 12, 126 | 'SnmpDellPowerEdge': 13, 127 | 'SnmpDellPowerConnect': 14, 128 | 'SnmpDellBladeChassis': 15, 129 | 'SnmpHPBladeChassis': 16, 130 | 'Forwarded': 17, 131 | 'SnmpArista': 18 132 | } 133 | 134 | 135 | def main(): 136 | argument_spec = orion_argument_spec() 137 | argument_spec.update( 138 | state=dict(required=True, choices=['present', 'absent']), 139 | polling_method=dict(type='str', required=False, choices=list(POLLING_METHOD_MAP.keys())), # Not required for absent state 140 | ) 141 | module = AnsibleModule( 142 | argument_spec, 143 | required_one_of=[('name', 'node_id', 'ip_address')], 144 | supports_check_mode=True, 145 | required_if=[ 146 | ('state', 'present', ['polling_method']) 147 | ], 148 | ) 149 | 150 | orion = OrionModule(module) 151 | node = orion.get_node() 152 | if not node: 153 | module.fail_json(skipped=True, msg='Node not found') 154 | changed = False 155 | 156 | try: 157 | hh_poller = orion.swis_query("SELECT PollingMethod FROM Orion.HardwareHealth.HardwareInfoBase WHERE ParentObjectID = '{0}'".format(node['nodeid'])) 158 | if module.params['state'] == 'present': 159 | polling_method_id = POLLING_METHOD_MAP[module.params['polling_method']] 160 | if not hh_poller: 161 | if not module.check_mode: 162 | orion.swis.invoke('Orion.HardwareHealth.HardwareInfoBase', 'EnableHardwareHealth', node['netobjectid'], polling_method_id) 163 | changed = True 164 | elif hh_poller[0]['PollingMethod'] != polling_method_id: 165 | module.fail_json(msg="HardwareHealth montior exists, but does not match provided polling_method parameter.") 166 | elif module.params['state'] == 'absent': 167 | if hh_poller: 168 | if not module.check_mode: 169 | orion.swis.invoke('Orion.HardwareHealth.HardwareInfoBase', 'DisableHardwareHealth', node['netobjectid']) 170 | changed = True 171 | except Exception as e: 172 | module.fail_json(msg=str(e)) 173 | 174 | module.exit_json(changed=changed, orion_node=node) 175 | 176 | 177 | if __name__ == '__main__': 178 | main() 179 | -------------------------------------------------------------------------------- /tests/integration/targets/orion_node_module/tasks/icmp_node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: (ICMP) Create node (check mode) 3 | orion_node: &add_icmp_node 4 | hostname: "{{ orion_test_solarwinds_server }}" 5 | username: "{{ orion_test_solarwinds_username }}" 6 | password: "{{ orion_test_solarwinds_password }}" 7 | state: present 8 | name: "{{ orion_test_node_name }}" 9 | ip_address: "{{ orion_test_node_ip_address }}" 10 | polling_method: ICMP 11 | delegate_to: localhost 12 | check_mode: true 13 | register: icmp_add_01 14 | 15 | - name: (ICMP) Create node 16 | orion_node: 17 | <<: *add_icmp_node 18 | delegate_to: localhost 19 | register: icmp_add_02 20 | 21 | - name: (ICMP) Create node (check) (idempotence) 22 | orion_node: 23 | <<: *add_icmp_node 24 | delegate_to: localhost 25 | check_mode: true 26 | register: icmp_add_03 27 | 28 | - name: (ICMP) Create node (idempotence) 29 | orion_node: 30 | <<: *add_icmp_node 31 | delegate_to: localhost 32 | register: icmp_add_04 33 | 34 | - name: (ICMP) Create node asserts 35 | ansible.builtin.assert: 36 | that: 37 | - icmp_add_01.changed 38 | - icmp_add_02.changed 39 | - icmp_add_02.orion_node.caption == orion_test_node_name 40 | - icmp_add_02.orion_node.ipaddress == orion_test_node_ip_address 41 | - icmp_add_02.orion_node.objectsubtype == "ICMP" 42 | - not icmp_add_03.changed 43 | - not icmp_add_04.changed 44 | 45 | - name: (ICMP) Unmanage node (check) 46 | orion_node: &unmanage_icmp_node 47 | hostname: "{{ orion_test_solarwinds_server }}" 48 | username: "{{ orion_test_solarwinds_username }}" 49 | password: "{{ orion_test_solarwinds_password }}" 50 | state: unmanaged 51 | name: "{{ orion_test_node_name }}" 52 | delegate_to: localhost 53 | check_mode: true 54 | register: icmp_unmanage_01 55 | 56 | - name: (ICMP) Unmanage node 57 | orion_node: 58 | <<: *unmanage_icmp_node 59 | delegate_to: localhost 60 | register: icmp_unmanage_02 61 | 62 | - name: (ICMP) Unmanage node (idempotence) (check) 63 | orion_node: 64 | <<: *unmanage_icmp_node 65 | delegate_to: localhost 66 | register: icmp_unmanage_03 67 | 68 | - name: (ICMP) Unmanage node (idempotence) 69 | orion_node: 70 | <<: *unmanage_icmp_node 71 | delegate_to: localhost 72 | register: icmp_unmanage_04 73 | 74 | - name: (ICMP) Unmanage node asserts 75 | ansible.builtin.assert: 76 | that: 77 | - icmp_unmanage_01.changed 78 | - icmp_unmanage_02.changed 79 | - not icmp_unmanage_03.changed 80 | - not icmp_unmanage_04.changed 81 | 82 | - name: (ICMP) Manage node (check) 83 | orion_node: &manage_icmp_node 84 | hostname: "{{ orion_test_solarwinds_server }}" 85 | username: "{{ orion_test_solarwinds_username }}" 86 | password: "{{ orion_test_solarwinds_password }}" 87 | state: managed 88 | name: "{{ orion_test_node_name }}" 89 | delegate_to: localhost 90 | check_mode: true 91 | register: icmp_manage_01 92 | 93 | - name: (ICMP) Manage node 94 | orion_node: 95 | <<: *manage_icmp_node 96 | delegate_to: localhost 97 | register: icmp_manage_02 98 | 99 | - name: (ICMP) Manage node (idempotence) (check) 100 | orion_node: 101 | <<: *manage_icmp_node 102 | delegate_to: localhost 103 | register: icmp_manage_03 104 | 105 | - name: (ICMP) Manage node (idempotence) 106 | orion_node: 107 | <<: *manage_icmp_node 108 | delegate_to: localhost 109 | register: icmp_manage_04 110 | 111 | - name: (ICMP) Manage node asserts 112 | ansible.builtin.assert: 113 | that: 114 | - icmp_manage_01.changed 115 | - icmp_manage_02.changed 116 | - not icmp_manage_03.changed 117 | - not icmp_manage_04.changed 118 | 119 | - name: (ICMP) Mute node (check) 120 | orion_node: &mute_icmp_node 121 | hostname: "{{ orion_test_solarwinds_server }}" 122 | username: "{{ orion_test_solarwinds_username }}" 123 | password: "{{ orion_test_solarwinds_password }}" 124 | state: muted 125 | name: "{{ orion_test_node_name }}" 126 | delegate_to: localhost 127 | check_mode: true 128 | register: icmp_mute_01 129 | 130 | - name: (ICMP) Mute node 131 | orion_node: 132 | <<: *mute_icmp_node 133 | delegate_to: localhost 134 | register: icmp_mute_02 135 | 136 | - name: (ICMP) Mute node (idempotence) (check) 137 | orion_node: 138 | <<: *mute_icmp_node 139 | delegate_to: localhost 140 | register: icmp_mute_03 141 | 142 | - name: (ICMP) Mute node (idempotence) 143 | orion_node: 144 | <<: *mute_icmp_node 145 | delegate_to: localhost 146 | register: icmp_mute_04 147 | 148 | - name: (ICMP) Mute node asserts 149 | ansible.builtin.assert: 150 | that: 151 | - icmp_mute_01.changed 152 | - icmp_mute_02.changed 153 | - not icmp_mute_03.changed 154 | - not icmp_mute_04.changed 155 | 156 | - name: (ICMP) Unmute node (check) 157 | orion_node: &unmute_icmp_node 158 | hostname: "{{ orion_test_solarwinds_server }}" 159 | username: "{{ orion_test_solarwinds_username }}" 160 | password: "{{ orion_test_solarwinds_password }}" 161 | state: unmuted 162 | name: "{{ orion_test_node_name }}" 163 | delegate_to: localhost 164 | check_mode: true 165 | register: icmp_unmute_01 166 | 167 | - name: (ICMP) Unmute node 168 | orion_node: 169 | <<: *unmute_icmp_node 170 | delegate_to: localhost 171 | register: icmp_unmute_02 172 | 173 | - name: (ICMP) Unmute node (idempotence) (check) 174 | orion_node: 175 | <<: *unmute_icmp_node 176 | delegate_to: localhost 177 | register: icmp_unmute_03 178 | 179 | - name: (ICMP) Unmute node (idempotence) 180 | orion_node: 181 | <<: *unmute_icmp_node 182 | delegate_to: localhost 183 | register: icmp_unmute_04 184 | 185 | - name: (ICMP) Unmute node asserts 186 | ansible.builtin.assert: 187 | that: 188 | - icmp_unmute_01.changed 189 | - icmp_unmute_02.changed 190 | - not icmp_unmute_03.changed 191 | - not icmp_unmute_04.changed 192 | 193 | - name: (ICMP) Delete node (check) 194 | orion_node: &remove_icmp_node 195 | hostname: "{{ orion_test_solarwinds_server }}" 196 | username: "{{ orion_test_solarwinds_username }}" 197 | password: "{{ orion_test_solarwinds_password }}" 198 | state: absent 199 | name: "{{ orion_test_node_name }}" 200 | delegate_to: localhost 201 | check_mode: true 202 | register: icmp_delete_01 203 | 204 | - name: (ICMP) Delete node 205 | orion_node: 206 | <<: *remove_icmp_node 207 | delegate_to: localhost 208 | register: icmp_delete_02 209 | 210 | - name: (ICMP) Delete node (check) (idempotence) 211 | orion_node: 212 | <<: *remove_icmp_node 213 | delegate_to: localhost 214 | check_mode: true 215 | register: icmp_delete_03 216 | 217 | - name: (ICMP) Delete node (idempotence) 218 | orion_node: 219 | <<: *remove_icmp_node 220 | delegate_to: localhost 221 | register: icmp_delete_04 222 | 223 | - name: (ICMP) Delete node asserts 224 | ansible.builtin.assert: 225 | that: 226 | - icmp_delete_01.changed 227 | - icmp_delete_02.changed 228 | - not icmp_delete_03.changed 229 | - not icmp_delete_04.changed 230 | ... 231 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_ncm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2024, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_node_ncm 13 | short_description: Manages a node in Solarwinds NCM 14 | description: 15 | - Adds or removes an existing Orion node to NCM. 16 | version_added: "1.3.0" 17 | author: "Josh M. Eisenbath (@jeisenbath)" 18 | options: 19 | state: 20 | description: 21 | - Desired state the node. 22 | required: True 23 | type: str 24 | choices: 25 | - present 26 | - absent 27 | profile_name: 28 | description: 29 | - Connection Profile Name Predefined on Orion NCM. 30 | default: '-1' 31 | required: false 32 | type: str 33 | extends_documentation_fragment: 34 | - jeisenbath.solarwinds.orion_auth_options 35 | - jeisenbath.solarwinds.orion_node_options 36 | requirements: 37 | - orionsdk 38 | - requests 39 | ''' 40 | 41 | EXAMPLES = r''' 42 | --- 43 | 44 | - name: Add Node to NCM 45 | jeisenbath.solarwinds.orion_node_ncm: 46 | hostname: "{{ solarwinds_server }}" 47 | username: "{{ solarwinds_user }}" 48 | password: "{{ solarwinds_pass }}" 49 | name: "{{ node_name }}" 50 | state: present 51 | profile_name: "{{ profile_name }}" 52 | delegate_to: localhost 53 | 54 | ''' 55 | 56 | RETURN = r''' 57 | orion_node: 58 | description: Info about an orion node. 59 | returned: always 60 | type: dict 61 | sample: { 62 | "caption": "localhost", 63 | "ipaddress": "127.0.0.1", 64 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 65 | "netobjectid": "N:12345", 66 | "nodeid": "12345", 67 | "objectsubtype": "SNMP", 68 | "status": 1, 69 | "statusdescription": "Node status is Up.", 70 | "unmanaged": false, 71 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 72 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 73 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 74 | } 75 | ''' 76 | 77 | from ansible.module_utils.basic import AnsibleModule 78 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 79 | try: 80 | import requests 81 | HAS_REQUESTS = True 82 | requests.packages.urllib3.disable_warnings() 83 | except ImportError: 84 | HAS_REQUESTS = False 85 | except Exception: 86 | raise Exception 87 | 88 | 89 | def index_connection_profiles(orion_module): 90 | """Takes an Orion module object and enumerates all available connection profiles for later use. Returns a dictionary.""" 91 | profile_list = orion_module.swis_get_ncm_connection_profiles() 92 | profile_dict = {} 93 | for k in range(0, len(profile_list)): 94 | profile_name = profile_list[k]['Name'] 95 | profile_id = profile_list[k]['ID'] 96 | # create a mapping between the profile name (i.e. "Juniper_NCM") and the back-end numeric ID number 97 | profile_dict.update({profile_name: profile_id}) 98 | return profile_dict 99 | 100 | 101 | def main(): 102 | # start with generic Orion arguments 103 | argument_spec = orion_argument_spec() 104 | # add desired fields to list of module arguments 105 | argument_spec.update( 106 | state=dict(required=True, choices=['present', 'absent']), 107 | profile_name=dict(required=False, type='str', default='-1'), # required field unless user wants to unset a connection profile 108 | ) 109 | # initialize the custom Ansible module 110 | module = AnsibleModule( 111 | argument_spec, 112 | supports_check_mode=True, 113 | required_one_of=[('name', 'node_id', 'ip_address')], 114 | ) 115 | 116 | # create an OrionModule object using our custom Ansible module 117 | orion = OrionModule(module) 118 | 119 | node = orion.get_node() 120 | if not node: 121 | # if get_node() returns None, there's no node 122 | module.fail_json(skipped=True, msg='Node not found') 123 | 124 | if module.params['state'] == 'present': 125 | try: 126 | ncm_node = orion.get_ncm_node(node) 127 | if ncm_node: 128 | profile_dict = index_connection_profiles(orion) 129 | if module.check_mode: 130 | if orion.get_ncm_node_object(ncm_node)['ConnectionProfile'] != profile_dict[module.params['profile_name']]: 131 | module.exit_json(changed=True, orion_node=node, msg="Check mode: no changes made.") 132 | else: 133 | module.exit_json(changed=False, orion_node=node) 134 | was_changed = orion.update_ncm_node_connection_profile(profile_dict, module.params['profile_name'], ncm_node) 135 | if was_changed: 136 | module.exit_json(changed=True, orion_node=node) 137 | else: 138 | module.exit_json(changed=False, orion_node=node) 139 | else: 140 | # if the node is not already in NCM, add the node and update the connection profile 141 | if module.check_mode: 142 | module.exit_json(changed=False, orion_node=node) 143 | else: 144 | # add the node to NCM 145 | orion.add_node_to_ncm(node) 146 | # collect the NCM node ID of the node 147 | ncm_node = orion.get_ncm_node(node) 148 | profile_dict = index_connection_profiles(orion) 149 | # update the connection profile 150 | was_changed = orion.update_ncm_node_connection_profile(profile_dict, module.params['profile_name'], ncm_node) 151 | module.exit_json(changed=True, orion_node=node) 152 | if was_changed: 153 | module.exit_json(changed=True, orion_node=node) 154 | else: 155 | module.exit_json(changed=False, orion_node=node) 156 | except Exception as OrionException: 157 | module.fail_json(msg='Failed to add or update node in NCM: {0}'.format(OrionException)) 158 | 159 | elif module.params['state'] == 'absent': 160 | try: 161 | ncm_node = orion.get_ncm_node(node) 162 | if ncm_node: 163 | if module.check_mode: 164 | module.exit_json(changed=True, orion_node=node) 165 | else: 166 | orion.remove_node_from_ncm(node) 167 | module.exit_json(changed=True, orion_node=node) 168 | else: 169 | module.exit_json(changed=False, orion_node=node) 170 | except Exception as OrionException: 171 | module.fail_json(msg='Failed to remove application from node: {0}'.format(OrionException)) 172 | 173 | module.exit_json(changed=False) 174 | 175 | 176 | if __name__ == "__main__": 177 | main() 178 | -------------------------------------------------------------------------------- /plugins/modules/orion_volume.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_volume 13 | short_description: Manage Volumes on Nodes in Solarwinds Orion NPM 14 | description: 15 | - Add or remove a volumes on a node in Orion NPM. 16 | - This module does not use discovery to find available volumes. 17 | version_added: "1.0.0" 18 | author: "Josh M. Eisenbath (@jeisenbath)" 19 | options: 20 | state: 21 | description: 22 | - The desired state of the volume. 23 | required: True 24 | type: str 25 | choices: 26 | - present 27 | - absent 28 | volume: 29 | description: 30 | - Attributes of the volume being managed. 31 | required: True 32 | type: dict 33 | suboptions: 34 | name: 35 | description: 36 | - Name of the volume. 37 | aliases: [ 'caption' ] 38 | required: True 39 | type: str 40 | volumeDescription: 41 | description: 42 | - Description of the volume. 43 | - If not given, this will default to name. 44 | type: str 45 | volumeType: 46 | description: 47 | - Type of volume. 48 | choices: [ 'Fixed Disk', 'RAM', 'Virtual Memory', 'Other' ] 49 | default: 'Fixed Disk' 50 | type: str 51 | volumeIcon: 52 | description: 53 | - Volume icon file. 54 | - Generally this should match volume type, but with no spaces and file extension at the end. 55 | default: 'FixedDisk.gif' 56 | type: str 57 | pollInterval: 58 | description: 59 | - Time in seconds between polling. 60 | type: int 61 | default: 420 62 | statCollection: 63 | description: 64 | - Time in seconds between stat collection. 65 | type: int 66 | default: 15 67 | rediscoveryInterval: 68 | description: 69 | - Time in seconds between rediscovery. 70 | type: int 71 | default: 60 72 | extends_documentation_fragment: 73 | - jeisenbath.solarwinds.orion_auth_options 74 | - jeisenbath.solarwinds.orion_node_options 75 | requirements: 76 | - orionsdk 77 | - requests 78 | ''' 79 | 80 | EXAMPLES = r''' 81 | --- 82 | 83 | - name: Add /home volume to Linux node 84 | jeisenbath.solarwinds.orion_volume: 85 | hostname: "{{ solarwinds_server }}" 86 | username: "{{ solarwinds_user }}" 87 | password: "{{ solarwinds_pass }}" 88 | name: "{{ node_name }}" 89 | state: present 90 | volume: 91 | name: /home 92 | delegate_to: localhost 93 | 94 | ''' 95 | 96 | RETURN = r''' 97 | orion_node: 98 | description: Info about an orion node. 99 | returned: always 100 | type: dict 101 | sample: { 102 | "caption": "localhost", 103 | "ipaddress": "127.0.0.1", 104 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 105 | "netobjectid": "N:12345", 106 | "nodeid": "12345", 107 | "objectsubtype": "SNMP", 108 | "status": 1, 109 | "statusdescription": "Node status is Up.", 110 | "unmanaged": false, 111 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 112 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 113 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 114 | } 115 | orion_volume: 116 | description: Info about an orion volume. 117 | returned: always 118 | type: dict 119 | sample: { 120 | "displayname": "/", 121 | "volumeindex": 0, 122 | "status": "0", 123 | "type": "Fixed Disk", 124 | "caption": "/", 125 | "pollinterval": "420", 126 | "statcollection": "15", 127 | "rediscoveryinterval": "60", 128 | "volumedescription": "/", 129 | "icon": "FixedDisk.gif", 130 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345/Volumes/VolumeID=67890" 131 | } 132 | ''' 133 | 134 | from ansible.module_utils.basic import AnsibleModule 135 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 136 | try: 137 | import requests 138 | HAS_REQUESTS = True 139 | requests.packages.urllib3.disable_warnings() 140 | except ImportError: 141 | HAS_REQUESTS = False 142 | except Exception: 143 | raise Exception 144 | 145 | 146 | def main(): 147 | argument_spec = orion_argument_spec() 148 | argument_spec.update( 149 | state=dict(required=True, choices=['present', 'absent']), 150 | volume=dict( 151 | required=True, type='dict', 152 | options=dict( 153 | volumeType=dict( 154 | type='str', default='Fixed Disk', choices=['Other', 'RAM', 'Virtual Memory', 'Fixed Disk'] 155 | ), 156 | volumeIcon=dict(type='str', default='FixedDisk.gif'), 157 | name=dict(required=True, type='str', aliases=['caption']), 158 | volumeDescription=dict(type='str'), 159 | pollInterval=dict(type='int', default=420), 160 | statCollection=dict(type='int', default=15), 161 | rediscoveryInterval=dict(type='int', default=60), 162 | ) 163 | ), 164 | ) 165 | module = AnsibleModule( 166 | argument_spec, 167 | supports_check_mode=True, 168 | required_one_of=[('name', 'node_id', 'ip_address')], 169 | ) 170 | 171 | orion = OrionModule(module) 172 | 173 | node = orion.get_node() 174 | if not node: 175 | module.fail_json(skipped=True, msg='Node not found') 176 | 177 | volume = orion.get_volume(node, module.params['volume']) 178 | changed = False 179 | if module.params['state'] == 'present': 180 | if not volume: 181 | try: 182 | if not module.check_mode: 183 | orion.add_volume(node, module.params['volume']) 184 | volume = orion.get_volume(node, module.params['volume']) 185 | orion.add_poller('V', str(volume['volumeid']), 'V.Status.SNMP.Generic', True) 186 | orion.add_poller('V', str(volume['volumeid']), 'V.Details.SNMP.Generic', True) 187 | orion.add_poller('V', str(volume['volumeid']), 'V.Statistics.SNMP.Generic', True) 188 | changed = True 189 | except Exception as OrionException: 190 | module.fail_json(msg='Failed to add volume: {0}'.format(str(OrionException))) 191 | elif module.params['state'] == 'absent': 192 | if volume: 193 | try: 194 | if not module.check_mode: 195 | orion.remove_volume(node, module.params['volume']) 196 | changed = True 197 | except Exception as OrionException: 198 | module.fail_json(msg='Failed to remove volume: {0}'.format(str(OrionException))) 199 | 200 | module.exit_json(changed=changed, orion_node=node, orion_volume=volume) 201 | 202 | 203 | if __name__ == "__main__": 204 | main() 205 | -------------------------------------------------------------------------------- /plugins/modules/orion_node_interface.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: (c) 2022, Josh M. Eisenbath 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | __metaclass__ = type 9 | 10 | DOCUMENTATION = r''' 11 | --- 12 | module: orion_node_interface 13 | short_description: Manage interfaces on Nodes in Solarwinds Orion NPM 14 | description: 15 | - Add or remove an interface on a Node in Orion NPM. 16 | - Adding an interface will run a discovery on the node to find available interfaces. 17 | version_added: "1.0.0" 18 | author: "Josh M. Eisenbath (@jeisenbath)" 19 | options: 20 | state: 21 | description: 22 | - The desired state of the interface. 23 | required: True 24 | type: str 25 | choices: 26 | - present 27 | - absent 28 | interface: 29 | description: 30 | - The name of the interface. 31 | - If omitted, the module will discover and manage all interfaces. 32 | required: False 33 | type: str 34 | regex: 35 | description: 36 | - Whether or not to use regex pattern matching for I(interface). 37 | - When I(regex=true), check mode will always show changed. 38 | required: False 39 | type: bool 40 | default: False 41 | extends_documentation_fragment: 42 | - jeisenbath.solarwinds.orion_auth_options 43 | - jeisenbath.solarwinds.orion_node_options 44 | requirements: 45 | - orionsdk 46 | - requests 47 | ''' 48 | 49 | EXAMPLES = r''' 50 | --- 51 | 52 | - name: Discover and add all interfaces to node 53 | jeisenbath.solarwinds.orion_node_interface: 54 | hostname: "{{ solarwinds_server }}" 55 | username: "{{ solarwinds_user }}" 56 | password: "{{ solarwinds_pass }}" 57 | name: "{{ node_name }}" 58 | state: present 59 | delegate_to: localhost 60 | 61 | - name: Add all interfaces matching regex pattern "Ethernet [0-9]$" 62 | jeisenbath.solarwinds.orion_node_interface: 63 | hostname: "{{ solarwinds_server }}" 64 | username: "{{ solarwinds_user }}" 65 | password: "{{ solarwinds_pass }}" 66 | name: "{{ node_name }}" 67 | state: present 68 | interface: "Ethernet [0-9]$" 69 | regex: true 70 | delegate_to: localhost 71 | 72 | - name: Remove an interface from node 73 | jeisenbath.solarwinds.orion_node_interface: 74 | hostname: "{{ solarwinds_server }}" 75 | username: "{{ solarwinds_user }}" 76 | password: "{{ solarwinds_pass }}" 77 | name: "{{ node_name }}" 78 | state: absent 79 | interface: "{{ interface_name }}" 80 | delegate_to: localhost 81 | ''' 82 | 83 | RETURN = r''' 84 | orion_node: 85 | description: Info about an orion node. 86 | returned: always 87 | type: dict 88 | sample: { 89 | "caption": "localhost", 90 | "ipaddress": "127.0.0.1", 91 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 92 | "netobjectid": "N:12345", 93 | "nodeid": "12345", 94 | "objectsubtype": "SNMP", 95 | "status": 1, 96 | "statusdescription": "Node status is Up.", 97 | "unmanaged": false, 98 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 99 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 100 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 101 | } 102 | discovered: 103 | description: List of discovered interfaces. 104 | returned: always 105 | type: list 106 | elements: dict 107 | sample: [ 108 | { 109 | "Caption": "lo", 110 | "InterfaceID": 0, 111 | "Manageable": true, 112 | "ifAdminStatus": 1, 113 | "ifIndex": 1, 114 | "ifOperStatus": 1, 115 | "ifSpeed": 0.0, 116 | "ifSubType": 0, 117 | "ifType": 24 118 | }, 119 | { 120 | "Caption": "eth0", 121 | "InterfaceID": 268420, 122 | "Manageable": true, 123 | "ifAdminStatus": 1, 124 | "ifIndex": 2, 125 | "ifOperStatus": 1, 126 | "ifSpeed": 0.0, 127 | "ifSubType": 0, 128 | "ifType": 6 129 | } 130 | ] 131 | interfaces: 132 | description: Interfaces added or removed by task. 133 | returned: always, except for when I(state=present), I(interface) is defined and running in check mode. 134 | type: list 135 | elements: dict 136 | sample: [ 137 | { 138 | "Caption": "lo", 139 | "InterfaceID": 0, 140 | "Manageable": true, 141 | "ifAdminStatus": 1, 142 | "ifIndex": 1, 143 | "ifOperStatus": 1, 144 | "ifSpeed": 0.0, 145 | "ifSubType": 0, 146 | "ifType": 24 147 | } 148 | ] 149 | ''' 150 | 151 | from ansible.module_utils.basic import AnsibleModule 152 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 153 | try: 154 | import requests 155 | HAS_REQUESTS = True 156 | requests.packages.urllib3.disable_warnings() 157 | except ImportError: 158 | HAS_REQUESTS = False 159 | except Exception: 160 | raise Exception 161 | 162 | 163 | def main(): 164 | argument_spec = orion_argument_spec() 165 | argument_spec.update( 166 | state=dict(required=True, choices=['present', 'absent']), 167 | interface=dict(required=False, type='str'), 168 | regex=dict(required=False, type='bool', default=False), 169 | ) 170 | module = AnsibleModule( 171 | argument_spec, 172 | supports_check_mode=True, 173 | required_one_of=[('name', 'node_id', 'ip_address')], 174 | ) 175 | 176 | orion = OrionModule(module) 177 | 178 | node = orion.get_node() 179 | if not node: 180 | module.fail_json(skipped=True, msg='Node not found') 181 | 182 | changed = False 183 | discovered = orion.discover_interfaces(node) 184 | interfaces = [] 185 | if module.params['state'] == 'present': 186 | try: 187 | if not module.params['interface']: 188 | for interface in discovered: 189 | if not orion.get_interface(node, interface['Caption']): 190 | interfaces.append(interface) 191 | if not module.check_mode: 192 | orion.add_interface(node, interface['Caption'], False, discovered) 193 | changed = True 194 | else: 195 | get_int = orion.get_interface(node, module.params['interface']) 196 | if not get_int: 197 | if module.check_mode: 198 | changed = True 199 | else: 200 | interfaces = orion.add_interface(node, module.params['interface'], module.params['regex'], discovered) 201 | if interfaces: 202 | changed = True 203 | except Exception as OrionException: 204 | module.fail_json(msg='Failed to add interfaces: {0}'.format(str(OrionException))) 205 | elif module.params['state'] == 'absent': 206 | try: 207 | if not module.params['interface']: 208 | for interface in discovered: 209 | if orion.get_interface(node, interface['Caption']): 210 | interfaces.append(interface) 211 | if not module.check_mode: 212 | orion.remove_interface(node, interface['Caption']) 213 | changed = True 214 | else: 215 | get_int = orion.get_interface(node, module.params['interface']) 216 | if get_int: 217 | interfaces.append(get_int) 218 | if not module.check_mode: 219 | orion.remove_interface(node, module.params['interface']) 220 | changed = True 221 | 222 | except Exception as OrionException: 223 | module.fail_json(msg='Failed to remove interface: {0}'.format(str(OrionException))) 224 | 225 | module.exit_json(changed=changed, orion_node=node, discovered=discovered, interfaces=interfaces) 226 | 227 | 228 | if __name__ == "__main__": 229 | main() 230 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | Solarwinds.Orion Release Notes 3 | ============================== 4 | 5 | .. contents:: Topics 6 | 7 | 8 | v3.1.0 9 | ====== 10 | 11 | Release Summary 12 | --------------- 13 | 14 | Released 2025-08-25 15 | 16 | New Modules 17 | ----------- 18 | 19 | - jeisenbath.solarwinds.orion_credential_set - Manage Credential Sets for Solarwinds. 20 | 21 | v3.0.1 22 | ====== 23 | 24 | Release Summary 25 | --------------- 26 | 27 | Released 2025-01-30 28 | 29 | Bugfixes 30 | -------- 31 | 32 | - orion_node_interface - fixed a bug with adding a defined interface. Check mode was adding the interface, while non-check mode wasn't. 33 | 34 | v3.0.0 35 | ====== 36 | 37 | Release Summary 38 | --------------- 39 | 40 | Released 2025-01-18 41 | Migrated collection to jeisenbath.solarwinds namespace in order to publish to Galaxy. 42 | Versions < 3.0 will stay on old solarwinds.orion namespace, and bugfixes will be ported to stable-2.x branch until 2025-12-31. 43 | 44 | 45 | Breaking Changes / Porting Guide 46 | -------------------------------- 47 | 48 | - Bumped min ansible version to be up to date with supported releases. Older versions may work but will no longer be tested. 49 | - Migrated collection namespace from solarwinds.orion to jeisenbath.solarwinds 50 | 51 | v2.1.1 52 | ====== 53 | 54 | Release Summary 55 | --------------- 56 | 57 | Released 2025-01-18 58 | Added ENV variable support for plugins 59 | Created integration tests for modules 60 | Adds a CONTRIBUTING doc 61 | Fix sanity test errors 62 | 63 | 64 | Minor Changes 65 | ------------- 66 | 67 | - Add support for Environment Variables for hostname, username, and password 68 | 69 | Deprecated Features 70 | ------------------- 71 | 72 | - Bugfixes will be ported to stable-2.x branch for this collection when applicable to existing plugins until 2026. 73 | - Starting with version 3.0, Collection has been moved to jeisenbath.solarwinds namespace in order to publish to Ansible Galaxy. 74 | 75 | v2.1.0 76 | ====== 77 | 78 | Release Summary 79 | --------------- 80 | 81 | Released 2024-10-02 82 | 83 | Major Changes 84 | ------------- 85 | 86 | - Added module orion_node_interface_info to get interfaces currently monitored for a node. 87 | - Added orion_node_hardware_health module. This module allows for adding and removing hardware health sensors in Solarwinds Orion. 88 | 89 | Minor Changes 90 | ------------- 91 | 92 | - Add a poll_now() function to the OrionModule 93 | - Add a profile_name parameter to orion_node_ncm 94 | - Add correct check_mode logic to orion_ndoe_ncm 95 | - Call poll_now() for SNMP nodes in orion_node_info module. This logic will allow using 'until' task logic to validate node is polling. 96 | - Modified the example playbook for orion_add_node.yml to use the role keyword, and include a task for SNMP poll verification. 97 | - Update get_node() function to also return LastSystemUptimePollUtc 98 | - Updated orion_node module to no longer require snmpv3 credential set. 99 | - Updated orion_update_node exmaples to show updating to SNMPv3. 100 | - orion_node role - added tasks for new modules orion_node_ncm and orion_node_hardware_health 101 | 102 | Bugfixes 103 | -------- 104 | 105 | - Fixed an issue where ansible-lint would complain about missing parameters when a single yaml doc used multiple modules. 106 | 107 | v2.0.0 108 | ====== 109 | 110 | Release Summary 111 | --------------- 112 | 113 | Released 2024-04-18 114 | 115 | Breaking Changes / Porting Guide 116 | -------------------------------- 117 | 118 | - All modules - add support for orionsdk 0.4.0 119 | - If using orionsdk 0.4.0 while still on a version of Solarwinds older than 2024.1.0, must set port to 17778 legacy API 120 | - SWIS API connection parameter for "port" added, with default "17774" to match orionsdk SwisClient default 121 | - SWIS API connection parameter for "verify" added, with default of "false" to match orionsdk SwisClient default 122 | 123 | v1.3.2 124 | ====== 125 | 126 | Release Summary 127 | --------------- 128 | 129 | Released 2024-04-17 130 | 131 | Minor Changes 132 | ------------- 133 | 134 | - inventory plugin orion_nodes_inventory - add ansible vault support for the password parameter 135 | 136 | v1.3.1 137 | ====== 138 | 139 | Release Summary 140 | --------------- 141 | 142 | Released 2024-03-11 143 | 144 | Minor Changes 145 | ------------- 146 | 147 | - orion_node_interface - refactored to try and make as idempotent as possible, and return 'discovered' and 'interface' 148 | 149 | v1.3.0 150 | ====== 151 | 152 | Release Summary 153 | --------------- 154 | 155 | Released 2024-03-07 156 | 157 | Major Changes 158 | ------------- 159 | 160 | - Add module orion_node_ncm - Adds/Removes an existing node to be managed in NCM. 161 | - Add module orion_node_poller_info - Gets pollers assigned to a node and their enabled status. 162 | - Add module orion_query - Runs a SWQL query against Orion database, outputs to json and optional CSV. 163 | 164 | New Modules 165 | ----------- 166 | 167 | - jeisenbath.solarwinds.orion_node_ncm - Manages a node in Solarwinds NCM 168 | - jeisenbath.solarwinds.orion_node_poller_info - Gets info about pollers assigned to a Node in Solarwinds Orion NPM 169 | - jeisenbath.solarwinds.orion_query - Queries the Solarwinds Orion database 170 | 171 | v1.2.0 172 | ====== 173 | 174 | Release Summary 175 | --------------- 176 | 177 | Released 2024-03-01 178 | 179 | Major Changes 180 | ------------- 181 | 182 | - Added a role orion_node 183 | - Updated the example playbook to use the new role 184 | 185 | v1.1.0 186 | ====== 187 | 188 | Release Summary 189 | --------------- 190 | 191 | | Released 2023-12-1 192 | 193 | 194 | Major Changes 195 | ------------- 196 | 197 | - Add dynamic inventory plugin jeisenbath.solarwinds.orion_nodes_inventory 198 | 199 | v1.0.4 200 | ====== 201 | 202 | Release Summary 203 | --------------- 204 | 205 | | Released 2023-09-26 206 | 207 | 208 | Major Changes 209 | ------------- 210 | 211 | - orion_node_interface module - add param 'regex' to explicitly state if you want to do pattern matching in interface name 212 | 213 | Bugfixes 214 | -------- 215 | 216 | - orion_node module - fix functionality for adding External nodes 217 | 218 | v1.0.3 219 | ====== 220 | 221 | Release Summary 222 | --------------- 223 | 224 | | Released 2023-08-27 225 | 226 | 227 | Minor Changes 228 | ------------- 229 | 230 | - orion_node module - add support for using credential sets for SNMPv3 nodes, updated documentation with params that are required for SNMPv3 231 | 232 | Bugfixes 233 | -------- 234 | 235 | - orion.py get_least_used_polling_engine - convert the query count to an int, to fix an issue with a deployment with only one poller 236 | 237 | v1.0.2 238 | ====== 239 | 240 | Release Summary 241 | --------------- 242 | 243 | | Released 2023-08-10 244 | 245 | 246 | Minor Changes 247 | ------------- 248 | 249 | - orion_node_interface module - add support for removing all interfaces if one is not specified 250 | 251 | Bugfixes 252 | -------- 253 | 254 | - orion.py add_interface function - only regex pattern match if exact interface name is not found 255 | - orion_node module - don't set snmpv3 properties for node unless parameters are passed 256 | - orion_node_application module - typo with param name 'skip_duplicates' 257 | - orion_node_interface - add to documentation and examples to clarify regex pattern matching is supported 258 | 259 | v1.0.1 260 | ====== 261 | 262 | Release Summary 263 | --------------- 264 | 265 | | Released 2023-07-14 266 | 267 | 268 | Minor Changes 269 | ------------- 270 | 271 | - orion_node module - use datetime.now() instead of datetime.utcnow() for muting and unmanaging. utcnow() works fine for managing, but for muting the time needs to match server time to work correctly. 272 | 273 | Bugfixes 274 | -------- 275 | 276 | - orion_node module - add snmp_version required_if polling_method == 'SNMP' 277 | - orion_node module - fix typo in logic for state 'managed' 278 | - orion_node module - unset default for snmp version in parameters, to fix issue 2 279 | 280 | v1.0.0 281 | ====== 282 | 283 | Release Summary 284 | --------------- 285 | 286 | | Released 2023-03-18 287 | 288 | 289 | New Modules 290 | ----------- 291 | 292 | - jeisenbath.solarwinds.orion_custom_property - Manage custom properties on Node in Solarwinds Orion NPM 293 | - jeisenbath.solarwinds.orion_node - Created/Removes/Edits Nodes in Solarwinds Orion NPM 294 | - jeisenbath.solarwinds.orion_node_application - Manages APM application templates assigned to nodes. 295 | - jeisenbath.solarwinds.orion_node_custom_poller - Creates/Removes custom pollers to a Node in Solarwinds Orion NPM 296 | - jeisenbath.solarwinds.orion_node_info - Gets info about a Node in Solarwinds Orion NPM 297 | - jeisenbath.solarwinds.orion_node_interface - Manage interfaces on Nodes in Solarwinds Orion NPM 298 | - jeisenbath.solarwinds.orion_node_poller - Manage Pollers on Nodes in Solarwinds Orion NPM 299 | - jeisenbath.solarwinds.orion_update_node - Updates Node in Solarwinds Orion NPM 300 | - jeisenbath.solarwinds.orion_volume - Manage Volumes on Nodes in Solarwinds Orion NPM 301 | - jeisenbath.solarwinds.orion_volume_info - Gets info about a Volume in Solarwinds Orion NPM 302 | -------------------------------------------------------------------------------- /changelogs/changelog.yaml: -------------------------------------------------------------------------------- 1 | ancestor: null 2 | releases: 3 | 1.0.0: 4 | changes: 5 | release_summary: '| Released 2023-03-18 6 | 7 | ' 8 | fragments: 9 | - release_summary_1.0.0.yml 10 | modules: 11 | - description: Manage custom properties on Node in Solarwinds Orion NPM 12 | name: orion_custom_property 13 | namespace: '' 14 | - description: Created/Removes/Edits Nodes in Solarwinds Orion NPM 15 | name: orion_node 16 | namespace: '' 17 | - description: Manages APM application templates assigned to nodes. 18 | name: orion_node_application 19 | namespace: '' 20 | - description: Creates/Removes custom pollers to a Node in Solarwinds Orion NPM 21 | name: orion_node_custom_poller 22 | namespace: '' 23 | - description: Gets info about a Node in Solarwinds Orion NPM 24 | name: orion_node_info 25 | namespace: '' 26 | - description: Manage interfaces on Nodes in Solarwinds Orion NPM 27 | name: orion_node_interface 28 | namespace: '' 29 | - description: Manage Pollers on Nodes in Solarwinds Orion NPM 30 | name: orion_node_poller 31 | namespace: '' 32 | - description: Updates Node in Solarwinds Orion NPM 33 | name: orion_update_node 34 | namespace: '' 35 | - description: Manage Volumes on Nodes in Solarwinds Orion NPM 36 | name: orion_volume 37 | namespace: '' 38 | - description: Gets info about a Volume in Solarwinds Orion NPM 39 | name: orion_volume_info 40 | namespace: '' 41 | release_date: '2023-09-26' 42 | 1.0.1: 43 | changes: 44 | bugfixes: 45 | - orion_node module - add snmp_version required_if polling_method == 'SNMP' 46 | - orion_node module - fix typo in logic for state 'managed' 47 | - orion_node module - unset default for snmp version in parameters, to fix issue 48 | 2 49 | minor_changes: 50 | - orion_node module - use datetime.now() instead of datetime.utcnow() for muting 51 | and unmanaging. utcnow() works fine for managing, but for muting the time 52 | needs to match server time to work correctly. 53 | release_summary: '| Released 2023-07-14 54 | 55 | ' 56 | fragments: 57 | - bugfixes_1.0.1.yml 58 | - minor_changes_1.0.1.yml 59 | - release_summary_1.0.1.yml 60 | release_date: '2023-09-26' 61 | 1.0.2: 62 | changes: 63 | bugfixes: 64 | - orion.py add_interface function - only regex pattern match if exact interface 65 | name is not found 66 | - orion_node module - don't set snmpv3 properties for node unless parameters 67 | are passed 68 | - orion_node_application module - typo with param name 'skip_duplicates' 69 | - orion_node_interface - add to documentation and examples to clarify regex 70 | pattern matching is supported 71 | minor_changes: 72 | - orion_node_interface module - add support for removing all interfaces if one 73 | is not specified 74 | release_summary: '| Released 2023-08-10 75 | 76 | ' 77 | fragments: 78 | - bugfixes_1.0.2.yml 79 | - minor_changes_1.0.2.yml 80 | - release_summary_1.0.2.yml 81 | release_date: '2023-09-26' 82 | 1.0.3: 83 | changes: 84 | bugfixes: 85 | - orion.py get_least_used_polling_engine - convert the query count to an int, 86 | to fix an issue with a deployment with only one poller 87 | minor_changes: 88 | - orion_node module - add support for using credential sets for SNMPv3 nodes, 89 | updated documentation with params that are required for SNMPv3 90 | release_summary: '| Released 2023-08-27 91 | 92 | ' 93 | fragments: 94 | - bugfixes_1.0.3.yml 95 | - minor_changes_1.0.3.yml 96 | - release_summary_1.0.3.yml 97 | release_date: '2023-09-26' 98 | 1.0.4: 99 | changes: 100 | bugfixes: 101 | - orion_node module - fix functionality for adding External nodes 102 | major_changes: 103 | - orion_node_interface module - add param 'regex' to explicitly state if you 104 | want to do pattern matching in interface name 105 | release_summary: '| Released 2023-09-26 106 | 107 | ' 108 | fragments: 109 | - bugfixes_1.0.4.yml 110 | - major_changes_1.0.4.yml 111 | - release_summary_1.0.4.yml 112 | release_date: '2023-09-26' 113 | 1.1.0: 114 | changes: 115 | major_changes: 116 | - Add dynamic inventory plugin jeisenbath.solarwinds.orion_nodes_inventory 117 | release_summary: '| Released 2023-12-1 118 | 119 | ' 120 | fragments: 121 | - major_changes_1.1.0.yml 122 | release_date: '2023-12-01' 123 | 1.2.0: 124 | changes: 125 | major_changes: 126 | - Added a role orion_node 127 | - Updated the example playbook to use the new role 128 | release_summary: Released 2024-03-01 129 | fragments: 130 | - major_changes.yml 131 | release_date: '2024-03-01' 132 | 1.3.0: 133 | changes: 134 | major_changes: 135 | - Add module orion_node_ncm - Adds/Removes an existing node to be managed in 136 | NCM. 137 | - Add module orion_node_poller_info - Gets pollers assigned to a node and their 138 | enabled status. 139 | - Add module orion_query - Runs a SWQL query against Orion database, outputs 140 | to json and optional CSV. 141 | release_summary: Released 2024-03-07 142 | fragments: 143 | - major_changes.yml 144 | modules: 145 | - description: Manages a node in Solarwinds NCM 146 | name: orion_node_ncm 147 | namespace: '' 148 | - description: Gets info about pollers assigned to a Node in Solarwinds Orion 149 | NPM 150 | name: orion_node_poller_info 151 | namespace: '' 152 | - description: Queries the Solarwinds Orion database 153 | name: orion_query 154 | namespace: '' 155 | release_date: '2024-03-07' 156 | 1.3.1: 157 | changes: 158 | minor_changes: 159 | - orion_node_interface - refactored to try and make as idempotent as possible, 160 | and return 'discovered' and 'interface' 161 | release_summary: Released 2024-03-11 162 | fragments: 163 | - minor_changes.yml 164 | release_date: '2024-03-11' 165 | 1.3.2: 166 | changes: 167 | minor_changes: 168 | - inventory plugin orion_nodes_inventory - add ansible vault support for the 169 | password parameter 170 | release_summary: Released 2024-04-17 171 | fragments: 172 | - minor_changes.yml 173 | release_date: '2024-04-17' 174 | 2.0.0: 175 | changes: 176 | breaking_changes: 177 | - All modules - add support for orionsdk 0.4.0 178 | - If using orionsdk 0.4.0 while still on a version of Solarwinds older than 179 | 2024.1.0, must set port to 17778 legacy API 180 | - SWIS API connection parameter for "port" added, with default "17774" to match 181 | orionsdk SwisClient default 182 | - SWIS API connection parameter for "verify" added, with default of "false" 183 | to match orionsdk SwisClient default 184 | release_summary: Released 2024-04-18 185 | fragments: 186 | - breaking.yml 187 | release_date: '2024-04-18' 188 | 2.1.0: 189 | changes: 190 | bugfixes: 191 | - Fixed an issue where ansible-lint would complain about missing parameters 192 | when a single yaml doc used multiple modules. 193 | major_changes: 194 | - Added module orion_node_interface_info to get interfaces currently monitored 195 | for a node. 196 | - Added orion_node_hardware_health module. This module allows for adding and 197 | removing hardware health sensors in Solarwinds Orion. 198 | minor_changes: 199 | - Add a poll_now() function to the OrionModule 200 | - Add a profile_name parameter to orion_node_ncm 201 | - Add correct check_mode logic to orion_ndoe_ncm 202 | - Call poll_now() for SNMP nodes in orion_node_info module. This logic will 203 | allow using 'until' task logic to validate node is polling. 204 | - Modified the example playbook for orion_add_node.yml to use the role keyword, 205 | and include a task for SNMP poll verification. 206 | - Update get_node() function to also return LastSystemUptimePollUtc 207 | - Updated orion_node module to no longer require snmpv3 credential set. 208 | - Updated orion_update_node exmaples to show updating to SNMPv3. 209 | - orion_node role - added tasks for new modules orion_node_ncm and orion_node_hardware_health 210 | release_summary: Released 2024-10-02 211 | fragments: 212 | - 2.1.0.yml 213 | - bugfix.yml 214 | - orion_node_interface_info.yml 215 | - orion_node_ncm.yml 216 | - orion_node_playbook.yml 217 | - role_updates.yml 218 | - utils.yml 219 | release_date: '2024-10-02' 220 | 2.1.1: 221 | changes: 222 | deprecated_features: 223 | - Bugfixes will be ported to stable-2.x branch for this collection when applicable 224 | to existing plugins until 2026. 225 | - Starting with version 3.0, Collection has been moved to jeisenbath.solarwinds 226 | namespace in order to publish to Ansible Galaxy. 227 | minor_changes: 228 | - Add support for Environment Variables for hostname, username, and password 229 | release_summary: 'Released 2025-01-18 230 | 231 | Added ENV variable support for plugins 232 | 233 | Created integration tests for modules 234 | 235 | Adds a CONTRIBUTING doc 236 | 237 | Fix sanity test errors 238 | 239 | ' 240 | fragments: 241 | - deprecate.yml 242 | - env.yml 243 | - summary.yml 244 | release_date: '2025-01-18' 245 | 3.0.0: 246 | changes: 247 | breaking_changes: 248 | - Bumped min ansible version to be up to date with supported releases. Older 249 | versions may work but will no longer be tested. 250 | - Migrated collection namespace from solarwinds.orion to jeisenbath.solarwinds 251 | release_summary: 'Released 2025-01-18 252 | 253 | Migrated collection to jeisenbath.solarwinds namespace in order to publish 254 | to Galaxy. 255 | 256 | Versions < 3.0 will stay on old solarwinds.orion namespace, and bugfixes will 257 | be ported to stable-2.x branch until 2025-12-31. 258 | 259 | ' 260 | fragments: 261 | - rename_namespace.yml 262 | release_date: '2025-01-18' 263 | 3.0.1: 264 | changes: 265 | bugfixes: 266 | - orion_node_interface - fixed a bug with adding a defined interface. Check 267 | mode was adding the interface, while non-check mode wasn't. 268 | release_summary: Released 2025-01-30 269 | fragments: 270 | - bugfixes.yml 271 | release_date: '2025-01-30' 272 | 3.1.0: 273 | changes: 274 | release_summary: Released 2025-08-25 275 | fragments: 276 | - release_summary.yml 277 | modules: 278 | - description: Manage Credential Sets for Solarwinds. 279 | name: orion_credential_set 280 | namespace: '' 281 | release_date: '2025-08-25' 282 | -------------------------------------------------------------------------------- /plugins/modules/orion_credential_set.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | from __future__ import absolute_import, division, print_function 7 | __metaclass__ = type 8 | 9 | DOCUMENTATION = r''' 10 | --- 11 | module: orion_credential_set 12 | short_description: Manage Credential Sets for Solarwinds. 13 | description: 14 | - Create Credential Sets in in Solarwinds Credential Set library. 15 | - Currently the SWIS API does not support removing credential sets. 16 | - Also can validate and assign credentials to a node. 17 | - Credentials sets must be created before assigned. 18 | version_added: "3.1.0" 19 | author: "Josh M. Eisenbath (@jeisenbath)" 20 | options: 21 | state: 22 | description: 23 | - Desired state of the credential set. 24 | required: True 25 | type: str 26 | choices: 27 | - present 28 | - assigned 29 | type: 30 | description: 31 | - Credential type. 32 | required: True 33 | type: str 34 | choices: 35 | - snmpv3 36 | - wmi 37 | credential_name: 38 | description: 39 | - Name of the credential set. 40 | required: True 41 | type: str 42 | snmpv3: 43 | description: 44 | - Properties of the credentials. 45 | - Required to create and verify credentials when assigned when I(type=snmpv3). 46 | required: False 47 | type: dict 48 | suboptions: 49 | username: 50 | description: 51 | - SNMPv3 username. 52 | type: str 53 | required: True 54 | auth_method: 55 | description: SNMPv3 Authentication Method 56 | type: str 57 | required: False 58 | choices: ['MD5', 'SHA1', 'SHA256', 'SHA512'] 59 | default: 'SHA1' 60 | auth_key: 61 | description: SNMPv3 Authentication Key 62 | type: str 63 | required: True 64 | auth_key_is_pwd: 65 | description: Whether or not SNMPv3 Authentication is password. 66 | type: bool 67 | required: False 68 | default: True 69 | priv_key: 70 | description: SNMPv3 Private Key 71 | type: str 72 | required: True 73 | priv_method: 74 | description: SNMPv3 Private Key Method 75 | type: str 76 | required: False 77 | choices: ['DES56', 'AES128', 'AES192', 'AES256'] 78 | default: 'DES56' 79 | priv_key_is_pwd: 80 | description: Whether or not SNMPv3 Authentication is password. 81 | type: bool 82 | required: False 83 | default: True 84 | owner: 85 | description: Credential set owner. 86 | type: str 87 | required: False 88 | default: 'Orion' 89 | wmi: 90 | description: 91 | - WMI credential properties. 92 | - Required to create and verify credentials when assigned when I(type=wmi). 93 | required: False 94 | type: dict 95 | suboptions: 96 | username: 97 | description: 98 | - WMI username. 99 | type: str 100 | required: True 101 | password: 102 | description: 103 | - WMI password. 104 | type: str 105 | required: True 106 | owner: 107 | description: Credential set owner. 108 | type: str 109 | required: False 110 | default: 'Orion' 111 | extends_documentation_fragment: 112 | - jeisenbath.solarwinds.orion_auth_options 113 | - jeisenbath.solarwinds.orion_node_options 114 | requirements: 115 | - orionsdk 116 | - requests 117 | ''' 118 | 119 | EXAMPLES = r''' 120 | - name: Create SNMPv3 credential set 121 | jeisenbath.solarwinds.orion_credential_set: 122 | hostname: "{{ solarwinds_server }}" 123 | username: "{{ solarwinds_username }}" 124 | password: "{{ solarwinds_password }}" 125 | state: present 126 | type: snmpv3 127 | credential_name: "{{ snmpv3_credential_set_name }}" 128 | snmpv3: 129 | username: "{{ snmpv3_username }}" 130 | auth_key: "{{ snmpv3_auth_key }}" 131 | priv_key: "{{ snmpv3_priv_key }}" 132 | delegate_to: localhost 133 | 134 | - name: Validate and assign Credential Set on an existing node 135 | jeisenbath.solarwinds.orion_credential_set: 136 | hostname: "{{ solarwinds_server }}" 137 | username: "{{ solarwinds_username }}" 138 | password: "{{ solarwinds_password }}" 139 | state: assigned 140 | type: snmpv3 141 | snmpv3: 142 | username: "{{ snmpv3_username }}" 143 | auth_key: "{{ snmpv3_auth_key }}" 144 | priv_key: "{{ snmpv3_priv_key }}" 145 | credential_name: "{{ snmpv3_credential_set_name }}" 146 | ip_address: "{{ node_ip_address }}" 147 | delegate_to: localhost 148 | 149 | ''' 150 | 151 | RETURN = r''' 152 | credential: 153 | description: Info about the credential set from Orion.Credential table. 154 | returned: always 155 | type: dict 156 | sample: { 157 | "CredentialType": "SolarWinds.Orion.Core.Models.Credentials.SnmpCredentialsV3", 158 | "ID": 1, 159 | "Name": "CredentialSetName" 160 | } 161 | orion_node: 162 | description: Info about an orion node. 163 | returned: When I(state=assigned) 164 | type: dict 165 | sample: { 166 | "caption": "localhost", 167 | "ipaddress": "127.0.0.1", 168 | "lastsystemuptimepollutc": "2024-09-25T18:34:20.7630000Z", 169 | "netobjectid": "N:12345", 170 | "nodeid": "12345", 171 | "objectsubtype": "SNMP", 172 | "status": 1, 173 | "statusdescription": "Node status is Up.", 174 | "unmanaged": false, 175 | "unmanagefrom": "1899-12-30T00:00:00+00:00", 176 | "unmanageuntil": "1899-12-30T00:00:00+00:00", 177 | "uri": "swis://host.domain.com/Orion/Orion.Nodes/NodeID=12345" 178 | } 179 | ''' 180 | 181 | from ansible.module_utils.basic import AnsibleModule 182 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.orion import OrionModule, orion_argument_spec 183 | from ansible_collections.jeisenbath.solarwinds.plugins.module_utils.credential import ( 184 | get_credentials, 185 | create_snmpv3_credentials, 186 | create_username_password_credentials, 187 | validate_snmp3_credentials, 188 | assign_credentials_to_node 189 | ) 190 | try: 191 | import requests 192 | HAS_REQUESTS = True 193 | requests.packages.urllib3.disable_warnings() 194 | except ImportError: 195 | HAS_REQUESTS = False 196 | except Exception: 197 | raise Exception 198 | 199 | 200 | def main(): 201 | argument_spec = orion_argument_spec() 202 | argument_spec.update( 203 | state=dict(required=True, choices=['present', 'assigned']), 204 | type=dict(required=True, choices=['snmpv3', 'wmi']), 205 | credential_name=dict(required=True, type='str'), 206 | snmpv3=dict(required=False, type='dict', 207 | options=dict( 208 | username=dict(required=True, type='str'), 209 | auth_key=dict(required=True, type='str', no_log=True), 210 | auth_method=dict(required=False, type='str', choices=['MD5', 'SHA1', 'SHA256', 'SHA512'], default='SHA1'), 211 | auth_key_is_pwd=dict(required=False, type='bool', default=True, no_log=False), 212 | priv_key=dict(required=True, type='str', no_log=True), 213 | priv_method=dict(required=False, type='str', choices=['DES56', 'AES128', 'AES192', 'AES256'], default='DES56'), 214 | priv_key_is_pwd=dict(required=False, type='bool', default=True, no_log=False), 215 | owner=dict(required=False, type='str', default='Orion'), 216 | )), 217 | wmi=dict(required=False, type='dict', 218 | options=dict( 219 | username=dict(required=True, type='str'), 220 | password=dict(required=True, type='str', no_log=True), 221 | owner=dict(required=False, type='str', default='Orion'), 222 | )), 223 | ) 224 | module = AnsibleModule( 225 | argument_spec, 226 | supports_check_mode=True, 227 | required_if=[ 228 | ('type', 'snmpv3', ['snmpv3']), 229 | ('type', 'wmi', ['wmi']), 230 | ('state', 'assigned', ['name', 'ip_address', 'node_id'], True) 231 | ], 232 | ) 233 | 234 | orion = OrionModule(module) 235 | 236 | credential_set = get_credentials(orion, module.params['credential_name']) 237 | 238 | if module.params['state'] == 'assigned': 239 | node = orion.get_node() 240 | if not node: 241 | module.fail_json(skipped=True, msg='Node not found') 242 | 243 | changed = False 244 | if module.params['state'] == 'present': 245 | try: 246 | if not credential_set: 247 | if not module.check_mode: 248 | if module.params['type'] == 'snmpv3': 249 | credential_set = create_snmpv3_credentials(orion, module.params['credential_name'], module.params['snmpv3']) 250 | elif module.params['type'] == 'wmi': 251 | credential_set = create_username_password_credentials(orion, module.params['credential_name'], module.params['wmi']) 252 | changed = True 253 | except Exception as OrionException: 254 | module.fail_json(msg='Failed to create Credential Set: {0}'.format(OrionException)) 255 | 256 | if module.params['state'] == 'assigned': 257 | try: 258 | if module.params['type'] == 'snmpv3': 259 | nodeSettingName = 'ROSNMPCredentialID' 260 | validated = validate_snmp3_credentials(orion, node, module.params['snmpv3']) 261 | if not validated: 262 | module.fail_json(msg='Failed to validate credentials on node.') 263 | elif module.params['type'] == 'wmi': 264 | nodeSettingName = 'WMICredential' 265 | assigned_cred = orion.swis_query( 266 | "SELECT SettingValue FROM Orion.NodeSettings WHERE NodeID = '{0}' and SettingName = '{1}'".format(node['nodeid'], nodeSettingName) 267 | ) 268 | if not assigned_cred: 269 | if not module.check_mode: 270 | assign_credentials_to_node(orion, node, credential_set, nodeSettingName) 271 | changed = True 272 | elif str(assigned_cred[0]['SettingValue']) != str(credential_set['ID']): 273 | if not module.check_mode: 274 | assign_credentials_to_node(orion, node, credential_set, nodeSettingName) 275 | changed = True 276 | module.exit_json(changed=changed, credential=credential_set, orion_node=node) 277 | except Exception as OrionException: 278 | module.fail_json(msg='Failed to assign credentials to node: {0}'.format(OrionException)) 279 | 280 | module.exit_json(changed=changed, credential=credential_set) 281 | 282 | 283 | if __name__ == "__main__": 284 | main() 285 | --------------------------------------------------------------------------------