get config 2x before reporting change

This commit is contained in:
ROTTLER Tamas 2023-05-15 01:59:30 +02:00
parent 83d4c0e6a4
commit c99e790fc8

View File

@ -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.')