You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
621 lines
30 KiB
Python
621 lines
30 KiB
Python
#!/usr/bin/python3
|
|
|
|
import argparse
|
|
import librouteros
|
|
import ssl
|
|
import os
|
|
import yaml
|
|
import re
|
|
import dns.resolver, dns.reversename
|
|
import ipaddress
|
|
|
|
# comment format:
|
|
# hostname.example.com [aM flag1 flag2 key1=value1 key2=option3:value3,option4:value4] selector.example.com
|
|
#
|
|
# flags:
|
|
# static - do not auto-enable or auto-disable this address (loopbacks, linknets)
|
|
# home - this is the normal home-location of the IP
|
|
# standby - this is an active interface, but not intended as primary route
|
|
# teardown - being destroyed
|
|
#
|
|
# keys:
|
|
# evacuated=router - this address has been moved from this host to router
|
|
# evacuee=router - this is an evacuated address originally from router
|
|
|
|
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 reverse_dns( address ):
|
|
try:
|
|
return str( dns.resolver.resolve( dns.reversename.from_address( address ), 'PTR' )[ 0 ] )
|
|
except:
|
|
return address
|
|
|
|
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() ) ) )
|
|
elif v:
|
|
rvalbits.append( "%s=%s" % ( k, v ) )
|
|
if selector == hostname or selector is None:
|
|
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:
|
|
( host, port ) = host.split( ":", 1 )
|
|
port = int( port )
|
|
kwargs = { 'username': username or os.environ.get( 'AUTOMONTY_USERNAME', None ),
|
|
'password': password or os.environ.get( 'AUTOMONTY_PASSWORD', None ),
|
|
'host': host,
|
|
'port': port,
|
|
}
|
|
kwargs[ 'ssl' ] = True
|
|
ssl_ctx = ssl.create_default_context()
|
|
ssl_ctx.check_hostname = False # XXX figure out how
|
|
ssl_option = 'CERT_REQUIRED'
|
|
ssl_ctx.verify_mode = getattr( ssl, ssl_option, ssl.CERT_REQUIRED )
|
|
ssl_ctx.set_ciphers( 'DHE-RSA-AES256-GCM-SHA384' )
|
|
kwargs[ 'ssl_wrapper' ] = ssl_ctx.wrap_socket
|
|
return librouteros.connect( **kwargs )
|
|
|
|
def router_in_groups( options, groups ):
|
|
if not groups:
|
|
return False
|
|
for group in groups:
|
|
if group in options.get( 'group', [] ):
|
|
return True
|
|
return False
|
|
|
|
def connect_routers( config, args ):
|
|
routers = config.get( 'router', {} )
|
|
rval = {}
|
|
for ( name, options ) in routers.items():
|
|
if ( not args.only_router and not args.only_group ) or ( name in args.only_router ) or router_in_groups( options, args.only_group ):
|
|
kwargs = {}
|
|
kwargs.update( options.get( 'connection', {} ) )
|
|
if 'host' not in kwargs:
|
|
kwargs[ 'host' ] = name
|
|
rval[ name ] = connection( **kwargs )
|
|
return rval
|
|
|
|
def monty_check( config, args, routers ):
|
|
for addr in args.addr:
|
|
for ( name, api ) in routers.items():
|
|
ip_address = api.path( 'ip', 'address' )
|
|
ipv6_address = api.path( 'ipv6', '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, ": v4", 'ENABLED' if active else 'disabled', item[ 'interface' ] )
|
|
for item in ipv6_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, ": v6", 'ENABLED' if active else 'disabled', item[ 'interface' ] )
|
|
|
|
def monty_fixup( config, args, routers ):
|
|
reverse = {}
|
|
statics = {}
|
|
for ( name, api ) in routers.items():
|
|
ip_address = api.path( 'ip', 'address' )
|
|
ipv6_address = api.path( 'ipv6', 'address' )
|
|
for item in ip_address:
|
|
( sel, rval, hst ) = parse_comment( item.get( 'comment', '' ) )
|
|
if sel is None:
|
|
rval = {}
|
|
active = not item[ 'disabled' ] and not item[ 'invalid' ]
|
|
if item[ 'address' ] in config[ 'loopbacks' ]:
|
|
if active:
|
|
rval[ 'home' ] = True
|
|
else:
|
|
rval[ 'static' ] = True
|
|
statics[ item[ 'interface' ] ] = True
|
|
if not hst:
|
|
hst = reverse_dns( item[ 'network' ] )
|
|
while hst.endswith( "." ):
|
|
hst = hst[ :-1 ]
|
|
if not sel:
|
|
if item[ 'interface' ] not in reverse:
|
|
rev = reverse_dns( item[ 'network' ] )
|
|
while rev.endswith( "." ):
|
|
rev = rev[ :-1 ]
|
|
reverse[ item[ 'interface' ] ] = rev
|
|
sel = reverse[ item[ 'interface' ] ]
|
|
new_comment = make_comment( sel, rval, hst )
|
|
if args.dry_run:
|
|
print( '/ip address set [find where interface="%s" and network="%s"] comment="%s"' % ( item[ 'interface' ], item[ 'network' ], new_comment ) )
|
|
else:
|
|
print( name, ":", item[ 'interface' ], new_comment )
|
|
ip_address.update( **{ 'comment': new_comment, '.id': item[ '.id' ] } )
|
|
addr = ipaddress.ip_interface( item[ 'address' ] )
|
|
if item[ 'address' ] in config[ 'loopbacks' ]:
|
|
addr = ipaddress.ip_interface( item[ 'network' ] )
|
|
_adjust_filter( config, api, addr.network, hst, sel, home = rval.get( 'home', False ), standby = False, static = rval.get( 'static', False ), rval = rval )
|
|
|
|
for item in ipv6_address:
|
|
if item[ 'interface' ].startswith( "loop" ):
|
|
continue
|
|
if item[ 'dynamic' ]:
|
|
continue
|
|
( sel, rval, hst ) = parse_comment( item.get( 'comment', '' ) )
|
|
if sel is None:
|
|
rval = {}
|
|
active = not item[ 'disabled' ] and not item[ 'invalid' ]
|
|
if statics.get( item[ 'interface' ], False ):
|
|
rval[ 'static' ] = True
|
|
elif active:
|
|
rval[ 'home' ] = True
|
|
if not sel:
|
|
sel = reverse.get( item[ 'interface' ], "XXX " + item[ 'interface' ] )
|
|
if not hst:
|
|
hst = reverse.get( item[ 'interface' ], "XXX " + item[ 'interface' ] )
|
|
new_comment = make_comment( sel, rval, hst )
|
|
if args.dry_run:
|
|
print( '/ipv6 address set [find where interface="%s" and address="%s" ] comment="%s"' % ( item[ 'interface' ], item[ 'address' ], new_comment ) )
|
|
else:
|
|
print( name, ":", item[ 'interface' ], new_comment )
|
|
ipv6_address.update( **{ 'comment': new_comment, '.id': item[ '.id' ] } )
|
|
addr = ipaddress.ip_interface( item[ 'address' ] )
|
|
_adjust_filter( config, api, addr.network, hst, sel, home = rval.get( 'home', False ), standby = False, static = rval.get( 'static', False ), rval = rval )
|
|
|
|
def _adjust_filter( config, api, network, hostname, selector = None, home = None, standby = None, static = None, delete = False, rval = None, disabled = False ):
|
|
|
|
def _make_filter_attrs( comment, disabled, home, static, standby ):
|
|
attrs = { 'action': 'accept',
|
|
'comment': comment,
|
|
'disabled': 'yes' if disabled else 'no',
|
|
}
|
|
if home or static:
|
|
attrs[ '!set-bgp-prepend' ] = ''
|
|
attrs[ 'set-bgp-local-pref' ] = 980
|
|
elif standby:
|
|
attrs[ 'set-bgp-prepend' ] = 1
|
|
attrs[ 'set-bgp-local-pref' ] = 970
|
|
else:
|
|
attrs[ 'set-bgp-prepend' ] = 2
|
|
attrs[ 'set-bgp-local-pref' ] = 960
|
|
return attrs
|
|
|
|
if isinstance( network, ( ipaddress.IPv4Interface, ipaddress.IPv6Interface ) ):
|
|
network = network.network
|
|
|
|
filters = api.path( 'routing', 'filter' )
|
|
found = False
|
|
if rval is None:
|
|
rval = {}
|
|
else:
|
|
_rval = rval
|
|
rval = {}
|
|
rval.update( _rval )
|
|
if home is not None:
|
|
rval[ 'home' ] = home
|
|
if standby is not None:
|
|
rval[ 'standby' ] = standby
|
|
if static is not None:
|
|
rval[ 'static' ] = static
|
|
comment = make_comment( selector, rval, hostname )
|
|
for f in filters:
|
|
if f[ 'chain' ] == config[ 'routing_filter_chain' ]:
|
|
match = str( network )
|
|
if isinstance( network, ipaddress.IPv4Network ) and network.prefixlen == 32:
|
|
match = match.split( "/", 1 )[ 0 ]
|
|
elif isinstance( network, ipaddress.IPv6Network ) and network.prefixlen == 128:
|
|
match = match.split( "/", 1 )[ 0 ]
|
|
if f[ 'prefix' ] == match:
|
|
if delete:
|
|
filters.delete( f[ '.id' ] )
|
|
else:
|
|
update = _make_filter_attrs( comment, disabled, home, static, standby )
|
|
update[ '.id' ] = f[ '.id' ]
|
|
filters.update( **update )
|
|
found = True
|
|
if not found and not delete:
|
|
create = _make_filter_attrs( comment, disabled, home, static, standby )
|
|
create[ 'chain' ] = config[ 'routing_filter_chain' ]
|
|
create[ 'prefix' ] = str( network )
|
|
filters.add( **create )
|
|
return rval
|
|
|
|
def monty_provision( config, args, routers ):
|
|
reverse = {}
|
|
statics = {}
|
|
if args.dry_run:
|
|
print()
|
|
for ( name, api ) in routers.items():
|
|
if args.dry_run:
|
|
print( '#' * ( len( name ) + 2 ) )
|
|
print( '#', name )
|
|
print()
|
|
vlans = api.path( 'interface', 'vlan' )
|
|
ip_address = api.path( 'ip', 'address' )
|
|
ipv6_address = api.path( 'ipv6', 'address' )
|
|
for vlan in vlans:
|
|
if vlan[ 'vlan-id' ] == args.vlan:
|
|
rval = {}
|
|
if args.static:
|
|
rval[ 'static' ] = True
|
|
disabled = 'no'
|
|
elif name in args.home:
|
|
rval[ 'home' ] = True
|
|
disabled = 'no'
|
|
elif name in args.standby:
|
|
rval[ 'standby' ] = True
|
|
disabled = 'no'
|
|
else:
|
|
disabled = 'yes'
|
|
comment = make_comment( args.selector, rval, args.hostname )
|
|
|
|
for addr in args.address:
|
|
if args.dry_run:
|
|
if isinstance( addr, ipaddress.IPv4Interface ):
|
|
if addr.network.prefixlen == 32:
|
|
print( '/ip address add interface="%s" address="%s" network="%s" disabled=%s comment="%s"' % ( vlan[ 'name' ], config[ 'default_ipv4_loopback' ], addr.network.network_address, disabled, comment ) )
|
|
else:
|
|
print( '/ip address add interface="%s" address="%s" disabled=%s comment="%s"' % ( vlan[ 'name' ], addr, disabled, comment ) )
|
|
elif isinstance( addr, ipaddress.IPv6Interface ):
|
|
if args.static:
|
|
print( '/ipv6 address add interface="%s" address="%s" disabled=%s comment="%s" advertise=no' % ( vlan[ 'name' ], addr, disabled, comment ) )
|
|
else:
|
|
print( '/ipv6 address add interface="%s" address="%s" disabled=%s comment="%s" eui-64=yes advertise=yes' % ( vlan[ 'name' ], addr.network, disabled, comment ) )
|
|
|
|
else:
|
|
addr = ipaddress.ip_interface( addr )
|
|
prefix = _extract_bgp_announcement( addr, addr )
|
|
_adjust_filter( config, api, prefix, args.hostname,
|
|
home = ( name in args.home ),
|
|
standby = ( name not in args.home ) and ( name in args.standby ),
|
|
selector = args.selector, rval = rval )
|
|
if isinstance( addr, ipaddress.IPv4Interface ):
|
|
v4addr = { 'interface': vlan[ 'name' ],
|
|
'disabled': disabled,
|
|
'comment': comment,
|
|
}
|
|
if addr.network.prefixlen == 32:
|
|
v4addr[ 'address' ] = config[ 'default_ipv4_loopback' ]
|
|
v4addr[ 'network' ] = str( addr.network.network_address )
|
|
else:
|
|
v4addr[ 'address' ] = str( addr )
|
|
ip_address.add( **v4addr )
|
|
|
|
elif isinstance( addr, ipaddress.IPv6Interface ):
|
|
v6addr = { 'interface': vlan[ 'name' ],
|
|
'disabled': disabled,
|
|
'comment': comment,
|
|
}
|
|
if args.static:
|
|
v6addr[ 'address' ] = str( addr )
|
|
else:
|
|
v6addr[ 'address' ] = str( addr.network )
|
|
v6addr[ 'eui-64' ] = 'yes'
|
|
v6addr[ 'advertise' ] = 'yes'
|
|
ipv6_address.add( **v6addr )
|
|
|
|
if args.dry_run:
|
|
print()
|
|
|
|
def monty_teardown( config, args, routers ):
|
|
reverse = {}
|
|
statics = {}
|
|
if args.dry_run:
|
|
print()
|
|
for ( name, api ) in routers.items():
|
|
if args.dry_run:
|
|
print( '#' * ( len( name ) + 2 ) )
|
|
print( '#', name )
|
|
print()
|
|
vlans = api.path( 'interface', 'vlan' )
|
|
ip_address = api.path( 'ip', 'address' )
|
|
ipv6_address = api.path( 'ipv6', 'address' )
|
|
|
|
for addr in ip_address:
|
|
( sel, rval, hst ) = parse_comment( addr.get( 'comment', '' ) )
|
|
if sel == args.hostname or hst == args.hostname:
|
|
if args.delete:
|
|
if rval.get( 'teardown', False ):
|
|
if args.dry_run:
|
|
print( '/ip address remove [find where interface="%s" and network="%s" and comment~"teardown" and comment~"%s" ]' % ( addr[ 'interface' ], addr[ 'network' ], hst ) )
|
|
else:
|
|
print( "deleting %s %s from %s on %s" % ( addr[ 'address' ], addr[ 'network' ], addr[ 'interface' ], name ) )
|
|
ip_address.remove( addr[ '.id' ] )
|
|
else:
|
|
if args.dry_run:
|
|
print( "# ignoring %s %s on %s on %s" % ( addr[ 'address' ], addr[ 'network' ], addr[ 'interface' ], name ) )
|
|
else:
|
|
print( "ignoring %s %s on %s on %s" % ( addr[ 'address' ], addr[ 'network' ], addr[ 'interface' ], name ) )
|
|
else:
|
|
rval[ 'teardown' ] = True
|
|
comment = make_comment( sel, rval, hst )
|
|
if args.dry_run:
|
|
print( '/ip address set [find where interface="%s" and network="%s" and comment~"%s" ] disabled=yes comment="%s"' % ( addr[ 'interface' ], addr[ 'network' ], hst, comment ) )
|
|
else:
|
|
print( "disabling %s %s on %s on %s" % ( addr[ 'address' ], addr[ 'network' ], addr[ 'interface' ], name ) )
|
|
ip_address.update( **{ '.id': addr[ '.id' ], 'disabled': 'yes', 'comment': comment } )
|
|
|
|
for addr in ipv6_address:
|
|
( sel, rval, hst ) = parse_comment( addr.get( 'comment', '' ) )
|
|
if sel == args.hostname or hst == args.hostname:
|
|
if args.delete:
|
|
if rval.get( 'teardown', False ):
|
|
if args.dry_run:
|
|
print( '/ipv6 address remove [find where interface="%s" and address="%s" and comment="%s" ]' % ( addr[ 'interface' ], addr[ 'address' ], addr.get( 'comment', '' ) ) )
|
|
else:
|
|
print( "deleting %s from %s on %s" % ( addr[ 'address' ], addr[ 'interface' ], name ) )
|
|
ipv6_address.remove( addr[ '.id' ] )
|
|
else:
|
|
if args.dry_run:
|
|
print( "# ignoring %s on %s on %s" % ( addr[ 'address' ], addr[ 'interface' ], name ) )
|
|
else:
|
|
print( "ignoring %s on %s on %s" % ( addr[ 'address' ], addr[ 'interface' ], name ) )
|
|
else:
|
|
rval[ 'teardown' ] = True
|
|
comment = make_comment( sel, rval, hst )
|
|
if args.dry_run:
|
|
print( '/ipv6 address set [find where interface="%s" and address="%s" and comment="%s" ] disabled=yes comment="%s"' % ( addr[ 'interface' ], addr[ 'address' ], addr.get( 'comment', '' ), comment ) )
|
|
else:
|
|
print( "disabling %s on %s on %s" % ( addr[ 'address' ], addr[ 'interface' ], name ) )
|
|
ipv6_address.update( **{ '.id': addr[ '.id' ], 'disabled': 'yes', 'comment': comment } )
|
|
|
|
if args.dry_run:
|
|
print()
|
|
|
|
def _extract_bgp_announcement( address, network = None ):
|
|
addr = ipaddress.ip_interface( address )
|
|
if isinstance( addr, ipaddress.IPv4Interface ):
|
|
if addr.network.prefixlen == 32:
|
|
return ipaddress.ip_interface( network )
|
|
return addr.network
|
|
elif isinstance( addr, ipaddress.IPv6Interface ):
|
|
return addr.network
|
|
|
|
def monty_promote( config, args, routers ):
|
|
for new_home in args.new_home:
|
|
if new_home not in routers:
|
|
raise ValueError( 'cannot find new home "%s" in routers' % new_home )
|
|
for standby in args.standby:
|
|
if standby not in routers:
|
|
raise ValueError( 'cannot find new standby "%s" in routers' % standby )
|
|
|
|
done_work = False
|
|
|
|
for ( name, api ) in routers.items():
|
|
vlans = api.path( 'interface', 'vlan' )
|
|
ip_address = api.path( 'ip', 'address' )
|
|
ipv6_address = api.path( 'ipv6', 'address' )
|
|
|
|
spare_vlans = {}
|
|
for vlan in vlans:
|
|
if vlan[ 'vlan-id' ] == args.vlan:
|
|
spare_vlans[ vlan[ 'name' ] ] = True
|
|
|
|
for af in ( ip_address, ipv6_address ):
|
|
for addr in af:
|
|
( sel, rval, hst ) = parse_comment( addr.get( 'comment', '' ) )
|
|
if sel == args.hostname or hst == args.hostname:
|
|
if addr.get( 'disabled', False ):
|
|
if ( name in args.standby ) or ( name in args.new_home ):
|
|
address = _extract_bgp_announcement( addr[ 'address' ], addr.get( 'network', None ) )
|
|
rval = _adjust_filter( config, api, address, hst, selector = sel,
|
|
home = ( name in args.new_home ),
|
|
standby = ( name not in args.new_home ) and ( name in args.standby ),
|
|
static = args.static,
|
|
rval = rval,
|
|
)
|
|
new_comment = make_comment( sel, rval, hst )
|
|
af.update( **{ '.id': addr[ '.id' ],
|
|
'disabled': False,
|
|
'comment': new_comment,
|
|
} )
|
|
done_work = True
|
|
else:
|
|
if ( name in args.new_home ):
|
|
address = _extract_bgp_announcement( addr[ 'address' ], addr.get( 'network', None ) )
|
|
rval = _adjust_filter( config, api, address, hst, selector = sel,
|
|
home = ( name in args.new_home ),
|
|
standby = False,
|
|
static = args.static,
|
|
rval = rval,
|
|
)
|
|
new_comment = make_comment( sel, rval, hst )
|
|
af.update( **{ '.id': addr[ '.id' ],
|
|
'disabled': False,
|
|
'comment': new_comment,
|
|
} )
|
|
done_work = True
|
|
|
|
spare_vlans.pop( addr[ 'interface' ], None )
|
|
|
|
for vlan in spare_vlans:
|
|
spare.append( ( name, ip_address, ipv6_address, vlan ) )
|
|
|
|
if args.create:
|
|
for ( name, ip_address, ipv6_address, vlan ) in spare:
|
|
rval = {}
|
|
disabled = 'yes'
|
|
if name in args.new_home:
|
|
rval[ 'home' ] = True
|
|
disabled = 'no'
|
|
elif name in args.standby:
|
|
disabled = 'no'
|
|
new_comment = make_comment( args.selector, rval, args.hostname )
|
|
for addr in args.create:
|
|
rval = _adjust_filter( config, api, addr.network, args.hostname, selector = args.selector,
|
|
home = ( name in args.new_home ),
|
|
standby = ( name not in args.new_home ) and ( name in args.standby ),
|
|
static = args.static,
|
|
rval = rval,
|
|
)
|
|
if isinstance( addr, ipnetwork.IPv4Interface ):
|
|
print( "creating address on interface" )
|
|
v4addr = { 'interface': vlan,
|
|
'disabled': disabled,
|
|
'comment': new_comment,
|
|
}
|
|
if addr.network.prefixlen == 32:
|
|
v4addr[ 'address' ] = config[ 'default_ipv4_loopback' ]
|
|
v4addr[ 'network' ] = str( addr.network.network_address )
|
|
else:
|
|
v4addr[ 'address' ] = str( addr )
|
|
ip_address.create( **v4addr )
|
|
done_work = True
|
|
elif isinstance( addr, ipnetwork.IPv6Interface ):
|
|
print( "creating address on interface" )
|
|
v6addr = { 'interface': vlan,
|
|
'disabled': disabled,
|
|
'comment': comment,
|
|
}
|
|
if args.static:
|
|
v6addr[ 'address' ] = str( addr )
|
|
else:
|
|
v6addr[ 'address' ] = str( addr.network )
|
|
v6addr[ 'eui-64' ] = 'yes'
|
|
v6addr[ 'advertise' ] = 'yes'
|
|
ipv6_address.create( **v6addr )
|
|
done_work = True
|
|
return done_work
|
|
|
|
def monty_demote( config, args, routers ):
|
|
for disable in args.disable:
|
|
if disable not in routers:
|
|
raise ValueError( 'cannot find disable "%s" in routers' % disable )
|
|
for standby in args.standby:
|
|
if standby not in routers:
|
|
raise ValueError( 'cannot find standby "%s" in routers' % standby )
|
|
|
|
done_work = False
|
|
|
|
for ( name, api ) in routers.items():
|
|
vlans = api.path( 'interface', 'vlan' )
|
|
ip_address = api.path( 'ip', 'address' )
|
|
ipv6_address = api.path( 'ipv6', 'address' )
|
|
|
|
for af in ( ip_address, ipv6_address ):
|
|
for addr in af:
|
|
( sel, rval, hst ) = parse_comment( addr.get( 'comment', '' ) )
|
|
if sel == args.hostname or hst == args.hostname:
|
|
if ( name in args.standby ) or ( name in args.disable ):
|
|
address = _extract_bgp_announcement( addr[ 'address' ], addr.get( 'network', None ) )
|
|
rval = _adjust_filter( config, api, address, hst, selector = sel,
|
|
home = False,
|
|
standby = ( name in args.standby ),
|
|
rval = rval,
|
|
)
|
|
new_comment = make_comment( sel, rval, hst )
|
|
af.update( **{ '.id': addr[ '.id' ],
|
|
'disabled': ( name in args.disable ),
|
|
'comment': new_comment,
|
|
} )
|
|
done_work = True
|
|
|
|
def monty_migrate( config, args, routers ):
|
|
if monty_promote( config, args, routers ):
|
|
monty_demote( config, args, routers )
|
|
|
|
def load_configuration():
|
|
try:
|
|
config = open( os.path.expanduser( '~/.automonty.yaml' ), 'r' )
|
|
except IOError:
|
|
return {}
|
|
return yaml.load( config.read(), yaml.SafeLoader )
|
|
|
|
def main():
|
|
config = load_configuration()
|
|
|
|
parser = argparse.ArgumentParser( prog = "automonty",
|
|
description = 'AutoMonty (re-)configures routers',
|
|
)
|
|
subparsers = parser.add_subparsers()
|
|
|
|
parser.add_argument( '--only-router', action = 'append', default = [] )
|
|
parser.add_argument( '--only-group', action = 'append', default = [] )
|
|
parser.add_argument( '-n', '--dry-run', action = 'store_true', default = False )
|
|
|
|
parser_check = subparsers.add_parser( 'check' )
|
|
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 )
|
|
|
|
parser_provision = subparsers.add_parser( 'provision' )
|
|
parser_provision.add_argument( '--home', action = 'append', default = [] )
|
|
parser_provision.add_argument( '--standby', action = 'append', default = [] )
|
|
parser_provision.add_argument( '-s', '--static', action = 'store_true', default = False )
|
|
parser_provision.add_argument( '--selector', type = str, default = None )
|
|
parser_provision.add_argument( 'hostname' )
|
|
parser_provision.add_argument( 'vlan', type = int )
|
|
parser_provision.add_argument( 'address', type = ipaddress.ip_interface, nargs = "+" )
|
|
parser_provision.set_defaults( func = monty_provision )
|
|
|
|
parser_promote = subparsers.add_parser( 'promote' )
|
|
parser_promote.add_argument( '-e', '--enable', action = 'store_true', default = False )
|
|
parser_promote.add_argument( '-c', '--create', type = ipaddress.ip_interface, default = [], action = 'append' )
|
|
parser_promote.add_argument( '-s', '--static', action = 'store_true', default = False )
|
|
parser_promote.add_argument( '--standby', action = 'append', default = [] )
|
|
parser_promote.add_argument( '--selector', type = str, default = None )
|
|
parser_promote.add_argument( 'hostname' )
|
|
parser_promote.add_argument( 'vlan', type = int )
|
|
parser_promote.add_argument( 'new_home', nargs = "*" )
|
|
parser_promote.set_defaults( func = monty_promote )
|
|
|
|
parser_demote = subparsers.add_parser( 'demote' )
|
|
parser_demote.add_argument( '--standby', action = 'append', default = [] )
|
|
parser_demote.add_argument( 'hostname' )
|
|
parser_demote.add_argument( 'vlan', type = int )
|
|
parser_demote.add_argument( 'disable', nargs = "*" )
|
|
parser_demote.set_defaults( func = monty_demote )
|
|
|
|
parser_migrate = subparsers.add_parser( 'migrate' )
|
|
parser_migrate.add_argument( '-e', '--enable', action = 'store_true', default = False )
|
|
parser_migrate.add_argument( '-c', '--create', type = ipaddress.ip_interface, default = [], action = 'append' )
|
|
parser_migrate.add_argument( '-s', '--static', action = 'store_true', default = False )
|
|
parser_migrate.add_argument( '--selector', type = str, default = None )
|
|
parser_migrate.add_argument( '--standby', action = 'append', default = [] )
|
|
parser_migrate.add_argument( '--disable', action = 'append', default = [] )
|
|
parser_migrate.add_argument( 'hostname' )
|
|
parser_migrate.add_argument( 'vlan', type = int )
|
|
parser_migrate.add_argument( 'new_home', nargs = "*" )
|
|
parser_migrate.set_defaults( func = monty_migrate )
|
|
|
|
parser_teardown = subparsers.add_parser( 'teardown' )
|
|
parser_teardown.add_argument( '--delete', action = 'store_true', default = False )
|
|
parser_teardown.add_argument( 'hostname' )
|
|
parser_teardown.add_argument( 'vlan', type = int, default = None, nargs = "?" )
|
|
parser_teardown.set_defaults( func = monty_teardown )
|
|
|
|
args = parser.parse_args()
|
|
routers = connect_routers( config, args )
|
|
args.func( config, args, routers )
|
|
|
|
if __name__ == '__main__':
|
|
main()
|