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/thin.py
""" Generate the salt thin tarball from the installed python files """ import contextvars as py_contextvars import copy import importlib.util import logging import os import shutil import site import subprocess import sys import tarfile import tempfile import zipfile import distro import jinja2 import looseversion import msgpack import packaging import yaml import salt import salt.exceptions import salt.ext.tornado as tornado import salt.utils.files import salt.utils.hashutils import salt.utils.json import salt.utils.path import salt.utils.stringutils import salt.version # This is needed until we drop support for python 3.6 has_immutables = False try: import immutables has_immutables = True except ImportError: pass try: import zlib except ImportError: zlib = None # pylint: disable=import-error,no-name-in-module try: import certifi except ImportError: certifi = None try: import singledispatch except ImportError: singledispatch = None try: import singledispatch_helpers except ImportError: singledispatch_helpers = None try: import backports_abc except ImportError: import salt.ext.backports_abc as backports_abc try: # New Jinja only import markupsafe except ImportError: markupsafe = None try: # Older python where the backport from pypi is installed from backports import ssl_match_hostname except ImportError: # Other older python we use our bundled copy try: from salt.ext import ssl_match_hostname except ImportError: ssl_match_hostname = None concurrent = None log = logging.getLogger(__name__) def import_module(name, path): """ Import a module from a specific path. Path can be a full or relative path to a .py file. :name: The name of the module to import :path: The path of the module to import """ try: spec = importlib.util.spec_from_file_location(name, path) except ValueError: spec = None if spec is not None: lib = importlib.util.module_from_spec(spec) try: spec.loader.exec_module(lib) except OSError: pass else: return lib def getsitepackages(): """ Some versions of Virtualenv ship a site.py without getsitepackages. This method will first try and return sitepackages from the default site module if no method exists we will try importing the site module from every other path in sys.paths until we find a getsitepackages method to return the results from. If for some reason no gesitepackages method can be found a RuntimeError will be raised :return: A list containing all global site-packages directories. """ if hasattr(site, "getsitepackages"): return site.getsitepackages() for path in sys.path: lib = import_module("site", os.path.join(path, "site.py")) if hasattr(lib, "getsitepackages"): return lib.getsitepackages() raise RuntimeError("Unable to locate a getsitepackages method") def find_site_modules(name): """ Finds and imports a module from site packages directories. :name: The name of the module to import :return: A list of imported modules, if no modules are imported an empty list is returned. """ libs = [] site_paths = [] try: site_paths = getsitepackages() except RuntimeError: log.debug("No site package directories found") for site_path in site_paths: path = os.path.join(site_path, f"{name}.py") lib = import_module(name, path) if lib: libs.append(lib) path = os.path.join(site_path, name, "__init__.py") lib = import_module(name, path) if lib: libs.append(lib) return libs def _get_salt_call(*dirs, **namespaces): """ Return salt-call source, based on configuration. This will include additional namespaces for another versions of Salt, if needed (e.g. older interpreters etc). :dirs: List of directories to include in the system path :namespaces: Dictionary of namespace :return: """ template = """# -*- coding: utf-8 -*- import os import sys # Namespaces is a map: {namespace: major/minor version}, like {'2016.11.4': [2, 6]} # Appears only when configured in Master configuration. namespaces = %namespaces% # Default system paths alongside the namespaces syspaths = %dirs% syspaths.append('py{0}'.format(sys.version_info[0])) curr_ver = (sys.version_info[0], sys.version_info[1],) namespace = '' for ns in namespaces: if curr_ver == tuple(namespaces[ns]): namespace = ns break for base in syspaths: sys.path.insert(0, os.path.join(os.path.dirname(__file__), namespace and os.path.join(namespace, base) or base)) if __name__ == '__main__': from salt.scripts import salt_call salt_call() """ for tgt, cnt in [("%dirs%", dirs), ("%namespaces%", namespaces)]: template = template.replace(tgt, salt.utils.json.dumps(cnt)) return salt.utils.stringutils.to_bytes(template) def thin_path(cachedir): """ Return the path to the thin tarball """ return os.path.join(cachedir, "thin", "thin.tgz") def _is_shareable(mod): """ Return True if module is share-able between major Python versions. :param mod: :return: """ # This list is subject to change shareable = ["salt", "jinja2", "msgpack", "certifi"] return os.path.basename(mod) in shareable def _add_dependency(container, obj): """ Add a dependency to the top list. :param obj: :param is_file: :return: """ if os.path.basename(obj.__file__).split(".")[0] == "__init__": container.append(os.path.dirname(obj.__file__)) else: container.append(obj.__file__.replace(".pyc", ".py")) def gte(): """ This function is called externally from the alternative Python interpreter from within _get_tops function. :param extra_mods: :param so_mods: :return: """ extra = salt.utils.json.loads(sys.argv[1]) tops = get_tops(**extra) return salt.utils.json.dumps(tops, ensure_ascii=False) def get_tops_python(py_ver, exclude=None, ext_py_ver=None): """ Get top directories for the ssh_ext_alternatives dependencies automatically for the given python version. This allows the user to add the dependency paths automatically. :param py_ver: python binary to use to detect binaries :param exclude: list of modules not to auto detect :param ext_py_ver: the py-version from the ssh_ext_alternatives config """ files = {} mods = [ "jinja2", "yaml", "tornado", "msgpack", "certifi", "singledispatch", "concurrent", "singledispatch_helpers", "ssl_match_hostname", "markupsafe", "backports_abc", "looseversion", "packaging", ] if ext_py_ver and tuple(ext_py_ver) >= (3, 0): mods.append("distro") for mod in mods: if exclude and mod in exclude: continue if not salt.utils.path.which(py_ver): log.error("%s does not exist. Could not auto detect dependencies", py_ver) return {} py_shell_cmd = [py_ver, "-c", "import {0}; print({0}.__file__)".format(mod)] cmd = subprocess.Popen(py_shell_cmd, stdout=subprocess.PIPE) stdout, _ = cmd.communicate() mod_file = os.path.abspath(salt.utils.data.decode(stdout).rstrip("\n")) if not stdout or not os.path.exists(mod_file): log.error( "Could not auto detect file location for module %s for python version %s", mod, py_ver, ) continue if os.path.basename(mod_file).split(".")[0] == "__init__": mod_file = os.path.dirname(mod_file) else: mod_file = mod_file.replace("pyc", "py") files[mod] = mod_file return files def get_ext_tops(config): """ Get top directories for the dependencies, based on external configuration. :return: """ config = copy.deepcopy(config) or {} alternatives = {} required = ["jinja2", "yaml", "tornado", "msgpack"] tops = [] for ns, cfg in config.items(): alternatives[ns] = cfg locked_py_version = cfg.get("py-version") err_msg = None if not locked_py_version: err_msg = "Alternative Salt library: missing specific locked Python version" elif not isinstance(locked_py_version, (tuple, list)): err_msg = ( "Alternative Salt library: specific locked Python version " "should be a list of major/minor version" ) if err_msg: raise salt.exceptions.SaltSystemExit(err_msg) if tuple(locked_py_version) >= (3, 0) and "distro" not in required: required.append("distro") if cfg.get("dependencies") == "inherit": # TODO: implement inheritance of the modules from _here_ raise NotImplementedError("This feature is not yet implemented") else: for dep in cfg.get("dependencies"): mod = cfg["dependencies"][dep] or "" if not mod: log.warning("Module %s has missing configuration", dep) continue elif mod.endswith(".py") and not os.path.isfile(mod): log.warning( "Module %s configured with not a file or does not exist: %s", dep, mod, ) continue elif not mod.endswith(".py") and not os.path.isfile( os.path.join(mod, "__init__.py") ): log.warning( "Module %s is not a Python importable module with %s", dep, mod ) continue tops.append(mod) if dep in required: required.pop(required.index(dep)) required = ", ".join(required) if required: msg = ( "Missing dependencies for the alternative version" " in the external configuration: {}".format(required) ) log.error(msg) raise salt.exceptions.SaltSystemExit(msg=msg) alternatives[ns]["dependencies"] = tops return alternatives def _get_ext_namespaces(config): """ Get namespaces from the existing configuration. :param config: :return: """ namespaces = {} if not config: return namespaces for ns in config: constraint_version = tuple(config[ns].get("py-version", [])) if not constraint_version: raise salt.exceptions.SaltSystemExit( "An alternative version is configured, but not defined " "to what Python's major/minor version it should be constrained." ) else: namespaces[ns] = constraint_version return namespaces def get_tops(extra_mods="", so_mods=""): """ Get top directories for the dependencies, based on Python interpreter. :param extra_mods: :param so_mods: :return: """ tops = [] mods = [ salt, distro, jinja2, yaml, tornado, msgpack, certifi, singledispatch, concurrent, singledispatch_helpers, ssl_match_hostname, markupsafe, backports_abc, looseversion, packaging, ] modules = find_site_modules("contextvars") if modules: contextvars = modules[0] else: contextvars = py_contextvars log.debug("Using contextvars %r", contextvars) mods.append(contextvars) if has_immutables: mods.append(immutables) for mod in mods: if mod: log.debug('Adding module to the tops: "%s"', mod.__name__) _add_dependency(tops, mod) for mod in [m for m in extra_mods.split(",") if m]: if mod not in locals() and mod not in globals(): try: locals()[mod] = __import__(mod) moddir, modname = os.path.split(locals()[mod].__file__) base, _ = os.path.splitext(modname) if base == "__init__": tops.append(moddir) else: tops.append(os.path.join(moddir, base + ".py")) except ImportError as err: log.error( 'Unable to import extra-module "%s": %s', mod, err, exc_info=True ) for mod in [m for m in so_mods.split(",") if m]: try: locals()[mod] = __import__(mod) tops.append(locals()[mod].__file__) except ImportError as err: log.error('Unable to import so-module "%s"', mod, exc_info=True) return tops def _get_supported_py_config(tops, extended_cfg): """ Based on the Salt SSH configuration, create a YAML configuration for the supported Python interpreter versions. This is then written into the thin.tgz archive and then verified by salt.client.ssh.ssh_py_shim.get_executable() Note: Current versions of Salt only Support Python 3, but the versions of Python (2.7,3.0) remain to include support for ssh_ext_alternatives if user is targeting an older version of Salt. :return: """ pymap = [] for py_ver, tops in copy.deepcopy(tops).items(): py_ver = int(py_ver) if py_ver == 2: pymap.append("py2:2:7") elif py_ver == 3: pymap.append("py3:3:0") cfg_copy = copy.deepcopy(extended_cfg) or {} for ns, cfg in cfg_copy.items(): pymap.append("{}:{}:{}".format(ns, *cfg.get("py-version"))) pymap.append("") return salt.utils.stringutils.to_bytes(os.linesep.join(pymap)) def _get_thintar_prefix(tarname): """ Make sure thintar temporary name is concurrent and secure. :param tarname: name of the chosen tarball :return: prefixed tarname """ tfd, tmp_tarname = tempfile.mkstemp( dir=os.path.dirname(tarname), prefix=".thin-", suffix=os.path.splitext(tarname)[1], ) os.close(tfd) return tmp_tarname def _pack_alternative(extended_cfg, digest_collector, tfp): # Pack alternative data config = copy.deepcopy(extended_cfg) # Check if auto_detect is enabled and update dependencies for ns, cfg in config.items(): if cfg.get("auto_detect"): py_ver = "python" + str(cfg.get("py-version", [""])[0]) if cfg.get("py_bin"): py_ver = cfg["py_bin"] exclude = [] # get any manually set deps deps = config[ns].get("dependencies") if deps: for dep in deps.keys(): exclude.append(dep) else: config[ns]["dependencies"] = {} # get auto deps auto_deps = get_tops_python( py_ver, exclude=exclude, ext_py_ver=cfg["py-version"] ) for dep in auto_deps: config[ns]["dependencies"][dep] = auto_deps[dep] for ns, cfg in get_ext_tops(config).items(): tops = [cfg.get("path")] + cfg.get("dependencies") py_ver_major, py_ver_minor = cfg.get("py-version") for top in tops: top = os.path.normpath(top) base, top_dirname = os.path.basename(top), os.path.dirname(top) os.chdir(top_dirname) site_pkg_dir = _is_shareable(base) and "pyall" or f"py{py_ver_major}" log.debug( 'Packing alternative "%s" to "%s/%s" destination', base, ns, site_pkg_dir, ) if not os.path.exists(top): log.error( "File path %s does not exist. Unable to add to salt-ssh thin", top ) continue if not os.path.isdir(top): # top is a single file module if os.path.exists(os.path.join(top_dirname, base)): tfp.add(base, arcname=os.path.join(ns, site_pkg_dir, base)) continue for root, dirs, files in salt.utils.path.os_walk(base, followlinks=True): for name in files: if not name.endswith((".pyc", ".pyo")): digest_collector.add(os.path.join(root, name)) arcname = os.path.join(ns, site_pkg_dir, root, name) if hasattr(tfp, "getinfo"): try: tfp.getinfo(os.path.join(site_pkg_dir, root, name)) arcname = None except KeyError: log.debug( 'ZIP: Unable to add "%s" with "getinfo"', arcname ) if arcname: tfp.add(os.path.join(root, name), arcname=arcname) def gen_thin( cachedir, extra_mods="", overwrite=False, so_mods="", absonly=True, compress="gzip", extended_cfg=None, ): """ Generate the salt-thin tarball and print the location of the tarball Optional additional mods to include (e.g. mako) can be supplied as a comma delimited string. Permits forcing an overwrite of the output file as well. CLI Example: .. code-block:: bash salt-run thin.generate salt-run thin.generate mako salt-run thin.generate mako,wempy 1 salt-run thin.generate overwrite=1 """ if sys.version_info < (3,): raise salt.exceptions.SaltSystemExit( 'The minimum required python version to run salt-ssh is "3".' ) if compress not in ["gzip", "zip"]: log.warning( 'Unknown compression type: "%s". Falling back to "gzip" compression.', compress, ) compress = "gzip" thindir = os.path.join(cachedir, "thin") if not os.path.isdir(thindir): os.makedirs(thindir) thintar = os.path.join(thindir, "thin." + (compress == "gzip" and "tgz" or "zip")) thinver = os.path.join(thindir, "version") pythinver = os.path.join(thindir, ".thin-gen-py-version") salt_call = os.path.join(thindir, "salt-call") pymap_cfg = os.path.join(thindir, "supported-versions") code_checksum = os.path.join(thindir, "code-checksum") digest_collector = salt.utils.hashutils.DigestCollector() with salt.utils.files.fopen(salt_call, "wb") as fp_: fp_.write(_get_salt_call("pyall", **_get_ext_namespaces(extended_cfg))) if os.path.isfile(thintar): if not overwrite: if os.path.isfile(thinver): with salt.utils.files.fopen(thinver) as fh_: overwrite = fh_.read() != salt.version.__version__ if overwrite is False and os.path.isfile(pythinver): with salt.utils.files.fopen(pythinver) as fh_: overwrite = fh_.read() != str(sys.version_info[0]) else: overwrite = True if overwrite: try: log.debug("Removing %s archive file", thintar) os.remove(thintar) except OSError as exc: log.error("Error while removing %s file: %s", thintar, exc) if os.path.exists(thintar): raise salt.exceptions.SaltSystemExit( "Unable to remove {} file. See logs for details.".format( thintar ) ) else: return thintar tops_failure_msg = "Failed %s tops for Python binary %s." tops_py_version_mapping = {} tops = get_tops(extra_mods=extra_mods, so_mods=so_mods) tops_py_version_mapping[sys.version_info.major] = tops with salt.utils.files.fopen(pymap_cfg, "wb") as fp_: fp_.write( _get_supported_py_config( tops=tops_py_version_mapping, extended_cfg=extended_cfg ) ) tmp_thintar = _get_thintar_prefix(thintar) if compress == "gzip": tfp = tarfile.open(tmp_thintar, "w:gz", dereference=True) elif compress == "zip": tfp = zipfile.ZipFile( tmp_thintar, "w", compression=zlib and zipfile.ZIP_DEFLATED or zipfile.ZIP_STORED, ) tfp.add = tfp.write try: # cwd may not exist if it was removed but salt was run from it start_dir = os.getcwd() except OSError: start_dir = None tempdir = None # Pack default data log.debug("Packing default libraries based on current Salt version") for py_ver, tops in tops_py_version_mapping.items(): for top in tops: if absonly and not os.path.isabs(top): continue base = os.path.basename(top) top_dirname = os.path.dirname(top) if os.path.isdir(top_dirname): os.chdir(top_dirname) else: # This is likely a compressed python .egg tempdir = tempfile.mkdtemp() egg = zipfile.ZipFile(top_dirname) egg.extractall(tempdir) # nosec top = os.path.join(tempdir, base) os.chdir(tempdir) site_pkg_dir = _is_shareable(base) and "pyall" or f"py{py_ver}" log.debug('Packing "%s" to "%s" destination', base, site_pkg_dir) if not os.path.isdir(top): # top is a single file module if os.path.exists(os.path.join(top_dirname, base)): tfp.add(base, arcname=os.path.join(site_pkg_dir, base)) continue for root, dirs, files in salt.utils.path.os_walk(base, followlinks=True): for name in files: if not name.endswith((".pyc", ".pyo")): digest_collector.add(os.path.join(root, name)) arcname = os.path.join(site_pkg_dir, root, name) if hasattr(tfp, "getinfo"): try: # This is a little slow but there's no clear way to detect duplicates tfp.getinfo(os.path.join(site_pkg_dir, root, name)) arcname = None except KeyError: log.debug( 'ZIP: Unable to add "%s" with "getinfo"', arcname ) if arcname: tfp.add(os.path.join(root, name), arcname=arcname) if tempdir is not None: shutil.rmtree(tempdir) tempdir = None if extended_cfg: log.debug("Packing libraries based on alternative Salt versions") _pack_alternative(extended_cfg, digest_collector, tfp) os.chdir(thindir) with salt.utils.files.fopen(thinver, "w+") as fp_: fp_.write(salt.version.__version__) with salt.utils.files.fopen(pythinver, "w+") as fp_: fp_.write(str(sys.version_info.major)) with salt.utils.files.fopen(code_checksum, "w+") as fp_: fp_.write(digest_collector.digest()) os.chdir(os.path.dirname(thinver)) for fname in [ "version", ".thin-gen-py-version", "salt-call", "supported-versions", "code-checksum", ]: tfp.add(fname) if start_dir and os.access(start_dir, os.R_OK) and os.access(start_dir, os.X_OK): os.chdir(start_dir) tfp.close() shutil.move(tmp_thintar, thintar) return thintar def thin_sum(cachedir, form="sha1"): """ Return the checksum of the current thin tarball """ thintar = gen_thin(cachedir) code_checksum_path = os.path.join(cachedir, "thin", "code-checksum") if os.path.isfile(code_checksum_path): with salt.utils.files.fopen(code_checksum_path, "r") as fh: code_checksum = f"'{fh.read().strip()}'" else: code_checksum = "'0'" return code_checksum, salt.utils.hashutils.get_hash(thintar, form) def gen_min( cachedir, extra_mods="", overwrite=False, so_mods="", ): """ Generate the salt-min tarball and print the location of the tarball Optional additional mods to include (e.g. mako) can be supplied as a comma delimited string. Permits forcing an overwrite of the output file as well. CLI Example: .. code-block:: bash salt-run min.generate salt-run min.generate mako salt-run min.generate mako,wempy 1 salt-run min.generate overwrite=1 """ mindir = os.path.join(cachedir, "min") if not os.path.isdir(mindir): os.makedirs(mindir) mintar = os.path.join(mindir, "min.tgz") minver = os.path.join(mindir, "version") pyminver = os.path.join(mindir, ".min-gen-py-version") salt_call = os.path.join(mindir, "salt-call") with salt.utils.files.fopen(salt_call, "wb") as fp_: fp_.write(_get_salt_call()) if os.path.isfile(mintar): if not overwrite: if os.path.isfile(minver): with salt.utils.files.fopen(minver) as fh_: overwrite = fh_.read() != salt.version.__version__ if overwrite is False and os.path.isfile(pyminver): with salt.utils.files.fopen(pyminver) as fh_: overwrite = fh_.read() != str(sys.version_info[0]) else: overwrite = True if overwrite: try: os.remove(mintar) except OSError: pass else: return mintar tops_py_version_mapping = {} tops = get_tops(extra_mods=extra_mods, so_mods=so_mods) tops_py_version_mapping["3"] = tops tfp = tarfile.open(mintar, "w:gz", dereference=True) try: # cwd may not exist if it was removed but salt was run from it start_dir = os.getcwd() except OSError: start_dir = None tempdir = None # This is the absolute minimum set of files required to run salt-call min_files = ( "salt/__init__.py", "salt/utils", "salt/utils/__init__.py", "salt/utils/atomicfile.py", "salt/utils/validate", "salt/utils/validate/__init__.py", "salt/utils/validate/path.py", "salt/utils/decorators", "salt/utils/decorators/__init__.py", "salt/utils/cache.py", "salt/utils/xdg.py", "salt/utils/odict.py", "salt/utils/minions.py", "salt/utils/dicttrim.py", "salt/utils/sdb.py", "salt/utils/migrations.py", "salt/utils/files.py", "salt/utils/parsers.py", "salt/utils/locales.py", "salt/utils/lazy.py", "salt/utils/s3.py", "salt/utils/dictupdate.py", "salt/utils/verify.py", "salt/utils/args.py", "salt/utils/kinds.py", "salt/utils/xmlutil.py", "salt/utils/debug.py", "salt/utils/jid.py", "salt/utils/openstack", "salt/utils/openstack/__init__.py", "salt/utils/openstack/swift.py", "salt/utils/asynchronous.py", "salt/utils/process.py", "salt/utils/jinja.py", "salt/utils/rsax931.py", "salt/utils/context.py", "salt/utils/minion.py", "salt/utils/error.py", "salt/utils/aws.py", "salt/utils/timed_subprocess.py", "salt/utils/zeromq.py", "salt/utils/schedule.py", "salt/utils/url.py", "salt/utils/yamlencoding.py", "salt/utils/network.py", "salt/utils/http.py", "salt/utils/gzip_util.py", "salt/utils/vt.py", "salt/utils/templates.py", "salt/utils/aggregation.py", "salt/utils/yaml.py", "salt/utils/yamldumper.py", "salt/utils/yamlloader.py", "salt/utils/event.py", "salt/utils/state.py", "salt/serializers", "salt/serializers/__init__.py", "salt/serializers/yamlex.py", "salt/template.py", "salt/_compat.py", "salt/loader.py", "salt/client", "salt/client/__init__.py", "salt/ext", "salt/ext/__init__.py", "salt/ext/ipaddress.py", "salt/version.py", "salt/syspaths.py", "salt/defaults", "salt/defaults/__init__.py", "salt/defaults/exitcodes.py", "salt/renderers", "salt/renderers/__init__.py", "salt/renderers/jinja.py", "salt/renderers/yaml.py", "salt/modules", "salt/modules/__init__.py", "salt/modules/test.py", "salt/modules/selinux.py", "salt/modules/cmdmod.py", "salt/modules/saltutil.py", "salt/minion.py", "salt/pillar", "salt/pillar/__init__.py", "salt/utils/textformat.py", "salt/log_handlers", "salt/log_handlers/__init__.py", "salt/_logging/__init__.py", "salt/_logging/handlers.py", "salt/_logging/impl.py", "salt/_logging/mixins.py", "salt/cli", "salt/cli/__init__.py", "salt/cli/caller.py", "salt/cli/daemons.py", "salt/cli/salt.py", "salt/cli/call.py", "salt/fileserver", "salt/fileserver/__init__.py", "salt/channel", "salt/channel/__init__.py", "salt/channel/client.py", "salt/transport", # XXX Are the transport imports still needed? "salt/transport/__init__.py", "salt/transport/client.py", "salt/exceptions.py", "salt/grains", "salt/grains/__init__.py", "salt/grains/extra.py", "salt/scripts.py", "salt/state.py", "salt/fileclient.py", "salt/crypt.py", "salt/config.py", "salt/beacons", "salt/beacons/__init__.py", "salt/payload.py", "salt/output", "salt/output/__init__.py", "salt/output/nested.py", ) for py_ver, tops in tops_py_version_mapping.items(): for top in tops: base = os.path.basename(top) top_dirname = os.path.dirname(top) if os.path.isdir(top_dirname): os.chdir(top_dirname) else: # This is likely a compressed python .egg tempdir = tempfile.mkdtemp() egg = zipfile.ZipFile(top_dirname) egg.extractall(tempdir) # nosec top = os.path.join(tempdir, base) os.chdir(tempdir) if not os.path.isdir(top): # top is a single file module tfp.add(base, arcname=os.path.join(f"py{py_ver}", base)) continue for root, dirs, files in salt.utils.path.os_walk(base, followlinks=True): for name in files: if name.endswith((".pyc", ".pyo")): continue if ( root.startswith("salt") and os.path.join(root, name) not in min_files ): continue tfp.add( os.path.join(root, name), arcname=os.path.join(f"py{py_ver}", root, name), ) if tempdir is not None: shutil.rmtree(tempdir) tempdir = None os.chdir(mindir) tfp.add("salt-call") with salt.utils.files.fopen(minver, "w+") as fp_: fp_.write(salt.version.__version__) with salt.utils.files.fopen(pyminver, "w+") as fp_: fp_.write(str(sys.version_info[0])) os.chdir(os.path.dirname(minver)) tfp.add("version") tfp.add(".min-gen-py-version") if start_dir: os.chdir(start_dir) tfp.close() return mintar def min_sum(cachedir, form="sha1"): """ Return the checksum of the current thin tarball """ mintar = gen_min(cachedir) return salt.utils.hashutils.get_hash(mintar, form)