get config 2x before reporting change
This commit is contained in:
parent
83d4c0e6a4
commit
c99e790fc8
117
routerbackup
117
routerbackup
@ -78,7 +78,10 @@ class Config:
|
||||
self.sshkey = None
|
||||
self.config = None
|
||||
self.extra = {}
|
||||
self.oldconfig = None
|
||||
self.filebase = None
|
||||
self.prev_config = None
|
||||
self.prev_timestamp = None
|
||||
self.prev_diff = []
|
||||
|
||||
def connect(self, host=None, user=None, passwd=None, sshkey=None):
|
||||
if host: self.host = host
|
||||
@ -92,8 +95,14 @@ class Config:
|
||||
|
||||
self.ssh = paramiko.SSHClient()
|
||||
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)
|
||||
self.ssh.connect(self.host, username=self.user,
|
||||
password=self.passwd, key_filename=self.sshkey)
|
||||
try:
|
||||
self.ssh.connect(self.host, username=self.user,
|
||||
password=self.passwd, key_filename=self.sshkey)
|
||||
except:
|
||||
# disabling sha2 makes sha1 default for older devices (routeros 6)
|
||||
self.ssh.connect(self.host, username=self.user,
|
||||
password=self.passwd, key_filename=self.sshkey,
|
||||
disabled_algorithms = dict(pubkeys=['rsa-sha2-256', 'rsa-sha2-512']))
|
||||
|
||||
def is_bad_config(self):
|
||||
if not self.config:
|
||||
@ -105,44 +114,58 @@ class Config:
|
||||
def _prepare_config_for_diff(self, configlines):
|
||||
return configlines
|
||||
|
||||
def set_filebase(self, base):
|
||||
self.filebase = base
|
||||
|
||||
def has_changed(self, fn, diffprint=False, difflog=False):
|
||||
def load_previous(self):
|
||||
try:
|
||||
with open(f'{self.workdir}/{fn}.config', 'r') as f:
|
||||
self.oldconfig = f.read()
|
||||
with open(f'{self.workdir}/{self.filebase}.config', 'r') as f:
|
||||
self.prev_config = f.read()
|
||||
except:
|
||||
log.info('No previous configuration found.')
|
||||
return True
|
||||
self.prev_config = None
|
||||
return
|
||||
|
||||
if self.oldconfig == self.config:
|
||||
return False
|
||||
|
||||
oldtimestamp = 'previous'
|
||||
self.prev_timestamp = 'previous'
|
||||
try:
|
||||
with open(f'{self.workdir}/{fn}.lastchange', 'r') as f:
|
||||
with open(f'{self.workdir}/{self.filebase}.lastchange', 'r') as f:
|
||||
oldunixts = int(f.readline())
|
||||
oldtimestamp = datetime.datetime.fromtimestamp(oldunixts)
|
||||
self.prev_timestamp = datetime.datetime.fromtimestamp(oldunixts)
|
||||
except:
|
||||
pass
|
||||
|
||||
def has_previous(self):
|
||||
return True if self.prev_config else False
|
||||
|
||||
def has_changed(self):
|
||||
self.prev_diff = []
|
||||
if not self.prev_config:
|
||||
return True
|
||||
if self.prev_config == self.config:
|
||||
return False
|
||||
|
||||
contents = {}
|
||||
for cf, n in ((self.oldconfig, 'old'), (self.config, 'new')):
|
||||
for cf, n in ((self.prev_config, 'old'), (self.config, 'new')):
|
||||
contents[n] = self._prepare_config_for_diff(cf.splitlines(keepends=True))
|
||||
|
||||
has_changed = False
|
||||
for line in difflib.unified_diff(
|
||||
contents['old'], contents['new'],
|
||||
fromfile=f'{fn} {oldtimestamp}', tofile=f'{fn} now'
|
||||
fromfile=f'{self.filebase} {self.prev_timestamp}', tofile=f'{self.filebase} now'
|
||||
):
|
||||
has_changed = True
|
||||
if diffprint:
|
||||
print(line.rstrip())
|
||||
if difflog:
|
||||
log.info(line.rstrip())
|
||||
self.prev_diff.append(line.rstrip())
|
||||
return has_changed
|
||||
|
||||
def write_to_disk(self, fn, has_changed=True):
|
||||
basepath = f'{self.workdir}/{fn}'
|
||||
def print_changed(self, diffprint=False, difflog=False):
|
||||
for line in self.prev_diff:
|
||||
if diffprint:
|
||||
print(line)
|
||||
if difflog:
|
||||
log.info(line)
|
||||
|
||||
def write_to_disk(self, has_changed=True):
|
||||
basepath = f'{self.workdir}/{self.filebase}'
|
||||
with open(f'{basepath}.config', 'w') as f:
|
||||
f.write(self.config)
|
||||
for e in self.extra:
|
||||
@ -155,10 +178,10 @@ class Config:
|
||||
if has_changed:
|
||||
with open(f'{basepath}.lastchange', 'w') as f:
|
||||
f.write(f'{int(time.time())}\n{time.strftime("%F %T")}\n')
|
||||
log.info(f'Written to: {basepath}.*')
|
||||
log.info(f'Written {"differing" if has_changed else "similar"} configuration to: {basepath}.*')
|
||||
|
||||
def backup_old(self, fn):
|
||||
basepath = f'{self.workdir}/{fn}'
|
||||
def backup_old(self):
|
||||
basepath = f'{self.workdir}/{self.filebase}'
|
||||
try:
|
||||
info = os.stat(basepath +'.config')
|
||||
oldtimestamp = datetime.datetime.fromtimestamp(info.st_mtime).strftime('%F_%H%M%S')
|
||||
@ -337,7 +360,7 @@ class ConfigWithShell(Config):
|
||||
while not channel.send_ready():
|
||||
if time.time() - start > send_timeout:
|
||||
raise RuntimeError('shell send timeout')
|
||||
time.sleep(0.05)
|
||||
time.sleep(0.1)
|
||||
channel.send(cmd +'\n')
|
||||
|
||||
start = time.time()
|
||||
@ -351,7 +374,7 @@ class ConfigWithShell(Config):
|
||||
return output.splitlines()
|
||||
if time.time() - start > recv_timeout:
|
||||
raise RuntimeError(f'shell recv timeout (after receiving {len(output)} characters)')
|
||||
time.sleep(0.05)
|
||||
time.sleep(0.1)
|
||||
while channel.recv_ready():
|
||||
output += str(channel.recv(65536), 'utf8')
|
||||
|
||||
@ -450,25 +473,47 @@ def main():
|
||||
elif args.type == 'comware':
|
||||
cf = ComwareConfig(workdir=args.backupdir)
|
||||
|
||||
filebase = args.host
|
||||
cf.set_filebase(filebase)
|
||||
log.debug(f'Backup filename base: {filebase}')
|
||||
cf.load_previous()
|
||||
|
||||
address = args.address or args.host
|
||||
log.info(f'Connecting to {args.type}: {args.user}@{address} with{" password" if args.password else""}{" ssh-key" if args.sshkey else""}')
|
||||
start = time.time()
|
||||
os.environ['HOME'] = '/' # prevent paramiko from searching private keys
|
||||
cf.connect(address, args.user, args.password, args.sshkey or '')
|
||||
cf.get_config()
|
||||
if reason := cf.is_bad_config():
|
||||
log.warning(f'Bad configuration: {reason}')
|
||||
return
|
||||
has_changed = False
|
||||
changed_count = 0
|
||||
for i in range(3):
|
||||
cf.connect(address, args.user, args.password, args.sshkey or '')
|
||||
cf.get_config()
|
||||
|
||||
filename = args.host
|
||||
log.debug(f'Backup filename base: {filename}')
|
||||
if reason := cf.is_bad_config():
|
||||
log.warning(f'Bad configuration: {reason}')
|
||||
#return
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
has_changed = cf.has_changed()
|
||||
if not has_changed:
|
||||
# first try: good
|
||||
# second try: first was probably incomplete -> good
|
||||
break
|
||||
if not cf.has_previous():
|
||||
break
|
||||
changed_count += 1
|
||||
log.info(f'Got differing configuration on {changed_count}. attempt.')
|
||||
if changed_count >= 2:
|
||||
# got changed config twice, that is no mistake
|
||||
break
|
||||
time.sleep(3)
|
||||
|
||||
has_changed = cf.has_changed(filename, diffprint=args.diffprint, difflog=args.difflog)
|
||||
if has_changed:
|
||||
cf.backup_old(filename)
|
||||
cf.print_changed(diffprint=args.diffprint, difflog=args.difflog)
|
||||
cf.backup_old()
|
||||
# overwrite unchanged config, too, to update modification time and
|
||||
# also store changes that get skipped by _prepare_config_for_diff():
|
||||
cf.write_to_disk(filename, has_changed)
|
||||
cf.write_to_disk(has_changed)
|
||||
log.info(f'Finished in {time.time()-start:.1f}s.')
|
||||
except KeyboardInterrupt:
|
||||
log.error(f'Killed by KeyboardInterrupt.')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user