"""A VM's snapshot tree, flattened, and snapshot lookup by name.""" from __future__ import annotations from collections.abc import Iterator from typing import Any from napalm_device_types.models import SnapshotDict def _walk(nodes: list[dict[str, Any]], parent: str) -> Iterator[tuple[dict[str, Any], str]]: for node in nodes: yield node, parent yield from _walk(node.get("childSnapshotList", []), node.get("name", "")) def _nodes(vm: dict[str, Any]) -> Iterator[tuple[dict[str, Any], str]]: return _walk((vm.get("snapshot") or {}).get("rootSnapshotList", []), "") def snapshot_list(vm: dict[str, Any]) -> list[SnapshotDict]: """Every snapshot, depth first, each naming its parent (``""`` for a root).""" return [ { "name": node.get("name", ""), "vm": vm.get("name", ""), "created": float(node.get("createTime", 0.0)), "description": node.get("description", ""), # A snapshot taken with memory records the VM as powered on. "has_memory": node.get("state") == "poweredOn", "parent": parent, } for node, parent in _nodes(vm) ] def find_snapshot(vm: dict[str, Any], name: str) -> str: """MoRef of the one snapshot called ``name``. Raises ``ValueError`` when there is none -- or more than one: vSphere does not require snapshot names to be unique, and choosing one of two candidates could delete or roll back to the wrong state. """ matches = [node["snapshot"] for node, _ in _nodes(vm) if node.get("name") == name] if not matches: raise ValueError(f"VM {vm.get('name')!r} has no snapshot named {name!r}") if len(matches) > 1: raise ValueError( f"VM {vm.get('name')!r} has {len(matches)} snapshots named {name!r}; " "rename one in vSphere first" ) return matches[0]