Bundle Yamale schema
TIL! Using the existence of obscure member sys._MEIPASS, I can detect if we're running from a bundled PyInstaller binary, versus running from Python directly. Add schema.yaml to the datas of the PyInstaller spec. Then, if the -/--schema flag is given, use it, and if it's not given, default to the built-in one if we're running from a bundled binary, or fall-through to ./schema.yaml in other cases. This avoids the need for config/schema.py as a carbon-copy of the schema, slick!
This commit is contained in:
		| @@ -22,7 +22,7 @@ sudo pip3 install pyinstaller | ||||
| ./tests.py -d -t unittest/yaml/*.yaml | ||||
|  | ||||
| ## Build the tool | ||||
| pyinstaller vppcfg  --onefile | ||||
| pyinstaller vppcfg.spec --onefile | ||||
| dist/vppcfg -h | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -20,6 +20,9 @@ from __future__ import ( | ||||
| ) | ||||
|  | ||||
| import logging | ||||
| import ipaddress | ||||
| import os.path | ||||
| import sys | ||||
| try: | ||||
|     import yamale | ||||
| except ImportError: | ||||
| @@ -30,10 +33,8 @@ from config.bondethernet import validate_bondethernets | ||||
| from config.interface import validate_interfaces | ||||
| from config.bridgedomain import validate_bridgedomains | ||||
| from config.vxlan_tunnel import validate_vxlan_tunnels | ||||
| from config.schema import yamale_schema | ||||
|  | ||||
| from yamale.validators import DefaultValidators, Validator | ||||
| import ipaddress | ||||
|  | ||||
| class IPInterfaceWithPrefixLength(Validator): | ||||
|     """ Custom IPAddress config - takes IP/prefixlen as input: | ||||
| @@ -74,15 +75,25 @@ class Validator(object): | ||||
|         if not yaml: | ||||
|             return ret_rv, ret_msgs | ||||
|  | ||||
|         validators = DefaultValidators.copy() | ||||
|         validators[IPInterfaceWithPrefixLength.tag] = IPInterfaceWithPrefixLength | ||||
|         if self.schema: | ||||
|             fn = self.schema | ||||
|             self.logger.debug("Validating against --schema %s" % fn) | ||||
|         elif hasattr(sys, "_MEIPASS"): | ||||
|             ## See vppcfg.spec data_files that includes schema.yaml into the bundle | ||||
|             self.logger.debug("Validating against built-in schema") | ||||
|             fn = os.path.join(sys._MEIPASS, "schema.yaml") | ||||
|         else: | ||||
|             fn = "./schema.yaml" | ||||
|             self.logger.debug("Validating against fallthrough default schema %s" % fn) | ||||
|  | ||||
|         if not os.path.isfile(fn): | ||||
|             self.logger.error("Cannot file schema file: %s" % fn) | ||||
|             return False, ret_msgs | ||||
|  | ||||
|         try: | ||||
|             validators = DefaultValidators.copy() | ||||
|             validators[IPInterfaceWithPrefixLength.tag] = IPInterfaceWithPrefixLength | ||||
|             if self.schema: | ||||
|                 self.logger.debug("Validating against schema %s" % self.schema) | ||||
|                 schema = yamale.make_schema(self.schema, validators=validators) | ||||
|             else: | ||||
|                 self.logger.debug("Validating against built-in schema") | ||||
|                 schema = yamale.make_schema(content=yamale_schema, validators=validators) | ||||
|             schema = yamale.make_schema(fn, validators=validators) | ||||
|             data = yamale.make_data(content=str(yaml)) | ||||
|             yamale.validate(schema, data) | ||||
|             self.logger.debug("Schema correctly validated by yamale") | ||||
|   | ||||
| @@ -1,80 +0,0 @@ | ||||
| # | ||||
| # Copyright (c) 2022 Pim van Pelt | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at: | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| ### NOTE(pim): The source of truth of this string lives in ../schema.yaml | ||||
| ###            Make sure to include it here, verbatim, if it ever changes. | ||||
| yamale_schema = r""" | ||||
| interfaces: map(include('interface'),key=str(),required=False) | ||||
| bondethernets: map(include('bondethernet'),key=str(matches='BondEthernet[0-9]+'),required=False) | ||||
| loopbacks: map(include('loopback'),key=str(matches='loop[0-9]+'),required=False) | ||||
| bridgedomains: map(include('bridgedomain'),key=str(matches='bd[0-9]+'),required=False) | ||||
| vxlan_tunnels: map(include('vxlan'),key=str(matches='vxlan_tunnel[0-9]+'),required=False) | ||||
| --- | ||||
| vxlan: | ||||
|   description: str(exclude='\'"',len=64,required=False) | ||||
|   local: ip() | ||||
|   remote: ip() | ||||
|   vni: int(min=1,max=16777215) | ||||
| --- | ||||
| bridgedomain: | ||||
|   description: str(exclude='\'"',len=64,required=False) | ||||
|   mtu: int(min=128,max=9216,required=False) | ||||
|   bvi: str(matches='loop[0-9]+',required=False) | ||||
|   interfaces: list(str(),required=False) | ||||
|   settings: include('bridgedomain-settings',required=False) | ||||
| --- | ||||
| bridgedomain-settings: | ||||
|   learn: bool(required=False) | ||||
|   unicast-flood: bool(required=False) | ||||
|   unknown-unicast-flood: bool(required=False) | ||||
|   unicast-forward: bool(required=False) | ||||
|   arp-termination: bool(required=False) | ||||
|   arp-unicast-forward: bool(required=False) | ||||
|   mac-age-minutes: int(min=0,max=255,required=False) | ||||
| --- | ||||
| loopback: | ||||
|   description: str(exclude='\'"',len=64,required=False) | ||||
|   lcp: str(max=15,matches='[a-z]+[a-z0-9-]*',required=False) | ||||
|   mtu: int(min=128,max=9216,required=False) | ||||
|   addresses: list(ip_interface(),min=1,max=6,required=False) | ||||
| --- | ||||
| bondethernet: | ||||
|   description: str(exclude='\'"',len=64,required=False) | ||||
|   interfaces: list(str(matches='.*GigabitEthernet[0-9]+/[0-9]+/[0-9]+')) | ||||
| --- | ||||
| interface: | ||||
|   description: str(exclude='\'"',len=64,required=False) | ||||
|   mac: mac(required=False) | ||||
|   lcp: str(max=15,matches='[a-z]+[a-z0-9-]*',required=False) | ||||
|   mtu: int(min=128,max=9216,required=False) | ||||
|   addresses: list(ip_interface(),min=1,max=6,required=False) | ||||
|   sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False) | ||||
|   l2xc: str(required=False) | ||||
|   state: enum('up', 'down', required=False) | ||||
| --- | ||||
| sub-interface: | ||||
|   description: str(exclude='\'"',len=64,required=False) | ||||
|   lcp: str(max=15,matches='[a-z]+[a-z0-9-]*',required=False) | ||||
|   mtu: int(min=128,max=9216,required=False) | ||||
|   addresses: list(ip_interface(),required=False) | ||||
|   encapsulation: include('encapsulation',required=False) | ||||
|   l2xc: str(required=False) | ||||
|   state: enum('up', 'down', required=False) | ||||
| --- | ||||
| encapsulation: | ||||
|   dot1q: int(min=1,max=4095,required=False) | ||||
|   dot1ad: int(min=1,max=4095,required=False) | ||||
|   inner-dot1q: int(min=1,max=4095,required=False) | ||||
|   exact-match: bool(required=False) | ||||
| """ | ||||
| @@ -1,6 +1,3 @@ | ||||
| ### NOTE(pim): This file is the source of truth for the Yamale schema validator. | ||||
| ###            Make sure to copy this file into config/schema.py's yamale_schema | ||||
| ###            when it is changed here. | ||||
| interfaces: map(include('interface'),key=str(),required=False) | ||||
| bondethernets: map(include('bondethernet'),key=str(matches='BondEthernet[0-9]+'),required=False) | ||||
| loopbacks: map(include('loopback'),key=str(matches='loop[0-9]+'),required=False) | ||||
|   | ||||
| @@ -4,10 +4,12 @@ | ||||
| block_cipher = None | ||||
|  | ||||
|  | ||||
| added_files = [ ( 'schema.yaml', '.') ] | ||||
|  | ||||
| a = Analysis(['vppcfg'], | ||||
|              pathex=['/home/pim/src/vppcfg'], | ||||
|              binaries=[], | ||||
|              datas=[], | ||||
|              datas=added_files, | ||||
|              hiddenimports=[], | ||||
|              hookspath=[], | ||||
|              hooksconfig={}, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user