Skip to content

Commit d3f6dee

Browse files
authored
Updated bulk config to use ncm library (#66)
Updated bulk config to use ncm library
1 parent 06a9971 commit d3f6dee

4 files changed

Lines changed: 221 additions & 105 deletions

File tree

bulk_config/README.md

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,66 @@
11
# bulk_config.py
22

3-
bulk configure devices in NCM from .csv file
3+
Bulk configure devices in NCM from router_grid.csv file using column headers.
44

5-
1. Create routers.csv with router IDs listed in column A and other
6-
device-specific values in subsequent columns (B, C, D, etc)
7-
2. Use NCM Config Editor to build a config template, then click
8-
"View Pending Changes" and copy the config
9-
3. Paste your config below in the build_config() function and replace
10-
config values with corresponding csv column letters
11-
4. Enter API Keys and run script
5+
## Setup
126

13-
Example config for a csv file with hostname in column B:
7+
1. Install dependencies:
8+
```bash
9+
pip install -r requirements.txt
10+
```
1411

15-
[{
16-
"system": {
17-
"system_id": column["B"]
12+
## Usage
13+
14+
1. Export router_grid.csv from the Device View in NCM
15+
2. Use NCM's device-level Edit Config screen to build your configuration
16+
3. Copy the pending config output and paste it into the build_config return value
17+
4. Update router_grid.csv to contain the columns that need to be applied to the device level config
18+
5. Update build_config to reference router_grid column headers with row_data.get('column_name')
19+
6. Update csv_file variable if using a different filename than 'router_grid.csv'
20+
7. Update API keys with your NCM API credentials
21+
8. Run the script:
22+
```bash
23+
python bulk_config.py
24+
```
25+
26+
## Example
27+
28+
For the included router_grid.csv with columns `id`, `name`, `desc`, `asset_id`, `primary_lan_ip`, `2ghz_ssid`, `5ghz_ssid`:
29+
30+
```python
31+
"system": {
32+
"system_id": row_data.get('name', ''),
33+
"asset_id": row_data.get('asset_id', ''),
34+
"desc": row_data.get('desc', '')
35+
},
36+
"lan": {
37+
"00000000-0d93-319d-8220-4a1fb0372b51": {
38+
"ip_address": row_data.get('primary_lan_ip', '')
39+
}
40+
},
41+
"wlan": {
42+
"radio": {
43+
"0": {
44+
"bss": {
45+
"0": {
46+
"ssid": row_data.get('2ghz_ssid', '')
47+
}
1848
}
1949
},
20-
[]
21-
]
50+
"1": {
51+
"bss": {
52+
"0": {
53+
"ssid": row_data.get('5ghz_ssid', '')
54+
}
55+
}
56+
}
57+
}
58+
}
59+
```
60+
61+
## Requirements
62+
63+
- Python 3.5+
64+
- NCM API credentials
65+
- router_grid.csv file exported from NCM Device View
66+
- Device-level configuration built using NCM Edit Config screen

bulk_config/bulk_config.py

Lines changed: 158 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,192 @@
1-
"""bulk_config.py - bulk configure devices in NCM from .csv file.
2-
3-
1. Create routers.csv with router IDs listed in column A and other
4-
device-specific values in subsequent columns (B, C, D, etc)
5-
2. Use NCM Config Editor to build a config template, then click
6-
"View Pending Changes" and copy the config
7-
3. Paste your config below in the build_config() function and replace
8-
config values with corresponding csv column letters
9-
4. Enter API Keys and run script
10-
11-
Example config for a csv file with hostname in column B:
12-
13-
[{
14-
"system": {
15-
"system_id": column["B"]
16-
}
17-
},
18-
[]
19-
]
1+
"""bulk_config.py - bulk configure devices in NCM from router_grid.csv file.
202
3+
Reads router_grid.csv with column headers and applies configurations
4+
using the NCM library's patch_configuration_managers method.
5+
6+
To use this script:
7+
1. Export router_grid.csv from the Device View in NCM
8+
2. Use NCM's device-level Edit Config screen to build your configuration
9+
3. Copy the pending config output and paste it into the build_config return value
10+
4. Update router_grid.csv to contain the columns that need to be applied to the device level config
11+
5. Update build_config to reference router_grid column headers with row_data.get('column_name')
12+
6. Update csv_file variable if using a different filename than 'router_grid.csv'
13+
7. Update API keys below with your NCM API credentials
2114
"""
2215

23-
import requests
2416
import csv
17+
from typing import Dict, List, Any
18+
import ncm
19+
20+
# Update this filename if using a different CSV file
21+
csv_file = 'router_grid.csv'
2522

26-
csv_file = 'routers.csv'
23+
# Update these API keys with your NCM credentials
2724
api_keys = {
28-
'X-ECM-API-ID': 'YOUR',
29-
'X-ECM-API-KEY': 'KEYS',
30-
'X-CP-API-ID': 'GO',
31-
'X-CP-API-KEY': 'HERE'
25+
"X-CP-API-ID": "YOUR_CP_API_ID",
26+
"X-CP-API-KEY": "YOUR_CP_API_KEY",
27+
"X-ECM-API-ID": "YOUR_ECM_API_ID",
28+
"X-ECM-API-KEY": "YOUR_ECM_API_KEY"
3229
}
30+
n2 = ncm.NcmClientv2(api_keys=api_keys)
3331

32+
def build_config(row_data: Dict[str, str]) -> List[Any]:
33+
"""Return router configuration with values from CSV row.
3434
35-
def build_config(column):
36-
"""Return router configuration with values from row.
35+
To update this configuration:
36+
1. Go to NCM device-level Edit Config screen
37+
2. Make your changes and view pending config
38+
3. Copy the pending config JSON and paste below the return \ line
39+
4. Replace static values with row_data.get('column_name') for CSV columns
3740
38-
:param column: mapping of values from csv columns for router_id
39-
:type column: dict
40-
:return: router configuration (list)
41+
Args:
42+
row_data (dict): Dictionary with CSV column headers as keys.
43+
44+
Returns:
45+
list: Router configuration list.
4146
"""
42-
# > > > Paste configuration *BELOW* the next line ("return \") < < <
4347
return \
4448
[
45-
{
46-
"system": {
47-
"system_id": column["B"]
48-
},
49-
"wlan": {
50-
"radio": {
51-
"0": {
52-
"bss": {
49+
{
50+
"lan": {
51+
"00000000-0d93-319d-8220-4a1fb0372b51": {
52+
"dhcpd": {
53+
"cur6_hop_limit": 64,
54+
"dad_transmits": 1,
55+
"dhcp6_mode": "slaacdhcp",
56+
"lease6_time": 3600,
57+
"max_rtr_adv_interval": 600,
58+
"min_rtr_adv_interval": 3,
59+
"ns_retransmit_interval": 1000,
60+
"options": [],
61+
"ra6_advr_interval": 600,
62+
"ra_mtu": 1500,
63+
"reachable6_time": 30000,
64+
"router_lifetime": 1800,
65+
"valid6_lifetime": 3600
66+
},
67+
"ip_address": row_data.get('primary_lan_ip', ''),
68+
"stp": {
69+
"priority": 32768
70+
},
71+
"vrrp": {
72+
"advert_int": 1,
73+
"auth_type": "none",
74+
"init_state": "master",
75+
"ipverify": {
76+
"test_id": ""
77+
},
78+
"priority": 100,
79+
"vrid": 10
80+
},
81+
"wired_8021x": {
82+
"eap": {
83+
"reauth_period": 3600
84+
},
85+
"radius": {
86+
"auth_servers": {
5387
"0": {
54-
"ssid": column["d"]
88+
"ip_address": "127.0.0.1",
89+
"mac": "00:00:00:00:00:00",
90+
"port": 1812
91+
},
92+
"1": {
93+
"ip_address": "127.0.0.1",
94+
"mac": "00:00:00:00:00:00",
95+
"port": 1812
5596
}
56-
}
57-
},
58-
"1": {
59-
"bss": {
97+
},
98+
"acct_servers": {
99+
"0": {
100+
"ip_address": "127.0.0.1",
101+
"mac": "00:00:00:00:00:00",
102+
"port": 1813
103+
},
104+
"1": {
105+
"ip_address": "127.0.0.1",
106+
"mac": "00:00:00:00:00:00",
107+
"port": 1813
108+
}
109+
},
110+
"allowed_macs": {
60111
"0": {
61-
"ssid": column["d"]
112+
"enabled": False,
113+
"macs": []
62114
}
63115
}
64116
}
65-
}
117+
},
118+
"ip6_prefixlen": 64,
119+
"passthrough_cycle_time": 10,
120+
"_id_": "00000000-0d93-319d-8220-4a1fb0372b51"
66121
}
67122
},
68-
[]
69-
]
70-
# > > > Paste configuration ^ ABOVE HERE ^ < < <
71-
# > > > Replace config values with corresponding csv column letters < < <
123+
"system": {
124+
"system_id": row_data.get('name', ''),
125+
"asset_id": row_data.get('asset_id', ''),
126+
"desc": row_data.get('desc', '')
127+
},
128+
"wlan": {
129+
"radio": {
130+
"0": {
131+
"bss": {
132+
"0": {
133+
"ssid": row_data.get('2ghz_ssid', '')
134+
}
135+
}
136+
},
137+
"1": {
138+
"bss": {
139+
"0": {
140+
"ssid": row_data.get('5ghz_ssid', '')
141+
}
142+
}
143+
}
144+
}
145+
}
146+
},
147+
[]
148+
]
72149

73150

74-
def load_csv(filename):
151+
def load_csv(filename: str) -> Dict[int, Dict[str, str]]:
75152
"""Return a dictionary of router_ids containing config values from csv.
76153
77-
:param filename: name of csv file
78-
:type filename: str
79-
:return: list of rows from csv file
154+
Args:
155+
filename (str): Name of csv file.
156+
157+
Returns:
158+
dict: Dictionary of router configs keyed by router_id.
80159
"""
81160
router_configs = {}
82161
try:
83-
with open(filename, 'rt') as f:
84-
rows = csv.reader(f)
85-
for row in rows:
162+
with open(filename, 'rt', encoding='utf-8-sig') as f:
163+
reader = csv.DictReader(f)
164+
for row in reader:
86165
try:
87-
router_id = int(row[0])
88-
column = {"A": router_id}
89-
i = 1
90-
while True:
91-
try:
92-
column[chr(i + 97).upper()] = row[i]
93-
column[chr(i + 97).lower()] = row[i]
94-
i += 1
95-
except:
96-
break
97-
router_configs[column["A"]] = column
98-
except:
99-
pass
166+
router_id = int(row['id'])
167+
router_configs[router_id] = row
168+
except (ValueError, KeyError) as e:
169+
print(f'Skipping invalid row: {row}, error: {e}')
100170
except Exception as e:
101171
print(f'Exception reading csv file: {e}')
102172
return router_configs
103173

104174

105-
server = 'https://www.cradlepointecm.com/api/v2'
106-
rows = load_csv(csv_file)
107-
for router_id in rows:
108-
config_url = f'{server}/configuration_managers/?router={router_id}'
109-
get_config = requests.get(config_url, headers=api_keys)
110-
if get_config.status_code < 300:
111-
get_config = get_config.json()
112-
config_data = get_config["data"]
113-
config_id = config_data[0]["id"]
114-
config = build_config(rows[router_id])
115-
patch_config = requests.patch(f'{server}/configuration_managers/'
116-
f'{config_id}/', headers=api_keys,
117-
json={"configuration": config})
118-
if patch_config.status_code < 300:
119-
print(f'Sucessfully patched config to router: {router_id}')
120-
else:
121-
print(f'Error patching config {router_id}: {patch_config.text}')
122-
else:
123-
print(f'Error getting configuration_managers/ ID for {router_id}: '
124-
f'{get_config.text}')
125-
print('Done!')
175+
def main() -> None:
176+
"""Main function to process router configurations."""
177+
rows = load_csv(csv_file)
178+
179+
for router_id, row_data in rows.items():
180+
try:
181+
config = {'configuration': build_config(row_data)}
182+
n2.patch_configuration_managers(
183+
router_id=router_id, config_man_json=config)
184+
print(f'Successfully patched config to router: {router_id}')
185+
except Exception as e:
186+
print(f'Error patching config for router {router_id}: {e}')
187+
188+
print('Done!')
189+
190+
191+
if __name__ == '__main__':
192+
main()

bulk_config/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ncm

bulk_config/router_grid.csv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
id,name,desc,asset_id,primary_lan_ip,2ghz_ssid,5ghz_ssid
2+
4388070,E100,E100,12345,10.0.0.1,Washington 2G,Washington 5G
3+
5115934,E400,E400,ABCDE,10.0.2.1,Adams 2G,Adams 5G

0 commit comments

Comments
 (0)