forked from solace-iot-team/ansible-solace-collection
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dynamic inventory plugin for Solace Cloud added. Documentation in REA…
…DME.md
- Loading branch information
1 parent
fb6ef95
commit 0bd8a47
Showing
2 changed files
with
257 additions
and
0 deletions.
There are no files selected for viewing
72 changes: 72 additions & 0 deletions
72
src/ansible_collections/solace/pubsub_plus/plugins/inventory/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# solace_cloud_inventory | ||
|
||
If you have a Solace cloud account with more than just a few services, it is no fun to fill the inventory file manually. | ||
|
||
Ansible has a concept of a "dynamic inventory plugin" - that is, the inventory "file" will be generated on the fly with some queries to the Solace Cloud API. | ||
|
||
## Basic Usage | ||
|
||
This description assumes, that you are able to use the ansible-solace-collection, that is: you have installed the collection and all requirements. | ||
|
||
This description assumes, that you have a valid Solace cloud token available. | ||
|
||
### Create inventory-config-yml file. | ||
|
||
As a pointer to the inventory plugin, you need an inventory config file. | ||
|
||
In the same directory as your playbooks live, create a yml file like this | ||
|
||
``` | ||
--- | ||
# solace_cloud_inventory.yml | ||
plugin: solace.pubsub_plus.solace_cloud_inventory | ||
solace_cloud_api_token: eyxwww... <place here your Solace cloud token> | ||
``` | ||
|
||
Check your inventory with: | ||
|
||
``` | ||
ansible-inventory -i solace_cloud_inventory.yml --playbook-dir ./ --list | ||
``` | ||
|
||
### Advanced examples | ||
#### Solace cloud token as environment variable | ||
|
||
If you dont want to put your Solace cloud token in a file on disk (and even worse : in a git repo), you could put the token in an environment variable SOLACE_CLOUD_TOKEN | ||
|
||
``` | ||
--- | ||
# solace_cloud_inventory.yml | ||
plugin: solace.pubsub_plus.solace_cloud_inventory | ||
``` | ||
|
||
Check your inventory with: | ||
|
||
``` | ||
SOLACE_CLOUD_TOKEN=eyxwwwxxxx ansible-inventory -i solace_cloud_inventory.yml --playbook-dir ./ --list | ||
``` | ||
|
||
#### Restrict your inventory to a subset of services | ||
|
||
You can restrict your inventory only to services, where the service name matches a (python) regular expression. | ||
|
||
For example, if you have a naming scheme for services, where all Integration brokers start with "int-", you could use a config file like this: | ||
|
||
``` | ||
--- | ||
# solace_cloud_inventory.yml | ||
plugin: solace.pubsub_plus.solace_cloud_inventory | ||
solace_cloud_api_token: eyxwww... <place here your Solace cloud token> | ||
service_filter: "^int-" | ||
``` | ||
|
||
Check your inventory with: | ||
|
||
``` | ||
ansible-inventory -i solace_cloud_inventory.yml --playbook-dir ./ --list | ||
``` | ||
|
||
|
185 changes: 185 additions & 0 deletions
185
src/ansible_collections/solace/pubsub_plus/plugins/inventory/solace_cloud_inventory.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
# solace_cloud_inventory.py | ||
|
||
# python 3 headers, required if submitting to Ansible | ||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
DOCUMENTATION = r''' | ||
name: solace_cloud_inventory.py | ||
author: Ulrich Herbst <[email protected]> | ||
version_added: "0.1" # for collections, use the collection version, not the Ansible version FIXME | ||
short_description: Get all services from a Solace Cloud org and return as ansible inventory | ||
description: Get all services from a Solace Cloud org and return as ansible inventory | ||
options: | ||
plugin: | ||
description: name of plugin. | ||
required: true | ||
choices: ['solace.pubsub_plus.solace_cloud_inventory'] | ||
solace_cloud_api_token: | ||
description: A token to identiy by Solace cloud. You can get your tokens in Solace cloud console. | ||
required: True | ||
env: | ||
- name: SOLACE_CLOUD_TOKEN | ||
solace_cloud_home: | ||
description: Location of Solace cloud home base | ||
default: us | ||
service_filter: | ||
description: If given, then all service_names will filtered with that regex | ||
''' | ||
|
||
import requests,re, urllib | ||
from pprint import pformat, pprint | ||
|
||
from ansible.plugins.inventory import BaseInventoryPlugin | ||
from ansible.errors import AnsibleError, AnsibleParserError | ||
|
||
class InventoryModule(BaseInventoryPlugin): | ||
|
||
NAME = 'solace.pubsub_plus.solace_cloud_inventory' | ||
|
||
def verify_file(self, path): | ||
'''Return true/false if this is possibly a valid file for solace_cloud_inventory. A token is absolutely necessary | ||
''' | ||
valid = False | ||
if super(InventoryModule, self).verify_file(path): | ||
# base class verifies that file exists and is readable by current user | ||
if path.endswith(('.yaml', '.yml')): | ||
valid = True | ||
return valid | ||
|
||
def parse(self, inventory, loader, path, cache=True): | ||
|
||
# call base method to ensure properties are available for use with other helper methods | ||
super(InventoryModule, self).parse(inventory, loader, path, cache) | ||
|
||
# this method will parse 'common format' inventory sources and | ||
# update any options declared in DOCUMENTATION as needed | ||
self._read_config_data(path) | ||
self.token = self.get_option('solace_cloud_api_token') | ||
self.homecloud = self.get_option('solace_cloud_home') | ||
self.service_filter = self.get_option('service_filter') | ||
self.debug=False | ||
|
||
self._generate_inventory(self.token,self.homecloud, self.debug) | ||
|
||
|
||
def _get_service_list(self, token, debug): | ||
"query Solace Cloud API to get a list of all services in this environment" | ||
|
||
solace_cloud_base_url = 'https://api.solace.cloud/api/v0' | ||
solace_cloud_auth_headers = {'Authorization' : f'Bearer {token}'} | ||
|
||
try: | ||
servicelist=requests.get(f'{solace_cloud_base_url}/services', | ||
headers = solace_cloud_auth_headers) | ||
servicelist.raise_for_status() | ||
except requests.exceptions.HTTPError as e: | ||
print("Unexpected HTTP response from server:") | ||
print(str(e)) | ||
except requests.ConnectionError as e: | ||
print("OOPS!! Connection Error. Make sure you are connected to Internet. Technical Details given below.\n") | ||
print(str(e)) | ||
except requests.Timeout as e: | ||
print("OOPS!! Timeout Error") | ||
print(str(e)) | ||
except requests.RequestException as e: | ||
print("OOPS!! General Error") | ||
print(str(e)) | ||
except KeyboardInterrupt: | ||
print("Someone closed the program") | ||
|
||
if debug: | ||
print( | ||
f'Services-List from Cloud-API:\n' | ||
f'{pformat(servicelist.json())}' | ||
) | ||
|
||
return servicelist.json()['data'] | ||
|
||
|
||
def _get_service_detail(self, serviceId, token, debug): | ||
"query Solace Cloud API to get details for a specific a list of all services in this environment" | ||
|
||
solace_cloud_base_url = 'https://api.solace.cloud/api/v0' | ||
solace_cloud_auth_headers = {'Authorization' : f'Bearer {token}'} | ||
|
||
try: | ||
service_detail=requests.get(f'{solace_cloud_base_url}/services/{serviceId}', | ||
headers = solace_cloud_auth_headers) | ||
service_detail.raise_for_status() | ||
except requests.exceptions.HTTPError as e: | ||
print("Unexpected HTTP response from server:") | ||
print(str(e)) | ||
except requests.ConnectionError as e: | ||
print("OOPS!! Connection Error. Make sure you are connected to Internet. Technical Details given below.\n") | ||
print(str(e)) | ||
except requests.Timeout as e: | ||
print("OOPS!! Timeout Error") | ||
print(str(e)) | ||
except requests.RequestException as e: | ||
print("OOPS!! General Error") | ||
print(str(e)) | ||
except KeyboardInterrupt: | ||
print("Someone closed the program") | ||
|
||
if debug: | ||
print( | ||
f'Service Details for {serviceId}:\n' | ||
f'{pformat(service_detail.json())}' | ||
) | ||
|
||
return service_detail.json()['data'] | ||
|
||
def _create_pod_name(self,routername): | ||
"translate the routername into a pod name" | ||
|
||
# Input: kiloproductioniiclitmopwysolaceprimary0 | ||
# Output: kilo-production-iiclitmopwy-solace-primary-0 | ||
|
||
# <size> - 'production' - <serviceID> - 'solace' - '<primary|backup|monitor>' - <number> | ||
|
||
m=re.fullmatch(r"(?P<sizing>.*)production(?P<serviceid>.*)solace(?P<node>primary|backup|monitor)(?P<counter>\d+)", routername) | ||
if m: | ||
return f"{m['sizing']}-production-{m['serviceid']}-solace-{m['node']}-{m['counter']}" | ||
else: | ||
return routername | ||
|
||
|
||
def _generate_inventory(self, token,homecloud, debug): | ||
|
||
for service in self._get_service_list(token, debug): | ||
service_name = service['name'] | ||
|
||
if self.service_filter and not re.search(self.service_filter, service_name): | ||
continue | ||
|
||
serviceId = service['serviceId'] | ||
service_detail = self._get_service_detail(serviceId, token, debug) | ||
|
||
self.inventory.add_host(host=service_name) | ||
self.inventory.set_variable(service_name, 'ansible_connection', 'local') | ||
self.inventory.set_variable(service_name, 'broker_type', 'solace_cloud') | ||
self.inventory.set_variable(service_name, 'sempv2_username', service_detail['msgVpnAttributes']['vpnAdminUsername']) | ||
self.inventory.set_variable(service_name, 'sempv2_password', service_detail['msgVpnAttributes']['vpnAdminPassword']) | ||
self.inventory.set_variable(service_name, 'sempv2_timeout', '60' ) | ||
self.inventory.set_variable(service_name, 'solace_cloud_service_id', service_detail['serviceId'] ) | ||
self.inventory.set_variable(service_name, 'solace_cloud_api_token', token) | ||
self.inventory.set_variable(service_name, 'virtual_router', 'primary' ) | ||
self.inventory.set_variable(service_name, 'vpn', service_detail['msgVpnName']) | ||
self.inventory.set_variable(service_name, 'podname_primary', self._create_pod_name(service_detail['cluster']['primaryRouterName'])) | ||
self.inventory.set_variable(service_name, 'podname_backup', self._create_pod_name(service_detail['cluster']['backupRouterName'])) | ||
self.inventory.set_variable(service_name, 'podname_monitor', self._create_pod_name(service_detail['cluster']['monitoringRouterName'])) | ||
self.inventory.set_variable(service_name, 'clusterName', service_detail['cluster']['name']) | ||
self.inventory.set_variable(service_name, 'clusterPassword', service_detail['cluster']['password']) | ||
|
||
# To get the SEMPv2 URI from the service_details is a bit complex: | ||
# in managementProtocols, there are multiple entries, one of it with the same "SolAdmin" | ||
# (for those known to Solace products for years... the name has historical reasons). | ||
# THe SolAdmin-entry has multiple endpoints defined, one of those with the name "Secured Management" | ||
endpoints=[mp for mp in service_detail['managementProtocols'] if mp['name']=='SolAdmin'][0]['endPoints'] | ||
uri=[ep for ep in endpoints if ep['name'] == 'Secured Management'][0]['uris'][0] | ||
parsed_uri=urllib.parse.urlparse(uri) | ||
self.inventory.set_variable(service_name, 'sempv2_host', parsed_uri.hostname ) | ||
self.inventory.set_variable(service_name, 'sempv2_port', parsed_uri.port) | ||
self.inventory.set_variable(service_name, 'sempv2_is_secure_connection', True ) |