update draft

This commit is contained in:
John Bowdre 2022-02-03 14:27:15 -06:00
parent a6a77ef4f9
commit 1ac29f9d6e
4 changed files with 149 additions and 125 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -24,59 +24,64 @@ tags:
comment: true # Disable comment if false. comment: true # Disable comment if false.
--- ---
I [recently wrote](tanzu-community-edition-k8s-homelab/#a-real-workload---phpipam) about getting started with VMware's [Tanzu Community Edition](https://tanzucommunityedition.io/) and deploying [phpIPAM](https://phpipam.net/) as my first real-world Kubernetes workload. Well I've spent much of my time since then working on a script which would help to populate my phpIPAM instance with a list of networks to monitor. I [recently wrote](/tanzu-community-edition-k8s-homelab/#a-real-workload---phpipam) about getting started with VMware's [Tanzu Community Edition](https://tanzucommunityedition.io/) and deploying [phpIPAM](https://phpipam.net/) as my first real-world Kubernetes workload. Well I've spent much of my time since then working on a script which would help to populate my phpIPAM instance with a list of networks to monitor.
### Planning and Exporting ### Planning and Exporting
The first step in making this work was to figure out which networks I wanted to import. We've got hundreds of different networks in use across our production vSphere environments. I focused only on those which are portgroups on distributed virtual switches since those configurations are pretty standardized (being vCenter constructs instead of configured on individual hosts). These dvPortGroups bear a naming standard which conveys all sorts of useful information, and it's easy and safe to rename any dvPortGroups which _don't_ fit the standard (unlike renaming portgroups on a standard virtual switch). The first step in making this work was to figure out which networks I wanted to import. We've got hundreds of different networks in use across our production vSphere environments. I focused only on those which are portgroups on distributed virtual switches since those configurations are pretty standardized (being vCenter constructs instead of configured on individual hosts). These dvPortGroups bear a naming standard which conveys all sorts of useful information, and it's easy and safe to rename any dvPortGroups which _don't_ fit the standard (unlike renaming portgroups on a standard virtual switch).
The standard naming convention is `[Site]-[Description] [Network Address]{/[Mask]}`. So the networks look something like this: The standard naming convention is `[Site/Description] [Network Address]{/[Mask]}`. So the networks (across two virtual datacenters and two dvSwitches) look something like this:
![Production dvPortGroups approximated in my testing lab environment](dvportgroups.png) ![Production dvPortGroups approximated in my testing lab environment](dvportgroups.png)
Some networks have masks in the name, some don't. Most networks correctly include the network address with a `0` in the last octet, but some use an `x` instead. And the VLANs associated with the networks have a varying number of digits. These are all things that I needed to keep in mind as I worked on a solution which would make a true best effort at importing all of these. Some networks have masks in the name, some don't; and some use an underscore (`_`) rather than a slash (`/`) to separate the network from the mask . Most networks correctly include the network address with a `0` in the last octet, but some use an `x` instead. And the VLANs associated with the networks have a varying number of digits. Consistency can be difficult so these are all things that I had to keep in mind as I worked on a solution which would make a true best effort at importing all of these.
As long as the dvPortGroup names stick to this format I can parse the name to come up with the site/location as well as the IP space of the network. The dvPortGroup also carries information about the associated VLAN, which is useful information to have. And I can easily export this information with a simple PowerCLI query: As long as the dvPortGroup names stick to this format I can parse the name to come up with a description as well as the IP space of the network. The dvPortGroup also carries information about the associated VLAN, which is useful information to have. And I can easily export this information with a simple PowerCLI query:
```powershell ```powershell
PS /home/john> get-vdportgroup | select Name, VlanConfiguration PS /home/john> get-vdportgroup | select Name, VlanConfiguration
Name VlanConfiguration Name VlanConfiguration
---- ----------------- ---- -----------------
MGT-Home 192.168.1.0 MGT-Home 192.168.1.0
MGT-Servers 172.16.10.0 VLAN 1610 MGT-Servers 172.16.10.0 VLAN 1610
BOW-Servers 172.16.20.0 VLAN 1620 BOW-Servers 172.16.20.0 VLAN 1620
BOW-Servers 172.16.30.x VLAN 4 BOW-Servers 172.16.30.0 VLAN 1630
BOW-Servers 172.16.40.0/26 VLAN 44 BOW-Servers 172.16.40.0 VLAN 1640
DRE-Servers 172.16.50.0/28 VLAN 400 DRE-Servers 172.16.50.0 VLAN 1650
DRE-Servers 172.16.60.0 VLAN 1660 DRE-Servers 172.16.60.x VLAN 1660
VPOT8-Mgmt 172.20.10.0/27 VLAN 20
VPOT8-Servers 172.20.10.32/27 VLAN 30
VPOT8-Servers 172.20.10.64_26 VLAN 40
``` ```
In my [homelab](/vmware-home-lab-on-intel-nuc-9/), I only have a single vCenter. In production, we've got a handful of vCenters, and each manages the hosts in a given region. So I can use information about which vCenter hosts a dvPortGroup to figure out which region a network is in. When I import this data into phpIPAM, I can use the vCenter name to assign [remote scan agents](https://github.com/jbowdre/phpipam-agent-docker) to networks based on the region that they're in. In my [homelab](/vmware-home-lab-on-intel-nuc-9/), I only have a single vCenter. In production, we've got a handful of vCenters, and each manages the hosts in a given region. So I can use information about which vCenter hosts a dvPortGroup to figure out which region a network is in. When I import this data into phpIPAM, I can use the vCenter name to assign [remote scan agents](https://github.com/jbowdre/phpipam-agent-docker) to networks based on the region that they're in. I can also grab information about which virtual datacenter a dvPortGroup lives in, which I'll use for grouping networks into sites or sections.
The vCenter can be found in the `Uid` property returned by `get-vdportgroup`: The vCenter can be found in the `Uid` property returned by `get-vdportgroup`:
```powershell ```powershell
PS /home/john> get-vdportgroup | select Name, VlanConfiguration, UID PS /home/john> get-vdportgroup | select Name, VlanConfiguration, Datacenter, Uid
Name VlanConfiguration Uid Name VlanConfiguration Datacenter Uid
---- ----------------- --- ---- ----------------- ---------- ---
MGT-Home 192.168.1.0 /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-27015/ MGT-Home 192.168.1.0 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-27015/
MGT-Servers 172.16.10.0 VLAN 1610 /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-27017/ MGT-Servers 172.16.10.0 VLAN 1610 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-27017/
BOW-Servers 172.16.20.0 VLAN 1620 /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28010/ BOW-Servers 172.16.20.0 VLAN 1620 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28010/
BOW-Servers 172.16.30.x VLAN 4 /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28011/ BOW-Servers 172.16.30.0 VLAN 1630 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28011/
BOW-Servers 172.16.40.0/26 VLAN 44 /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28012/ BOW-Servers 172.16.40.0 VLAN 1640 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28012/
DRE-Servers 172.16.50.0/28 VLAN 400 /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28013/ DRE-Servers 172.16.50.0 VLAN 1650 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28013/
DRE-Servers 172.16.60.0 VLAN 1660 /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28014/ DRE-Servers 172.16.60.x VLAN 1660 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28014/
VPOT8-Mgmt 172.20.10.0/… VLAN 20 Other Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-35018/
VPOT8-Servers 172.20.10… VLAN 30 Other Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-35019/
VPOT8-Servers 172.20.10… VLAN 40 Other Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-35020/
``` ```
It's not pretty, but it'll do the trick. All that's left is to export this data into a handy-dandy CSV-formatted file that I can easily parse for import: It's not pretty, but it'll do the trick. All that's left is to export this data into a handy-dandy CSV-formatted file that I can easily parse for import:
```powershell ```powershell
get-vdportgroup | select Name, VlanConfiguration, Uid | export-csv -NoTypeInformation ./networks.csv get-vdportgroup | select Name, VlanConfiguration, Datacenter, Uid | export-csv -NoTypeInformation ./networks.csv
``` ```
![My networks.csv export, including the networks which don't match the naming criteria and will be skipped by the import process.](/networks.csv.png) ![My networks.csv export, including the networks which don't match the naming criteria and will be skipped by the import process.](/networks.csv.png)
### Setting up phpIPAM ### Setting up phpIPAM
There are a few additional steps needed to enable API access to a freshly-installed phpIPAM installation. To start, I log in to my phpIPAM instance and navigate to the **Administration > Server Management > phpIPAM Settings** page, where I enabled both the *Prettify links* and *API* feature settings - making sure to hit the **Save** button at the bottom of the page once I do so. After [deploying a fresh phpIPAM instance on my Tanzu Community Edition Kubernetes cluster](/tanzu-community-edition-k8s-homelab/#a-real-workload---phpipam), there are a few additional steps needed to enable API access. To start, I log in to my phpIPAM instance and navigate to the **Administration > Server Management > phpIPAM Settings** page, where I enabled both the *Prettify links* and *API* feature settings - making sure to hit the **Save** button at the bottom of the page once I do so.
![Enabling the API](/server_settings.png) ![Enabling the API](/server_settings.png)
Then I need to head to the **User Management** page to create a new user that will be used to authenticate against the API: Then I need to head to the **User Management** page to create a new user that will be used to authenticate against the API:
@ -85,6 +90,9 @@ Then I need to head to the **User Management** page to create a new user that wi
And finally, I head to the **API** section to create a new API key with Read/Write permissions: And finally, I head to the **API** section to create a new API key with Read/Write permissions:
![API key creation](/api_user.png) ![API key creation](/api_user.png)
I'm also going to head in to **Administration > IP Related Management > Sections** and delete the default sample sections so that the inventory will be nice and empty:
![We don't need no stinkin' sections!](/empty_sections.png)
### Script time ### Script time
Well that's enough prep work; now it's time for the script. It's going to start by prompting the user to input required details like the fully-qualified host name of the phpIPAM server, the credentials and API key to use for the connection, and the CSV file from which to import the networks. Well that's enough prep work; now it's time for the script. It's going to start by prompting the user to input required details like the fully-qualified host name of the phpIPAM server, the credentials and API key to use for the connection, and the CSV file from which to import the networks.
@ -96,13 +104,30 @@ Notice that the script also prompts for a default set of DNS nameservers to be u
# The latest version of this script can be found on Github: # The latest version of this script can be found on Github:
# https://github.com/jbowdre/misc-scripts/blob/main/Python/phpipam-bulk-import.py # https://github.com/jbowdre/misc-scripts/blob/main/Python/phpipam-bulk-import.py
"""
This interactive script helps to import vSphere dvPortGroup networks into phpIPAM for monitoring IP usage.
It is assumed that the dvPortGroups are named like '[Description] [Network address]{/[mask]}':
Ex:
LAB-Management 192.168.1.0
BOW-Servers 172.16.10.0/26
Networks can be exported from vSphere via PowerCLI:
Get-VDPortgroup | Select Name, Datacenter, VlanConfiguration, Uid | Export-Csv -NoTypeInformation ./networks.csv
Subnets added to phpIPAM will be automatically configured for monitoring either using the built-in scan agent (default)
or a new remote scan agent named for the source vCenter ('vcenter_name-agent').
"""
import requests import requests
from collections import namedtuple from collections import namedtuple
check_cert = True check_cert = True
created = 0 created = 0
remote_agent = False remote_agent = False
mapping_set = namedtuple('mapping_set', ['name', 'id']) name_to_id = namedtuple('name_to_id', ['name', 'id'])
#for testing only #for testing only
# from requests.packages.urllib3.exceptions import InsecureRequestWarning # from requests.packages.urllib3.exceptions import InsecureRequestWarning
@ -119,8 +144,11 @@ def validate_input_is_not_empty(field, prompt):
return user_input return user_input
def get_unique_values_for_key(key, list_of_dict): def get_sorted_list_of_unique_values(key, list_of_dict):
return set(sub[key] for sub in list_of_dict) valueSet = set(sub[key] for sub in list_of_dict)
valueList = list(valueSet)
valueList.sort()
return valueList
def get_id_from_sets(name, sets): def get_id_from_sets(name, sets):
@ -142,70 +170,58 @@ def auth_session(uri, auth):
return token return token
def get_agent_sets(uri, token, vcenters): def get_agent_sets(uri, token, regions):
# check for and create missing remote scan agents, and return
# a list of namedTuples mapping the names to IDs
agent_sets = [] agent_sets = []
def create_agent_set(uri, token, agentName): def create_agent_set(uri, token, name):
import secrets import secrets
payload = { payload = {
'name': agentName, 'name': name,
'type': 'mysql', 'type': 'mysql',
'code': secrets.base64.urlsafe_b64encode(secrets.token_bytes(24)).decode("utf-8") 'code': secrets.base64.urlsafe_b64encode(secrets.token_bytes(24)).decode("utf-8"),
'description': f'Remote scan agent for region {name}'
} }
req = requests.post(f'{uri}/tools/scanagents/', data=payload, headers=token, verify=check_cert) req = requests.post(f'{uri}/tools/scanagents/', data=payload, headers=token, verify=check_cert)
id = req.json()['id'] id = req.json()['id']
agent_set = mapping_set(agentName, id) agent_set = name_to_id(name, id)
print(f'[AGENT_CREATE] Remote scan agent {agentName} created.') print(f'[AGENT_CREATE] {name} created.')
return agent_set return agent_set
for vcenter in vcenters: for region in regions:
agentName = f'{vcenter}-agent' name = regions[region]['name']
req = requests.get(f'{uri}/tools/scanagents/?filter_by=name&filter_value={agentName}', headers=token, verify=check_cert) req = requests.get(f'{uri}/tools/scanagents/?filter_by=name&filter_value={name}', headers=token, verify=check_cert)
if req.status_code == 200: if req.status_code == 200:
id = req.json()['data'][0]['id'] id = req.json()['data'][0]['id']
print(f'[AGENT_FOUND] Remote scan agent {agentName} found.') agent_set = name_to_id(name, id)
agent_set = mapping_set(agentName, id)
else: else:
agent_set = create_agent_set(uri, token, agentName) agent_set = create_agent_set(uri, token, name)
agent_sets.append(agent_set) agent_sets.append(agent_set)
return agent_sets return agent_sets
def get_section_sets(uri, token, sections, topSectionId): def get_section(uri, token, section, parentSectionId):
# check for and create missing sections, and return a list of
# namedTuples mapping the names to IDs
section_sets = []
def create_section_set(uri, token, section, topSectionId): def create_section(uri, token, section, parentSectionId):
payload = { payload = {
'name': section, 'name': section,
'masterSection': topSectionId, 'masterSection': parentSectionId,
'permissions': '{"2":"2"}', 'permissions': '{"2":"2"}',
'showVLAN': '1' 'showVLAN': '1'
} }
req = requests.post(f'{uri}/sections/', data=payload, headers=token, verify=check_cert) req = requests.post(f'{uri}/sections/', data=payload, headers=token, verify=check_cert)
id = req.json()['id'] id = req.json()['id']
section_set = mapping_set(section, id)
print(f'[SECTION_CREATE] Section {section} created.') print(f'[SECTION_CREATE] Section {section} created.')
return section_set return id
for section in sections: req = requests.get(f'{uri}/sections/{section}/', headers=token, verify=check_cert)
req = requests.get(f'{uri}/sections/{section}/', headers=token, verify=check_cert) if req.status_code == 200:
if req.status_code == 200: id = req.json()['data']['id']
id = req.json()['data']['id'] else:
print(f'[SECTION_FOUND] Section {section} found.') id = create_section(uri, token, section, parentSectionId)
section_set = mapping_set(section, id) return id
else:
section_set = create_section_set(uri, token, section, topSectionId)
section_sets.append(section_set)
return section_sets
def get_vlan_sets(uri, token, vlans): def get_vlan_sets(uri, token, vlans):
# check for and create missing VLANs, and return a list of
# namedTuples mapping the VLAN numbers to IDs
vlan_sets = [] vlan_sets = []
def create_vlan_set(uri, token, vlan): def create_vlan_set(uri, token, vlan):
@ -215,60 +231,53 @@ def get_vlan_sets(uri, token, vlans):
} }
req = requests.post(f'{uri}/vlan/', data=payload, headers=token, verify=check_cert) req = requests.post(f'{uri}/vlan/', data=payload, headers=token, verify=check_cert)
id = req.json()['id'] id = req.json()['id']
vlan_set = mapping_set(vlan, id) vlan_set = name_to_id(vlan, id)
print(f'[VLAN_CREATE] VLAN {vlan} created.') print(f'[VLAN_CREATE] VLAN {vlan} created.')
return vlan_set return vlan_set
for vlan in vlans: for vlan in vlans:
if vlan is not '': if vlan != 0:
req = requests.get(f'{uri}/vlan/?filter_by=number&filter_value={vlan}', headers=token, verify=check_cert) req = requests.get(f'{uri}/vlan/?filter_by=number&filter_value={vlan}', headers=token, verify=check_cert)
if req.status_code == 200: if req.status_code == 200:
id = req.json()['data'][0]['vlanId'] id = req.json()['data'][0]['vlanId']
print(f'[VLAN_FOUND] VLAN {vlan} found.') vlan_set = name_to_id(vlan, id)
vlan_set = mapping_set(vlan, id)
else: else:
vlan_set = create_vlan_set(uri, token, vlan) vlan_set = create_vlan_set(uri, token, vlan)
vlan_sets.append(vlan_set) vlan_sets.append(vlan_set)
return vlan_sets return vlan_sets
def get_nameserver_sets(uri, token, vcenters, ips): def get_nameserver_sets(uri, token, regions):
# check for and create missing nameservers, and return a list
# of namedTuples mapping the names to IDs
nameserver_sets = [] nameserver_sets = []
def create_nameserver_set(uri, token, ips, vcenter): def create_nameserver_set(uri, token, name, nameservers):
name = f'{vcenter}-nameserver'
payload = { payload = {
'name': name, 'name': name,
'namesrv1': ips, 'namesrv1': nameservers,
'description': f'Nameserver created for {vcenter}' 'description': f'Nameserver created for region {name}'
} }
req = requests.post(f'{uri}/tools/nameservers/', data=payload, headers=token, verify=check_cert) req = requests.post(f'{uri}/tools/nameservers/', data=payload, headers=token, verify=check_cert)
id = req.json()['id'] id = req.json()['id']
nameserver_set = mapping_set(name, id) nameserver_set = name_to_id(name, id)
print(f'[NAMESERVER_CREATE] Nameserver {name} created.') print(f'[NAMESERVER_CREATE] Nameserver {name} created.')
return nameserver_set return nameserver_set
for vcenter in vcenters: for region in regions:
name = f'{vcenter}-nameserver' name = regions[region]['name']
req = requests.get(f'{uri}/tools/nameservers/?filter_by=name&filter_value={name}', headers=token, verify=check_cert) req = requests.get(f'{uri}/tools/nameservers/?filter_by=name&filter_value={name}', headers=token, verify=check_cert)
if req.status_code == 200: if req.status_code == 200:
id = req.json()['data'][0]['id'] id = req.json()['data'][0]['id']
print(f'[NAMESERVER_FOUND] Nameserver {name} found.') nameserver_set = name_to_id(name, id)
nameserver_set = mapping_set(name, id)
else: else:
nameserver_set = create_nameserver_set(uri, token, ips, vcenter) nameserver_set = create_nameserver_set(uri, token, name, regions[region]['nameservers'])
nameserver_sets.append(nameserver_set) nameserver_sets.append(nameserver_set)
return nameserver_sets return nameserver_sets
def create_subnet(uri, token, network): def create_subnet(uri, token, network):
# create new subnets
def update_nameserver_permissions(uri, token, network): def update_nameserver_permissions(uri, token, network):
# update nameserver permissions so a subnet's parent section
# has access to the nameserver
nameserverId = network['nameserverId'] nameserverId = network['nameserverId']
sectionId = network['sectionId'] sectionId = network['sectionId']
req = requests.get(f'{uri}/tools/nameservers/{nameserverId}/', headers=token, verify=check_cert) req = requests.get(f'{uri}/tools/nameservers/{nameserverId}/', headers=token, verify=check_cert)
@ -328,21 +337,19 @@ def import_networks(filepath):
if network['subnet'].split('.')[-1].lower() == 'x': if network['subnet'].split('.')[-1].lower() == 'x':
network['subnet'] = network['subnet'].lower().replace('x', '0') network['subnet'] = network['subnet'].lower().replace('x', '0')
network['name'] = row['Name'] network['name'] = row['Name']
network['section'] = row['Name'].split('-')[0]
if '/' in row['Name'][-3]: if '/' in row['Name'][-3]:
network['mask'] = row['Name'].split('/')[-1] network['mask'] = row['Name'].split('/')[-1]
else: else:
network['mask'] = '24' network['mask'] = '24'
network['section'] = row['Datacenter']
try: try:
network['vlan'] = row['VlanConfiguration'].split('VLAN ')[1] network['vlan'] = int(row['VlanConfiguration'].split('VLAN ')[1])
except: except:
network['vlan'] = '' network['vlan'] = 0
network['vcenter'] = f"{(row['Uid'].split('@'))[1].split(':')[0].split('.')[0]}" network['vcenter'] = f"{(row['Uid'].split('@'))[1].split(':')[0].split('.')[0]}"
networks.append(network) networks.append(network)
line_count += 1 line_count += 1
print(f'\nProcessed {line_count} lines.') print(f'Processed {line_count} lines and found:')
print(f'Found {len(networks)} networks:\n')
print(networks)
return networks return networks
@ -350,7 +357,11 @@ def main():
# gather inputs # gather inputs
import socket import socket
import getpass import getpass
import os.path import argparse
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("filepath", type=Path)
print("""\n\n print("""\n\n
This script helps to add vSphere networks to phpIPAM for IP address management. It is expected This script helps to add vSphere networks to phpIPAM for IP address management. It is expected
@ -358,12 +369,47 @@ def main():
named like '[Site]-[Purpose] [Subnet IP]{/[mask]}' (ex: 'LAB-Servers 192.168.1.0'). The following PowerCLI named like '[Site]-[Purpose] [Subnet IP]{/[mask]}' (ex: 'LAB-Servers 192.168.1.0'). The following PowerCLI
command can be used to export the networks from vSphere: command can be used to export the networks from vSphere:
Get-VDPortgroup | Select Name, VlanConfiguration, Uid | Export-Csv -NoTypeInformation ./networks.csv Get-VDPortgroup | Select Name, Datacenter, VlanConfiguration, Uid | Export-Csv -NoTypeInformation ./networks.csv
Subnets added to phpIPAM will be automatically configured for monitoring either using the built-in Subnets added to phpIPAM will be automatically configured for monitoring either using the built-in
scan agent (default) or a new remote scan agent named for the source vCenter ('vcenter_name-agent'). scan agent (default) or a new remote scan agent named for the source vCenter ('vcenter_name-agent').
""") """)
input('Press enter to continue...')
try:
p = parser.parse_args()
filepath = p.filepath
except:
# make sure filepath is a path to an actual file
while True:
filepath = Path(validate_input_is_not_empty('Filepath', 'Path to CSV-formatted export from vCenter'))
if filepath.exists():
break
else:
print(f'[ERROR] Unable to find file at {filepath.name}.')
continue
# get collection of networks to import
networks = import_networks(filepath)
networkNames = get_sorted_list_of_unique_values('name', networks)
print(f'\n- {len(networkNames)} networks:\n\t{networkNames}')
vcenters = get_sorted_list_of_unique_values('vcenter', networks)
print(f'\n- {len(vcenters)} vCenter servers:\n\t{vcenters}')
vlans = get_sorted_list_of_unique_values('vlan', networks)
print(f'\n- {len(vlans)} VLANs:\n\t{vlans}')
sections = get_sorted_list_of_unique_values('section', networks)
print(f'\n- {len(sections)} Datacenters:\n\t{sections}')
regions = {}
for vcenter in vcenters:
nameservers = None
name = validate_input_is_not_empty('Region Name', f'Region name for vCenter {vcenter}')
for region in regions:
if name in regions[region]['name']:
nameservers = regions[region]['nameservers']
if not nameservers:
nameservers = validate_input_is_not_empty('Nameserver IPs', f"Comma-separated list of nameserver IPs in {name}")
nameservers = nameservers.replace(',',';').replace(' ','')
regions[vcenter] = {'name': name, 'nameservers': nameservers}
# make sure hostname resolves # make sure hostname resolves
while True: while True:
@ -383,19 +429,8 @@ def main():
username = validate_input_is_not_empty('Username', f'Username with read/write access to {hostname}') username = validate_input_is_not_empty('Username', f'Username with read/write access to {hostname}')
password = getpass.getpass(f'Password for {username}:\n') password = getpass.getpass(f'Password for {username}:\n')
apiAppId = validate_input_is_not_empty('App ID', f'App ID for API key (from https://{hostname}/administration/api/)') apiAppId = validate_input_is_not_empty('App ID', f'App ID for API key (from https://{hostname}/administration/api/)')
topSectionName = validate_input_is_not_empty('Top-level Section Name', f'Top-level Section name (from https://{hostname}/administration/sections/)')
nameserver_ips = validate_input_is_not_empty('Nameserver IPs', f'Comma-separated list default nameserver IPs for DNS lookups from {hostname}') agent = input('\nUse per-region remote scan agents instead of a single local scanner? (y/N):\n')
# make sure filepath is a path to an actual file
while True:
filepath = validate_input_is_not_empty('Filepath', 'Path to CSV-formatted export from vCenter')
if os.path.isfile(filepath):
break
else:
print(f'[ERROR] Unable to find file at {filepath}.')
continue
agent = input('\nUse per-vCenter remote scan agents instead of a single local scanner? (y/N):\n')
try: try:
if agent.lower()[0] == 'y': if agent.lower()[0] == 'y':
global remote_agent global remote_agent
@ -403,7 +438,7 @@ def main():
except: except:
pass pass
proceed = input(f'\n\nProceed with importing networks from {filepath} to {hostname}? (y/N):\n') proceed = input(f'\n\nProceed with importing {len(networkNames)} networks to {hostname}? (y/N):\n')
try: try:
if proceed.lower()[0] == 'y': if proceed.lower()[0] == 'y':
pass pass
@ -415,41 +450,30 @@ def main():
sys.exit("Operation aborted.") sys.exit("Operation aborted.")
del proceed del proceed
# get collection of networks to import
networks = import_networks(filepath)
vcenters = get_unique_values_for_key('vcenter', networks)
sections = get_unique_values_for_key('section', networks)
vlans = get_unique_values_for_key('vlan', networks)
# assemble variables # assemble variables
nameserver_ips = nameserver_ips.replace(',',';').replace(' ','')
uri = f'https://{hostname}/api/{apiAppId}' uri = f'https://{hostname}/api/{apiAppId}'
auth = (username, password) auth = (username, password)
topSectionName = [topSectionName]
# auth to phpIPAM # auth to phpIPAM
token = auth_session(uri, auth) token = auth_session(uri, auth)
# get lists of dictionaries matching names to IDs nameserver_sets = get_nameserver_sets(uri, token, regions)
nameserver_sets = get_nameserver_sets(uri, token, vcenters, nameserver_ips)
vlan_sets = get_vlan_sets(uri, token, vlans) vlan_sets = get_vlan_sets(uri, token, vlans)
topSection_sets = get_section_sets(uri, token, topSectionName, None)
topSectionId = topSection_sets[0].id
section_sets = get_section_sets(uri, token, sections, topSectionId)
if remote_agent: if remote_agent:
agent_sets = get_agent_sets(uri, token, vcenters) agent_sets = get_agent_sets(uri, token, regions)
# create the networks # create the networks
for network in networks: for network in networks:
network['nameserverId'] = get_id_from_sets(f"{network['vcenter']}-nameserver", nameserver_sets) network['region'] = regions[network['vcenter']]['name']
network['sectionId'] = get_id_from_sets(network['section'], section_sets) network['regionId'] = get_section(uri, token, network['region'], None)
network['topSectionId'] = topSectionId network['nameserverId'] = get_id_from_sets(network['region'], nameserver_sets)
if network['vlan'] is '': network['sectionId'] = get_section(uri, token, network['section'], network['regionId'])
if network['vlan'] == 0:
network['vlanId'] = None network['vlanId'] = None
else: else:
network['vlanId'] = get_id_from_sets(network['vlan'], vlan_sets) network['vlanId'] = get_id_from_sets(network['vlan'], vlan_sets)
if remote_agent: if remote_agent:
network['agentId'] = get_id_from_sets(f"{network['vcenter']}-agent", agent_sets) network['agentId'] = get_id_from_sets(network['region'], agent_sets)
else: else:
network['agentId'] = '1' network['agentId'] = '1'
create_subnet(uri, token, network) create_subnet(uri, token, network)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 416 KiB