123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569 |
- #encoding=UTF-8
- #
- # 通过expect执行远程命令或传输文件
- #
- #python zo.py action=local cmds=?
- #python zo.py action=remote host=? username=? password=? cmds=?
- #python zo.py action=fget host=? username=? password=? local=? remote=?
- #python zo.py action=fput host=? username=? password=? local=? remote=?
- #
- import os
- import sys
- import re
- import csv
- import subprocess
- import time
- import datetime
- import threading
- #this file
- zo_py=os.path.realpath(__file__)
- #获取一个新的ID
- HOSTIP = "127.0.0.1" if not "SSH_CONNECTION" in os.environ else os.environ["SSH_CONNECTION"].split(" ")[2]
- #zo_dir="/tmp/zo%s"%("%s%03d"%(time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())), datetime.datetime.now().microsecond/1000))
- zo_dir="/tmp/zo"
- os.system('''if [[ -e "'''+zo_dir+'''" ]]; then ret=0; else mkdir -p '''+zo_dir+'''; chmod a+w '''+zo_dir+'''; ret=$?; fi; exit $ret''')
- __tid = 0
- def ntid():
- global __tid
- __tid += 1
- nid = "%d%d"%(os.getpid(), 100000000 + __tid)
- return nid
-
- #输出调试信息
- tl_none='' #level 0 不输出任何信息
- tl_fatal='F' #level 1 输出严重错误信息
- tl_error='E' #level 2 输出一般错误信息
- tl_warn='W' #level 3 输出警告信息
- tl_status='S' #level 4 状态信息
- tl_info='I' #level 5 输出提示信息
- tl_proc='P' #level 6 输出处理流程信息
- tl_response='R' #level 7 输出处理过程反馈信息
- tl_detail='D' #level 8 输出数据处理信息
- tl_debug='X' #level 9 输出详细调试信息,仅用于开发期间
- tl_any='A' #level a 输出任何信息,仅用于开发期间
- TRACE="FEWSI" #默认调试信息跟踪级别,输出到console
- #调试信息输出格式
- pt_trace="^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \[.+\] \[(.*)\] (.*)"
- pt_status_msg="(.*)\[([0-9]+)\]"
- #返回YYYY-mm-dd HH:MM:SS.sss格式的当前时间
- def tracetime():
- return "%s.%03d"%(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), datetime.datetime.now().microsecond/1000)
- class setting_item:
- def __init__(self, filename, lineno, sid, skey, sfcmd, svalue, sdesc):
- idkey = re.sub("\W", "_", sid)
- if idkey != sid:
- raise Exception("键名称不合法(invalid key name) %s"%(sid))
- self.fn = filename
- self.ln = lineno
- self.id = sid
- self.key = skey
- self.fcmd = sfcmd
- self.orig_value = svalue
- self.script = self.value_format(svalue)
- self.value = self.value_format(svalue)
- self.desc = sdesc
-
- #生成环境变量导出脚本
- def str_export(self):
- script = ""
- script += "export {0}_key=\"{1}\"\n".format(self.id, self.key)
- script += 'export {0}_name=`if [[ "${0}_name" == "" ]]; then echo \'{1}\'; else echo "${0}_name"; fi`\n'.format(self.id, self.key if self.desc == "" else self.desc.replace("'", "'\"'\"'"))
- script += "export {0}_filename=\"{1}\"\n".format(self.id, self.fn if self.fn is not None else "")
- script += "export {0}_lineno=\"{1}\"\n".format(self.id, self.ln)
- script += "export {0}_fcmd=\"{1}\"\n".format(self.id, self.fcmd)
- script += "export {0}_script='{1}'\n".format(self.id, self.value.replace("'", "'\"'\"'"))
- if self.key == "":
- #非格式化内容,直接执行或显示
- if self.value.strip() != "":
- if self.fcmd == "~":
- script += "eval \"${{{0}_script}}\"\n".format(self.id)
- else:
- script += "echo \"${{{0}_script}}\"\n".format(self.id)
- else:
- if self.fcmd == "~":
- script += "eval \"${{{0}_script}}\"\n".format(self.id)
- elif self.fcmd == "@":
- script += "{0}() {{\n eval \"${{{1}_script}}\" \n}}\n".format(self.key, self.id)
- script += "export {0}=`{1}`\n".format(self.id, self.key)
- else:
- script += "export {0}=`echo \"${{{0}_script}}\"`\n".format(self.id)
- return script
-
- #引号处理
- def value_format(self, value):
- value = value.strip()
- if self.key == "":
- return value
- if len(value)>1 and value[0] == '"' and value[-1] == '"':
- value = value[1:-1]
- return value
-
- class settings:
- base_script = "#zo script built on " + tracetime() + "\n%s"%(
- '''
- export EVAL='`'
- export OSNAME=`uname`
- export HOSTNAME=`hostname`
- export USERNAME=`whoami`
- if [[ ${OSNAME} == 'AIX' ]]; then export LANG=ZH_CN.UTF-8; else export LANG=zh_CN.utf8; fi
- if [[ ${OSNAME} == 'AIX' ]]; then export LC_ALL=ZH_CN.UTF-8; else export LC_ALL=zh_CN.utf8; fi
- export ZO_DIR='''+zo_dir+'''
- echo "#通用设置完成"
- #系统差别设置
- if [[ ${OSNAME} == "AIX" || ${OSNAME} == "Darwin" ]]; then
- usleep() {
- python -c "import time; time.sleep($1.0/1000000)"
- }
- fi
- #改变工作目录到当前脚本所在路径
- CUR_DIR=`pwd`
- echo "${CUR_DIR}"
- #仅支持通过SSH连接
- export HOSTIP=`echo ${SSH_CONNECTION} | awk -F ' ' {'print $3'}`
- if [[ "${HOSTIP}" == "" ]]; then
- HOSTIP="127.0.0.1"
- fi
- #输出信息时间,aix系统没有微秒时间,所以用python输出微秒级时间
- #`date "+%Y-%m-%d %H:%M:%S.%N"`
- p_logtime() {
- python -c 'import time,datetime; print("%s.%03d"%(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), datetime.datetime.now().microsecond/1000))'
- }
- replace() {
- local regx="$1"
- local rstr="$2"
- local gflg="$3"
- }
- #通过meval控制执行的脚本,前面发生错误,禁止后续带错运行
- export last_meval_script_return=0
- #全局错误处理
- export ZO_COMMAND_RUNNING=0
- export errline=0
- export errcode=0
- errproc() {
- export errline=$1
- export errcode=$2
- #只处理不在meval控制范围的脚本错误,即当前zo script自身的错误
- if [[ ${last_meval_script_return} == 0 ]]; then
- cmdscript="`echo "${ZO_SCRIPT}" | cat -n 2>&1 | head -n $errline | tail -n 1`"
- echo "`p_logtime` [$HOSTIP.$$] [F] error occur when eval script [$errline]: ${cmdscript} --> return $errcode"
- fi
- }
- trap 'errproc "$LINENO" "$?"' ERR
- #禁止中断
- trap '' INT
- decode_string() {
- python -c "
- import base64
- import sys
- s = sys.stdin.readline().strip()
- try:
- if s[0] == '=':
- print(s[1:])
- else:
- if sys.version_info.major == 3:
- s = bytes(s, encoding='utf8')
- s = base64.b64decode(s)
- if sys.version_info.major == 3:
- s = str(s, encoding='utf8')
- print(s)
- except TypeError as e:
- #print(e)
- print(s)
- "
- }
- encode_string() {
- python -c "
- import base64;
- import sys;
- s = sys.stdin.readline().strip()
- try:
- if s[0] == '=':
- print(s[1:])
- else:
- if sys.version_info.major == 3:
- s = bytes(s, encoding='utf8')
- s = base64.b64encode(s)
- if sys.version_info.major == 3:
- s = str(s, encoding='utf8')
- print(s)
- except TypeError as e:
- #print(e)
- print(s)
- "
- }
- decode_password() {
- export EXP_PASSWORD="`printf "${EXP_PASSWORD}" | decode_string`"
- trace X "EXP_PASSWORD=${EXP_PASSWORD}"
- }
- #特殊字符常量
- export newline=`printf "\\n"`
- export tab=" "
- export space=" "
- export quote='"'
- export single_quote="'"
- export point_quote='`'
- export slash='\/'
- export backslash='\\'
- export symbol_at='@'
- export symbol_star='*'
- export symbol_question='?'
- export semicolon=';'
- export dollar='$'
- export brace='{'
- export dash='-'
- export return_char=`printf "\\r"`
- #输出信息分类定义
- tl_fatal='F' #level 1 输出严重错误信息
- tl_error='E' #level 2 输出一般错误信息
- tl_warn='W' #level 3 输出警告信息
- tl_status='S' #level 4 状态信息
- tl_info='I' #level 5 输出提示信息
- tl_proc='P' #level 6 输出处理流程信息
- tl_response='R' #level 7 输出处理过程反馈信息
- tl_detail='D' #level 8 输出数据处理信息
- tl_debug='X' #level 9 输出详细调试信息,仅用于开发期间
- tl_any='A' #level a 输出任何信息,仅用于开发期间
- pt_trace="^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \[.+\] \[.*\] .*"
- pt_status_msg=".*\[[0-9]+\]$"
- pt_number="^[0-9]+$"
- is_trace_msg() {
- echo "$1" | grep -E "${pt_trace}"
- }
- is_number() {
- echo "$1" | grep -E "${pt_number}"
- }
- is_status_msg() {
- echo "$1" | grep -E "${pt_status_msg}"
- }
- #状态码
- export status_code=0
- #输出信息
- trace() {
- local level="$1"
- local msg="$2"
- local flag="$3"
- if [[ "${level}" == "S" ]]; then
- #验证&修正状态信息格式
- if [[ "`is_status_msg "${msg}"`" == "" ]]; then
- if [[ "`is_number "${status_code}"`" == "" ]]; then
- status_code=0
- fi
- msg="${msg}[${status_code}]"
- fi
- fi
- if [[ "`is_trace_msg "${msg}"`" == "" ]]; then
- dtms=`p_logtime`
- #逐行格式化
- additioninfo=""
- if [[ "${flag}" != "" ]]; then
- additioninfo="${flag} "
- fi
- msg="`echo "${msg}" | sed -e "s/^/${dtms} [${HOSTIP}.$$] [${level}] ${additioninfo}/"`"
- else
- #截取已格式化的日志level
- level=`echo "${msg}" | sed -e "s/.*\[.*\] \[//" -e "s/\].*$//"`
- fi
- if [[ "${level}" == "S" ]]; then
- #截取状态码
- export status_code=`echo "${msg}" | sed -e "s/.*\[//" -e "s/\].*$//"`
- fi
- #输出所有级别信息,由python程序决定是否显示输出内容
- echo "${msg}"
- }
- #执行脚本,确保脚本中的语句错误不影响整体的运行
- evalscriptfunction() {
- export BASELINENO=$LINENO
- eval "`echo "$1"`"
- local ret=$?
- return $ret
- }
- #执行脚本,截取脚本错误信息写入指定文件
- evalscript() {
- local zo_seq_id="$1"
- local scmdscript="$2"
- local tmperrfile="$3"
- trap '' INT
- exit_in_evalscript=0
- #if [[ "$BASH" != "" ]]; then
- # #只有bash能重定义exit命令
- # exit() {
- # trace E "部署过程脚本中不能使用exit,请用return代替"
- # exit_in_evalscript=$1
- # return $1
- # }
- #fi
- evalscriptfunction "${scmdscript}"
- local ret=$?
- if [[ $ret == 0 && `is_number "${exit_in_evalscript}"` != "" ]]; then
- ret=${exit_in_evalscript}
- fi
- return $ret
- }
- #检查脚本执行中是否发生错误
- check_evalscript_error() {
- local zo_seq_id="$1"
- local scmdscript="$2"
- local tmperrfile="$3"
-
- if [[ -e "${tmperrfile}" ]]; then
- local errorinfo=(`cat "${tmperrfile}"`)
- if [[ "${errorinfo}" != "" && "${errorinfo[3]}" == "${zo_seq_id}" ]]; then
- trace X "errorinfo=${errorinfo[*]}"
- local errorline=`expr ${errorinfo[1]} - ${errorinfo[0]} - 1`
- local errorno="${errorinfo[2]}"
- if [[ "`is_number "${errorno}"`" != "" ]]; then
- linecode="`echo "${scmdscript}" | cat -n 2>&1 | head -n ${errorline} | tail -n 1`"
- trace W "命令执行中返回码不为0:[line ${errorline}: return ${errorno}] (${linecode})"
- local errorlinepl=`expr ${errorline} + 2`
- trace D "`echo "${scmdscript}" | cat -n 2>&1 | head -n ${errorlinepl} | tail -n 5 | sed -e "s/^/~ /"`"
- return ${errorno}
- else
- trace W "命令执行中返回错误信息:${errorinfo[*]}"
- trace E "返回码不是数"
- return 128
- fi
- fi
- fi
- return 0
- }
- #执行脚本,如果脚本执行中发生错误,后续将无法再次调用meval,除非通过ignore_previous_error忽略错误对后续脚本的影响
- export ZO_SEQ_ID=10000
- export on_error=""
- meval() {
- if [[ ${last_meval_script_return} != 0 ]]; then
- trace X "meval last_meval_script_return=${last_meval_script_return}"
- return ${last_meval_script_return}
- fi
- local scmdscript="$*"
- trace X "---- meval script ----"
- trace X "`echo "${scmdscript}" | cat -n 2>&1 | sed -e "s/^/~ /"`"
- trace X "---- meval run ----"
- export ZO_SEQ_ID=`expr ${ZO_SEQ_ID} + 1`
- local zo_seq_id=${ZO_SEQ_ID}
- local tmperrfile="'''+zo_dir+'''/zerone.errcat.$$.${zo_seq_id}.txt"
- rm -f "${tmperrfile}"
- #忽略脚本中可能发生的错误
- on() {
- if [[ "$1" == "error" ]]; then
- shift
- if [[ "$1" == "continue" || "$1" == "break" ]]; then
- on_error="$1"
- elif [[ "$1" == "" || "$1" == "report" ]]; then
- on_error=""
- elif [[ "$1" == "occur" ]]; then
- shift
- if [[ "${on_error}" == "" ]]; then
- echo "$*"
- fi
- else
- trace W "unkown command 'on error $1'"
- fi
- else
- trace W "unkown command 'on $*'"
- fi
- }
- ignore_previous_error() {
- export last_meval_script_return=0
- echo > "${tmperrfile}"
- }
- split() {
- awk -F "$1" "{print $"$2"}"
- }
- margs() {
- script=`echo "$*" | sed -e "s/$1 //"`
- while read LINE
- do
- s=`echo "${script}" | sed -e "s/$1/${LINE}/g"`
- eval "${s}"
- done
- }
- #保存递归运行计数
- local keep_running_count=${ZO_COMMAND_RUNNING}
- local running_count=`expr ${ZO_COMMAND_RUNNING} + 1`
- export ZO_COMMAND_RUNNING=${running_count}
- #执行脚本
- local on_error_pack="${on_error}"
- on_error=""
- xscmdscript="trap 'on error occur $""BASELINENO $""LINENO $""? "${zo_seq_id}" >>"${tmperrfile}"' ERR\n${scmdscript}"
- evalscript "${zo_seq_id}" "${xscmdscript}" "${tmperrfile}"
- local evalscript_return=$?
- on_error="${on_error_pack}"
- #处理错误记录
- #错误记录只处理一次
- if [[ ${last_meval_script_return} == 0 ]]; then
- check_evalscript_error "${zo_seq_id}" "${scmdscript}" "${tmperrfile}"
- local evalscript_error=$?
- if [[ ${evalscript_return} == 0 ]]; then
- evalscript_return=${evalscript_error}
- fi
- export last_meval_script_return=${evalscript_return}
- fi
- #恢复递归运行计数
- if [[ ${last_meval_script_return} == 0 ]]; then
- export ZO_COMMAND_RUNNING=${keep_running_count}
- fi
- rm -f "${tmperrfile}"
- trace X "---- meval end ----"
- trace X "return ${last_meval_script_return}"
- return ${last_meval_script_return}
- }
- #get script mapped by id
- mscript() {
- local scmdid="$1"
- local scmdscript='${'"${scmdid}"'_script}'
- local cmds='echo "'${scmdscript}'"'
- eval echo "`${cmds}`"
- }
- #call executable key,读取参数key对应脚本,并通过meval执行
- mcall() {
- local scmdid="$1"
- if [[ ${last_meval_script_return} != 0 ]]; then
- trace X "mcall ${scmdid} last_meval_script_return=${last_meval_script_return}"
- return ${last_meval_script_return}
- fi
- trace X "==== mcall ${scmdid} script ===="
- local scmdscript="`mscript "${scmdid}"`"
- local scmdname_key="$""{${scmdid}_name}"
- local scmdname=`eval echo "${scmdname_key}"`
- if [[ "${scmdname}" == "" ]]; then
- scmdname="${scmdid}"
- fi
- trace S "${scmdname}"
- meval "${scmdscript}"
- local eval_script_return=$?
- trace X "==== mcall ${scmdid} end ===="
- return ${eval_script_return}
- }
- #重定向输出到日志格式输出
- zo_command_log() {
- local scmdscript="$1"
- local tmpoutfile="$2"
- local tmpretfile="$3"
-
- local linecount=1
- local charcount=0
- local running="."
- local trace_level="${ZO_TRACE}"
- if [[ "${trace_level}" == "" ]]; then
- trace_level="${tl_response}"
- fi
- touch "${tmpoutfile}"
- local outputtime=`date +%s`
- while [[ "${running}" != "" ]]; do
- if [[ -e "${tmpretfile}" ]]; then
- running="-"
- fi
- #tail -n +从1开始的行号,至少会有一行内容
- local lines=(`tail -n +${linecount} "${tmpoutfile}" | sed -e "s/${symbol_at}/@{symbol_at}/g" | sed -e "s/${symbol_star}/@{symbol_star}/g" | sed -e "s/${symbol_question}/@{symbol_question}/g" | sed -e "s/${space}/@{space}/g" | sed -e "s/${tab}/@{tab}/g" | sed -e "s/${return_char}/@{return_char}/g" | sed -e "s/^/@{start}/g"`)
- local n=1
- for line in ${lines[@]}
- do
- line=`printf "%s" "${line}" | sed -e "s/@{start}//g" | sed -e "s/@{space}/${space}/g" | sed -e "s/@{tab}/${tab}/g" | sed -e "s/@{return_char}//g" | sed -e "s/@{symbol_star}/${symbol_star}/g" | sed -e "s/@{symbol_question}/${symbol_question}/g" | sed -e "s/@{symbol_at}/${symbol_at}/g"`
- xline=${line:${charcount}}
- if [[ ${n} == ${#lines[@]} ]]; then
- #最后一行,可能尚未输出完成
- local currenttime=`date +%s`
- local waittime=`expr ${currenttime} - ${outputtime}`
- if [[ ${waittime} -gt 1 && ${#xline} -gt 0 ]]; then
- trace X "等1秒未完行输出"
- # 输出行数不增加
- trace "${trace_level}" "${xline}" ">"
- # 输出部分信息,继续输出剩余内容
- charcount=`expr ${#xline} + ${charcount}`
- outputtime=`date +%s`
- fi
- else
- if [[ ${#xline} -gt 0 || ${charcount} -eq 0 ]]; then
- trace "${trace_level}" "${xline}" ">"
- fi
- linecount=`expr ${linecount} + 1`
- n=`expr ${n} + 1`
- charcount=0
- outputtime=`date +%s`
- fi
- done
- if [[ "${running}" == "-" ]]; then
- #结束
- running=""
- if [[ ${#xline} -gt 0 ]]; then
- #输出最后一行剩余内容
- trace "${trace_level}" "${xline}" ">"
- fi
- elif [[ ${n} == 1 ]]; then
- #没有输出
- usleep 50000
- fi
- done
- local eval_script_return=""
- if [[ -e "${tmpretfile}" ]]; then
- eval_script_return=`cat "${tmpretfile}"`
- else
- trace W "命令执行中断"
- eval_script_return=128
- fi
- trace X "zo_command return ${eval_script_return}"
- return ${eval_script_return}
- }
- #后台执行命令,重定向输出到日志格式输出
- export zo_command=""
- zo_command_eval() {
- if [[ "${zo_command}" != "" ]]; then
- #后台执行命令,环境变量无法共享输出
- trace F "zo_command_eval不允许嵌套"
- fi
- zo_command="$*"
- local tmpoutfile="'''+zo_dir+'''/zerone.stdout.$$.txt"
- local tmpretfile="'''+zo_dir+'''/zerone.retcode.$$.txt"
- rm -f "${tmpoutfile}"
- rm -f "${tmpretfile}"
- #后台执行脚本,变量变化不能共享
- zo_command_eval_function() {
- meval "${zo_command}"
- echo $? > "${tmpretfile}"
- }
- if [[ ${#ZO_SCRIPT} == 0 ]]; then
- if [[ -e "${ZO_FILE}" ]]; then
- export ZO_SCRIPT=`cat "${ZO_FILE}"`;
- else
- trace W "没有定义ZO_SCRIPT或ZO_FILE"
- fi
- fi
- #后台执行命令
- zo_command_eval_function 1>${tmpoutfile} 2>&1 <&0 &
- #处理日志输出
- zo_command_log "${zo_command}" "${tmpoutfile}" "${tmpretfile}"
- local ret=$?
- rm -f "${tmpoutfile}"
- rm -f "${tmpretfile}"
- zo_command=""
- return ${ret}
- }
- #expect script
- export EXP_TIMEOUT=20
- export EXP_PROMPT='(%|#|\\$|>)'
- export EXP_SCRIPT='
- expect {
- timeout { send_user "\n连接超时\n"; exit 233 }
- "yes/no" { send "yes\n"; exp_continue }
- "assword:" { send "$env(EXP_PASSWORD)\n" }
- eof { send_user "\n连接错误\n"; exit 232 }
- }
- expect {
- timeout { exp_continue }
- "assword:" { send_user "\n密码错误**$env(EXP_PASSWORD)**\n"; exit 231 }
- " --:-- ETA" {
- send_user "\n开始传输\n"
- expect {
- timeout { exp_continue }
- "100%" { send_user "\n传输完成\n" }
- ":" { send_user "\n正在传输\n"; exp_continue }
- }
- expect {
- timeout { exp_continue }
- eof { catch wait retval; send_user "\n退出传输连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
- }
- }
- -re $env(EXP_PROMPT) {
- send "$env(REMOTE_COMMANDS)\nexit $?\n"
- expect {
- timeout { exp_continue }
- expect $env(EXP_INTERACT)
- eof { catch wait retval; send_user "\n退出命令连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
- }
- }
- eof { catch wait retval; send_user "\n退出连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
- }
- expect {
- timeout { exp_continue }
- eof { catch wait retval; send_user "\n退出不能处理的连接[lindex $retval 3]\n"; exit [lindex $retval 3] }
- }
- '
- exp_run() {
- local exp_command="$1"
- trace X "EXP_PROMPT='${EXP_PROMPT}'"
- trace X "REMOTE_COMMANDS='${REMOTE_COMMANDS}'"
- expect -c "
- #trap {} {SIGINT SIGTERM}
- set timeout ${EXP_TIMEOUT}
- spawn ${exp_command}
- ${EXP_SCRIPT}"
- ret=$?
- return $ret
- }
- exp_fget() {
- remote_file="$1"
- local_file="$2"
-
- if [[ "$PIPE_HOST" == "" ]]; then
- trace E "没有指定通道"
- return 22
- fi
-
- #上传文件
- export EXP_INTERACT=""
- export REMOTE_COMMANDS=""
- export EXP_HOST="${PIPE_HOST%% *}"
- export EXP_USERNAME="${PIPE_USERNAME%% *}"
- export EXP_PASSWORD="${PIPE_PASSWORD%% *}"
- decode_password
- exp_run "scp \"${EXP_USERNAME}@${EXP_HOST}:${remote_file}\" \"${local_file}\""
- ret=$?
- return $ret
- }
- exp_fput() {
- local_file="$1"
- remote_file="$2"
-
- if [[ "$PIPE_HOST" == "" ]]; then
- trace E "没有指定通道"
- return 22
- fi
- #上传文件
- export EXP_INTERACT=""
- export REMOTE_COMMANDS=""
- export EXP_HOST="${PIPE_HOST%% *}"
- export EXP_USERNAME="${PIPE_USERNAME%% *}"
- export EXP_PASSWORD="${PIPE_PASSWORD%% *}"
- decode_password
- exp_run "scp \"${local_file}\" \"${EXP_USERNAME}@${EXP_HOST}:${remote_file}\""
- ret=$?
- return $ret
- }
- exp_remote() {
- local cmds="$@"
- if [[ "$PIPE_HOST" == "" ]]; then
- trace E "没有指定通道"
- return 22
- fi
- #执行预处理脚本,创建工作目录
- trace D "远程命令预处理"
- if [[ "${EXP_REMOTE_SCRIPT_FILE}" == "" ]]; then
- EXP_REMOTE_SCRIPT_FILE="${ZO_FILE}"
- fi
- export EXP_HOST="${PIPE_HOST%% *}"
- export EXP_USERNAME="${PIPE_USERNAME%% *}"
- export EXP_PASSWORD="${PIPE_PASSWORD%% *}"
- local NEXT_PIPE_HOST="${PIPE_HOST:${#EXP_HOST}}"
- local NEXT_PIPE_USERNAME="${PIPE_USERNAME:${#EXP_USERNAME}}"
- local NEXT_PIPE_PASSWORD="${PIPE_PASSWORD:${#EXP_PASSWORD}}"
- NEXT_PIPE_HOST="${NEXT_PIPE_HOST# }"
- NEXT_PIPE_USERNAME="${NEXT_PIPE_USERNAME# }"
- NEXT_PIPE_PASSWORD="${NEXT_PIPE_PASSWORD# }"
- export EXP_INTERACT=""
- export REMOTE_COMMANDS='if [[ -e "'''+zo_dir+'''" ]]; then ret=0; else mkdir -p '''+zo_dir+'''; chmod a+w '''+zo_dir+'''; ret=$?; fi; exit $ret'
- decode_password
- exp_run "ssh ${EXP_USERNAME}@${EXP_HOST}"
- ret=$?
- if [[ $ret != 0 ]]; then
- return $ret
- fi
- trace D "远程命令预处理完毕"
- #上传文件
- if [[ "$PIPE_UPLOAD_FILE" != "" ]]; then
- if [[ "$PIPE_UPLOAD_DEST" == "" ]]; then
- PIPE_UPLOAD_DEST="$PIPE_UPLOAD_FILE"
- fi
- trace D "开始上传文件 {PIPE_UPLOAD_FILE} -> ${PIPE_UPLOAD_DEST}"
- exp_fput "${PIPE_UPLOAD_FILE}" "${PIPE_UPLOAD_DEST}"
- ret=$?
- if [[ $ret != 0 ]]; then
- return $ret
- fi
- trace D "文件上传完毕"
- fi
- #上传脚本
- trace D "上传远程脚本"
- exp_fput "${ZO_FILE}" "${EXP_REMOTE_SCRIPT_FILE}"
- ret=$?
- if [[ $ret != 0 ]]; then
- return $ret
- fi
- #执行脚本
- trace D "执行远程脚本,${EXP_USERNAME}@${EXP_HOST}"
- if [[ "${NEXT_PIPE_HOST}" == "" ]]; then
- export REMOTE_COMMANDS="
- export OSNAME=${EVAL}uname${EVAL}
- if [[ $""{OSNAME} == 'AIX' ]]; then export LANG=ZH_CN.UTF-8; else export LANG=zh_CN.utf8; fi
- if [[ $""{OSNAME} == 'AIX' ]]; then export LC_ALL=ZH_CN.UTF-8; else export LC_ALL=zh_CN.utf8; fi
- export PIPE_UPLOAD_FILE='"${PIPE_UPLOAD_FILE}"'
- export PIPE_DOWNLOAD_FILE='"${PIPE_DOWNLOAD_FILE}"'
- export ZO_FILE='"${EXP_REMOTE_SCRIPT_FILE}"'
- export CUR_DIR='''+zo_dir+'''
- cd $""CUR_DIR
- export ZO_COMMAND='${cmds//\\\'/\\\'\\\"\\\'\\\"\\\'}'
- export PATH=/tmp/zo:$""{PATH}
- bash -c 'bash "${EXP_REMOTE_SCRIPT_FILE}"'
- ret=$""?
- rm -f '"${EXP_REMOTE_SCRIPT_FILE}"'
- exit $""ret"
- else
- export REMOTE_COMMANDS="
- export OSNAME=${EVAL}uname${EVAL}
- if [[ $""{OSNAME} == 'AIX' ]]; then export LANG=ZH_CN.UTF-8; else export LANG=zh_CN.utf8; fi
- if [[ $""{OSNAME} == 'AIX' ]]; then export LC_ALL=ZH_CN.UTF-8; else export LC_ALL=zh_CN.utf8; fi
- export PIPE_UPLOAD_FILE='"${PIPE_UPLOAD_FILE}"'
- export PIPE_DOWNLOAD_FILE='"${PIPE_DOWNLOAD_FILE}"'
- export PIPE_HOST='"${NEXT_PIPE_HOST}"'
- export PIPE_USERNAME='"${NEXT_PIPE_USERNAME}"'
- export PIPE_PASSWORD='"${NEXT_PIPE_PASSWORD}"'
- export ZO_FILE='"${EXP_REMOTE_SCRIPT_FILE}"'
- export CUR_DIR='''+zo_dir+'''
- cd $""CUR_DIR
- export ZO_COMMAND='exp_remote ${cmds//\\\'/\\\'\\\"\\\'\\\"\\\'}'
- export PATH=/tmp/zo:$""{PATH}
- bash -c 'bash "${EXP_REMOTE_SCRIPT_FILE}"'
- ret=$""?
- rm -f '"${EXP_REMOTE_SCRIPT_FILE}"'
- exit $""ret"
- fi
- exp_run "ssh ${EXP_USERNAME}@${EXP_HOST}"
- ret=$?
- if [[ $ret != 0 ]]; then
- return $ret
- fi
- trace D "远程脚本执行完毕"
- trace X "PIPE_DOWNLOAD_FILE=$PIPE_DOWNLOAD_FILE"
- #下载文件
- if [[ "$PIPE_DOWNLOAD_FILE" != "" ]]; then
- if [[ "$PIPE_DOWNLOAD_DEST" == "" ]]; then
- PIPE_DOWNLOAD_DEST="$PIPE_DOWNLOAD_FILE"
- fi
- trace D "开始下载文件 ${PIPE_DOWNLOAD_FILE} -> ${PIPE_DOWNLOAD_DEST}"
- exp_fget "${PIPE_DOWNLOAD_FILE}" "${PIPE_DOWNLOAD_DEST}"
- ret=$?
- if [[ $ret != 0 ]]; then
- return $ret
- fi
- trace D "文件下载完毕"
- fi
- #执行完毕,清理临时文件(临时目录还在使用,现在还不能清理整个临时目录)
- return 0
- }
- fget() {
- remote_file="$1"
- local_file="$2"
-
- trace P "文件下载"
-
- export PIPE_DOWNLOAD_FILE="${remote_file}"
- export PIPE_DOWNLOAD_DEST="${local_file}"
-
- remote ls -l "${PIPE_DOWNLOAD_FILE}"
- ret=$?
- ls -l "${PIPE_DOWNLOAD_DEST}"
- if [[ $ret == 0 ]]; then
- trace P "文件下载完成"
- else
- trace P "文件下载失败[$ret]"
- fi
- return $ret
- }
- fput() {
- local_file="$1"
- remote_file="$2"
-
- trace P "文件上传"
-
- export PIPE_UPLOAD_FILE="${local_file}"
- export PIPE_UPLOAD_DEST="${remote_file}"
-
- ls -l "${PIPE_UPLOAD_FILE}"
- remote ls -l "${PIPE_UPLOAD_DEST}"
- ret=$?
- if [[ $ret == 0 ]]; then
- trace P "文件上传完成"
- else
- trace P "文件上传失败[$ret]"
- fi
- return $ret
- }
- #命令参数:
- # 要远程执行的命令
- #环境变量参数:
- #完整的脚本 ZO_SCRIPT
- #目标服务器,可以顺序指定跳板机信息,用空格分割,password中的空格引号等特殊字符需要转码
- # PIPE_HOST PIPE_USERNAME PIPE_PASSWORD
- #执行命令前上传指定文件
- # PIPE_UPLOAD_FILE PIPE_UPLOAD_DEST
- #执行命令后下载指定文件
- # PIPE_DOWNLOAD_FILE PIPE_DOWNLOAD_DEST
- remote() {
- local cmds="$@"
-
- trace D "远程命令执行开始"
-
- if [[ "$ZO_FILE" == "" ]]; then
- #本地生成脚本文件
- export ZO_FILE="'''+zo_dir+'''/zerone.script.$$.txt"
- export EXP_REMOTE_SCRIPT_FILE="'''+zo_dir+'''/zerone.script.$HOSTIP.$$.txt"
- echo "${ZO_SCRIPT}" > "${ZO_FILE}"
- else
- export EXP_REMOTE_SCRIPT_FILE="${ZO_FILE}"
- fi
-
- exp_remote "${cmds}"
- ret=$?
-
- #清理
- rm -f "${ZO_FILE}"
-
- #保留远程命令设定状态
- if [[ $ret == 0 ]]; then
- trace D "远程命令执行完成"
- else
- trace D "远程命令执行失败[$ret]"
- fi
- return $ret
- }
- ''')
- def __init__(self, base=None):
- self.items = []
- self.id_items = {}
- self.script = ""
- if base is not None:
- self.items += base.items
- self.base_script = settings.base_script
-
- def check_value_self_refference(self, sid, svalue):
- #键值引用仅支持一种形式 "${"+sid+"}"
- kn = svalue.find("${"+sid+"}")
- if kn >= 0:
- if sid in self.id_items:
- #变量引用前已经定义,修改已定义键名,原键值将被覆盖,修正键值自引用
- sitem = self.id_items[sid]
- sitem.id = "zerone_" + ntid()
- self.id_items[sitem.id] = sitem
- svalue = svalue.replace("${"+sid+"}", "${"+sitem.id+"}")
- else:
- #变量引用前没有定义
- svalue = svalue.replace("${"+sid+"}", "")
- return svalue
-
- def keep_setting(self, filename, lineno, skey, sfcmd, svalue, scomments):
- if skey != "":
- #标准键值格式
- sid = skey
- svalue = self.check_value_self_refference(sid, svalue)
- else:
- #非标准键值
- sid = "zerone_" + ntid()
- #关键字映射为 export id
- #runner.trace(tl_debug, skey+"='"+svalue+"'")
- sitem = setting_item(filename, lineno, sid, skey, sfcmd, svalue, scomments)
- self.items += [sitem]
- #覆盖保存sid对应的sitem
- self.id_items[sitem.id] = sitem
-
- #配置文件解析
- def analyze(self, filename):
- with open(filename, "r") as f:
- #读取文件
- st = f.read()
- stlines = st.splitlines()
- return self.analyze_lines(stlines, filename=filename)
-
- def analyze_lines(self, stlines, filename=None):
- #runner.trace(tl_debug, "{}".format(stlines))
- if len(stlines) > 0:
- #重置已生成脚本
- self.script = ""
- sln = 0
- skey = ""
- sflag = ""
- sfcmd = ""
- svalue = ""
- scomments = ""
- lineno = 0
- for line in stlines:
- lineno += 1
- if sflag == ":":
- #多行处理标记(以!!开始的行,!!后面的内容会被丢弃,内容中出现的!!可以通过变量定义解决)
- #多行累加
- if line.strip()[:2] == "!!":
- #多行结束标记
- self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
- skey = ""
- sflag = ""
- sfcmd = ""
- svalue = ""
- scomments = ""
- sln = lineno + 1
- else:
- svalue += "\n" + line
- elif len(line.strip()) == 0:
- #跳过空行
- pass
- elif line.strip()[0] == '#':
- #注释行,作为下一键值对行的名称
- scomments = line.strip()[1:].strip()
- else:
- remo = re.match("\s*([^:=]*)\s*(:=?|=)([@~]?)\s*(.*)", line)
- if remo is not None:
- if svalue != "":
- #非格式化内容
- #skey == ""
- sfcmd = svalue[0] if len(svalue)>0 and (svalue[0] == "@" or svalue[0] == "~") else ""
- svalue = svalue[1:] if sfcmd != "" else svalue
- self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
- #标准键值格式
- sln = lineno
- skey = remo.group(1)
- sflag = remo.group(2)[0]
- sfcmd = remo.group(3)
- svalue = remo.group(4)
- if sflag != ":":
- self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
- skey = ""
- sflag = ""
- sfcmd = ""
- svalue = ""
- scomments = ""
- sln = lineno + 1
- else:
- #非格式化内容
- #与上一非格式化行合并
- svalue += line + "\n"
- if svalue != "":
- #非格式化内容,包括多行没有结束标记
- sfcmd = svalue[0] if len(svalue)>0 and (svalue[0] == "@" or svalue[0] == "~") else ""
- svalue = svalue[1:] if sfcmd != "" else svalue
- self.keep_setting(filename, sln, skey, sfcmd, svalue, scomments)
- #返回对象自身
- return self
- #生成环境变量导出脚本
- def str_export(self, exports, item, nokey_item_proc_method):
- if item.key == "":
- #非格式化内容,没有键名
- if nokey_item_proc_method is None:
- #不做任何处理
- return ""
- else:
- #按普通item处理
- pass
- else:
- #赋值脚本只保留最后一次结果
- if item.id in exports:
- return "# ${{{0}_key}}=\"{1}\"\n".format(item.id, item.key)
- #用最后键值设置输出
- item = self.id_items[item.id]
- #记录已导出标记
- exports[item.id] = item
- #生成导出脚本
- script = ""
- value = item.value
- for sid in self.id_items:
- if value.find("${"+sid+"}") >= 0:
- sitem = self.id_items[sid]
- script += self.str_export(exports, sitem, nokey_item_proc_method)
- script += item.str_export()
- return script
-
- #生成脚本
- def __str__(self, nokey_item_proc_method=None):
- if self.script != "":
- return self.script
- #基础脚本
- self.script = self.base_script
- self.script += '''trace X "base script done"\n'''
- #定义脚本
- exports = {}
- for item in self.items:
- self.script += self.str_export(exports, item, nokey_item_proc_method)
- self.script += '''trace X "extend settings done"\n'''
- return self.script
- #命令执行状态
- class CommandStatus:
- def __init__(self):
- self.connected = False
- self.code = 0
- self.info = ""
- self.warning_message=""
-
- #进程内线程间锁,日志输出与进程相关
- trace_stdout_lock = threading.Lock()
- #日志信息跟踪解析输出
- class CommandTracer:
- trace_file_locks = {}
-
- def __init__(self, status, logfile="", logfile_renew=True):
- self.status = status
- self.logfile = logfile
- if self.logfile is not None and self.logfile != "":
- if logfile_renew:
- with open(self.logfile, 'w') as f:
- f.write("")
- if not self.logfile in CommandTracer.trace_file_locks:
- CommandTracer.trace_file_locks[self.logfile] = threading.Lock()
- self.trace_file_lock = CommandTracer.trace_file_locks[self.logfile]
- else:
- self.trace_file_lock = None
-
- def log(self, level, msg, exp_host=None):
-
- #去掉多余的换行符
- if len(msg)>0 and msg[-1] == "\n":
- msg = msg[:-1]
- lines = msg.splitlines()
- if len(lines)>1:
- for line in lines:
- self.log(level, line, exp_host)
- return
- #强制输出标记
- force = level[0] == '_'
- if force:
- level = level[1:]
-
- #是否已经是trace格式
- remo = re.match(pt_trace, msg)
- if remo is not None:
- #已经是trace格式,取原来的level
- level = remo.group(1)
- orig_msg = remo.group(2)
- else:
- #格式化
- orig_msg = msg
- msg = "%s [%s] [%s] %s"%(tracetime(), ("%s.%d"%(HOSTIP,os.getpid())) if exp_host is None or exp_host == "" else exp_host, level, msg)
- #最后状态记录
- if level == tl_status:
- smremo = re.match(pt_status_msg, orig_msg)
- if smremo is not None:
- self.status.info = smremo.group(1)
- self.status.code = int(smremo.group(2))
- else:
- self.status.info = orig_msg
- self.status.code = 0
- #保留警告信息
- if re.match('.*[FEW].*', level) is not None:
- self.status.warning_message += "\n" + msg
- #输出到console
- if force or level in TRACE:
- trace_stdout_lock.acquire()
- try:
- print(msg)
- sys.stdout.flush()
- finally:
- trace_stdout_lock.release()
-
- #全输出到日志文件
- if self.trace_file_lock is not None:
- self.trace_file_lock.acquire()
- try:
- with open(self.logfile, 'a') as f:
- f.write(msg+"\r\n")
- finally:
- self.trace_file_lock.release()
-
-
- class CommandRunner:
- def __init__(self, logfile="", logfile_renew=False):
- self.stimeout = 3600.0 #等待超时秒
- self.sout = ""
- self.status = CommandStatus()
- self.tracer = CommandTracer(self.status, logfile, logfile_renew=logfile_renew)
- #因为状态信息只有一个,所以一个CommandRunner同时只能跑一个命令
- #相当于一个执行窗口
- self.subprocess_lock = threading.Lock()
- def trace(self, level, msg, exp_host=None):
- self.tracer.log(level, msg, exp_host)
-
- def output(self, subprocess_outfile, exp_host, silent):
- with open(subprocess_outfile, 'r') as fout:
- sout = fout.read()
- n = len(sout) - len(self.sout)
- if n>0:
- if not silent:
- st = sout[-n:]
- st = st.replace("\r", "")
- if len(st)>0 and st[-1] == "\n":
- st = st[:-1]
- lines = st.split("\n")
- for line in lines:
- if len(self.sout)>0:
- self.trace(tl_detail, line, exp_host)
- else:
- self.trace(tl_detail, line)
- self.sout += line + "\n"
- self.sout = sout
- return n
- return 0
-
- def subprocess(self, cmd, exp_host=None, silent=False, envs=None):
- self.subprocess_lock.acquire()
- subprocess_outfile = "%s/zerone.stdout.%s.txt"%(zo_dir, ntid())
- try:
- env = os.environ.copy()
- if envs is not None:
- env.update(envs)
- env["ZO_SCRIPT"] = cmd
- self.sout = "" #clear last sout
- with open(subprocess_outfile, 'w') as fout:
- child = subprocess.Popen('bash -c "${ZO_SCRIPT}"', shell=True, stdout=fout, stderr=fout, stdin=None, env=env)
- timeout = time.time() + self.stimeout
- while (self.stimeout<=0 or time.time()<timeout) and child.poll() is None:
- if self.output(subprocess_outfile, exp_host, silent) == 0:
- time.sleep(0.1) #sleep s
- if child.poll() is None:
- child.kill()
- self.output(subprocess_outfile, exp_host, silent)
- ret = child.returncode
- finally:
- os.remove(subprocess_outfile)
- self.subprocess_lock.release()
- return ret, self.sout
-
-
- #执行shell脚本
- def command_run(self, cmd, exp_host=None, silent=False, envs=None):
- ret,sout = self.subprocess(cmd, exp_host, silent, envs)
- self.trace(tl_debug, "command_run return %d"%(ret))
- return ret, sout
- #生成脚本
- def build_script(self, host="", username="", password="", cmds="", zo_envs=None, sys_argv=None, setting_files=None):
- #缺省环境设置
- env_settings = settings()
- if setting_files is not None:
- for setting_file in setting_files:
- if os.access(setting_file, os.R_OK):
- env_settings.analyze(setting_file)
- else:
- self.trace(tl_warn, "File not found %s"%(agent_settings_file))
-
- #命令行参数指定配置信息
- #先将命令行参数中型为 key=value 的参数转换为 key:=value!! 的形式,以支持 value 为多行的情况
- if sys_argv is not None and len(sys_argv)>1:
- for skv in sys_argv:
- n = skv.find("=")
- if n >= 0 and re.match("\w+", skv[:n]):
- sk = skv[:n]
- sv = skv[n+1:]
- env_settings.analyze_lines("{0}:={1}\n!!".format(sk, sv).splitlines())
-
- #传递的环境变量,zo.py的嵌套调用时,py进程之间传递环境变量
- zo_env_keys=[]
- if "ZO_ENV_KEYS" in os.environ and os.environ["ZO_ENV_KEYS"] != "":
- zo_env_keys=os.environ["ZO_ENV_KEYS"].split(",")
- for envk in zo_env_keys:
- if envk != "" and envk in os.environ and os.environ[envk] != "":
- env_settings.analyze_lines("{0}:\n{1}\n!!".format(envk, os.environ[envk]).splitlines())
- #参数指定可向下传递的环境变量
- if zo_envs is not None:
- for envk in zo_envs:
- env_settings.analyze_lines("{0}:\n{1}\n!!".format(envk, zo_envs[envk]).splitlines())
- #向下传递的环境变量
- zo_env_keys += zo_envs.keys()
- zo_env_keys = dict.fromkeys(zo_env_keys)
- #向下传递的环境变量
- env_settings.analyze_lines(["ZO_ENV_KEYS={0}".format(",".join(zo_env_keys))])
- #参数指定信息
- env_settings.analyze_lines([
- "host=%s"%host,
- "username=%s"%username,
- "password=%s"%password])
- #覆盖关键配置信息及待执行命令
- scmdscript = str(env_settings) + ('''
- trace X "specified commands begin"
- trace A "${ZO_COMMAND}"
- zo_command_eval "${ZO_COMMAND}"
- export ret=$?
- trace X "specified commands end [${ret}]"
- exit $ret
- ''')
- return scmdscript
- def run_script(self, host, username, password, cmds, sys_argv=None, envs=None):
- scmdscript = self.build_script(host, username, password, cmds, sys_argv=sys_argv)
- renvs={}
- if envs is not None:
- renvs.update(envs);
- renvs["PIPE_HOST"]=host
- renvs["PIPE_USERNAME"]=username
- renvs["PIPE_PASSWORD"]=password
- renvs["ZO_COMMAND"]=cmds
- if tl_any in TRACE:
- ret,sout = self.command_run('echo "${SCMDS_SCRIPT}" | cat -n', silent=True, envs={"SCMDS_SCRIPT": scmdscript})
- self.trace(tl_any, sout)
- ret,sout = self.command_run(scmdscript, envs=renvs)
- if tl_any in TRACE and ret != 0:
- with open("scmdscript.txt", 'w') as fcmds:
- fcmds.write(scmdscript)
- return ret,sout
-
- #action=local cmds=?
- #action=remote host=? username=? password=? cmds=?
- #action=fget host=? username=? password=? local=? remote=?
- #action=fput host=? username=? password=? local=? remote=?
- def run(self, action="", host="", username="", password="", cmds="", local="", remote="", sys_argv=None):
- self.trace(tl_debug, '''action="{0}", host="{1}", username="{2}", password="{3}", cmds="{4}", local="{5}", remote="{6}", sys_argv="{7}"'''.format(action, host, username, password, cmds, local, remote, sys_argv))
- if action == "fget" and remote != "":
- if local == "":
- #缺省取当前目录下的同名文件
- local = remote.split("/")[-1]
- self.trace(tl_proc, "下载文件 %s@%s:%s 到 %s"%(username, host, remote, local))
- cmds = "fget '{0}' '{1}'".format(remote, local)
- ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_detail})
- elif action == "fput" and remote != "":
- if local == "":
- #缺省取当前目录下的同名文件
- local = remote.split("/")[-1]
- self.trace(tl_proc, "上传文件 %s 到 %s@%s:%s"%(local, username, host, remote))
- cmds = "fput '{0}' '{1}'".format(local, remote)
- ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_detail})
- else:
- if action == "":
- if username != "":
- action = "remote"
- if host == "":
- #缺省连接本机
- host = "127.0.0.1"
- else:
- action = "local"
- if action == "remote":
- self.trace(tl_proc, "执行远程命令 %s@%s: %s"%(username, host, cmds))
- cmds = "remote '%s'"%(cmds.replace("'", "'\"'\"'"))
- ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_detail})
- elif action == "local":
- self.trace(tl_proc, "执行本地命令 %s"%(cmds))
- ret, sout = self.run_script(host, username, password, cmds, sys_argv, envs={"ZO_TRACE": tl_response})
- else:
- self.trace(tl_error, "unknow action %s"%(action))
- ret = 22 #Invalid argument
- sout = ""
- if ret is None:
- #设置状态,命令执行超时,已经强制结束
- self.trace(tl_status, "操作超时[230]")
- ret = 230
- if ret != 0:
- #设置状态,代替返回错误码
- errmsg = ERROR_MSG[ret].strip()
- self.trace(tl_status, "%s[%d]"%(errmsg, ret), exp_host="%s.%d%s"%(HOSTIP,os.getpid(),"" if host == "" else ("--"+host)))
-
- #ERROR_MSG_230="操作超时 "
- #ERROR_MSG_231="密码错误 "
- #ERROR_MSG_232="远程连接错误 "
- #ERROR_MSG_233="远程连接超时 "
- #ERROR_MSG_234="运行时错误 "
- #ERROR_MSG_235="交互处理表达式语法错误 "
- self.status.connected = (self.status.code < 230 or self.status.code >235)
-
- if self.status.code == 0:
- self.trace(tl_status, "操作完成[0]", exp_host="%s.%d%s"%(HOSTIP,os.getpid(),"" if host == "" else ("--"+host)))
-
- return ret, sout
-
- #命令行参数
- class zoargs:
- def __init__(self, argv):
- global TRACE
-
- self.host = ""
- self.username = ""
- self.password = ""
- self.action = ""
- self.cmds = ""
- self.local = ""
- self.remote = ""
- self.TRACE = TRACE
- self.logdir = ""
- self.debug = ""
- #parse argv
- self.parse(argv)
- TRACE = self.TRACE
-
- def parse(self, argv):
- cmds = None
- for s in argv:
- if cmds is None:
- n = s.find("=")
- if n >= 0:
- self.__dict__[s[:n]] = s[n+1:]
- elif s == "cmds":
- cmds = ""
- else:
- self.__dict__[s] = s
- elif cmds == "":
- cmds = s
- else:
- cmds += " " + s
- if cmds is not None:
- self.cmds = cmds
- if self.debug != "":
- self.cmds = self.debug + " \"" + self.cmds.replace("'", "").replace("\"", "") + "\""
- args = zoargs(sys.argv)
- runner = CommandRunner(logfile=(args.logdir+"/"+args.username+"@"+args.host+".txt") if args.logdir != "" else "")
- #密码加密
- #python -c "import base64; print(base64.b64encode('123456'));"
- #执行远程命令
- #可以通过参数设置命令所需环境变量
- #在指定节点上执行命令
- #python zo.py TRACE=FEWSIPD action=remote host=17.192.94.65 password='QkpAbnBjMjAxOA==' cmds="ls"
- #
- #python zo.py action=local cmds=?
- #python zo.py action=remote host=? username=? password=? cmds=?
- #python zo.py action=fget host=? username=? password=? local=? remote=?
- #python zo.py action=fput host=? username=? password=? local=? remote=?
- def main():
- #切换到当前目录的上一级目录
- cur_file_dir = os.path.split(os.path.abspath(__file__))[0];
- envs_work_dir = os.path.split(cur_file_dir)[0];
- os.chdir(envs_work_dir);
- runner.trace(tl_debug, "current work dir: "+envs_work_dir)
- runner.trace(tl_debug, "ro: python '"+"' '".join(sys.argv)+"'")
- runner.trace(tl_detail, "args: "+str(args.__dict__))
- if args.cmds.find("exit") >= 0:
- print("远程命令中不能使用exit,需要用return替代,确实需要exit可以通过变量实现!")
- return 1
- ret, sout = runner.run(args.action, args.host, args.username, args.password, cmds=args.cmds, local=args.local, remote=args.remote, sys_argv=sys.argv)
- #不能清除临时目录,可能还在使用,os.system("rm -rf %s"%zo_dir)
- return ret
-
- ERROR_MSG = {}
- ERROR_MSG[1]="Operation not permitted "
- ERROR_MSG[2]="No such file or directory "
- ERROR_MSG[3]="No such process "
- ERROR_MSG[4]="Interrupted system call "
- ERROR_MSG[5]="Input/output error "
- ERROR_MSG[6]="No such device or address "
- ERROR_MSG[7]="Argument list too long "
- ERROR_MSG[8]="Exec format error "
- ERROR_MSG[9]="Bad file descriptor "
- ERROR_MSG[10]="No child processes "
- ERROR_MSG[11]="Resource temporarily unavailable "
- ERROR_MSG[12]="Cannot allocate memory "
- ERROR_MSG[13]="Permission denied "
- ERROR_MSG[14]="Bad address "
- ERROR_MSG[15]="Block device required "
- ERROR_MSG[16]="Device or resource busy "
- ERROR_MSG[17]="File exists "
- ERROR_MSG[18]="Invalid cross-device link "
- ERROR_MSG[19]="No such device "
- ERROR_MSG[20]="Not a directory "
- ERROR_MSG[21]="Is a directory "
- ERROR_MSG[22]="Invalid argument "
- ERROR_MSG[23]="Too many open files in system "
- ERROR_MSG[24]="Too many open files "
- ERROR_MSG[25]="Inappropriate ioctl for device "
- ERROR_MSG[26]="Text file busy "
- ERROR_MSG[27]="File too large "
- ERROR_MSG[28]="No space left on device "
- ERROR_MSG[29]="Illegal seek "
- ERROR_MSG[30]="Read-only file system "
- ERROR_MSG[31]="Too many links "
- ERROR_MSG[32]="Broken pipe "
- ERROR_MSG[33]="Numerical argument out of domain "
- ERROR_MSG[34]="Numerical result out of range "
- ERROR_MSG[35]="Resource deadlock avoided "
- ERROR_MSG[36]="File name too long "
- ERROR_MSG[37]="No locks available "
- ERROR_MSG[38]="Function not implemented "
- ERROR_MSG[39]="Directory not empty "
- ERROR_MSG[40]="Too many levels of symbolic links "
- ERROR_MSG[42]="No message of desired type "
- ERROR_MSG[43]="Identifier removed "
- ERROR_MSG[44]="Channel number out of range "
- ERROR_MSG[45]="Level 2 not synchronized "
- ERROR_MSG[46]="Level 3 halted "
- ERROR_MSG[47]="Level 3 reset "
- ERROR_MSG[48]="Link number out of range "
- ERROR_MSG[49]="Protocol driver not attached "
- ERROR_MSG[50]="No CSI structure available "
- ERROR_MSG[51]="Level 2 halted "
- ERROR_MSG[52]="Invalid exchange "
- ERROR_MSG[53]="Invalid request descriptor "
- ERROR_MSG[54]="Exchange full "
- ERROR_MSG[55]="No anode "
- ERROR_MSG[56]="Invalid request code "
- ERROR_MSG[57]="Invalid slot "
- ERROR_MSG[59]="Bad font file format "
- ERROR_MSG[60]="Device not a stream "
- ERROR_MSG[61]="No data available "
- ERROR_MSG[62]="Timer expired "
- ERROR_MSG[63]="Out of streams resources "
- ERROR_MSG[64]="Machine is not on the network "
- ERROR_MSG[65]="Package not installed "
- ERROR_MSG[66]="Object is remote "
- ERROR_MSG[67]="Link has been severed "
- ERROR_MSG[68]="Advertise error "
- ERROR_MSG[69]="Srmount error "
- ERROR_MSG[70]="Communication error on send "
- ERROR_MSG[71]="Protocol error "
- ERROR_MSG[72]="Multihop attempted "
- ERROR_MSG[73]="RFS specific error "
- ERROR_MSG[74]="Bad message "
- ERROR_MSG[75]="Value too large for defined data type "
- ERROR_MSG[76]="Name not unique on network "
- ERROR_MSG[77]="File descriptor in bad state "
- ERROR_MSG[78]="Remote address changed "
- ERROR_MSG[79]="Can not access a needed shared library "
- ERROR_MSG[80]="Accessing a corrupted shared library "
- ERROR_MSG[81]=".lib section in a.out corrupted "
- ERROR_MSG[82]="Attempting to link in too many shared libraries "
- ERROR_MSG[83]="Cannot exec a shared library directly "
- ERROR_MSG[84]="Invalid or incomplete multibyte or wide character "
- ERROR_MSG[85]="Interrupted system call should be restarted "
- ERROR_MSG[86]="Streams pipe error "
- ERROR_MSG[87]="Too many users "
- ERROR_MSG[88]="Socket operation on non-socket "
- ERROR_MSG[89]="Destination address required "
- ERROR_MSG[90]="Message too long "
- ERROR_MSG[91]="Protocol wrong type for socket "
- ERROR_MSG[92]="Protocol not available "
- ERROR_MSG[93]="Protocol not supported "
- ERROR_MSG[94]="Socket type not supported "
- ERROR_MSG[95]="Operation not supported "
- ERROR_MSG[96]="Protocol family not supported "
- ERROR_MSG[97]="Address family not supported by protocol "
- ERROR_MSG[98]="Address already in use "
- ERROR_MSG[99]="Cannot assign requested address "
- ERROR_MSG[100]="Network is down "
- ERROR_MSG[101]="Network is unreachable "
- ERROR_MSG[102]="Network dropped connection on reset "
- ERROR_MSG[103]="Software caused connection abort "
- ERROR_MSG[104]="Connection reset by peer "
- ERROR_MSG[105]="No buffer space available "
- ERROR_MSG[106]="Transport endpoint is already connected "
- ERROR_MSG[107]="Transport endpoint is not connected "
- ERROR_MSG[108]="Cannot send after transport endpoint shutdown "
- ERROR_MSG[109]="Too many references=cannot splice "
- ERROR_MSG[110]="Connection timed out "
- ERROR_MSG[111]="Connection refused "
- ERROR_MSG[112]="Host is down "
- ERROR_MSG[113]="No route to host "
- ERROR_MSG[114]="Operation already in progress "
- ERROR_MSG[115]="Operation now in progress "
- ERROR_MSG[116]="Stale NFS file handle "
- ERROR_MSG[117]="Structure needs cleaning "
- ERROR_MSG[118]="Not a XENIX named type file "
- ERROR_MSG[119]="No XENIX semaphores available "
- ERROR_MSG[120]="Is a named type file "
- ERROR_MSG[121]="Remote I/O error "
- ERROR_MSG[122]="Disk quota exceeded "
- ERROR_MSG[123]="No medium found "
- ERROR_MSG[124]="Wrong medium type "
- ERROR_MSG[125]="Operation canceled "
- ERROR_MSG[126]="权限不够(Required key not available) "
- ERROR_MSG[127]="Command not found(Key has expired) "
- ERROR_MSG[128]="Key has been revoked "
- ERROR_MSG[129]="Key was rejected by service "
- ERROR_MSG[130]="执行过程被中断(Owner died) "
- ERROR_MSG[131]="State not recoverable "
- ERROR_MSG[143]="主进程被杀 "
- ERROR_MSG[230]="操作超时 "
- ERROR_MSG[231]="密码错误 "
- ERROR_MSG[232]="远程连接错误 "
- ERROR_MSG[233]="远程连接超时 "
- ERROR_MSG[234]="运行时错误 "
- ERROR_MSG[235]="交互处理表达式语法错误 "
-
- ERROR_MSG[239]="找不到安装介质 "
- ERROR_MSG[240]="脚本执行过程中发生未知错误 "
- ERROR_MSG[241]="部署依赖不正确 "
- ERROR_MSG[242]="启动失败 "
- ERROR_MSG[243]="返回格式不正确 "
- ERROR_MSG[244]="服务器名称不符 "
- ERROR_MSG[245]="卸载失败 "
- ERROR_MSG[246]="参数错误 "
- ERROR_MSG[247]="参数错误 "
- ERROR_MSG[248]="参数错误 "
- ERROR_MSG[249]="远程脚本执行错误 "
-
- ERROR_MSG[255]="部署已存在,命令执行失败,请重试 "
- ERROR_MSG[254]="安装介质已分发 "
- ERROR_MSG[253]="部署存在未启动 "
- ERROR_MSG[252]="未部署 "
- ERROR_MSG[251]="路径存在未安装 "
- ERROR_MSG[250]="status250 "
-
- if __name__ == '__main__':
- try:
- exit(main())
- except KeyboardInterrupt as ki:
- print("")
- exit(130)
-
- # _oo0oo_
- # o8888888o
- # 88" . "88
- # (| -_- |)
- # 0\ = /0
- # ___/`---'\___
- # .' \\| |// '.
- # / \\||| : |||// \
- # / _||||| -:- |||||- \
- # | | \\\ - /// | |
- # | \_| ''\---/'' |_/ |
- # \ .-\__ '-' ___/-. /
- # ___'. .' /--.--\ `. .'___
- # ."" '< `.___\_<|>_/___.' >' "".
- # | | : `- \`.;`\ _ /`;.`/ - ` : | |
- # \ \ `_. \_ __\ /__ _/ .-` / /
- # =====`-.____`.___ \_____/___.-`___.-'=====
- # `=---='
- #
- #
- # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #
- # 见见之时 见非是见 见犹离见 见不能及
- #
|