#!/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=None, remote_path='pastafari2', task_id=0, data={}, port=22): self.config=config_task self.server=server self.name_task='' self.codename_task='' self.description_task='' self.txt_error='' self.os_server='' self.port=port 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.url_return='' self.links='' self.path_module='admin_app.pastafari2_dashboard' """ 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 self.logtask.insert({'task_id': self.id, 'progress': 0, 'message': I18n.lang('pastafari', 'pre_tasks', 'Begin connection to the server...'), 'error': 0, 'status': 0, 'server': self.server}) 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!='': try: rsa=paramiko.RSAKey.from_private_key_file(self.private_key, self.password_key) except paramiko.ssh_exception.SSHException: rsa=paramiko.Ed25519Key.from_private_key_file(self.private_key, self.password_key) """ #if not os.path.isfile(self.private_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 else: if self.port!=22: if check_ssh_host.lookup('[{}]:{}'.format(self.server, self.port))==None: #Be tolerant and save if port is different self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #add_host=True try: #self.ssh.connect(self.server, port=self.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 self.remote_password: self.ssh.connect(self.server, port=self.port, username=self.remote_user, password=self.remote_password, timeout=None, allow_agent=False, look_for_keys=False, compress=False, sock=None, gss_auth=False, gss_kex=False, gss_deleg_creds=True, gss_host=None, banner_timeout=None) else: self.ssh.connect(self.server, port=self.port, username=self.remote_user, key_filename=self.private_key, passphrase=self.password_key, timeout=None, allow_agent=False, look_for_keys=False, 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).strip(), '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) json_code['message']=json.loads(json_code['message']) 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).strip(), '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}) try: 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 except: #traceback.print_exception(exc_type, exc_value, tb) #traceback.format_exc() self.logtask.insert({'task_id': self.id, 'progress': 100, 'message': I18n.lang('pastafari', 'error_post_tasks_executed', 'Error executing post task -> '+traceback.format_exc()), '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