Browse Source

Merge pull request #2 from gpsingh-1991/master

Add tugboat plugin required files
hemanthnakkina 4 months ago
parent
commit
0c9fb93e51
No account linked to committer's email address

+ 9
- 0
spyglass/data_extractor/custom_exceptions.py View File

@@ -26,6 +26,15 @@ class BaseError(Exception):
26 26
         sys.exit(1)
27 27
 
28 28
 
29
+class NoSpecMatched(BaseError):
30
+    def __init__(self, excel_specs):
31
+        self.specs = excel_specs
32
+
33
+    def display_error(self):
34
+        print('No spec matched. Following are the available specs:\n'.format(
35
+            self.specs))
36
+        sys.exit(1)
37
+
29 38
 class MissingAttributeError(BaseError):
30 39
     pass
31 40
 

+ 410
- 0
spyglass/data_extractor/plugins/tugboat/excel_parser.py View File

@@ -0,0 +1,410 @@
1
+# Copyright 2018 AT&T Intellectual Property.  All other rights reserved.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+import logging
16
+import pprint
17
+import re
18
+import sys
19
+import yaml
20
+from openpyxl import load_workbook
21
+from openpyxl import Workbook
22
+from spyglass.data_extractor.custom_exceptions import
23
+    NoSpecMatched, )
24
+# from spyglass.data_extractor.custom_exceptions
25
+
26
+LOG = logging.getLogger(__name__)
27
+
28
+
29
+class ExcelParser():
30
+    """ Parse data from excel into a dict """
31
+
32
+    def __init__(self, file_name, excel_specs):
33
+        self.file_name = file_name
34
+        with open(excel_specs, 'r') as f:
35
+            spec_raw_data = f.read()
36
+        self.excel_specs = yaml.safe_load(spec_raw_data)
37
+        # A combined design spec, returns a workbok object after combining
38
+        # all the inputs excel specs
39
+        combined_design_spec = self.combine_excel_design_specs(file_name)
40
+        self.wb_combined = combined_design_spec
41
+        self.filenames = file_name
42
+        self.spec = 'xl_spec'
43
+
44
+    @staticmethod
45
+    def sanitize(string):
46
+        """ Remove extra spaces and convert string to lower case """
47
+        return string.replace(' ', '').lower()
48
+
49
+    def compare(self, string1, string2):
50
+        """ Compare the strings """
51
+        return bool(re.search(self.sanitize(string1), self.sanitize(string2)))
52
+
53
+    def validate_sheet(self, spec, sheet):
54
+        """ Check if the sheet is correct or not """
55
+        ws = self.wb_combined[sheet]
56
+        header_row = self.excel_specs['specs'][spec]['header_row']
57
+        ipmi_header = self.excel_specs['specs'][spec]['ipmi_address_header']
58
+        ipmi_column = self.excel_specs['specs'][spec]['ipmi_address_col']
59
+        header_value = ws.cell(row=header_row, column=ipmi_column).value
60
+        return bool(self.compare(ipmi_header, header_value))
61
+
62
+    def find_correct_spec(self):
63
+        """ Find the correct spec """
64
+        for spec in self.excel_specs['specs']:
65
+            sheet_name = self.excel_specs['specs'][spec]['ipmi_sheet_name']
66
+            for sheet in self.wb_combined.sheetnames:
67
+                if self.compare(sheet_name, sheet):
68
+                    self.excel_specs['specs'][spec]['ipmi_sheet_name'] = sheet
69
+                    if self.validate_sheet(spec, sheet):
70
+                        return spec
71
+        raise NoSpecMatched(self.excel_specs)
72
+
73
+    def get_ipmi_data(self):
74
+        """ Read IPMI data from the sheet """
75
+        ipmi_data = {}
76
+        hosts = []
77
+        provided_sheetname = self.excel_specs['specs'][self.
78
+                                                       spec]['ipmi_sheet_name']
79
+        workbook_object, extracted_sheetname = self.get_xl_obj_and_sheetname(
80
+            provided_sheetname)
81
+        if workbook_object is not None:
82
+            ws = workbook_object[extracted_sheetname]
83
+        else:
84
+            ws = self.wb_combined[provided_sheetname]
85
+        row = self.excel_specs['specs'][self.spec]['start_row']
86
+        end_row = self.excel_specs['specs'][self.spec]['end_row']
87
+        hostname_col = self.excel_specs['specs'][self.spec]['hostname_col']
88
+        ipmi_address_col = self.excel_specs['specs'][self.
89
+                                                     spec]['ipmi_address_col']
90
+        host_profile_col = self.excel_specs['specs'][self.
91
+                                                     spec]['host_profile_col']
92
+        ipmi_gateway_col = self.excel_specs['specs'][self.
93
+                                                     spec]['ipmi_gateway_col']
94
+        previous_server_gateway = None
95
+        while row <= end_row:
96
+            hostname = self.sanitize(
97
+                ws.cell(row=row, column=hostname_col).value)
98
+            hosts.append(hostname)
99
+            ipmi_address = ws.cell(row=row, column=ipmi_address_col).value
100
+            if '/' in ipmi_address:
101
+                ipmi_address = ipmi_address.split('/')[0]
102
+            ipmi_gateway = ws.cell(row=row, column=ipmi_gateway_col).value
103
+            if ipmi_gateway:
104
+                previous_server_gateway = ipmi_gateway
105
+            else:
106
+                ipmi_gateway = previous_server_gateway
107
+            host_profile = ws.cell(row=row, column=host_profile_col).value
108
+            try:
109
+                if host_profile is None:
110
+                    raise RuntimeError("No value read from {} ".format(
111
+                        self.file_name) + "sheet:{} row:{}, col:{}".format(
112
+                            self.spec, row, host_profile_col))
113
+            except RuntimeError as rerror:
114
+                LOG.critical(rerror)
115
+                sys.exit("Tugboat exited!!")
116
+            ipmi_data[hostname] = {
117
+                'ipmi_address': ipmi_address,
118
+                'ipmi_gateway': ipmi_gateway,
119
+                'host_profile': host_profile,
120
+                'type': type,
121
+            }
122
+            row += 1
123
+        LOG.debug("ipmi data extracted from excel:\n{}".format(
124
+            pprint.pformat(ipmi_data)))
125
+        LOG.debug("host data extracted from excel:\n{}".format(
126
+            pprint.pformat(hosts)))
127
+        return [ipmi_data, hosts]
128
+
129
+    def get_private_vlan_data(self, ws):
130
+        """ Get private vlan data from private IP sheet """
131
+        vlan_data = {}
132
+        row = self.excel_specs['specs'][self.spec]['vlan_start_row']
133
+        end_row = self.excel_specs['specs'][self.spec]['vlan_end_row']
134
+        type_col = self.excel_specs['specs'][self.spec]['net_type_col']
135
+        vlan_col = self.excel_specs['specs'][self.spec]['vlan_col']
136
+        while row <= end_row:
137
+            cell_value = ws.cell(row=row, column=type_col).value
138
+            if cell_value:
139
+                vlan = ws.cell(row=row, column=vlan_col).value
140
+                if vlan:
141
+                    vlan = vlan.lower()
142
+                vlan_data[vlan] = cell_value
143
+            row += 1
144
+        LOG.debug("vlan data extracted from excel:\n%s",
145
+                  pprint.pformat(vlan_data))
146
+        return vlan_data
147
+
148
+    def get_private_network_data(self):
149
+        """ Read network data from the private ip sheet """
150
+        provided_sheetname = self.excel_specs['specs'][
151
+            self.spec]['private_ip_sheet']
152
+        workbook_object, extracted_sheetname = self.get_xl_obj_and_sheetname(
153
+            provided_sheetname)
154
+        if workbook_object is not None:
155
+            ws = workbook_object[extracted_sheetname]
156
+        else:
157
+            ws = self.wb_combined[provided_sheetname]
158
+        vlan_data = self.get_private_vlan_data(ws)
159
+        network_data = {}
160
+        row = self.excel_specs['specs'][self.spec]['net_start_row']
161
+        end_row = self.excel_specs['specs'][self.spec]['net_end_row']
162
+        col = self.excel_specs['specs'][self.spec]['net_col']
163
+        vlan_col = self.excel_specs['specs'][self.spec]['net_vlan_col']
164
+        old_vlan = ''
165
+        while row <= end_row:
166
+            vlan = ws.cell(row=row, column=vlan_col).value
167
+            if vlan:
168
+                vlan = vlan.lower()
169
+            network = ws.cell(row=row, column=col).value
170
+            if vlan and network:
171
+                net_type = vlan_data[vlan]
172
+                if 'vlan' not in network_data:
173
+                    network_data[net_type] = {
174
+                        'vlan': vlan,
175
+                        'subnet': [],
176
+                    }
177
+            elif not vlan and network:
178
+                # If vlan is not present then assign old vlan to vlan as vlan
179
+                # value is spread over several rows
180
+                vlan = old_vlan
181
+            else:
182
+                row += 1
183
+                continue
184
+            network_data[vlan_data[vlan]]['subnet'].append(network)
185
+            old_vlan = vlan
186
+            row += 1
187
+        for network in network_data:
188
+            network_data[network]['is_common'] = True
189
+            """
190
+            if len(network_data[network]['subnet']) > 1:
191
+                network_data[network]['is_common'] = False
192
+            else:
193
+                network_data[network]['is_common'] = True
194
+        LOG.debug(
195
+            "private network data extracted from\
196
+                          excel:\n%s", pprint.pformat(network_data))
197
+            """
198
+        return network_data
199
+
200
+    def get_public_network_data(self):
201
+        """ Read public network data from public ip data """
202
+        network_data = {}
203
+        provided_sheetname = self.excel_specs['specs'][self.
204
+                                                       spec]['public_ip_sheet']
205
+        workbook_object, extracted_sheetname = self.get_xl_obj_and_sheetname(
206
+            provided_sheetname)
207
+        if workbook_object is not None:
208
+            ws = workbook_object[extracted_sheetname]
209
+        else:
210
+            ws = self.wb_combined[provided_sheetname]
211
+        oam_row = self.excel_specs['specs'][self.spec]['oam_ip_row']
212
+        oam_col = self.excel_specs['specs'][self.spec]['oam_ip_col']
213
+        oam_vlan_col = self.excel_specs['specs'][self.spec]['oam_vlan_col']
214
+        ingress_row = self.excel_specs['specs'][self.spec]['ingress_ip_row']
215
+        oob_row = self.excel_specs['specs'][self.spec]['oob_net_row']
216
+        col = self.excel_specs['specs'][self.spec]['oob_net_start_col']
217
+        end_col = self.excel_specs['specs'][self.spec]['oob_net_end_col']
218
+        network_data = {
219
+            'oam': {
220
+                'subnet': [ws.cell(row=oam_row, column=oam_col).value],
221
+                'vlan': ws.cell(row=oam_row, column=oam_vlan_col).value,
222
+            },
223
+            'ingress': ws.cell(row=ingress_row, column=oam_col).value,
224
+        }
225
+        network_data['oob'] = {
226
+            'subnet': [],
227
+        }
228
+        while col <= end_col:
229
+            cell_value = ws.cell(row=oob_row, column=col).value
230
+            if cell_value:
231
+                network_data['oob']['subnet'].append(self.sanitize(cell_value))
232
+            col += 1
233
+        LOG.debug(
234
+            "public network data extracted from\
235
+                          excel:\n%s", pprint.pformat(network_data))
236
+        return network_data
237
+
238
+    def get_site_info(self):
239
+        """ Read location, dns, ntp and ldap data"""
240
+        site_info = {}
241
+        provided_sheetname = self.excel_specs['specs'][
242
+            self.spec]['dns_ntp_ldap_sheet']
243
+        workbook_object, extracted_sheetname = self.get_xl_obj_and_sheetname(
244
+            provided_sheetname)
245
+        if workbook_object is not None:
246
+            ws = workbook_object[extracted_sheetname]
247
+        else:
248
+            ws = self.wb_combined[provided_sheetname]
249
+        dns_row = self.excel_specs['specs'][self.spec]['dns_row']
250
+        dns_col = self.excel_specs['specs'][self.spec]['dns_col']
251
+        ntp_row = self.excel_specs['specs'][self.spec]['ntp_row']
252
+        ntp_col = self.excel_specs['specs'][self.spec]['ntp_col']
253
+        domain_row = self.excel_specs['specs'][self.spec]['domain_row']
254
+        domain_col = self.excel_specs['specs'][self.spec]['domain_col']
255
+        login_domain_row = self.excel_specs['specs'][self.
256
+                                                     spec]['login_domain_row']
257
+        ldap_col = self.excel_specs['specs'][self.spec]['ldap_col']
258
+        global_group = self.excel_specs['specs'][self.spec]['global_group']
259
+        ldap_search_url_row = self.excel_specs['specs'][
260
+            self.spec]['ldap_search_url_row']
261
+        dns_servers = ws.cell(row=dns_row, column=dns_col).value
262
+        ntp_servers = ws.cell(row=ntp_row, column=ntp_col).value
263
+        try:
264
+            if dns_servers is None:
265
+                raise RuntimeError(
266
+                    "No value for dns_server from:{} Sheet:'{}' Row:{} Col:{}".
267
+                    format(self.file_name, provided_sheetname, dns_row,
268
+                           dns_col))
269
+                raise RuntimeError(
270
+                    "No value for ntp_server frome:{} Sheet:'{}' Row:{} Col:{}"
271
+                    .format(self.file_name, provided_sheetname, ntp_row,
272
+                            ntp_col))
273
+        except RuntimeError as rerror:
274
+            LOG.critical(rerror)
275
+            sys.exit("Tugboat exited!!")
276
+
277
+        dns_servers = dns_servers.replace('\n', ' ')
278
+        ntp_servers = ntp_servers.replace('\n', ' ')
279
+        if ',' in dns_servers:
280
+            dns_servers = dns_servers.split(',')
281
+        else:
282
+            dns_servers = dns_servers.split()
283
+        if ',' in ntp_servers:
284
+            ntp_servers = ntp_servers.split(',')
285
+        else:
286
+            ntp_servers = ntp_servers.split()
287
+        site_info = {
288
+            'location': self.get_location_data(),
289
+            'dns': dns_servers,
290
+            'ntp': ntp_servers,
291
+            'domain': ws.cell(row=domain_row, column=domain_col).value,
292
+            'ldap': {
293
+                'subdomain': ws.cell(row=login_domain_row,
294
+                                     column=ldap_col).value,
295
+                'common_name': ws.cell(row=global_group,
296
+                                       column=ldap_col).value,
297
+                'url': ws.cell(row=ldap_search_url_row, column=ldap_col).value,
298
+            }
299
+        }
300
+        LOG.debug(
301
+            "Site Info extracted from\
302
+                          excel:\n%s", pprint.pformat(site_info))
303
+        return site_info
304
+
305
+    def get_location_data(self):
306
+        """ Read location data from the site and zone sheet """
307
+        provided_sheetname = self.excel_specs['specs'][self.
308
+                                                       spec]['location_sheet']
309
+        workbook_object, extracted_sheetname = self.get_xl_obj_and_sheetname(
310
+            provided_sheetname)
311
+        if workbook_object is not None:
312
+            ws = workbook_object[extracted_sheetname]
313
+        else:
314
+            ws = self.wb_combined[provided_sheetname]
315
+        corridor_row = self.excel_specs['specs'][self.spec]['corridor_row']
316
+        column = self.excel_specs['specs'][self.spec]['column']
317
+        site_name_row = self.excel_specs['specs'][self.spec]['site_name_row']
318
+        state_name_row = self.excel_specs['specs'][self.spec]['state_name_row']
319
+        country_name_row = self.excel_specs['specs'][self.
320
+                                                     spec]['country_name_row']
321
+        clli_name_row = self.excel_specs['specs'][self.spec]['clli_name_row']
322
+        return {
323
+            'corridor': ws.cell(row=corridor_row, column=column).value,
324
+            'name': ws.cell(row=site_name_row, column=column).value,
325
+            'state': ws.cell(row=state_name_row, column=column).value,
326
+            'country': ws.cell(row=country_name_row, column=column).value,
327
+            'physical_location': ws.cell(row=clli_name_row,
328
+                                         column=column).value,
329
+        }
330
+
331
+    def validate_sheet_names_with_spec(self):
332
+        """ Checks is sheet name in spec file matches with excel file"""
333
+        spec = list(self.excel_specs['specs'].keys())[0]
334
+        spec_item = self.excel_specs['specs'][spec]
335
+        sheet_name_list = []
336
+        ipmi_header_sheet_name = spec_item['ipmi_sheet_name']
337
+        sheet_name_list.append(ipmi_header_sheet_name)
338
+        private_ip_sheet_name = spec_item['private_ip_sheet']
339
+        sheet_name_list.append(private_ip_sheet_name)
340
+        public_ip_sheet_name = spec_item['public_ip_sheet']
341
+        sheet_name_list.append(public_ip_sheet_name)
342
+        dns_ntp_ldap_sheet_name = spec_item['dns_ntp_ldap_sheet']
343
+        sheet_name_list.append(dns_ntp_ldap_sheet_name)
344
+        location_sheet_name = spec_item['location_sheet']
345
+        sheet_name_list.append(location_sheet_name)
346
+        try:
347
+            for sheetname in sheet_name_list:
348
+                workbook_object, extracted_sheetname = \
349
+                    self.get_xl_obj_and_sheetname(sheetname)
350
+                if workbook_object is not None:
351
+                    wb = workbook_object
352
+                    sheetname = extracted_sheetname
353
+                else:
354
+                    wb = self.wb_combined
355
+
356
+                if sheetname not in wb.sheetnames:
357
+                    raise RuntimeError(
358
+                        "SheetName '{}' not found ".format(sheetname))
359
+        except RuntimeError as rerror:
360
+            LOG.critical(rerror)
361
+            sys.exit("Tugboat exited!!")
362
+
363
+        LOG.info("Sheet names in excel spec validated")
364
+
365
+    def get_data(self):
366
+        """ Create a dict with combined data """
367
+        self.validate_sheet_names_with_spec()
368
+        ipmi_data = self.get_ipmi_data()
369
+        network_data = self.get_private_network_data()
370
+        public_network_data = self.get_public_network_data()
371
+        site_info_data = self.get_site_info()
372
+        data = {
373
+            'ipmi_data': ipmi_data,
374
+            'network_data': {
375
+                'private': network_data,
376
+                'public': public_network_data,
377
+            },
378
+            'site_info': site_info_data,
379
+        }
380
+        LOG.debug(
381
+            "Location data extracted from\
382
+                          excel:\n%s", pprint.pformat(data))
383
+        return data
384
+
385
+    def combine_excel_design_specs(self, filenames):
386
+        """ Combines multiple excel file to a single design spec"""
387
+        design_spec = Workbook()
388
+        for exel_file in filenames:
389
+            loaded_workbook = load_workbook(exel_file, data_only=True)
390
+            for names in loaded_workbook.sheetnames:
391
+                design_spec_worksheet = design_spec.create_sheet(names)
392
+                loaded_workbook_ws = loaded_workbook[names]
393
+                for row in loaded_workbook_ws:
394
+                    for cell in row:
395
+                        design_spec_worksheet[cell.
396
+                                              coordinate].value = cell.value
397
+        return design_spec
398
+
399
+    def get_xl_obj_and_sheetname(self, sheetname):
400
+        """
401
+        The logic confirms if the sheetname is specified for example as:
402
+            "MTN57a_AEC_Network_Design_v1.6.xlsx:Public IPs"
403
+        """
404
+        if (re.search('.xlsx', sheetname) or re.search('.xls', sheetname)):
405
+            """ Extract file name """
406
+            source_xl_file = sheetname.split(':')[0]
407
+            wb = load_workbook(source_xl_file, data_only=True)
408
+            return [wb, sheetname.split(':')[1]]
409
+        else:
410
+            return [None, sheetname]

+ 21
- 13
spyglass/schemas/data_schema.json View File

@@ -140,8 +140,11 @@
140 140
           "properties": {
141 141
             "subnet": {
142 142
               "description": "Subnet address of the network",
143
-              "type": "string",
144
-              "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[1-2][0-9]|3[0-2])$"
143
+              "type": "array",
144
+	      "items": {
145
+		"type": "string",
146
+              	"pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[1-2][0-9]|3[0-2])$"
147
+		}
145 148
             },
146 149
             "vlan": {
147 150
               "description": "Vlan id of the network",
@@ -166,13 +169,8 @@
166 169
 		"pattern":"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[1-2][0-9]|3[0-2])$"
167 170
 		}
168 171
 		]
172
+              }
169 173
             },
170
-            "vlan": {
171
-              "description": "Vlan id of the network",
172
-              "type": "string",
173
-              "pattern": "^([0-9]|[0-9][0-9]|[0-9][0-9][0-9]|[0-3][0-9][0-9][0-9]|40[0-9][0-5])$"
174
-            }
175
-          },
176 174
           "required": [
177 175
             "subnet"
178 176
           ]
