diff --git a/Pipfile b/Pipfile index 7274f66..7867ba7 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ verify_ssl = true [dev-packages] [packages] -librouteros = "==2.*" +librouteros = "==3.*" progress = "*" pyyaml = "*" diff --git a/automonty b/automonty index c1f9508..ccafc44 100755 --- a/automonty +++ b/automonty @@ -6,6 +6,51 @@ import progress.bar import ssl import os import yaml +import re + +# comment format: +# hostname.example.com [aM flag1 flag2 key1=value1 key2=option3:value3,option4:value4] selector.example.com + +aM1 = re.compile( r"\[aM ?([^]]*)\]" ) +def parse_comment( comment ): + bits = aM1.split( comment ) + if len( bits ) == 3: + hostname = bits[ 0 ].strip() + selector = bits[ 2 ].strip() + if not selector: + selector = hostname + rval = {} + for bit in bits[ 1 ].split(): + try: + ( k, v ) = bit.split( "=", 1 ) + except ValueError: + if bit: + rval[ bit ] = True + else: + if ':' in v: + rval[ k ] = {} + for subbit in v.split( "," ): + ( subk, subv ) = subbit.split( ":", 1 ) + rval[ k ][ subk ] = subv + else: + rval[ k ] = v + return ( selector, rval, hostname ) + else: + return ( None, None, comment ) + +def make_comment( selector, rval, hostname ): + rvalbits = [ "aM" ] + for ( k, v ) in rval.items(): + if v is True: + rvalbits.append( k ) + elif isinstance( v, dict ): + rvalbits.append( "%s=%s" % ( k, ",".join( "%s:%s" % vi for vi in v.items() ) ) ) + else: + rvalbits.append( "%s=%s" % ( k, v ) ) + if selector == hostname: + return "%s [%s]" % ( hostname, " ".join( rvalbits ) ) + else: + return "%s [%s] %s" % ( hostname, " ".join( rvalbits ), selector ) def connection( host, username = None, password = None, port = 8729 ): if ':' in host: @@ -44,9 +89,30 @@ def connect_routers( config, args ): def monty_check( args ): for addr in args.addr: for name, api in args.router.items(): - for item in api( cmd ="/ip/address/print", detail = True ): - if item[ 'network' ] == addr: - print( name, ":", 'ENABLED' if not item[ 'disabled' ] else 'disabled', item[ 'interface' ], '#', item[ 'comment' ] ) + ip_address = api.path( 'ip', 'address' ) + for item in ip_address: + ( sel, rval, hst ) = parse_comment( item.get( 'comment', '' ) ) + active = not item[ 'disabled' ] and not item[ 'invalid' ] + if sel == addr or hst == addr: + print( name, ":", 'ENABLED' if active else 'disabled', item[ 'interface' ] ) + +def monty_fixup( args ): + global config + for name, api in args.router.items(): + ip_address = api.path( 'ip', 'address' ) + for item in ip_address: + if item[ 'interface' ].startswith( "loop" ): + continue + if item[ 'address' ] in config[ 'loopbacks' ]: + ( sel, rval, hst ) = parse_comment( item.get( 'comment', '' ) ) + if sel is None: + rval = {} + active = not item[ 'disabled' ] and not item[ 'invalid' ] + if active: + rval[ 'home' ] = True + new_comment = make_comment( hst, rval, hst ) + print( name, ":", item[ 'interface' ], new_comment ) + ip_address.update( **{ 'comment': new_comment, '.id': item[ '.id' ] } ) def load_configuration(): try: @@ -55,8 +121,9 @@ def load_configuration(): return {} return yaml.load( config.read(), yaml.SafeLoader ) +config = load_configuration() + def main(): - config = load_configuration() parser = argparse.ArgumentParser( prog = "automonty", description = 'AutoMonty (re-)configures routers', ) @@ -65,9 +132,12 @@ def main(): parser.add_argument( '--router', action = 'append' ) parser_check = subparsers.add_parser( 'check' ) - parser_check.add_argument( 'addr', action = 'append' ) + parser_check.add_argument( 'addr', nargs = "*" ) parser_check.set_defaults( func = monty_check ) + parser_fixup = subparsers.add_parser( 'fixup' ) + parser_fixup.set_defaults( func = monty_fixup ) + args = parser.parse_args() args.router = connect_routers( config = config.get( 'router', {} ), args = args.router )