diff --git a/docs/operating_system_filtering.md b/docs/operating_system_filtering.md new file mode 100644 index 0000000..6bb69f5 --- /dev/null +++ b/docs/operating_system_filtering.md @@ -0,0 +1,65 @@ +# **Understanding the OS Filtering Mechanism** + +## **Overview** +This function filters devices based on their **reachability** and an optional **OS category filter**. It supports: + +- **Broad OS categories** (e.g., `"Linux"` includes all OS versions under `"Linux"`) +- **Specific OS categories** (e.g., `"Debian"` only includes OS versions under `"Linux" -> "Debian"`) +- **Single category selection** (Only `target_os="Linux"` OR `target_os="Debian"` is used, never both at once) + +--- + +## **How It Works (Simplified)** + +### **1. OS Category Expansion** +The function first expands the `target_os` category by retrieving all valid OS names under it. + +#### **Example OS Category Structure:** +```json +{ + "Linux": { + "Debian": [ + "Debian GNU/Linux 12 (bookworm)", + "Debian GNU/Linux 11 (bullseye)" + ], + "Ubuntu": [ + "Ubuntu 24.04.1 LTS" + ] + } +} +``` + +#### **Expanding Different `target_os` Values:** + +| `target_os` | Expanded OS Versions | +|--------------|---------------------------------------------------| +| `"Linux"` | `{ "Debian GNU/Linux 12 (bookworm)", "Debian GNU/Linux 11 (bullseye)", "Ubuntu 24.04.1 LTS" }` | +| `"Debian"` | `{ "Debian GNU/Linux 12 (bookworm)", "Debian GNU/Linux 11 (bullseye)" }` | + +--- + +### **2. Device Filtering** +Once the function has the allowed OS versions, it checks each device: + +#### **Example Device List:** +```json +[ + {"device_id": "A1", "device_os": "Debian GNU/Linux 12 (bookworm)", "reachable": true}, + {"device_id": "A2", "device_os": "Ubuntu 24.04.1 LTS", "reachable": true}, + {"device_id": "A3", "device_os": "Windows 11", "reachable": true}, + {"device_id": "A4", "device_os": "Debian GNU/Linux 11 (bullseye)", "reachable": false} +] +``` + +#### **Filtering Behavior:** +| Device ID | Device OS | Reachable | Matches `target_os="Linux"` | Matches `target_os="Debian"` | +|-----------|----------------------------------|-----------|-------------------------------|-------------------------------| +| A1 | Debian GNU/Linux 12 (bookworm) | ✅ | ✅ | ✅ | +| A2 | Ubuntu 24.04.1 LTS | ✅ | ✅ | ❌ | +| A3 | Windows 11 | ✅ | ❌ | ❌ | +| A4 | Debian GNU/Linux 11 (bullseye) | ❌ | ❌ (Unreachable) | ❌ (Unreachable) | + +#### **Final Output:** +- If `target_os="Linux"`: `["A1", "A2"]` +- If `target_os="Debian"`: `["A1"]` +- If `target_os=None`: `["A1", "A2", "A3"]` or `target_os` is undefined \ No newline at end of file diff --git a/examples/aggregate_example.yaml b/examples/aggregate_example.yaml index b43ded5..ecf7648 100644 --- a/examples/aggregate_example.yaml +++ b/examples/aggregate_example.yaml @@ -1,7 +1,7 @@ --- name: Echo some text in the terminal of the device group: "Development" -target_os: "Debian" +target_os: "Linux" # <---- variables: - name: package_manager value: "apt" diff --git a/meshbook.py b/meshbook.py index 05a32ae..3f1bd52 100644 --- a/meshbook.py +++ b/meshbook.py @@ -96,22 +96,40 @@ async def compile_group_list(session: meshctrl.Session) -> dict: return local_device_list async def filter_targets(devices: list[dict], os_categories: dict, target_os: str = None) -> list[str]: - """Filters devices based on reachability and optional OS criteria.""" + """Filters devices based on reachability and optional OS criteria, supporting nested OS categories.""" valid_devices = [] + def get_os_variants(category: str, os_map: dict) -> set: + """Extracts all OS names under a given category if it exists.""" + for key, value in os_map.items(): + if key == category: + if isinstance(value, dict): # Expand nested categories + os_set = set() + for subcat in value: + os_set.update(get_os_variants(subcat, value)) + return os_set + elif isinstance(value, list): # Direct OS list + return set(value) + return set() + + allowed_os = set() + + # Identify correct OS filtering scope + for key in os_categories: + if key == target_os: + allowed_os = get_os_variants(target_os, os_categories) + break # Stop searching once a match is found + + if isinstance(os_categories[key], dict) and target_os in os_categories[key]: + allowed_os = get_os_variants(target_os, os_categories[key]) + break # Stop searching once a match is found + for device in devices: if not device["reachable"]: continue # Skip unreachable devices. - match target_os: - case None: # No filtering needed. - pass - case _ if target_os in os_categories and device["device_os"] in os_categories[target_os]: - pass # Allowed OS, continue processing. - case _: - continue # Skip if the OS is not in the allowed category. - - valid_devices.append(device["device_id"]) + if not target_os or device["device_os"] in allowed_os: + valid_devices.append(device["device_id"]) return valid_devices diff --git a/os_categories.json b/os_categories.json index 7346750..4eaefe9 100644 --- a/os_categories.json +++ b/os_categories.json @@ -1,9 +1,16 @@ { - "Debian": [ - "Debian GNU/Linux 12 (bookworm)", - "Debian GNU/Linux 11 (bullseye)" - ], - "Ubuntu": [ - "Ubuntu 24.04.1 LTS" - ] + "Linux": { + "Debian": [ + "Debian GNU/Linux 12 (bookworm)", + "Debian GNU/Linux 11 (bullseye)" + ], + "Ubuntu": [ + "Ubuntu 24.04.1 LTS" + ] + }, + "MacOS": { + "Sequoia": [ + "macOS 15.0.1" + ] + } } \ No newline at end of file