@@ -182,8 +180,11 @@
182 180
           "properties": {
183 181
             "subnet": {
184 182
               "description": "Subnet address of the network",
183
+              "type": "array",
184
+	      "items": {
185 185
               "type": "string",
186 186
               "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[1-2][0-9]|3[0-2])$"
187
+	      }
187 188
             },
188 189
             "vlan": {
189 190
               "description": "Vlan id of the network",
@@ -201,18 +202,20 @@
201 202
           "properties": {
202 203
             "subnet": {
203 204
               "description": "Subnet address of the network",
205
+	      "type": "array",
206
+	      "items": {
204 207
               "type": "string",
205 208
               "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[1-2][0-9]|3[0-2])$"
209
+	      }
206 210
             },
207 211
             "vlan": {
208 212
               "description": "Vlan id of the network",
209 213
               "type": "string",
210
-              "pattern": "^([0-9]|[0-9][0-9]|[0-9][0-9][0-9]|[0-3][0-9][0-9][0-9]|40[0-9][0-5])$"
214
+              "pattern": "^([0-9]|[0-9][0-9]|[0-9][0-9][0-9]|[0-3][0-9][0-9][0-9]|40[0-9][0-5])?$"
211 215
             }
212 216
           },
213 217
           "required": [
214
-            "subnet",
215
-	    "vlan"
218
+            "subnet"
216 219
           ]
217 220
         },
218 221
           "pxe": {
@@ -220,8 +223,11 @@
220 223
           "properties": {
221 224
             "subnet": {
222 225
               "description": "Subnet address of the network",
226
+	      "type": "array",
227
+	      "items": {
223 228
               "type": "string",
224 229
               "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[1-2][0-9]|3[0-2])$"
230
+	      }
225 231
             },
226 232
             "vlan": {
227 233
               "description": "Vlan id of the network",
@@ -239,8 +245,11 @@
239 245
           "properties": {
240 246
             "subnet": {
241 247
               "description": "Subnet address of the network",
248
+	      "type": "array",
249
+	      "items": {
242 250
               "type": "string",
243 251
               "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[1-2][0-9]|3[0-2])$"
252
+	     }
244 253
             },
245 254
             "vlan": {
246 255
               "description": "Vlan id of the network",
@@ -252,8 +261,7 @@
252 261
             "subnet",
253 262
 	    "vlan"
254 263
           ]
255
-        }
256
-
264
+      }
257 265
       },
258 266
         "required" :[
259 267
           "calico",

Loading…
Cancel
Save