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/modules/inspectlib
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/inspectlib/collector.py
# # Copyright 2015 SUSE LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import os import subprocess import sys import salt.utils.files import salt.utils.fsutils import salt.utils.path import salt.utils.stringutils from salt.exceptions import CommandExecutionError from salt.modules.inspectlib import EnvLoader, kiwiproc from salt.modules.inspectlib.entities import ( AllowedDir, IgnoredDir, Package, PackageCfgFile, PayloadFile, ) from salt.modules.inspectlib.exceptions import InspectorSnapshotException try: import kiwi except ImportError: kiwi = None log = logging.getLogger(__name__) class Inspector(EnvLoader): DEFAULT_MINION_CONFIG_PATH = "/etc/salt/minion" MODE = ["configuration", "payload", "all"] IGNORE_MOUNTS = ["proc", "sysfs", "devtmpfs", "tmpfs", "fuse.gvfs-fuse-daemon"] IGNORE_FS_TYPES = ["autofs", "cifs", "nfs", "nfs4"] IGNORE_PATHS = [ "/tmp", "/var/tmp", "/lost+found", "/var/run", "/var/lib/rpm", "/.snapshots", "/.zfs", "/etc/ssh", "/root", "/home", ] def __init__(self, cachedir=None, piddir=None, pidfilename=None): EnvLoader.__init__( self, cachedir=cachedir, piddir=piddir, pidfilename=pidfilename ) def create_snapshot(self): """ Open new snapshot. :return: """ self.db.open(new=True) return self def reuse_snapshot(self): """ Open an existing, latest snapshot. :return: """ self.db.open() return self def _syscall(self, command, input=None, env=None, *params): """ Call an external system command. """ return subprocess.Popen( [command] + list(params), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, env=env or os.environ, ).communicate(input=input) def _get_cfg_pkgs(self): """ Package scanner switcher between the platforms. :return: """ if self.grains_core.os_data().get("os_family") == "Debian": return self.__get_cfg_pkgs_dpkg() elif self.grains_core.os_data().get("os_family") in ["Suse", "redhat"]: return self.__get_cfg_pkgs_rpm() else: return dict() def __get_cfg_pkgs_dpkg(self): """ Get packages with configuration files on Dpkg systems. :return: """ # Get list of all available packages data = dict() for pkg_name in salt.utils.stringutils.to_str( self._syscall("dpkg-query", None, None, "-Wf", "${binary:Package}\\n")[0] ).split(os.linesep): pkg_name = pkg_name.strip() if not pkg_name: continue data[pkg_name] = list() for pkg_cfg_item in salt.utils.stringutils.to_str( self._syscall( "dpkg-query", None, None, "-Wf", "${Conffiles}\\n", pkg_name )[0] ).split(os.linesep): pkg_cfg_item = pkg_cfg_item.strip() if not pkg_cfg_item: continue pkg_cfg_file, pkg_cfg_sum = pkg_cfg_item.strip().split(" ", 1) data[pkg_name].append(pkg_cfg_file) # Dpkg meta data is unreliable. Check every package # and remove which actually does not have config files. if not data[pkg_name]: data.pop(pkg_name) return data def __get_cfg_pkgs_rpm(self): """ Get packages with configuration files on RPM systems. """ out, err = self._syscall( "rpm", None, None, "-qa", "--configfiles", "--queryformat", "%{name}-%{version}-%{release}\\n", ) data = dict() pkg_name = None pkg_configs = [] out = salt.utils.stringutils.to_str(out) for line in out.split(os.linesep): line = line.strip() if not line: continue if not line.startswith("/"): if pkg_name and pkg_configs: data[pkg_name] = pkg_configs pkg_name = line pkg_configs = [] else: pkg_configs.append(line) if pkg_name and pkg_configs: data[pkg_name] = pkg_configs return data def _get_changed_cfg_pkgs(self, data): """ Filter out unchanged packages on the Debian or RPM systems. :param data: Structure {package-name -> [ file .. file1 ]} :return: Same structure as data, except only files that were changed. """ f_data = dict() for pkg_name, pkg_files in data.items(): cfgs = list() cfg_data = list() if self.grains_core.os_data().get("os_family") == "Debian": cfg_data = salt.utils.stringutils.to_str( self._syscall("dpkg", None, None, "--verify", pkg_name)[0] ).split(os.linesep) elif self.grains_core.os_data().get("os_family") in ["Suse", "redhat"]: cfg_data = salt.utils.stringutils.to_str( self._syscall( "rpm", None, None, "-V", "--nodeps", "--nodigest", "--nosignature", "--nomtime", "--nolinkto", pkg_name, )[0] ).split(os.linesep) for line in cfg_data: line = line.strip() if not line or line.find(" c ") < 0 or line.split(" ")[0].find("5") < 0: continue cfg_file = line.split(" ")[-1] if cfg_file in pkg_files: cfgs.append(cfg_file) if cfgs: f_data[pkg_name] = cfgs return f_data def _save_cfg_packages(self, data): """ Save configuration packages. (NG) :param data: :return: """ pkg_id = 0 pkg_cfg_id = 0 for pkg_name, pkg_configs in data.items(): pkg = Package() pkg.id = pkg_id pkg.name = pkg_name self.db.store(pkg) for pkg_config in pkg_configs: cfg = PackageCfgFile() cfg.id = pkg_cfg_id cfg.pkgid = pkg_id cfg.path = pkg_config self.db.store(cfg) pkg_cfg_id += 1 pkg_id += 1 def _save_payload(self, files, directories, links): """ Save payload (unmanaged files) :param files: :param directories: :param links: :return: """ idx = 0 for p_type, p_list in ( ("f", files), ("d", directories), ("l", links), ): for p_obj in p_list: stats = os.stat(p_obj) payload = PayloadFile() payload.id = idx payload.path = p_obj payload.p_type = p_type payload.mode = stats.st_mode payload.uid = stats.st_uid payload.gid = stats.st_gid payload.p_size = stats.st_size payload.atime = stats.st_atime payload.mtime = stats.st_mtime payload.ctime = stats.st_ctime idx += 1 self.db.store(payload) def _get_managed_files(self): """ Build a in-memory data of all managed files. """ if self.grains_core.os_data().get("os_family") == "Debian": return self.__get_managed_files_dpkg() elif self.grains_core.os_data().get("os_family") in ["Suse", "redhat"]: return self.__get_managed_files_rpm() return list(), list(), list() def __get_managed_files_dpkg(self): """ Get a list of all system files, belonging to the Debian package manager. """ dirs = set() links = set() files = set() for pkg_name in salt.utils.stringutils.to_str( self._syscall("dpkg-query", None, None, "-Wf", "${binary:Package}\\n")[0] ).split(os.linesep): pkg_name = pkg_name.strip() if not pkg_name: continue for resource in salt.utils.stringutils.to_str( self._syscall("dpkg", None, None, "-L", pkg_name)[0] ).split(os.linesep): resource = resource.strip() if not resource or resource in ["/", "./", "."]: continue if os.path.isdir(resource): dirs.add(resource) elif os.path.islink(resource): links.add(resource) elif os.path.isfile(resource): files.add(resource) return sorted(files), sorted(dirs), sorted(links) def __get_managed_files_rpm(self): """ Get a list of all system files, belonging to the RedHat package manager. """ dirs = set() links = set() files = set() for line in salt.utils.stringutils.to_str( self._syscall("rpm", None, None, "-qlav")[0] ).split(os.linesep): line = line.strip() if not line: continue line = line.replace("\t", " ").split(" ") if line[0][0] == "d": dirs.add(line[-1]) elif line[0][0] == "l": links.add(line[-1]) elif line[0][0] == "-": files.add(line[-1]) return sorted(files), sorted(dirs), sorted(links) def _get_all_files(self, path, *exclude): """ Walk implementation. Version in python 2.x and 3.x works differently. """ files = list() dirs = list() links = list() if os.access(path, os.R_OK): for obj in os.listdir(path): obj = os.path.join(path, obj) valid = True for ex_obj in exclude: if obj.startswith(str(ex_obj)): valid = False continue if not valid or not os.path.exists(obj) or not os.access(obj, os.R_OK): continue if salt.utils.path.islink(obj): links.append(obj) elif os.path.isdir(obj): dirs.append(obj) f_obj, d_obj, l_obj = self._get_all_files(obj, *exclude) files.extend(f_obj) dirs.extend(d_obj) links.extend(l_obj) elif os.path.isfile(obj): files.append(obj) return sorted(files), sorted(dirs), sorted(links) def _get_unmanaged_files(self, managed, system_all): """ Get the intersection between all files and managed files. """ m_files, m_dirs, m_links = managed s_files, s_dirs, s_links = system_all return ( sorted(list(set(s_files).difference(m_files))), sorted(list(set(s_dirs).difference(m_dirs))), sorted(list(set(s_links).difference(m_links))), ) def _scan_payload(self): """ Scan the system. """ # Get ignored points allowed = list() for allowed_dir in self.db.get(AllowedDir): if os.path.exists(allowed_dir.path): allowed.append(allowed_dir.path) ignored = list() if not allowed: for ignored_dir in self.db.get(IgnoredDir): if os.path.exists(ignored_dir.path): ignored.append(ignored_dir.path) all_files = list() all_dirs = list() all_links = list() for entry_path in [pth for pth in (allowed or os.listdir("/")) if pth]: if entry_path[0] != "/": entry_path = f"/{entry_path}" if entry_path in ignored or os.path.islink(entry_path): continue e_files, e_dirs, e_links = self._get_all_files(entry_path, *ignored) all_files.extend(e_files) all_dirs.extend(e_dirs) all_links.extend(e_links) return self._get_unmanaged_files( self._get_managed_files(), ( all_files, all_dirs, all_links, ), ) def _prepare_full_scan(self, **kwargs): """ Prepare full system scan by setting up the database etc. """ self.db.open(new=True) # Add ignored filesystems ignored_fs = set() ignored_fs |= set(self.IGNORE_PATHS) mounts = salt.utils.fsutils._get_mounts() for device, data in mounts.items(): if device in self.IGNORE_MOUNTS: for mpt in data: ignored_fs.add(mpt["mount_point"]) continue for mpt in data: if mpt["type"] in self.IGNORE_FS_TYPES: ignored_fs.add(mpt["mount_point"]) # Remove leafs of ignored filesystems ignored_all = list() for entry in sorted(list(ignored_fs)): valid = True for e_entry in ignored_all: if entry.startswith(e_entry): valid = False break if valid: ignored_all.append(entry) # Save to the database for further scan for ignored_dir in ignored_all: dir_obj = IgnoredDir() dir_obj.path = ignored_dir self.db.store(dir_obj) # Add allowed filesystems (overrides all above at full scan) allowed = [elm for elm in kwargs.get("filter", "").split(",") if elm] for allowed_dir in allowed: dir_obj = AllowedDir() dir_obj.path = allowed_dir self.db.store(dir_obj) return ignored_all def _init_env(self): """ Initialize some Salt environment. """ from salt.config import minion_config from salt.grains import core as g_core g_core.__opts__ = minion_config(self.DEFAULT_MINION_CONFIG_PATH) self.grains_core = g_core def snapshot(self, mode): """ Take a snapshot of the system. """ self._init_env() self._save_cfg_packages(self._get_changed_cfg_pkgs(self._get_cfg_pkgs())) self._save_payload(*self._scan_payload()) def request_snapshot(self, mode, priority=19, **kwargs): """ Take a snapshot of the system. """ if mode not in self.MODE: raise InspectorSnapshotException(f"Unknown mode: '{mode}'") if is_alive(self.pidfile): raise CommandExecutionError("Inspection already in progress.") self._prepare_full_scan(**kwargs) subprocess.run( [ "nice", f"-{priority}", "python", __file__, os.path.dirname(self.pidfile), os.path.dirname(self.dbfile), mode, ], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) def export(self, description, local=False, path="/tmp", format="qcow2"): """ Export description for Kiwi. :param local: :param path: :return: """ kiwiproc.__salt__ = __salt__ return ( kiwiproc.KiwiExporter(grains=__grains__, format=format) .load(**description) .export("something") ) def build(self, format="qcow2", path="/tmp"): """ Build an image using Kiwi. :param format: :param path: :return: """ if kiwi is None: msg = ( "Unable to build the image due to the missing dependencies: Kiwi module" " is not available." ) log.error(msg) raise CommandExecutionError(msg) raise CommandExecutionError("Build is not yet implemented") def is_alive(pidfile): """ Check if PID is still alive. """ try: with salt.utils.files.fopen(pidfile) as fp_: os.kill(int(fp_.read().strip()), 0) return True except Exception as ex: # pylint: disable=broad-except if os.access(pidfile, os.W_OK) and os.path.isfile(pidfile): os.unlink(pidfile) return False def main(dbfile, pidfile, mode): """ Main analyzer routine. """ Inspector(dbfile, pidfile).reuse_snapshot().snapshot(mode) if __name__ == "__main__": if len(sys.argv) != 4: print("This module is not intended to use directly!", file=sys.stderr) sys.exit(1) pidfile, dbfile, mode = sys.argv[1:] # pylint: disable=unbalanced-tuple-unpacking if is_alive(pidfile): sys.exit(1) # Double-fork stuff try: if os.fork() > 0: sys.exit(0) else: pass except OSError as ex: sys.exit(1) os.setsid() os.umask(0o000) # pylint: disable=blacklisted-function try: pid = os.fork() if pid > 0: with salt.utils.files.fopen( os.path.join(pidfile, EnvLoader.PID_FILE), "w" ) as fp_: fp_.write(f"{pid}\n") sys.exit(0) except OSError as ex: sys.exit(1) main(dbfile, pidfile, mode)