added bulk delete vpg by vm example

This commit is contained in:
Mushkin
2026-01-23 16:23:16 -05:00
parent ad5f6ac4c4
commit 71067072e2
2 changed files with 183 additions and 0 deletions
+17
View File
@@ -118,12 +118,29 @@ The script will:
3. Ask for confirmation before applying
4. Apply changes if confirmed
## Step 6: Delete VPGs by VM List
Delete VPGs associated with VMs listed in a CSV file. The CSV must include a `VMName` column
(case-insensitive). Each VM is validated with the `vms.list_vms` API before deletion.
```bash
cd examples/bulk_vpg_actions
python delete_vpgs_from_csv.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--csv_file "vm_list.csv" \
--sync \
--ignore_ssl
```
## File Structure
All scripts and templates are located in the `examples/bulk_vpg_actions` directory:
```
examples/bulk_vpg_actions/
├── README.md
├── delete_vpgs_from_csv.py
├── export_site_resources_to_csv.py
├── export_vpg_settings_nics_to_csv.py
├── import_vpg_settings_nics_from_csv.py
@@ -0,0 +1,166 @@
#!/usr/bin/env python3
# Legal Disclaimer
# This script is an example script and is not supported under any Zerto support program or service.
# The author and Zerto further disclaim all implied warranties including, without limitation,
# any implied warranties of merchantability or of fitness for a particular purpose.
# In no event shall Zerto, its authors or anyone else involved in the creation,
# production or delivery of the scripts be liable for any damages whatsoever (including,
# without limitation, damages for loss of business profits, business interruption, loss of business
# information, or other pecuniary loss) arising out of the use of or the inability to use the sample
# scripts or documentation, even if the author or Zerto has been advised of the possibility of such damages.
# The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
"""
Delete VPGs by VM list (CSV)
This script reads a CSV file with a required "VMName" column (case-insensitive),
validates each VM using the VMs.list_vms API, and deletes the VPG associated
with each VM using VPGs.delete_vpg.
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
--csv_file: Path to CSV file containing VMName column
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python delete_vpgs_from_csv.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--csv_file "vm_list.csv" \
--ignore_ssl
"""
import argparse
import csv
import logging
import os
import sys
import urllib3
from typing import List, Optional
# Add parent directory to Python path
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def setup_argparse() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Delete VPGs by VM list from a CSV file")
parser.add_argument('--zvm_address', required=True, help='ZVM address')
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument('--csv_file', required=True, help='Path to CSV file with VMName column')
parser.add_argument('--ignore_ssl', action='store_true', help='Ignore SSL certificate validation')
return parser
def find_vmname_column(fieldnames: List[str]) -> Optional[str]:
for field in fieldnames:
if field and field.strip().lower() == "vmname":
return field
return None
def load_vm_names(csv_file: str) -> List[str]:
with open(csv_file, newline='', encoding='utf-8-sig') as file_handle:
reader = csv.DictReader(file_handle)
if not reader.fieldnames:
raise ValueError("CSV file has no header row")
vmname_column = find_vmname_column(reader.fieldnames)
if not vmname_column:
raise ValueError("CSV file must include a VMName column (case-insensitive)")
vm_names: List[str] = []
for row_index, row in enumerate(reader, start=2):
raw_name = row.get(vmname_column, "")
vm_name = raw_name.strip() if raw_name else ""
if not vm_name:
logger.warning(f"Row {row_index} has empty VMName. Skipping.")
continue
vm_names.append(vm_name)
if not vm_names:
raise ValueError("No VM names found in CSV file")
return vm_names
def main() -> None:
args = setup_argparse().parse_args()
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
vm_names = load_vm_names(args.csv_file)
deleted_vpg_ids = set()
for vm_name in vm_names:
logger.info(f"Processing VM '{vm_name}'...")
vms = client.vms.list_vms(vm_name=vm_name)
if not vms:
print(f"VM '{vm_name}' not found. Skipping.")
continue
if isinstance(vms, dict):
vms = [vms]
matching_vms = [
vm for vm in vms
if vm.get("VmName", "").lower() == vm_name.lower()
]
if not matching_vms:
print(f"VM '{vm_name}' not found (case-insensitive match). Skipping.")
continue
for vm in matching_vms:
vpg_identifier = vm.get("VpgIdentifier")
if not vpg_identifier:
print(f"VM '{vm_name}' has no VPG identifier. Skipping.")
continue
if vpg_identifier in deleted_vpg_ids:
logger.info(f"VPG '{vpg_identifier}' already deleted. Skipping.")
continue
vpg_name = vm.get("VpgName")
if not vpg_name:
vpg_info = client.vpgs.list_vpgs(vpg_identifier=vpg_identifier)
if isinstance(vpg_info, dict):
vpg_name = vpg_info.get("VpgName")
if not vpg_name:
print(f"Could not determine VPG name for VM '{vm_name}' (ID: {vpg_identifier}). Skipping.")
continue
try:
logger.info(f"Deleting VPG '{vpg_name}' (ID: {vpg_identifier}) for VM '{vm_name}'...")
client.vpgs.delete_vpg(vpg_name=vpg_name, keep_recovery_volumes=False)
deleted_vpg_ids.add(vpg_identifier)
except Exception as exc:
logger.error(f"Failed to delete VPG '{vpg_name}' for VM '{vm_name}': {exc}")
continue
if __name__ == '__main__':
main()