pastafari2/libraries/task.py
2023-04-02 01:04:29 +02:00

641 lines
24 KiB
Python

#!/usr/bin/env python3
import paramiko, json
import os, sys,traceback
from stat import S_ISDIR
from modules.pastafari2.models import tasks
#from modules.pastafari2.models.servers import ServerGroupTask
from modules.pastafari2.libraries.configtask import config_task
from paramecio2.libraries.i18n import I18n
from paramecio2.libraries.db.webmodel import WebModel
class Task:
#($server='', $ssh_user='root', $ssh_key_priv='./ssh/id_rsa', $ssh_key_password='', $ssh_path='leviathan', $mysql_conn=false)
def __init__(self, server, conn, remote_user='root', remote_password='', private_key='./ssh/id_rsa', password_key='', remote_path='pastafari2', task_id=0, data={}):
self.config=config_task
self.server=server
self.name_task=''
self.codename_task=''
self.description_task=''
self.txt_error=''
self.os_server=''
self.files=[]
# Format first array element is command with the interpreter, the task is agnostic, the files in os directory. The commands are setted with 750 permission.
# First element is the file, next elements are the arguments
self.commands_to_execute=[];
#THe files to delete
self.delete_files=[];
self.delete_directories=[];
#The id of the task in db
self.id=task_id
self.user=''
self.password=''
self.connection=conn
self.logtask=tasks.LogTask(self.connection)
self.resulttask=tasks.ResultTask(self.connection)
self.task=tasks.Task(self.connection)
self.taskdone=tasks.TaskDone(self.connection)
self.ssh=paramiko.SSHClient()
self.logtask.reset_require()
self.task.reset_require()
self.resulttask.reset_require()
self.one_time=False
self.version='1.0'
self.simultaneous=False
self.error_post_task=''
self.data=data
self.remote_user=remote_user
self.remote_password=remote_password
self.private_key=private_key
self.password_key=password_key
self.remote_path=remote_path
"""
self.pre_task=None
self.error_task=None
self.post_task=None
"""
def prepare_connection(self):
#self.ssh.load_system_host_keys()
#Check if the unknown host keys are rejected or not
#if self.config.deny_missing_host_key == False:
#self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Check if in known_hosts
check_ssh_host= paramiko.hostkeys.HostKeys()
if not os.path.isfile(self.config.ssh_directory+'/known_hosts'):
f=open(self.config.ssh_directory+'/known_hosts', 'w')
f.write('')
f.close()
self.ssh.load_system_host_keys(self.config.ssh_directory+'/known_hosts')
check_ssh_host.load(self.config.ssh_directory+'/known_hosts')
host_key=self.ssh.get_host_keys()
add_host=False
rsa=None
if self.private_key!='':
rsa=paramiko.RSAKey.from_private_key_file(self.private_key, self.password_key)
if check_ssh_host.lookup(self.server)==None:
# Be tolerant for the first connect with hostkey policy
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
add_host=True
try:
self.ssh.connect(self.server, port=self.config.port, username=self.remote_user, password=self.remote_password, pkey=rsa, key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, compress=False, sock=None, gss_auth=False, gss_kex=False, gss_deleg_creds=True, gss_host=None, banner_timeout=None)
if add_host:
host_key.save(self.config.ssh_directory+'/known_hosts')
except paramiko.SSHException as e:
self.txt_error="Error: cannot connect to the server SSHException\n"+str(e)
return False
except paramiko.AuthenticationException as e:
self.txt_error="Error: cannot connect to the server AuthenticationException \n"+str(e)
return False
except paramiko.BadHostKeyException as e:
self.txt_error="Error: cannot connect to the server BadHostKeyException\n"+str(e)
return False
except OSError as e:
self.txt_error="Error: cannot connect to the server OsError \n"+str(e)
return False
except:
self.txt_error="Error: cannot connect to the server Generic\n"+traceback.format_exc()
return False
#finally:
# self.ssh.close()
return True
def upload_files(self):
try:
sftp=self.ssh.open_sftp()
except:
self.txt_error='Sorry, error connecting to sftp: '+traceback.format_exc()
return False
c=len(self.files)
if c>0:
percent=100/c
progress=0
for f in self.files:
source_file=f[0]
source_file=source_file.replace('${os_server}', self.os_server)
permissions=f[1]
#dest_file=self.remote_path+'/'+source_file
dest_file=source_file
try:
if not os.path.isfile(source_file):
self.txt_error="Sorry, you don't have source file to upload "+source_file
return False
dir_file=os.path.dirname(dest_file)
parts_dir_file=dir_file.split('/')
# Create remote directory
try:
f_stat=sftp.stat(dir_file)
except IOError:
try:
final_path=''
for d in parts_dir_file:
final_path+=d+'/'
#print(self.remote_path+'/'+final_path)
try:
f_stat=sftp.stat(final_path)
except IOError:
sftp.mkdir(final_path)
except:
self.txt_error='Sorry, error creating the directories for the files: '+traceback.format_exc()
return False
# Upload file
try:
sftp.put(source_file, dest_file, callback=None, confirm=True)
sftp.chmod(dest_file, permissions)
progress+=percent
self.logtask.insert({'task_id': self.id, 'progress': progress, 'message': I18n.lang('pastafari', 'uploading_files', 'Uploading file: ')+source_file, 'error': 0, 'server': self.server})
except:
self.txt_error='Sorry, cannot upload file '+source_file+': '+traceback.format_exc()
return False
# Create directory recursively if not exists
except:
self.txt_error='Error: '+traceback.format_exc()
return False
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'upload_successful', 'Files uploaded successfully...'), 'error': 0, 'server': self.server})
return True
def delete_files_and_dirs(self):
sftp=self.ssh.open_sftp()
c=len(self.delete_files)
if c>0:
percent=100/c
progress=0
for filepath in self.delete_files:
filepath=filepath.replace('${os_server}', self.os_server)
try:
#print(self.remote_path+'/'+filepath)
sftp.remove(filepath)
progress+=percent
self.logtask.insert({'task_id': self.id, 'progress': progress, 'message': I18n.lang('pastafari', 'cleaning_files', 'Cleaning file: ')+filepath, 'error': 0, 'server': self.server})
except IOError:
self.txt_error="Sorry, cannot remove file "+filepath+" from server."
return False
c=len(self.delete_directories)
if c>0:
percent=100/c
progress=0
for path in self.delete_directories:
try:
path=path.replace('${os_server}', self.os_server)
self.delete_dir(path, sftp)
progress+=percent
self.logtask.insert({'task_id': self.id, 'progress': progress, 'message': I18n.lang('pastafari', 'cleaning_directories', 'Cleaning directory: ')+path, 'error': 0, 'server': self.server})
except IOError:
self.txt_error+="Sorry, cannot remove directory "+path+" from server."
return False
return True
def isdir(self, path, sftp):
try:
return S_ISDIR(sftp.stat(path).st_mode)
except IOError:
return False
def delete_dir(self, path, sftp):
#path=self.config.remote_path+'/'+path
if path != "/":
files = sftp.listdir(path)
for f in files:
filepath = os.path.join(path, f)
try:
if self.isdir(filepath, sftp):
self.delete_dir(filepath, sftp)
else:
sftp.remove(filepath)
except IOError:
self.txt_error="Sorry, cannot remove "+filepath+" from server."
return False
sftp.rmdir(path)
return True
def exec(self):
# Get task
#self.id=task_id
self.task.reset_require()
self.task.valid_fields=['name_task', 'description_task', 'error', 'status', 'server']
self.logtask.valid_fields=self.logtask.fields.keys()
self.resulttask.valid_fields=self.resulttask.fields.keys()
if self.id==0:
# Insert the task
self.task.reset_require()
self.task.insert({'name_task': self.name_task, 'description_task': self.description_task, 'server': self.server})
self.id=self.task.insert_id()
if not self.prepare_connection():
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 1, 'status': 1})
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': self.txt_error, 'error': 1, 'status': 1, 'server': self.server})
self.make_error_task()
return False
# Pre task
if hasattr(self, 'pre_task'):
self.logtask.insert({'task_id': self.id, 'progress': 0, 'message': I18n.lang('pastafari', 'pre_tasks', 'Begin pre tasks execution...'), 'error': 0, 'status': 0, 'server': self.server})
if self.pre_task():
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'pre_tasks_executed', 'Pre tasks executed successfully...'), 'error': 0, 'status': 0, 'server': self.server})
else:
#self.logtask.set_conditions('where id=%s', [last_log_id])
self.logtask.insert({'progress': 100, 'error': 1, 'message': "Error executing pre task "+self.txt_error, 'status': 1, 'server': self.server, 'task_id': self.id})
self.make_error_task()
return False
#Check if script was executed
if self.codename_task!='':
if self.one_time==True:
with self.ssh.open_sftp() as sftp:
try:
with sftp.file(self.remote_path+'/tasks/'+self.codename_task) as f:
version=f.read()
version=version.decode('utf-8').strip()
if version==self.version:
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 0, 'status': 1})
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': 'This script was executed correctly in this server', 'error': 0, 'status': 1, 'server': self.server})
return True
except IOError:
# It was not executed
pass
if not self.upload_files():
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 1, 'status': 1})
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': self.txt_error, 'error': 1, 'status': 1, 'server': self.server})
self.make_error_task()
return False
self.logtask.insert({'task_id': self.id, 'progress': 0, 'message': 'Executing commands...', 'error': 0, 'status': 0, 'server': self.server})
# Execute commands
json_code=[]
for c in self.commands_to_execute:
try:
command=c[0]
command=command.replace('${os_server}', self.os_server)
arguments=c[1]
sudo_str=''
if len(c)==3:
sudo_str='sudo '
#, get_pty=True
stdin, stdout, stderr = self.ssh.exec_command(sudo_str+command+' '+arguments)
for line in stdout:
if line==None:
line="[]"
line=line.strip()
try:
json_code=json.loads(line)
if not 'progress' in json_code or not 'message' in json_code or not 'error' in json_code:
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 1, 'status': 1})
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': 'Malformed json code: '+str(line), 'error': 1, 'status': 1, 'server': self.server})
self.make_error_task()
return False
else:
json_code['task_id']=self.id
json_code['server']=self.server
if 'result' in json_code:
#{'result': 1, 'message': {'username': 'hosting', 'uid': 1002, 'gid': 1002, 'home': '/srv/sites'}, 'error': 0, 'progress': 100, 'task_id': 483, 'server': '192.168.122.55'}
#json_code['message']=json.dumps(json_code['message'])
#print(json_code)
self.resulttask.insert(json_code)
#print(self.resulttask.show_errors())
else:
self.logtask.insert(json_code)
if json_code['error']==1:
self.make_error_task()
return False
except:
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 0, 'status':0})
self.logtask.insert({'task_id': self.id, 'progress': 0, 'no_progress': 1, 'message': str(line), 'error': 0, 'status': 0, 'server': self.server})
#return False
last_log_id=self.logtask.insert_id()
if stdout.channel.recv_exit_status()>0:
#line=stdout.readlines()
#logging.warning(action.codename+" WARNING: "+line)
final_text='Error executing the command: %s' % command
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 1, 'status': 1})
for line in stdout:
final_text+=' '+line
for line in stderr:
final_text+=' '+line
self.logtask.set_conditions('where id=%s', [last_log_id])
self.logtask.update({'progress': 100, 'error': 1, 'message': final_text, 'status': 1, 'server': self.server})
self.make_error_task()
return False
except:
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 1, 'status': 1})
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': traceback.format_exc(), 'error': 1, 'status': 1, 'server': self.server})
self.make_error_task()
return False
# Clean files
if not self.delete_files_and_dirs():
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 1, 'status': 1})
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': self.txt_error, 'error': 1, 'status': 1, 'server': self.server})
self.make_error_task()
return False
#Upload files
#self.ssh.close()
# FInish task
#Put this version how executed
if self.codename_task!='':
if self.one_time==True:
with self.ssh.open_sftp() as sftp:
try:
path_check=self.remote_path+'/tasks/'
f_stat=sftp.stat(path_check)
except IOError:
sftp.mkdir(path_check)
with sftp.file(path_check+self.codename_task, 'w') as f:
f.write(self.version)
if hasattr(self, 'post_task'):
self.logtask.insert({'task_id': self.id, 'progress': 0, 'message': I18n.lang('pastafari', 'post_tasks', 'Post tasks executing...'), 'error': 0, 'status': 0, 'server': self.server})
if self.post_task():
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'post_tasks_executed', 'Post tasks executed successfully...'), 'error': 0, 'status': 0, 'server': self.server})
else:
#self.logtask.set_conditions('where id=%s', [last_log_id])
#self.logtask.update({'progress': 100, 'error': 1, 'message': "Error executing post task", 'status': 1, 'server': self.server})
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'error_post_tasks_executed', 'Error executing post task -> '+self.error_post_task), 'error': 1, 'status': 1, 'server': self.server})
self.make_error_task()
return False
if 'progress' in json_code:
#if json_code['progress']!=100:
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'finished_successfully', 'All tasks done successfully...'), 'error': 0, 'status': 1, 'server': self.server})
else:
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'finished_successfully', 'All tasks done successfully...'), 'error': 0, 'status': 1, 'server': self.server})
# Add
self.taskdone.create_forms()
self.taskdone.insert({'name_task': self.codename_task, 'ip': self.server})
#self.task.conditions=['WHERE id=%s', [self.id]]
#self.task.update({'error': 0, 'status': 1})
#connection.close()
return True
def make_error_task(self):
if hasattr(self, 'error_task'):
self.logtask.insert({'task_id': self.id, 'progress': 0, 'message': I18n.lang('pastafari', 'error_tasks', 'Error tasks executing...'), 'error': 0, 'status': 0, 'server': self.server})
if self.error_task():
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'error_tasks_executed', 'Sorry, you have an error, please, review the log...'), 'error': 1, 'status': 1, 'server': self.server})
else:
self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'error_tasks_executed', 'Error Post task cannot be executed...'), 'error': 1, 'status': 1, 'server': self.server})
return False
def __del__(self):
#self.connection.close()
if self.ssh!=None:
self.ssh.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
if exc_type is not None:
traceback.print_exception(exc_type, exc_value, tb)
return True