#!/bin/env python3
# coding:utf-8
"""
Usage:
auto_task [options] cmd [--skip-err] [--parallel]
auto_task [options] put [--parallel]
auto_task [options] get
Options:
-h --help Show this screen.
-u Remote username [default: root]
-p User's password
--pkey Local private key [default: /root/.ssh/id_rsa]
--server
File include the remote server's information,
With the format of 'name-ip:port', such as 'web1-192.168.1.100:22',one server one line.
--skip-err Use with cmd, if sikp any server's error and continue process the other servers [default: False].
--parallel Parallel execution, only use with cmd or put. This option implies the --skip-err [default: False].
cmd Run command on remote server(s),multiple commands sperate by ';'
put Transfer from local to remote. Transport mechanism similar to rsync.
get Transfer from remote to local. Transport mechanism similar to rsync.
Notice: cmd, get, put can only use one at once
For Windows: always use double quotes for quote something;
it's highly recommend that with get or put in Windows,always use '/' instead of ''
"""
"""
by ljk 20160704
update at 2017011,20170320
"""
fromdocoptimportdocopt
fromparamikoimportSSHClient, AutoAddPolicy
fromosimportpath, walk, makedirs, stat, utime
fromreimportsplit, match, search
fromsysimportexit, stdout
importplatform
frommathimportfloor
importthreading
"""
因为涉及了(多)线程,所以我们将串行也归为单线程,这样可以统一用线程的一些思路,而不必编写一套多线程模型一套串行模型。
也因为多线程,所以输出用print()的话,各server的输出会对不上号,所以引入了OutputText类,将每个server的输出统一保存起来,最后打印出来
但是这样依然无法避免多个线程同时完成了,同时打印各自的最终结果。也就是说多线程任务最终需要输出时,输出这个动作必须要串行
"""
classOutputText:
"""该类的对象具有write()方法,用来存储每台server的执行结果.
因为引入了多线程异步执行才需要这么做,以保证异步执行多台server的输出不会乱.
为了简洁,并行与串行的输出就都用这一套东西了"""
def__init__(self):
self.buffer = []
defwrite(self, *args, color=None):
ifcolor:
ifplatform.uname().system =='Windows':
self.buffer.extend(args)
else:
self.buffer.extend('033[0;{}m'.format(color))
self.buffer.extend(args)
self.buffer.extend('033[0m')
else:
self.buffer.extend(args)
defprint_lock(self):
"""并发模式下,所有的输出动作都要加锁"""
global_lock.acquire()
forlineinself.buffer:
print(line, end='')
global_lock.release()
defprint_color(text, color=31, sep=' ', end='n', file=stdout, flush=False):
"""打印彩色字体,color默认为红色
该方法只针对Linux有效"""
ifplatform.uname().system =='Windows':
print(text, sep=sep, end=end, file=file, flush=flush)
else:
print('033[0;{}m'.format(color), end='')
print(text, sep=sep, end=end, file=file, flush=flush)
print('033[0m', end='')
defget_ip_port(fname):
"""从制定文件(特定格式)中,取得主机名/主机ip/端口
output:存储输出的对象"""
try:
with open(fname, 'r') as fobj:
forlineinfobj.readlines():
ifline !='n'andnotmatch('#', line): # 过滤空行和注释行
list_tmp = split('[-:]', line)
server_name = list_tmp[0]
server_ip = list_tmp[1]
port = int(list_tmp[2])
yield(server_name, server_ip, port)
exceptException as err:
print_color('{}n'.format(err))
exit(10)
defcreate_sshclient(server_ip, port, output):
"""根据命令行提供的参数,建立到远程server的ssh链接.这段本应在run_command()函数内部。
摘出来的目的是为了让sftp功能也通过sshclient对象来创建sftp对象,因为初步观察t.connect()方法在使用key时有问题
output:存储输出的对象"""
local_client = threading.local() # 多线程中每个线程要在函数内某些保持自己特定值
local_client.client = SSHClient()
local_client.client.set_missing_host_key_policy(AutoAddPolicy())
try:
local_client.client.connect(server_ip, port=port, username=arguments['-u'], password=arguments['-p'], key_filename=arguments['--pkey'])
exceptException as err:# 有异常,打印异常,并返回'error'
output.write('{}----{} ssh connect error: {}n'.format(' '*4, server_ip, err), color=31)
return'error'
else:
returnlocal_client.client# 返回的client对象在每个线程内是不同的
# ----------
# run_command()执行远程命令
# ----------
defrun_command(client, output):
"""
执行远程命令的主函数
client: paramiko.client.SSHClient object
output: 存储输出的对象
"""
# stdout 假如通过分号提供单行的多条命令,所有命令的输出(在linux终端会输出的内容)都会存储于stdout
# 据观察,下面三个变量的特点是无论"如何引用过一次"之后,其内容就会清空
# 有readlines()的地方都是流,用过之后就没有了
stdin, stdout, stderr = client.exec_command(arguments[''])
copy_out, copy_err = stdout.readlines(), stderr.readlines()
iflen(copy_out)andlen(copy_err):
output.write('%s----result:n'% (' '*8))
foriincopy_out:
output.write('%s%s'% (' '*12, i))
foriincopy_err:
output.write('%s%s'% (' '*12, i), color=31)
ifnotarguments['--skip-err']:# 忽略命令执行错误的情况
output.print_lock()
exit(10)
eliflen(copy_out):
output.write('%s----result:n'% (' '*8))
foriincopy_out:
output.write('%s%s'% (' '*12, i))
eliflen(copy_err):
output.write('%s----error:n'% (' '*8), color=31)
foriincopy_err:
output.write('%s%s'% (' '*12, i), color=31)
ifnotarguments['--skip-err']:
client.close()
output.print_lock()
exit(10)
client.close()
# ----------
# sftp_transfer() 远程传输文件的主函数
# ----------
defsftp_transfer(source_path, destination_path, method, client, output):
"""
文件传输的 主函数
paramiko的sftp client传输,只能单个文件作为参数,并且不会保留文件的时间信息,这两点都需要代码里额外处理
client: paramiko.client.SSHClient object
output:存储输出的对象
"""
sftp = client.open_sftp()
ifplatform.system() =='Windows':
'''''根据put或get,将windows路径中的 分隔符替换为 / '''
ifarguments["put"]:
source_path = source_path.replace('', '/')
elifarguments["get"]:
destination_path = destination_path.replace('', '/')
# -----下面定义sftp_transfer()函数所需的一些子函数-----
defprocess_arg_dir(target):
"""处理目录时,检查用户输入,在路径后面加上/"""
ifnottarget.endswith('/'):
target = target + '/'
returntarget
defsftp_put(src, dst, space):
"""封装put,增加相应输出,并依据m_time和size判断两端文件一致性,决定是否传输该文件"""
ifcheck_remote_path(dst) =='file':
src_stat = stat(src)
dst_stat = sftp.stat(dst)
else:
src_stat = ''
dst_stat = ''
if(src_stat ==''anddst_stat =='')ornot(floor(src_stat.st_mtime) == dst_stat.st_mtimeandsrc_stat.st_size == dst_stat.st_size):
try:
sftp.put(src, dst)
output.write('%s%sn'% (' '* space, src))
exceptException as err:
output.write('%s----Uploading %s Failedn'% (' '* (space-4), src), color=31)
output.write('{}----{}n'.format(' '* (space-4), err), color=31)
client.close()
output.print_lock()
exit(10)