I recently wrote about getting started with VMware's Tanzu Community Edition and deploying phpIPAM 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
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 (across two virtual datacenters and two dvSwitches) look something like this:
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 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:
1PS /home/john> get-vdportgroup | select Name, VlanConfiguration
2
3Name VlanConfiguration
4---- -----------------
5MGT-Home 192.168.1.0
6MGT-Servers 172.16.10.0 VLAN 1610
7BOW-Servers 172.16.20.0 VLAN 1620
8BOW-Servers 172.16.30.0 VLAN 1630
9BOW-Servers 172.16.40.0 VLAN 1640
10DRE-Servers 172.16.50.0 VLAN 1650
11DRE-Servers 172.16.60.x VLAN 1660
12VPOT8-Mgmt 172.20.10.0/27 VLAN 20
13VPOT8-Servers 172.20.10.32/27 VLAN 30
14VPOT8-Servers 172.20.10.64_26 VLAN 40
In my homelab, 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 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
:
1PS /home/john> get-vdportgroup | select Name, VlanConfiguration, Datacenter, Uid
2
3Name VlanConfiguration Datacenter Uid
4---- ----------------- ---------- ---
5MGT-Home 192.168.1.0 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-27015/
6MGT-Servers 172.16.10.0 VLAN 1610 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-27017/
7BOW-Servers 172.16.20.0 VLAN 1620 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28010/
8BOW-Servers 172.16.30.0 VLAN 1630 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28011/
9BOW-Servers 172.16.40.0 VLAN 1640 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28012/
10DRE-Servers 172.16.50.0 VLAN 1650 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28013/
11DRE-Servers 172.16.60.x VLAN 1660 Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-28014/
12VPOT8-Mgmt 172.20.10.0/… VLAN 20 Other Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-35018/
13VPOT8-Servers 172.20.10… VLAN 30 Other Lab /VIServer=lab\john@vcsa.lab.bowdre.net:443/DistributedPortgroup=DistributedVirtualPortgroup-dvportgroup-35019/
14VPOT8-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:
1get-vdportgroup | select Name, VlanConfiguration, Datacenter, Uid | export-csv -NoTypeInformation ./networks.csv
Setting up phpIPAM
After deploying a fresh phpIPAM instance on my Tanzu Community Edition Kubernetes cluster, 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.
Then I need to head to the User Management page to create a new user that will be used to authenticate against the API:
And finally, I head to the API section to create a new API key with Read/Write permissions:
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:
Script time
Well that's enough prep work; now it's time for the Python3 script:
1# The latest version of this script can be found on Github:
2# https://github.com/jbowdre/misc-scripts/blob/main/Python/phpipam-bulk-import.py
3
4import requests
5from collections import namedtuple
6
7check_cert = True
8created = 0
9remote_agent = False
10name_to_id = namedtuple('name_to_id', ['name', 'id'])
11
12## for testing only:
13# from requests.packages.urllib3.exceptions import InsecureRequestWarning
14# requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
15# check_cert = False
16
17## Makes sure input fields aren't blank.
18def validate_input_is_not_empty(field, prompt):
19 while True:
20 user_input = input(f'\n{prompt}:\n')
21 if len(user_input) == 0:
22 print(f'[ERROR] {field} cannot be empty!')
23 continue
24 else:
25 return user_input
26
27
28## Takes in a list of dictionary items, extracts all the unique values for a given key,
29# and returns a sorted list of those.
30def get_sorted_list_of_unique_values(key, list_of_dict):
31 valueSet = set(sub[key] for sub in list_of_dict)
32 valueList = list(valueSet)
33 valueList.sort()
34 return valueList
35
36
37## Match names and IDs
38def get_id_from_sets(name, sets):
39 return [item.id for item in sets if name == item.name][0]
40
41
42## Authenticate to phpIPAM endpoint and return an auth token
43def auth_session(uri, auth):
44 print(f'Authenticating to {uri}...')
45 try:
46 req = requests.post(f'{uri}/user/', auth=auth, verify=check_cert)
47 except:
48 raise requests.exceptions.RequestException
49 if req.status_code != 200:
50 print(f'[ERROR] Authentication failure: {req.json()}')
51 raise requests.exceptions.RequestException
52 token = {"token": req.json()['data']['token']}
53 print('\n[AUTH_SUCCESS] Authenticated successfully!')
54 return token
55
56
57## Find or create a remote scan agent for each region (vcenter)
58def get_agent_sets(uri, token, regions):
59 agent_sets = []
60
61 def create_agent_set(uri, token, name):
62 import secrets
63 # generate a random secret to be used for identifying this agent
64 payload = {
65 'name': name,
66 'type': 'mysql',
67 'code': secrets.base64.urlsafe_b64encode(secrets.token_bytes(24)).decode("utf-8"),
68 'description': f'Remote scan agent for region {name}'
69 }
70 req = requests.post(f'{uri}/tools/scanagents/', data=payload, headers=token, verify=check_cert)
71 id = req.json()['id']
72 agent_set = name_to_id(name, id)
73 print(f'[AGENT_CREATE] {name} created.')
74 return agent_set
75
76 for region in regions:
77 name = regions[region]['name']
78 req = requests.get(f'{uri}/tools/scanagents/?filter_by=name&filter_value={name}', headers=token, verify=check_cert)
79 if req.status_code == 200:
80 id = req.json()['data'][0]['id']
81 agent_set = name_to_id(name, id)
82 else:
83 agent_set = create_agent_set(uri, token, name)
84 agent_sets.append(agent_set)
85 return agent_sets
86
87
88## Find or create a section for each virtual datacenter
89def get_section(uri, token, section, parentSectionId):
90
91 def create_section(uri, token, section, parentSectionId):
92 payload = {
93 'name': section,
94 'masterSection': parentSectionId,
95 'permissions': '{"2":"2"}',
96 'showVLAN': '1'
97 }
98 req = requests.post(f'{uri}/sections/', data=payload, headers=token, verify=check_cert)
99 id = req.json()['id']
100 print(f'[SECTION_CREATE] Section {section} created.')
101 return id
102
103 req = requests.get(f'{uri}/sections/{section}/', headers=token, verify=check_cert)
104 if req.status_code == 200:
105 id = req.json()['data']['id']
106 else:
107 id = create_section(uri, token, section, parentSectionId)
108 return id
109
110
111## Find or create VLANs
112def get_vlan_sets(uri, token, vlans):
113 vlan_sets = []
114
115 def create_vlan_set(uri, token, vlan):
116 payload = {
117 'name': f'VLAN {vlan}',
118 'number': vlan
119 }
120 req = requests.post(f'{uri}/vlan/', data=payload, headers=token, verify=check_cert)
121 id = req.json()['id']
122 vlan_set = name_to_id(vlan, id)
123 print(f'[VLAN_CREATE] VLAN {vlan} created.')
124 return vlan_set
125
126 for vlan in vlans:
127 if vlan != 0:
128 req = requests.get(f'{uri}/vlan/?filter_by=number&filter_value={vlan}', headers=token, verify=check_cert)
129 if req.status_code == 200:
130 id = req.json()['data'][0]['vlanId']
131 vlan_set = name_to_id(vlan, id)
132 else:
133 vlan_set = create_vlan_set(uri, token, vlan)
134 vlan_sets.append(vlan_set)
135 return vlan_sets
136
137
138## Find or create nameserver configurations for each region
139def get_nameserver_sets(uri, token, regions):
140
141 nameserver_sets = []
142
143 def create_nameserver_set(uri, token, name, nameservers):
144 payload = {
145 'name': name,
146 'namesrv1': nameservers,
147 'description': f'Nameserver created for region {name}'
148 }
149 req = requests.post(f'{uri}/tools/nameservers/', data=payload, headers=token, verify=check_cert)
150 id = req.json()['id']
151 nameserver_set = name_to_id(name, id)
152 print(f'[NAMESERVER_CREATE] Nameserver {name} created.')
153 return nameserver_set
154
155 for region in regions:
156 name = regions[region]['name']
157 req = requests.get(f'{uri}/tools/nameservers/?filter_by=name&filter_value={name}', headers=token, verify=check_cert)
158 if req.status_code == 200:
159 id = req.json()['data'][0]['id']
160 nameserver_set = name_to_id(name, id)
161 else:
162 nameserver_set = create_nameserver_set(uri, token, name, regions[region]['nameservers'])
163 nameserver_sets.append(nameserver_set)
164 return nameserver_sets
165
166
167## Find or create subnet for each dvPortGroup
168def create_subnet(uri, token, network):
169
170 def update_nameserver_permissions(uri, token, network):
171 nameserverId = network['nameserverId']
172 sectionId = network['sectionId']
173 req = requests.get(f'{uri}/tools/nameservers/{nameserverId}/', headers=token, verify=check_cert)
174 permissions = req.json()['data']['permissions']
175 permissions = str(permissions).split(';')
176 if not sectionId in permissions:
177 permissions.append(sectionId)
178 if 'None' in permissions:
179 permissions.remove('None')
180 permissions = ';'.join(permissions)
181 payload = {
182 'permissions': permissions
183 }
184 req = requests.patch(f'{uri}/tools/nameservers/{nameserverId}/', data=payload, headers=token, verify=check_cert)
185
186 payload = {
187 'subnet': network['subnet'],
188 'mask': network['mask'],
189 'description': network['name'],
190 'sectionId': network['sectionId'],
191 'scanAgent': network['agentId'],
192 'nameserverId': network['nameserverId'],
193 'vlanId': network['vlanId'],
194 'pingSubnet': '1',
195 'discoverSubnet': '1',
196 'resolveDNS': '1',
197 'DNSrecords': '1'
198 }
199 req = requests.post(f'{uri}/subnets/', data=payload, headers=token, verify=check_cert)
200 if req.status_code == 201:
201 network['subnetId'] = req.json()['id']
202 update_nameserver_permissions(uri, token, network)
203 print(f"[SUBNET_CREATE] Created subnet {req.json()['data']}")
204 global created
205 created += 1
206 elif req.status_code == 409:
207 print(f"[SUBNET_EXISTS] Subnet {network['subnet']}/{network['mask']} already exists.")
208 else:
209 print(f"[ERROR] Problem creating subnet {network['name']}: {req.json()}")
210
211
212## Import list of networks from the specified CSV file
213def import_networks(filepath):
214 print(f'Importing networks from {filepath}...')
215 import csv
216 import re
217 ipPattern = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.[0-9xX]{1,3}')
218 networks = []
219 with open(filepath) as csv_file:
220 reader = csv.DictReader(csv_file)
221 line_count = 0
222 for row in reader:
223 network = {}
224 if line_count > 0:
225 if(re.search(ipPattern, row['Name'])):
226 network['subnet'] = re.findall(ipPattern, row['Name'])[0]
227 if network['subnet'].split('.')[-1].lower() == 'x':
228 network['subnet'] = network['subnet'].lower().replace('x', '0')
229 network['name'] = row['Name']
230 if '/' in row['Name'][-3]:
231 network['mask'] = row['Name'].split('/')[-1]
232 elif '_' in row['Name'][-3]:
233 network['mask'] = row['Name'].split('_')[-1]
234 else:
235 network['mask'] = '24'
236 network['section'] = row['Datacenter']
237 try:
238 network['vlan'] = int(row['VlanConfiguration'].split('VLAN ')[1])
239 except:
240 network['vlan'] = 0
241 network['vcenter'] = f"{(row['Uid'].split('@'))[1].split(':')[0].split('.')[0]}"
242 networks.append(network)
243 line_count += 1
244 print(f'Processed {line_count} lines and found:')
245 return networks
246
247
248def main():
249 import socket
250 import getpass
251 import argparse
252 from pathlib import Path
253
254 parser = argparse.ArgumentParser()
255 parser.add_argument("filepath", type=Path)
256
257 # Accept CSV file as an argument to the script or prompt for input if necessary
258 try:
259 p = parser.parse_args()
260 filepath = p.filepath
261 except:
262 # make sure filepath is a path to an actual file
263 print("""\n\n
264 This script helps to add vSphere networks to phpIPAM for IP address management. It is expected
265 that the vSphere networks are configured as portgroups on distributed virtual switches and
266 named like '[Description] [Subnet IP]{/[mask]}' (ex: 'LAB-Servers 192.168.1.0'). The following PowerCLI
267 command can be used to export the networks from vSphere:
268
269 Get-VDPortgroup | Select Name, Datacenter, VlanConfiguration, Uid | Export-Csv -NoTypeInformation ./networks.csv
270
271 Subnets added to phpIPAM will be automatically configured for monitoring either using the built-in
272 scan agent (default) or a new remote scan agent for each vCenter.
273 """)
274 while True:
275 filepath = Path(validate_input_is_not_empty('Filepath', 'Path to CSV-formatted export from vCenter'))
276 if filepath.exists():
277 break
278 else:
279 print(f'[ERROR] Unable to find file at {filepath.name}.')
280 continue
281
282 # get collection of networks to import
283 networks = import_networks(filepath)
284 networkNames = get_sorted_list_of_unique_values('name', networks)
285 print(f'\n- {len(networkNames)} networks:\n\t{networkNames}')
286 vcenters = get_sorted_list_of_unique_values('vcenter', networks)
287 print(f'\n- {len(vcenters)} vCenter servers:\n\t{vcenters}')
288 vlans = get_sorted_list_of_unique_values('vlan', networks)
289 print(f'\n- {len(vlans)} VLANs:\n\t{vlans}')
290 sections = get_sorted_list_of_unique_values('section', networks)
291 print(f'\n- {len(sections)} Datacenters:\n\t{sections}')
292
293 regions = {}
294 for vcenter in vcenters:
295 nameservers = None
296 name = validate_input_is_not_empty('Region Name', f'Region name for vCenter {vcenter}')
297 for region in regions:
298 if name in regions[region]['name']:
299 nameservers = regions[region]['nameservers']
300 if not nameservers:
301 nameservers = validate_input_is_not_empty('Nameserver IPs', f"Comma-separated list of nameserver IPs in {name}")
302 nameservers = nameservers.replace(',',';').replace(' ','')
303 regions[vcenter] = {'name': name, 'nameservers': nameservers}
304
305 # make sure hostname resolves
306 while True:
307 hostname = input('\nFully-qualified domain name of the phpIPAM host:\n')
308 if len(hostname) == 0:
309 print('[ERROR] Hostname cannot be empty.')
310 continue
311 try:
312 test = socket.gethostbyname(hostname)
313 except:
314 print(f'[ERROR] Unable to resolve {hostname}.')
315 continue
316 else:
317 del test
318 break
319
320 username = validate_input_is_not_empty('Username', f'Username with read/write access to {hostname}')
321 password = getpass.getpass(f'Password for {username}:\n')
322 apiAppId = validate_input_is_not_empty('App ID', f'App ID for API key (from https://{hostname}/administration/api/)')
323
324 agent = input('\nUse per-region remote scan agents instead of a single local scanner? (y/N):\n')
325 try:
326 if agent.lower()[0] == 'y':
327 global remote_agent
328 remote_agent = True
329 except:
330 pass
331
332 proceed = input(f'\n\nProceed with importing {len(networkNames)} networks to {hostname}? (y/N):\n')
333 try:
334 if proceed.lower()[0] == 'y':
335 pass
336 else:
337 import sys
338 sys.exit("Operation aborted.")
339 except:
340 import sys
341 sys.exit("Operation aborted.")
342 del proceed
343
344 # assemble variables
345 uri = f'https://{hostname}/api/{apiAppId}'
346 auth = (username, password)
347
348 # auth to phpIPAM
349 token = auth_session(uri, auth)
350
351 # create nameserver entries
352 nameserver_sets = get_nameserver_sets(uri, token, regions)
353 vlan_sets = get_vlan_sets(uri, token, vlans)
354 if remote_agent:
355 agent_sets = get_agent_sets(uri, token, regions)
356
357 # create the networks
358 for network in networks:
359 network['region'] = regions[network['vcenter']]['name']
360 network['regionId'] = get_section(uri, token, network['region'], None)
361 network['nameserverId'] = get_id_from_sets(network['region'], nameserver_sets)
362 network['sectionId'] = get_section(uri, token, network['section'], network['regionId'])
363 if network['vlan'] == 0:
364 network['vlanId'] = None
365 else:
366 network['vlanId'] = get_id_from_sets(network['vlan'], vlan_sets)
367 if remote_agent:
368 network['agentId'] = get_id_from_sets(network['region'], agent_sets)
369 else:
370 network['agentId'] = '1'
371 create_subnet(uri, token, network)
372
373 print(f'\n[FINISH] Created {created} of {len(networks)} networks.')
374
375
376if __name__ == "__main__":
377 main()
I'll run it and provide the path to the network export CSV file:
1python3 phpipam-bulk-import.py ~/networks.csv
The script will print out a little descriptive bit about what sort of networks it's going to try to import and then will straight away start processing the file to identify the networks, vCenters, VLANs, and datacenters which will be imported:
1Importing networks from /home/john/networks.csv...
2Processed 17 lines and found:
3
4- 10 networks:
5 ['BOW-Servers 172.16.20.0', 'BOW-Servers 172.16.30.0', 'BOW-Servers 172.16.40.0', 'DRE-Servers 172.16.50.0', 'DRE-Servers 172.16.60.x', 'MGT-Home 192.168.1.0', 'MGT-Servers 172.16.10.0', 'VPOT8-Mgmt 172.20.10.0/27', 'VPOT8-Servers 172.20.10.32/27', 'VPOT8-Servers 172.20.10.64_26']
6
7- 1 vCenter servers:
8 ['vcsa']
9
10- 10 VLANs:
11 [0, 20, 30, 40, 1610, 1620, 1630, 1640, 1650, 1660]
12
13- 2 Datacenters:
14 ['Lab', 'Other Lab']
It then starts prompting for the additional details which will be needed:
1Region name for vCenter vcsa:
2Labby
3
4Comma-separated list of nameserver IPs in Lab vCenter:
5192.168.1.5
6
7Fully-qualified domain name of the phpIPAM host:
8ipam-k8s.lab.bowdre.net
9
10Username with read/write access to ipam-k8s.lab.bowdre.net:
11api-user
12Password for api-user:
13
14
15App ID for API key (from https://ipam-k8s.lab.bowdre.net/administration/api/):
16api-user
17
18Use per-region remote scan agents instead of a single local scanner? (y/N):
19y
Up to this point, the script has only been processing data locally, getting things ready for talking to the phpIPAM API. But now, it prompts to confirm that we actually want to do the thing (yes please) and then gets to work:
1Proceed with importing 10 networks to ipam-k8s.lab.bowdre.net? (y/N):
2y
3Authenticating to https://ipam-k8s.lab.bowdre.net/api/api-user...
4
5[AUTH_SUCCESS] Authenticated successfully!
6[VLAN_CREATE] VLAN 20 created.
7[VLAN_CREATE] VLAN 30 created.
8[VLAN_CREATE] VLAN 40 created.
9[VLAN_CREATE] VLAN 1610 created.
10[VLAN_CREATE] VLAN 1620 created.
11[VLAN_CREATE] VLAN 1630 created.
12[VLAN_CREATE] VLAN 1640 created.
13[VLAN_CREATE] VLAN 1650 created.
14[VLAN_CREATE] VLAN 1660 created.
15[SECTION_CREATE] Section Labby created.
16[SECTION_CREATE] Section Lab created.
17[SUBNET_CREATE] Created subnet 192.168.1.0/24
18[SUBNET_CREATE] Created subnet 172.16.10.0/24
19[SUBNET_CREATE] Created subnet 172.16.20.0/24
20[SUBNET_CREATE] Created subnet 172.16.30.0/24
21[SUBNET_CREATE] Created subnet 172.16.40.0/24
22[SUBNET_CREATE] Created subnet 172.16.50.0/24
23[SUBNET_CREATE] Created subnet 172.16.60.0/24
24[SECTION_CREATE] Section Other Lab created.
25[SUBNET_CREATE] Created subnet 172.20.10.0/27
26[SUBNET_CREATE] Created subnet 172.20.10.32/27
27[SUBNET_CREATE] Created subnet 172.20.10.64/26
28
29[FINISH] Created 10 of 10 networks.
Success! Now I can log in to my phpIPAM instance and check out my newly-imported subnets:
Even the one with the weird name formatting was parsed and imported correctly:
So now phpIPAM knows about the vSphere networks I care about, and it can keep track of which vLAN and nameservers go with which networks. Great! But it still isn't scanning or monitoring those networks, even though I told the script that I wanted to use a remote scan agent. And I can check in the Administration > Server management > Scan agents section of the phpIPAM interface to see my newly-created agent configuration.
... but I haven't actually deployed an agent yet. I'll do that by following the same basic steps described here to spin up my phpipam-agent
on Kubernetes, and I'll plug in that automagically-generated code for the IPAM_AGENT_KEY
environment variable:
1---
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: phpipam-agent
6spec:
7 selector:
8 matchLabels:
9 app: phpipam-agent
10 replicas: 1
11 template:
12 metadata:
13 labels:
14 app: phpipam-agent
15 spec:
16 containers:
17 - name: phpipam-agent
18 image: ghcr.io/jbowdre/phpipam-agent:latest
19 env:
20 - name: IPAM_DATABASE_HOST
21 value: "ipam-k8s.lab.bowdre.net"
22 - name: IPAM_DATABASE_NAME
23 value: "phpipam"
24 - name: IPAM_DATABASE_USER
25 value: "phpipam"
26 - name: IPAM_DATABASE_PASS
27 value: "VMware1!"
28 - name: IPAM_DATABASE_PORT
29 value: "3306"
30 - name: IPAM_AGENT_KEY
31 value: "CxtRbR81r1ojVL2epG90JaShxIUBl0bT"
32 - name: IPAM_SCAN_INTERVAL
33 value: "15m"
34 - name: IPAM_RESET_AUTODISCOVER
35 value: "false"
36 - name: IPAM_REMOVE_DHCP
37 value: "false"
38 - name: TZ
39 value: "UTC"
I kick it off with a kubectl apply
command and check back a few minutes later (after the 15-minute interval defined in the above YAML) to see that it worked, the remote agent scanned like it was supposed to and is reporting IP status back to the phpIPAM database server:
I think I've got some more tweaks to do with this environment (why isn't phpIPAM resolving hostnames despite the correct DNS servers getting configured?) but this at least demonstrates a successful proof-of-concept import thanks to my Python script. Sure, I only imported 10 networks here, but I feel like I'm ready to process the several hundred which are available in our production environment now.
And who knows, maybe this script will come in handy for someone else. Until next time!