Rewrite working now only needs output formatting.

This commit is contained in:
2025-01-08 16:56:25 +01:00
parent e5e4aba47e
commit 6a0127be78
7 changed files with 128 additions and 56 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: Refresh the apt cache name: Refresh the apt cache
company: "Temp-Agents" group: "Dev"
variables: variables:
- name: package_manager - name: package_manager
value: "apt" value: "apt"
+3 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: Echo some text in the terminal of the device name: Echo some text in the terminal of the device
device: "raspberrypi5" group: "Kubernetes"
variables: variables:
- name: var1 - name: var1
value: "Testing" value: "Testing"
@@ -9,3 +9,5 @@ variables:
tasks: tasks:
- name: Echo! - name: Echo!
command: "echo {{ var1 }}/{{ var2 }}" command: "echo {{ var1 }}/{{ var2 }}"
- name: Echo 2!
command: "echo womp womp"
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: Refresh the apt cache name: Refresh the apt cache
company: "Temp-Agents" group: "Temp-Agents"
variables: variables:
- name: package_manager - name: package_manager
value: "apt" value: "apt"
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: Ping Multiple Points name: Ping Multiple Points
company: "Temp-Agents" group: "Temp-Agents"
variables: variables:
- name: host1 - name: host1
value: "1.1.1.1" value: "1.1.1.1"
+1
View File
@@ -1,3 +1,4 @@
argparse
asyncio asyncio
configparser configparser
pyyaml pyyaml
+110 -41
View File
@@ -10,41 +10,51 @@ import meshctrl
import os import os
import yaml import yaml
'''
Script utilities are handled in the following section.
'''
class ScriptEndTrigger(Exception): class ScriptEndTrigger(Exception):
"""Custom Exception to handle script termination events."""
pass pass
def load_config(conffile: str = None, segment: str = 'meshcentral-account') -> ConfigParser: async def load_config(conf_file: str = './api.conf', segment: str = 'meshcentral-account') -> ConfigParser:
conffile = conffile or './api.conf' if not os.path.exists(conf_file):
raise ScriptEndTrigger(f'Missing config file {conf_file}. Provide an alternative path.')
if not os.path.exists(conffile):
raise ScriptEndTrigger(f'Missing config file {conffile}. Provide an alternative path.')
config = ConfigParser() config = ConfigParser()
try: try:
config.read(conffile) config.read(conf_file)
except Exception as err: except Exception as err:
raise ConfigError(f"Error reading configuration file '{conffile}': {err}") raise ScriptEndTrigger(f"Error reading configuration file '{conf_file}': {err}")
if segment not in config: if segment not in config:
raise ScriptEndTrigger(f'Segment "{segment}" not found in config file {conffile}.') raise ScriptEndTrigger(f'Segment "{segment}" not found in config file {conf_file}.')
return config[segment] return config[segment]
class meshbook(): async def init_connection(credentials: dict) -> meshctrl.Session:
@staticmethod session = meshctrl.Session(
def compile_book(pb_path) -> dict: credentials['websocket_url'],
playbook = meshbook.read_yaml(pb_path) user=credentials['username'],
playbook = meshbook.replace_placeholders(playbook) password=credentials['password']
)
await session.initialized.wait()
return session
def output(args: argparse.Namespace, message: str):
if not args.silent or args.information:
print(message)
'''
Creation and compilation happends in the following section, where the yaml gets read in, and edited accordingly.
'''
async def compile_book(playbook_file: dict) -> dict:
playbook = open(playbook_file, 'r')
playbook = await replace_placeholders(yaml.safe_load(playbook))
return playbook return playbook
@staticmethod async def replace_placeholders(playbook: dict) -> dict:
def read_yaml(file_path: str) -> dict:
with open(file_path, 'r') as file:
return yaml.safe_load(file)
@staticmethod
def replace_placeholders(playbook: dict) -> dict:
variables = {var["name"]: var["value"] for var in playbook.get("variables", [])} variables = {var["name"]: var["value"] for var in playbook.get("variables", [])}
for task in playbook.get("tasks", []): for task in playbook.get("tasks", []):
@@ -55,40 +65,99 @@ class meshbook():
task["command"] = command task["command"] = command
return playbook return playbook
async def run_book(creds): '''
print(creds) Creation and compilation of the MeshCentral nodes list (list of all nodes available to the user in the configuration) is handled in the following section.
'''
async def compile_group_list(session: meshctrl.Session) -> dict:
devices_response = await session.list_devices(details=False, timeout=10)
local_device_list = {}
for device in devices_response:
if device.meshname not in local_device_list:
local_device_list[device.meshname] = []
local_device_list[device.meshname].append({
"device_id": device.nodeid,
"device_name": device.name,
"device_os": device.os_description,
"device_tags": device.tags,
"reachable": device.connected
})
return local_device_list
async def gather_targets(group_list: dict, playbook: dict) -> dict:
target_list = []
if "device" in playbook and "group" not in playbook:
pseudo_target = playbook["device"]
for group in group_list:
for device in group_list[group]:
if device["reachable"] and pseudo_target == device["device_name"]:
target_list.append(device["device_id"])
elif "group" in playbook and "device" not in playbook:
pseudo_target = playbook["group"]
for group in group_list:
if pseudo_target == group:
for device in group_list[group]:
if device["reachable"]:
target_list.append(device["device_id"])
return target_list
async def execute_playbook(session: meshctrl.Session, targets: dict, playbook: dict):
for task in playbook["tasks"]:
print("Running:", task["name"])
response = await session.run_command(nodeids=targets, command=task["command"], timeout=300)
print(json.dumps(response,indent=4))
for device in response:
device_result = str(response[device]["result"])
device_result = device_result.replace("Run commands completed.", "")
print("AFTER", device_result)
async def main(): async def main():
parser = argparse.ArgumentParser(description="Process command-line arguments") parser = argparse.ArgumentParser(description="Process command-line arguments")
parser.add_argument("-pb", "--playbook", type=str, help="Path to the playbook file.", required=True) parser.add_argument("-pb", "--playbook", type=str, help="Path to the playbook file.", required=True)
parser.add_argument("--conf", type=str, help="Path for the API configuration file (default: ./api.conf).", required=False)
parser.add_argument("--conf", type=str, help="Path for the API configuration file (default: ./api.conf).") parser.add_argument("--noout", action="store_true", help="Makes the program not output response data.", required=False)
parser.add_argument("--nojson", action="store_true", help="Makes the program not output the JSON response data.") parser.add_argument("-s", "--silent", action="store_true", help="Suppress terminal output.", required=False)
parser.add_argument("-s", "--silent", action="store_true", help="Suppress terminal output.") parser.add_argument("-i", "--information", action="store_true", help="Add the calculations and other informational data to the output.", required=False)
parser.add_argument("-i", "--information", action="store_true", help="Add the calculations and other informational data to the output.")
args = parser.parse_args() args = parser.parse_args()
try: try:
credentials = load_config(args.conf) output(args, "Trying to load the MeshCentral account credential file...")
playbook = meshbook.compile_book(args.playbook) output(args, "Trying to load the Playbook yaml file and compile it into something workable...")
session = meshctrl.Session( credentials, playbook = await asyncio.gather(
credentials['websocket_url'], (load_config() if args.conf is None else load_config(args.conf)),
user=credentials['username'], (compile_book(args.playbook))
password=credentials['password']
) )
await session.initialized.wait()
raw_device_list = await session.list_devices(timeout=10)
device_list = raw_device_list
print(device_list) output(args, "Connecting to MeshCentral and establish a session using variables from previous credential file.")
session = await init_connection(credentials)
output(args, "Generating group list with nodes and reference the targets from that.")
group_list = await compile_group_list(session)
targets_list = await gather_targets(group_list, playbook)
if len(targets_list) == 0:
output(args, "No targets found or targets unreachable, quitting.")
else:
output(args, "Executing playbook on the targets.")
output(args, json.dumps(targets_list,indent=4))
await execute_playbook(session, targets_list, playbook)
await session.close() await session.close()
except ScriptEndTrigger as e: except ScriptEndTrigger as message:
if not args.silent or args.information: output(args, message)
print(e)
if __name__ == "__main__": if __name__ == "__main__":
+1 -1
View File
@@ -1,4 +1,4 @@
[meshcentral-service] [meshcentral-account]
websocket_url = websocket_url =
username = username =
password = password =