PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils/reactor.py
""" Functions which implement running reactor jobs """ import fnmatch import glob import logging import os import salt.client import salt.defaults.exitcodes import salt.runner import salt.state import salt.utils.args import salt.utils.cache import salt.utils.data import salt.utils.event import salt.utils.files import salt.utils.master import salt.utils.process import salt.utils.yaml import salt.wheel log = logging.getLogger(__name__) REACTOR_INTERNAL_KEYWORDS = frozenset( ["__id__", "__sls__", "name", "order", "fun", "key", "state"] ) class Reactor(salt.utils.process.SignalHandlingProcess, salt.state.Compiler): """ Read in the reactor configuration variable and compare it to events processed on the master. The reactor has the capability to execute pre-programmed executions as reactions to events """ aliases = { "cmd": "local", } def __init__(self, opts, **kwargs): super().__init__(**kwargs) local_minion_opts = opts.copy() local_minion_opts["file_client"] = "local" self.minion = salt.minion.MasterMinion(local_minion_opts) salt.state.Compiler.__init__(self, opts, self.minion.rend) self.is_leader = True def render_reaction(self, glob_ref, tag, data): """ Execute the render system against a single reaction file and return the data structure """ react = {} if glob_ref.startswith("salt://"): glob_ref = self.minion.functions["cp.cache_file"](glob_ref) or "" globbed_ref = glob.glob(glob_ref) if not globbed_ref: log.error( "Can not render SLS %s for tag %s. File missing or not found.", glob_ref, tag, ) for fn_ in globbed_ref: try: res = self.render_template(fn_, tag=tag, data=data) # for #20841, inject the sls name here since verify_high() # assumes it exists in case there are any errors for name in res: res[name]["__sls__"] = fn_ react.update(res) except Exception: # pylint: disable=broad-except log.exception('Failed to render "%s": ', fn_) return react def list_reactors(self, tag): """ Take in the tag from an event and return a list of the reactors to process """ log.debug("Gathering reactors for tag %s", tag) reactors = [] if isinstance(self.opts["reactor"], str): try: with salt.utils.files.fopen(self.opts["reactor"]) as fp_: react_map = salt.utils.yaml.safe_load(fp_) except OSError: log.error('Failed to read reactor map: "%s"', self.opts["reactor"]) except Exception: # pylint: disable=broad-except log.error( 'Failed to parse YAML in reactor map: "%s"', self.opts["reactor"] ) else: react_map = self.opts["reactor"] for ropt in react_map: if not isinstance(ropt, dict): continue if len(ropt) != 1: continue key = next(iter(ropt.keys())) val = ropt[key] if fnmatch.fnmatch(tag, key): if isinstance(val, str): reactors.append(val) elif isinstance(val, list): reactors.extend(val) return reactors def list_all(self): """ Return a list of the reactors """ if isinstance(self.minion.opts["reactor"], str): log.debug("Reading reactors from yaml %s", self.opts["reactor"]) try: with salt.utils.files.fopen(self.opts["reactor"]) as fp_: react_map = salt.utils.yaml.safe_load(fp_) except OSError: log.error('Failed to read reactor map: "%s"', self.opts["reactor"]) except Exception: # pylint: disable=broad-except log.error( 'Failed to parse YAML in reactor map: "%s"', self.opts["reactor"] ) else: log.debug("Not reading reactors from yaml") react_map = self.minion.opts["reactor"] return react_map def add_reactor(self, tag, reaction): """ Add a reactor """ reactors = self.list_all() for reactor in reactors: _tag = next(iter(reactor.keys())) if _tag == tag: return {"status": False, "comment": "Reactor already exists."} self.minion.opts["reactor"].append({tag: reaction}) return {"status": True, "comment": "Reactor added."} def delete_reactor(self, tag): """ Delete a reactor """ reactors = self.list_all() for reactor in reactors: _tag = next(iter(reactor.keys())) if _tag == tag: self.minion.opts["reactor"].remove(reactor) return {"status": True, "comment": "Reactor deleted."} return {"status": False, "comment": "Reactor does not exists."} def resolve_aliases(self, chunks): """ Preserve backward compatibility by rewriting the 'state' key in the low chunks if it is using a legacy type. """ for idx, _ in enumerate(chunks): new_state = self.aliases.get(chunks[idx]["state"]) if new_state is not None: chunks[idx]["state"] = new_state def reactions(self, tag, data, reactors): """ Render a list of reactor files and returns a reaction struct """ log.debug("Compiling reactions for tag %s", tag) high = {} chunks = [] try: for fn_ in reactors: high.update(self.render_reaction(fn_, tag, data)) if high: errors = self.verify_high(high) if errors: log.error( "Unable to render reactions for event %s due to " "errors (%s) in one or more of the sls files (%s)", tag, errors, reactors, ) return [] # We'll return nothing since there was an error chunks = self.order_chunks(self.compile_high_data(high)) except Exception as exc: # pylint: disable=broad-except log.exception("Exception encountered while compiling reactions") self.resolve_aliases(chunks) return chunks def call_reactions(self, chunks): """ Execute the reaction state """ for chunk in chunks: self.wrap.run(chunk) def run(self): """ Enter into the server loop """ if self.opts["reactor_niceness"] and not salt.utils.platform.is_windows(): log.info("Reactor setting niceness to %i", self.opts["reactor_niceness"]) os.nice(self.opts["reactor_niceness"]) # instantiate some classes inside our new process with salt.utils.event.get_event( self.opts["__role"], self.opts["sock_dir"], opts=self.opts, listen=True, ) as event: self.wrap = ReactWrap(self.opts) for data in event.iter_events(full=True): # skip all events fired by ourselves if data["data"].get("user") == self.wrap.event_user: continue # NOTE: these events must contain the masters key in order to be accepted # see salt.runners.reactor for the requesting interface if "salt/reactors/manage" in data["tag"]: master_key = salt.utils.master.get_master_key("root", self.opts) if data["data"].get("key") != master_key: log.error( "received salt/reactors/manage event without matching" " master_key. discarding" ) continue if data["tag"].endswith("salt/reactors/manage/is_leader"): event.fire_event( {"result": self.is_leader}, "salt/reactors/manage/leader/value" ) if data["tag"].endswith("salt/reactors/manage/set_leader"): # we only want to register events from the local master if data["data"].get("id") == self.opts["id"]: self.is_leader = data["data"]["value"] event.fire_event( {"result": self.is_leader}, "salt/reactors/manage/leader/value" ) if data["tag"].endswith("salt/reactors/manage/add"): _data = data["data"] res = self.add_reactor(_data["event"], _data["reactors"]) event.fire_event( {"reactors": self.list_all(), "result": res}, "salt/reactors/manage/add-complete", ) elif data["tag"].endswith("salt/reactors/manage/delete"): _data = data["data"] res = self.delete_reactor(_data["event"]) event.fire_event( {"reactors": self.list_all(), "result": res}, "salt/reactors/manage/delete-complete", ) elif data["tag"].endswith("salt/reactors/manage/list"): event.fire_event( {"reactors": self.list_all()}, "salt/reactors/manage/list-results", ) else: # do not handle any reactions if not leader in cluster if not self.is_leader: continue else: reactors = self.list_reactors(data["tag"]) if not reactors: continue chunks = self.reactions(data["tag"], data["data"], reactors) if chunks: try: self.call_reactions(chunks) except SystemExit: log.warning("Exit ignored by reactor") class ReactWrap: """ Wrapper that executes low data for the Reactor System """ # class-wide cache of clients client_cache = None event_user = "Reactor" reaction_class = { "local": salt.client.LocalClient, "runner": salt.runner.RunnerClient, "wheel": salt.wheel.Wheel, "caller": salt.client.Caller, } def __init__(self, opts): self.opts = opts if ReactWrap.client_cache is None: ReactWrap.client_cache = salt.utils.cache.CacheDict( opts["reactor_refresh_interval"] ) self.pool = salt.utils.process.ThreadPool( self.opts["reactor_worker_threads"], # number of workers for runner/wheel queue_size=self.opts["reactor_worker_hwm"], # queue size for those workers ) def populate_client_cache(self, low): """ Populate the client cache with an instance of the specified type """ reaction_type = low["state"] # pylint: disable=unsupported-membership-test,unsupported-assignment-operation if reaction_type not in self.client_cache: log.debug("Reactor is populating %s client cache", reaction_type) if reaction_type in ("runner", "wheel"): # Reaction types that run locally on the master want the full # opts passed. self.client_cache[reaction_type] = self.reaction_class[reaction_type]( self.opts ) # The len() function will cause the module functions to load if # they aren't already loaded. We want to load them so that the # spawned threads don't need to load them. Loading in the # spawned threads creates race conditions such as sometimes not # finding the required function because another thread is in # the middle of loading the functions. len(self.client_cache[reaction_type].functions) else: # Reactions which use remote pubs only need the conf file when # instantiating a client instance. self.client_cache[reaction_type] = self.reaction_class[reaction_type]( self.opts["conf_file"] ) # pylint: enable=unsupported-membership-test,unsupported-assignment-operation def run(self, low): """ Execute a reaction by invoking the proper wrapper func """ self.populate_client_cache(low) try: l_fun = getattr(self, low["state"]) except AttributeError: log.error("ReactWrap is missing a wrapper function for '%s'", low["state"]) try: wrap_call = salt.utils.args.format_call(l_fun, low) args = wrap_call.get("args", ()) kwargs = wrap_call.get("kwargs", {}) # TODO: Setting user doesn't seem to work for actual remote pubs if low["state"] in ("runner", "wheel"): # Update called function's low data with event user to # segregate events fired by reactor and avoid reaction loops kwargs["__user__"] = self.event_user # Replace ``state`` kwarg which comes from high data compiler. # It breaks some runner functions and seems unnecessary. kwargs["__state__"] = kwargs.pop("state") # NOTE: if any additional keys are added here, they will also # need to be added to filter_kwargs() if "args" in kwargs: # New configuration reactor_args = kwargs.pop("args") for item in ("arg", "kwarg"): if item in low: log.warning( "Reactor '%s' is ignoring '%s' param %s due to " "presence of 'args' param. Check the Reactor System " "documentation for the correct argument format.", low["__id__"], item, low[item], ) if ( low["state"] == "caller" and isinstance(reactor_args, list) and not salt.utils.data.is_dictlist(reactor_args) ): # Legacy 'caller' reactors were already using the 'args' # param, but only supported a list of positional arguments. # If low['args'] is a list but is *not* a dictlist, then # this is actually using the legacy configuration. So, put # the reactor args into kwarg['arg'] so that the wrapper # interprets them as positional args. kwargs["arg"] = reactor_args kwargs["kwarg"] = {} else: kwargs["arg"] = () kwargs["kwarg"] = reactor_args if not isinstance(kwargs["kwarg"], dict): kwargs["kwarg"] = salt.utils.data.repack_dictlist(kwargs["kwarg"]) if not kwargs["kwarg"]: log.error( "Reactor '%s' failed to execute %s '%s': " "Incorrect argument format, check the Reactor System " "documentation for the correct format.", low["__id__"], low["state"], low["fun"], ) return else: # Legacy configuration react_call = {} if low["state"] in ("runner", "wheel"): if "arg" not in kwargs or "kwarg" not in kwargs: # Runner/wheel execute on the master, so we can use # format_call to get the functions args/kwargs react_fun = self.client_cache[low["state"]].functions.get( low["fun"] ) if react_fun is None: log.error( "Reactor '%s' failed to execute %s '%s': " "function not available", low["__id__"], low["state"], low["fun"], ) return react_call = salt.utils.args.format_call( react_fun, low, expected_extra_kws=REACTOR_INTERNAL_KEYWORDS ) if "arg" not in kwargs: kwargs["arg"] = react_call.get("args", ()) if "kwarg" not in kwargs: kwargs["kwarg"] = react_call.get("kwargs", {}) # Execute the wrapper with the proper args/kwargs. kwargs['arg'] # and kwargs['kwarg'] contain the positional and keyword arguments # that will be passed to the client interface to execute the # desired runner/wheel/remote-exec/etc. function. ret = l_fun(*args, **kwargs) if ret is False: log.error( "Reactor '%s' failed to execute %s '%s': " "TaskPool queue is full!" "Consider tuning reactor_worker_threads and/or" " reactor_worker_hwm", low["__id__"], low["state"], low["fun"], ) except SystemExit: log.warning("Reactor '%s' attempted to exit. Ignored.", low["__id__"]) except Exception: # pylint: disable=broad-except log.error( "Reactor '%s' failed to execute %s '%s'", low["__id__"], low["state"], low["fun"], exc_info=True, ) def runner(self, fun, **kwargs): """ Wrap RunnerClient for executing :ref:`runner modules <all-salt.runners>` """ # pylint: disable=unsupported-membership-test,unsupported-assignment-operation if "runner" not in self.client_cache: log.debug("reactor edge case: re-populating client_cache for runner") low = {"state": "runner"} self.populate_client_cache(low) return self.pool.fire_async(self.client_cache["runner"].low, args=(fun, kwargs)) def wheel(self, fun, **kwargs): """ Wrap Wheel to enable executing :ref:`wheel modules <all-salt.wheel>` """ # pylint: disable=unsupported-membership-test,unsupported-assignment-operation if "wheel" not in self.client_cache: log.debug("reactor edge case: re-populating client_cache for wheel") low = {"state": "wheel"} self.populate_client_cache(low) return self.pool.fire_async(self.client_cache["wheel"].low, args=(fun, kwargs)) def local(self, fun, tgt, **kwargs): """ Wrap LocalClient for running :ref:`execution modules <all-salt.modules>` """ # pylint: disable=unsupported-membership-test,unsupported-assignment-operation if "local" not in self.client_cache: log.debug("reactor edge case: re-populating client_cache for local") low = {"state": "local"} self.populate_client_cache(low) self.client_cache["local"].cmd_async(tgt, fun, **kwargs) def caller(self, fun, **kwargs): """ Wrap LocalCaller to execute remote exec functions locally on the Minion """ # pylint: disable=unsupported-membership-test,unsupported-assignment-operation if "caller" not in self.client_cache: log.debug("reactor edge case: re-populating client_cache for caller") low = {"state": "caller"} self.populate_client_cache(low) self.client_cache["caller"].cmd(fun, *kwargs["arg"], **kwargs["kwarg"])