Updated OS categorisation and bumped libmeshctrl to 1.1.1
This commit is contained in:
+67
-40
@@ -17,13 +17,14 @@ Script utilities are handled in the following section.
|
|||||||
class ScriptEndTrigger(Exception):
|
class ScriptEndTrigger(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def output_text(message: str, required=False):
|
def console(message: str, required: bool=False):
|
||||||
if required:
|
if required:
|
||||||
print(message)
|
print(message)
|
||||||
elif not args.silent:
|
elif not args.silent:
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
async def load_config(conf_file: str = './api.conf', segment: str = 'meshcentral-account') -> ConfigParser:
|
async def load_config(segment: str = 'meshcentral-account') -> ConfigParser:
|
||||||
|
conf_file = args.conf
|
||||||
if not os.path.exists(conf_file):
|
if not os.path.exists(conf_file):
|
||||||
raise ScriptEndTrigger(f'Missing config file {conf_file}. Provide an alternative path.')
|
raise ScriptEndTrigger(f'Missing config file {conf_file}. Provide an alternative path.')
|
||||||
|
|
||||||
@@ -94,31 +95,44 @@ async def compile_group_list(session: meshctrl.Session) -> dict:
|
|||||||
})
|
})
|
||||||
return local_device_list
|
return local_device_list
|
||||||
|
|
||||||
async def gather_targets(playbook: dict, group_list: dict) -> dict:
|
async def filter_devices(devices: list[dict], os_categories: dict, target_os: str = None) -> list[str]:
|
||||||
target_list = []
|
"""Filters devices based on reachability and optional OS criteria."""
|
||||||
|
valid_devices = []
|
||||||
|
|
||||||
if "device" in playbook and "group" not in playbook:
|
for device in devices:
|
||||||
|
if not device["reachable"]:
|
||||||
|
continue # Skip unreachable devices
|
||||||
|
|
||||||
|
if target_os:
|
||||||
|
if target_os in os_categories:
|
||||||
|
if device["device_os"] not in os_categories[target_os]:
|
||||||
|
continue # Skip if the device's OS is not in the allowed OS category
|
||||||
|
|
||||||
|
valid_devices.append(device["device_id"])
|
||||||
|
|
||||||
|
return valid_devices
|
||||||
|
|
||||||
|
async def gather_targets(playbook: dict, group_list: dict[str, list[dict]], os_categories: dict) -> list[str]:
|
||||||
|
"""Finds target devices based on playbook criteria (device or group)."""
|
||||||
|
|
||||||
|
target_list = []
|
||||||
|
target_os = playbook.get("target_os")
|
||||||
|
|
||||||
|
if "device" in playbook:
|
||||||
pseudo_target = playbook["device"]
|
pseudo_target = playbook["device"]
|
||||||
|
|
||||||
for group in group_list:
|
for group in group_list:
|
||||||
for device in group_list[group]:
|
for device in group_list[group]:
|
||||||
if device["reachable"] and pseudo_target == device["device_name"]:
|
if device["device_name"] == pseudo_target:
|
||||||
if "target_os" in playbook and str(playbook["target_os"]).lower() == str(device["device_os"]).lower():
|
matched_devices = await filter_devices([device], os_categories, target_os)
|
||||||
target_list.append(device["device_id"])
|
target_list.extend(matched_devices)
|
||||||
elif "target_os" not in playbook:
|
|
||||||
target_list.append(device["device_id"])
|
|
||||||
|
|
||||||
elif "group" in playbook and "device" not in playbook:
|
elif "group" in playbook:
|
||||||
pseudo_target = playbook["group"]
|
pseudo_target = playbook["group"]
|
||||||
|
|
||||||
for group in group_list:
|
if pseudo_target in group_list:
|
||||||
if pseudo_target == group:
|
matched_devices = await filter_devices(group_list[pseudo_target], os_categories, target_os)
|
||||||
for device in group_list[group]:
|
target_list.extend(matched_devices)
|
||||||
if device["reachable"]:
|
|
||||||
if "target_os" in playbook and str(playbook["target_os"]).lower() == str(device["device_os"]).lower():
|
|
||||||
target_list.append(device["device_id"])
|
|
||||||
elif "target_os" not in playbook:
|
|
||||||
target_list.append(device["device_id"])
|
|
||||||
|
|
||||||
return target_list
|
return target_list
|
||||||
|
|
||||||
@@ -126,8 +140,8 @@ async def execute_playbook(session: meshctrl.Session, targets: dict, playbook: d
|
|||||||
responses_list = {}
|
responses_list = {}
|
||||||
round = 1
|
round = 1
|
||||||
for task in playbook["tasks"]:
|
for task in playbook["tasks"]:
|
||||||
output_text(("\033[1m\033[92m" + str(round) + ". Running: " + task["name"] + "\033[0m"), False)
|
console(("\033[1m\033[92m" + str(round) + ". Running: " + task["name"] + "\033[0m"))
|
||||||
response = await session.run_command(nodeids=targets, command=task["command"], ignore_output=False, timeout=900)
|
response = await session.run_command(nodeids=targets, command=task["command"],ignore_output=False,timeout=900)
|
||||||
|
|
||||||
task_batch = []
|
task_batch = []
|
||||||
for device in response:
|
for device in response:
|
||||||
@@ -143,55 +157,68 @@ async def execute_playbook(session: meshctrl.Session, targets: dict, playbook: d
|
|||||||
}
|
}
|
||||||
round += 1
|
round += 1
|
||||||
|
|
||||||
output_text(("-" * 40), False)
|
console(("-" * 40))
|
||||||
output_text((json.dumps(responses_list,indent=4)), True)
|
console((json.dumps(responses_list,indent=4)), True)
|
||||||
|
|
||||||
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 yaml file.", required=True)
|
||||||
parser.add_argument("--conf", type=str, help="Path for the API configuration file (default: ./api.conf).", required=False)
|
|
||||||
|
parser.add_argument("-oc", "--oscategories", type=str, help="Path to the Operating System categories JSON file.", required=False, default="./os_categories.json")
|
||||||
|
parser.add_argument("--conf", type=str, help="Path for the API configuration file (default: ./meshcentral.conf).", required=False, default="./meshcentral.conf")
|
||||||
parser.add_argument("--nograce", action="store_true", help="Disable the grace 3 seconds before running the playbook.", required=False)
|
parser.add_argument("--nograce", action="store_true", help="Disable the grace 3 seconds before running the playbook.", required=False)
|
||||||
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", required=False)
|
||||||
|
|
||||||
global args
|
global args
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
local_categories_file = "./os_categories.json"
|
||||||
|
|
||||||
|
console(("-" * 40))
|
||||||
|
console("Playbook: " + args.playbook)
|
||||||
|
console("Operating System Categorisation file: " + args.oscategories)
|
||||||
|
console("Congiguration file: " + args.conf)
|
||||||
|
console("Grace: " + str((not args.nograce))) # Negation of bool for correct explanation
|
||||||
|
console("Silent: False") # Can be pre-defined because if silent flag was passed then none of this would be printed.
|
||||||
|
|
||||||
|
console(("-" * 40))
|
||||||
|
console(("\x1B[3mTrying to load the MeshCentral account credential file...\x1B[0m"))
|
||||||
|
console(("\x1B[3mTrying to load the Playbook yaml file and compile it into something workable...\x1B[0m"))
|
||||||
|
console(("\x1B[3mTrying to load the Operating System categorisation JSON file...\x1B[0m"))
|
||||||
try:
|
try:
|
||||||
output_text(("-" * 40), False)
|
with open(local_categories_file, "r") as file:
|
||||||
output_text(("\x1B[3mTrying to load the MeshCentral account credential file...\x1B[0m"), False)
|
os_categories = json.load(file)
|
||||||
output_text(("\x1B[3mTrying to load the Playbook yaml file and compile it into something workable...\x1B[0m"), False)
|
|
||||||
|
|
||||||
credentials, playbook = await asyncio.gather(
|
credentials, playbook = await asyncio.gather(
|
||||||
(load_config() if args.conf is None else load_config(args.conf)),
|
(load_config()),
|
||||||
(compile_book(args.playbook))
|
(compile_book(args.playbook))
|
||||||
)
|
)
|
||||||
|
|
||||||
output_text(("\x1B[3mConnecting to MeshCentral and establish a session using variables from previous credential file.\x1B[0m"), False)
|
console(("\x1B[3mConnecting to MeshCentral and establish a session using variables from previous credential file.\x1B[0m"))
|
||||||
session = await init_connection(credentials)
|
session = await init_connection(credentials)
|
||||||
|
|
||||||
output_text(("\x1B[3mGenerating group list with nodes and reference the targets from that.\x1B[0m"), False)
|
console(("\x1B[3mGenerating group list with nodes and reference the targets from that.\x1B[0m"))
|
||||||
group_list = await compile_group_list(session)
|
group_list = await compile_group_list(session)
|
||||||
targets_list = await gather_targets(playbook, group_list)
|
targets_list = await gather_targets(playbook, group_list, os_categories)
|
||||||
|
|
||||||
if len(targets_list) == 0:
|
if len(targets_list) == 0:
|
||||||
output_text(("\033[91mNo targets found or targets unreachable, quitting.\x1B[0m"), True)
|
console(("\033[91mNo targets found or targets unreachable, quitting.\x1B[0m"), True)
|
||||||
else:
|
else:
|
||||||
output_text(("-" * 40), False)
|
console(("-" * 40))
|
||||||
target_name = playbook["group"] if "group" in playbook else playbook["device"] # Quickly get the name.
|
target_name = playbook["group"] if "group" in playbook else playbook["device"] # Quickly get the name.
|
||||||
output_text(("\033[91mExecuting playbook on the target(s): " + target_name + ".\x1B[0m"), False)
|
console(("\033[91mExecuting playbook on the target(s): " + target_name + ".\x1B[0m"))
|
||||||
|
|
||||||
if not args.nograce:
|
if not args.nograce:
|
||||||
output_text(("\033[91mInitiating grace-period...\x1B[0m"), False)
|
console(("\033[91mInitiating grace-period...\x1B[0m"))
|
||||||
for x in range(3):
|
for x in range(3):
|
||||||
output_text(("\033[91m{}...\x1B[0m".format(x+1)), False)
|
console(("\033[91m{}...\x1B[0m".format(x+1)))
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
output_text(("-" * 40), False)
|
console(("-" * 40))
|
||||||
await execute_playbook(session, targets_list, playbook, group_list)
|
await execute_playbook(session, targets_list, playbook, group_list)
|
||||||
|
|
||||||
await session.close()
|
await session.close()
|
||||||
|
|
||||||
except OSError as message:
|
except OSError as message:
|
||||||
output_text(message, True)
|
console(message, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Debian": [
|
||||||
|
"Debian GNU/Linux 12 (bookworm)",
|
||||||
|
"Debian GNU/Linux 11 (bullseye)"
|
||||||
|
],
|
||||||
|
"Ubuntu": [
|
||||||
|
"Ubuntu 24.04.1 LTS"
|
||||||
|
]
|
||||||
|
}
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
asyncio==3.4.3
|
asyncio==3.4.3
|
||||||
pyyaml==6.0.2
|
pyyaml==6.0.2
|
||||||
libmeshctrl==1.1.0
|
libmeshctrl==1.1.1
|
||||||
|
|||||||
Reference in New Issue
Block a user