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/templates.py
""" Template render systems """ import codecs import importlib.machinery import importlib.util import logging import os import sys import tempfile import traceback from pathlib import Path import jinja2 import jinja2.ext import jinja2.sandbox import salt.modules.match import salt.utils.data import salt.utils.dateutils import salt.utils.files import salt.utils.hashutils import salt.utils.http import salt.utils.jinja import salt.utils.network import salt.utils.platform import salt.utils.stringutils import salt.utils.yamlencoding from salt import __path__ as saltpath from salt.exceptions import CommandExecutionError, SaltInvocationError, SaltRenderError from salt.loader.context import NamedLoaderContext from salt.loader.dunder import __file_client__ from salt.utils.decorators.jinja import JinjaFilter, JinjaGlobal, JinjaTest from salt.utils.odict import OrderedDict from salt.utils.versions import Version log = logging.getLogger(__name__) TEMPLATE_DIRNAME = os.path.join(saltpath[0], "templates") # FIXME: also in salt/template.py SLS_ENCODING = "utf-8" # this one has no BOM. SLS_ENCODER = codecs.getencoder(SLS_ENCODING) class AliasedLoader: """ Light wrapper around the LazyLoader to redirect 'cmd.run' calls to 'cmd.shell', for easy use of shellisms during templating calls Dotted aliases ('cmd.run') must resolve to another dotted alias (e.g. 'cmd.shell') Non-dotted aliases ('cmd') must resolve to a dictionary of function aliases for that module (e.g. {'run': 'shell'}) """ def __init__(self, wrapped): self.wrapped = wrapped def __getitem__(self, name): return self.wrapped[name] def __getattr__(self, name): return getattr(self.wrapped, name) def __contains__(self, name): return name in self.wrapped class AliasedModule: """ Light wrapper around module objects returned by the LazyLoader's getattr for the purposes of `salt.cmd.run()` syntax in templates Allows for aliasing specific functions, such as `run` to `shell` for easy use of shellisms during templating calls """ def __init__(self, wrapped, aliases): self.aliases = aliases self.wrapped = wrapped def __getattr__(self, name): return getattr(self.wrapped, name) def generate_sls_context(tmplpath, sls): """ Generate SLS/Template Context Items Return values: tplpath - full path to template on filesystem including filename tplfile - relative path to template -- relative to file roots tpldir - directory of the template relative to file roots. If none, "." tpldot - tpldir using dots instead of slashes, if none, "" slspath - directory containing current sls - (same as tpldir), if none, "" sls_path - slspath with underscores separating parts, if none, "" slsdotpath - slspath with dots separating parts, if none, "" slscolonpath- slspath with colons separating parts, if none, "" """ sls_context = {} # Normalize SLS as path. slspath = sls.replace(".", "/") if tmplpath: # Normalize template path template = str(Path(tmplpath).as_posix()) # Determine proper template name without root if not sls: template = template.rsplit("/", 1)[-1] elif template.endswith(f"{slspath}.sls"): template = template[-(4 + len(slspath)) :] elif template.endswith(f"{slspath}/init.sls"): template = template[-(9 + len(slspath)) :] else: # Something went wrong log.warning("Failed to determine proper template path") slspath = template.rsplit("/", 1)[0] if "/" in template else "" sls_context.update( dict( tplpath=tmplpath, tplfile=template, tpldir=slspath if slspath else ".", tpldot=slspath.replace("/", "."), ) ) # Should this be normalized? sls_context.update( dict( slspath=slspath, slsdotpath=slspath.replace("/", "."), slscolonpath=slspath.replace("/", ":"), sls_path=slspath.replace("/", "_"), ) ) return sls_context def wrap_tmpl_func(render_str): """ Each template processing function below, ``render_*_tmpl``, is wrapped by ``render_tmpl`` before being inserted into the ``TEMPLATE_REGISTRY``. Some actions are taken here that are common to all renderers. Perhaps a standard decorator construct would have been more legible. :param function render_str: Template rendering function to be wrapped. Each function is responsible for rendering the source data for its repective template language. :returns function render_tmpl: The wrapper function """ def render_tmpl( tmplsrc, from_str=False, to_str=False, context=None, tmplpath=None, **kws ): if context is None: context = {} # Alias cmd.run to cmd.shell to make python_shell=True the default for # templated calls if "salt" in kws: kws["salt"] = AliasedLoader(kws["salt"]) # We want explicit context to overwrite the **kws kws.update(context) context = kws assert "opts" in context assert "saltenv" in context if "sls" in context: sls_context = generate_sls_context(tmplpath, context["sls"]) context.update(sls_context) if isinstance(tmplsrc, str): if from_str: tmplstr = tmplsrc else: try: if tmplpath is not None: tmplsrc = os.path.join(tmplpath, tmplsrc) with codecs.open(tmplsrc, "r", SLS_ENCODING) as _tmplsrc: tmplstr = _tmplsrc.read() except (UnicodeDecodeError, ValueError, OSError) as exc: if salt.utils.files.is_binary(tmplsrc): # Template is a bin file, return the raw file return dict(result=True, data=tmplsrc) log.error( "Exception occurred while reading file %s: %s", tmplsrc, exc, exc_info_on_loglevel=logging.DEBUG, ) raise else: # assume tmplsrc is file-like. tmplstr = tmplsrc.read() tmplsrc.close() try: output = render_str(tmplstr, context, tmplpath) if salt.utils.platform.is_windows(): newline = False if salt.utils.stringutils.to_unicode( output, encoding=SLS_ENCODING ).endswith(("\n", os.linesep)): newline = True # Write out with Windows newlines output = os.linesep.join(output.splitlines()) if newline: output += os.linesep except SaltRenderError as exc: log.exception("Rendering exception occurred") # return dict(result=False, data=str(exc)) raise except Exception: # pylint: disable=broad-except return dict(result=False, data=traceback.format_exc()) else: if to_str: # then render as string return dict(result=True, data=output) with tempfile.NamedTemporaryFile( "wb", delete=False, prefix=salt.utils.files.TEMPFILE_PREFIX ) as outf: outf.write( salt.utils.stringutils.to_bytes(output, encoding=SLS_ENCODING) ) # Note: If nothing is replaced or added by the rendering # function, then the contents of the output file will # be exactly the same as the input. return dict(result=True, data=outf.name) render_tmpl.render_str = render_str return render_tmpl def _get_jinja_error_slug(tb_data): """ Return the line number where the template error was found """ try: return [ x for x in tb_data if x[2] in ("top-level template code", "template", "<module>") ][-1] except IndexError: pass def _get_jinja_error_message(tb_data): """ Return an understandable message from jinja error output """ try: line = _get_jinja_error_slug(tb_data) return "{0}({1}):\n{3}".format(*line) except IndexError: pass return None def _get_jinja_error_line(tb_data): """ Return the line number where the template error was found """ try: return _get_jinja_error_slug(tb_data)[1] except IndexError: pass return None def _get_jinja_error(trace, context=None): """ Return the error line and error message output from a stacktrace. If we are in a macro, also output inside the message the exact location of the error in the macro """ if not context: context = {} out = "" error = _get_jinja_error_slug(trace) line = _get_jinja_error_line(trace) msg = _get_jinja_error_message(trace) # if we failed on a nested macro, output a little more info # to help debugging # if sls is not found in context, add output only if we can # resolve the filename add_log = False template_path = None if "sls" not in context: if (error[0] != "<unknown>") and os.path.exists(error[0]): template_path = error[0] add_log = True else: # the offender error is not from the called sls filen = context["sls"].replace(".", "/") if not error[0].endswith(filen) and os.path.exists(error[0]): add_log = True template_path = error[0] # if we add a log, format explicitly the exception here # by telling to output the macro context after the macro # error log place at the beginning if add_log: if template_path: out = f"\n{msg.splitlines()[0]}\n" with salt.utils.files.fopen(template_path) as fp_: template_contents = salt.utils.stringutils.to_unicode(fp_.read()) out += salt.utils.stringutils.get_context( template_contents, line, marker=" <======================" ) else: out = f"\n{msg}\n" line = 0 return line, out def render_jinja_tmpl(tmplstr, context, tmplpath=None): """ Render a Jinja template. :param str tmplstr: A string containing the source to be rendered. :param dict context: Any additional context data used by the renderer. :param str tmplpath: Base path from which ``tmplstr`` may load additional template files. :returns str: The string rendered by the template. """ opts = context["opts"] saltenv = context["saltenv"] loader = None newline = False if tmplstr and not isinstance(tmplstr, str): # https://jinja.palletsprojects.com/en/2.11.x/api/#unicode tmplstr = tmplstr.decode(SLS_ENCODING) if tmplstr.endswith(os.linesep): newline = os.linesep elif tmplstr.endswith("\n"): newline = "\n" try: if not saltenv: if tmplpath: loader = jinja2.FileSystemLoader(os.path.dirname(tmplpath)) else: loader = salt.utils.jinja.SaltCacheLoader( opts, saltenv, pillar_rend=context.get("_pillar_rend", False), _file_client=context.get("fileclient", __file_client__.value()), ) env_args = {"extensions": [], "loader": loader} if hasattr(jinja2.ext, "with_"): env_args["extensions"].append("jinja2.ext.with_") if hasattr(jinja2.ext, "do"): env_args["extensions"].append("jinja2.ext.do") if hasattr(jinja2.ext, "loopcontrols"): env_args["extensions"].append("jinja2.ext.loopcontrols") env_args["extensions"].append(salt.utils.jinja.SerializerExtension) opt_jinja_env = opts.get("jinja_env", {}) opt_jinja_sls_env = opts.get("jinja_sls_env", {}) opt_jinja_env = opt_jinja_env if isinstance(opt_jinja_env, dict) else {} opt_jinja_sls_env = ( opt_jinja_sls_env if isinstance(opt_jinja_sls_env, dict) else {} ) # Pass through trim_blocks and lstrip_blocks Jinja parameters # trim_blocks removes newlines around Jinja blocks # lstrip_blocks strips tabs and spaces from the beginning of # line to the start of a block. if opts.get("jinja_trim_blocks", False): log.debug("Jinja2 trim_blocks is enabled") log.warning( "jinja_trim_blocks is deprecated and will be removed in a future release," " please use jinja_env and/or jinja_sls_env instead" ) opt_jinja_env["trim_blocks"] = True opt_jinja_sls_env["trim_blocks"] = True if opts.get("jinja_lstrip_blocks", False): log.debug("Jinja2 lstrip_blocks is enabled") log.warning( "jinja_lstrip_blocks is deprecated and will be removed in a future release," " please use jinja_env and/or jinja_sls_env instead" ) opt_jinja_env["lstrip_blocks"] = True opt_jinja_sls_env["lstrip_blocks"] = True def opt_jinja_env_helper(opts, optname): for k, v in opts.items(): k = k.lower() if hasattr(jinja2.defaults, k.upper()): log.debug( "Jinja2 environment %s was set to %s by %s", k, v, optname ) env_args[k] = v else: log.warning("Jinja2 environment %s is not recognized", k) if "sls" in context and context["sls"] != "": opt_jinja_env_helper(opt_jinja_sls_env, "jinja_sls_env") else: opt_jinja_env_helper(opt_jinja_env, "jinja_env") if opts.get("allow_undefined", False): jinja_env = jinja2.sandbox.SandboxedEnvironment(**env_args) else: jinja_env = jinja2.sandbox.SandboxedEnvironment( undefined=jinja2.StrictUndefined, **env_args ) indent_filter = jinja_env.filters.get("indent") jinja_env.tests.update(JinjaTest.salt_jinja_tests) jinja_env.filters.update(JinjaFilter.salt_jinja_filters) if salt.utils.jinja.JINJA_VERSION >= Version("2.11"): # Use the existing indent filter on Jinja versions where it's not broken jinja_env.filters["indent"] = indent_filter jinja_env.globals.update(JinjaGlobal.salt_jinja_globals) # globals jinja_env.globals["odict"] = OrderedDict jinja_env.globals["show_full_context"] = salt.utils.jinja.show_full_context jinja_env.tests["list"] = salt.utils.data.is_list decoded_context = {} for key, value in context.items(): if not isinstance(value, str): if isinstance(value, NamedLoaderContext): decoded_context[key] = value.value() else: decoded_context[key] = value continue try: decoded_context[key] = salt.utils.stringutils.to_unicode( value, encoding=SLS_ENCODING ) except UnicodeDecodeError: log.debug( "Failed to decode using default encoding (%s), trying system encoding", SLS_ENCODING, ) decoded_context[key] = salt.utils.data.decode(value) jinja_env.globals.update(decoded_context) try: template = jinja_env.from_string(tmplstr) output = template.render(**decoded_context) except jinja2.exceptions.UndefinedError as exc: trace = traceback.extract_tb(sys.exc_info()[2]) line, out = _get_jinja_error(trace, context=decoded_context) if not line: tmplstr = "" raise SaltRenderError(f"Jinja variable {exc}{out}", line, tmplstr) except ( jinja2.exceptions.TemplateRuntimeError, jinja2.exceptions.TemplateSyntaxError, jinja2.exceptions.SecurityError, ) as exc: trace = traceback.extract_tb(sys.exc_info()[2]) line, out = _get_jinja_error(trace, context=decoded_context) if not line: tmplstr = "" raise SaltRenderError(f"Jinja syntax error: {exc}{out}", line, tmplstr) except (SaltInvocationError, CommandExecutionError) as exc: trace = traceback.extract_tb(sys.exc_info()[2]) line, out = _get_jinja_error(trace, context=decoded_context) if not line: tmplstr = "" raise SaltRenderError( "Problem running salt function in Jinja template: {}{}".format( exc, out ), line, tmplstr, ) except Exception as exc: # pylint: disable=broad-except tracestr = traceback.format_exc() trace = traceback.extract_tb(sys.exc_info()[2]) line, out = _get_jinja_error(trace, context=decoded_context) if not line: tmplstr = "" else: tmplstr += f"\n{tracestr}" log.debug("Jinja Error") log.debug("Exception:", exc_info=True) log.debug("Out: %s", out) log.debug("Line: %s", line) log.debug("TmplStr: %s", tmplstr) log.debug("TraceStr: %s", tracestr) raise SaltRenderError( f"Jinja error: {exc}{out}", line, tmplstr, trace=tracestr ) finally: if loader and isinstance(loader, salt.utils.jinja.SaltCacheLoader): loader.destroy() # Workaround a bug in Jinja that removes the final newline # (https://github.com/mitsuhiko/jinja2/issues/75) if newline: output += newline return output # pylint: disable=3rd-party-module-not-gated def render_mako_tmpl(tmplstr, context, tmplpath=None): """ Render a Mako template. :param str tmplstr: A string containing the source to be rendered. :param dict context: Any additional context data used by the renderer. :param str tmplpath: Base path from which ``tmplstr`` may load additional template files. :returns str: The string rendered by the template. """ import mako.exceptions # pylint: disable=no-name-in-module from mako.template import Template # pylint: disable=no-name-in-module from salt.utils.mako import SaltMakoTemplateLookup saltenv = context["saltenv"] lookup = None if not saltenv: if tmplpath: # i.e., the template is from a file outside the state tree from mako.lookup import TemplateLookup # pylint: disable=no-name-in-module lookup = TemplateLookup(directories=[os.path.dirname(tmplpath)]) else: lookup = SaltMakoTemplateLookup( context["opts"], saltenv, pillar_rend=context.get("_pillar_rend", False) ) try: return Template( tmplstr, strict_undefined=True, uri=context["sls"].replace(".", "/") if "sls" in context else None, lookup=lookup, ).render(**context) except Exception: # pylint: disable=broad-except raise SaltRenderError(mako.exceptions.text_error_template().render()) finally: if lookup and isinstance(lookup, SaltMakoTemplateLookup): lookup.destroy() def render_wempy_tmpl(tmplstr, context, tmplpath=None): """ Render a Wempy template. :param str tmplstr: A string containing the source to be rendered. :param dict context: Any additional context data used by the renderer. :param str tmplpath: Unused. :returns str: The string rendered by the template. """ from wemplate.wemplate import TemplateParser as Template return Template(tmplstr).render(**context) def render_genshi_tmpl(tmplstr, context, tmplpath=None): """ Render a Genshi template. A method should be passed in as part of the context. If no method is passed in, xml is assumed. Valid methods are: .. code-block: - xml - xhtml - html - text - newtext - oldtext Note that the ``text`` method will call ``NewTextTemplate``. If ``oldtext`` is desired, it must be called explicitly """ method = context.get("method", "xml") if method == "text" or method == "newtext": from genshi.template import NewTextTemplate # pylint: disable=no-name-in-module tmpl = NewTextTemplate(tmplstr) elif method == "oldtext": from genshi.template import OldTextTemplate # pylint: disable=no-name-in-module tmpl = OldTextTemplate(tmplstr) else: from genshi.template import MarkupTemplate # pylint: disable=no-name-in-module tmpl = MarkupTemplate(tmplstr) return tmpl.generate(**context).render(method) def render_cheetah_tmpl(tmplstr, context, tmplpath=None): """ Render a Cheetah template. """ from Cheetah.Template import Template # Compile the template and render it into the class tclass = Template.compile(tmplstr) data = tclass(namespaces=[context]) # Figure out which method to call based on the type of tmplstr if isinstance(tmplstr, str): # This should call .__unicode__() res = str(data) elif isinstance(tmplstr, bytes): # This should call .__str() res = str(data) else: raise SaltRenderError( "Unknown type {!s} for Cheetah template while trying to render.".format( type(tmplstr) ) ) # Now we can decode it to the correct encoding return salt.utils.data.decode(res) # pylint: enable=3rd-party-module-not-gated def py(sfn, string=False, **kwargs): # pylint: disable=C0103 """ Render a template from a python source file Returns:: {'result': bool, 'data': <Error data or rendered file path>} """ if not os.path.isfile(sfn): return {} base_fname = os.path.basename(sfn) name = base_fname.split(".")[0] loader = importlib.machinery.SourceFileLoader(name, sfn) spec = importlib.util.spec_from_file_location(name, sfn, loader=loader) if spec is None: raise ImportError() mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) sys.modules[name] = mod # File templates need these set as __var__ if "__env__" not in kwargs and "saltenv" in kwargs: setattr(mod, "__env__", kwargs["saltenv"]) builtins = ["salt", "grains", "pillar", "opts"] for builtin in builtins: arg = f"__{builtin}__" setattr(mod, arg, kwargs[builtin]) for kwarg in kwargs: setattr(mod, kwarg, kwargs[kwarg]) try: data = mod.run() if string: return {"result": True, "data": data} tgt = salt.utils.files.mkstemp() with salt.utils.files.fopen(tgt, "w+") as target: target.write(salt.utils.stringutils.to_str(data)) return {"result": True, "data": tgt} except Exception: # pylint: disable=broad-except trb = traceback.format_exc() return {"result": False, "data": trb} JINJA = wrap_tmpl_func(render_jinja_tmpl) MAKO = wrap_tmpl_func(render_mako_tmpl) WEMPY = wrap_tmpl_func(render_wempy_tmpl) GENSHI = wrap_tmpl_func(render_genshi_tmpl) CHEETAH = wrap_tmpl_func(render_cheetah_tmpl) TEMPLATE_REGISTRY = { "jinja": JINJA, "mako": MAKO, "py": py, "wempy": WEMPY, "genshi": GENSHI, "cheetah": CHEETAH, }