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
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/debuild_pkgbuild.py
""" Debian Package builder system .. versionadded:: 2015.8.0 This system allows for all of the components to build debs safely in chrooted environments. This also provides a function to generate debian repositories This module implements the pkgbuild interface """ import errno import logging import os import re import shutil import tempfile import time import traceback import urllib.parse import salt.utils.files import salt.utils.path import salt.utils.stringutils import salt.utils.vt from salt.exceptions import CommandExecutionError, SaltInvocationError HAS_LIBS = False SIGN_PROMPT_RE = re.compile(r"Enter passphrase: ", re.M) REPREPRO_SIGN_PROMPT_RE = re.compile(r"Passphrase: ", re.M) try: import gnupg # pylint: disable=unused-import import salt.modules.gpg HAS_LIBS = True except ImportError: pass log = logging.getLogger(__name__) __virtualname__ = "pkgbuild" def __virtual__(): """ Confirm this module is on a Debian-based system, and has required utilities """ if __grains__.get("os_family", False) in ("Kali", "Debian"): missing_util = False utils_reqd = ["gpg", "debuild", "pbuilder", "reprepro"] for named_util in utils_reqd: if not salt.utils.path.which(named_util): missing_util = True break if HAS_LIBS and not missing_util: return __virtualname__ else: return ( False, "The debbuild module could not be loaded: requires python-gnupg, gpg," " debuild, pbuilder and reprepro utilities to be installed", ) else: return (False, "The debbuild module could not be loaded: unsupported OS family") def _check_repo_sign_utils_support(name): """ Check for specified command name in search path """ if salt.utils.path.which(name): return True else: raise CommandExecutionError( "utility '{}' needs to be installed or made available in search path".format( name ) ) def _check_repo_gpg_phrase_utils(): """ Check for /usr/lib/gnupg2/gpg-preset-passphrase is installed """ util_name = "/usr/lib/gnupg2/gpg-preset-passphrase" if __salt__["file.file_exists"](util_name): return True else: raise CommandExecutionError(f"utility '{util_name}' needs to be installed") def _get_build_env(env): """ Get build environment overrides dictionary to use in build process """ env_override = "" if env is None: return env_override if not isinstance(env, dict): raise SaltInvocationError("'env' must be a Python dictionary") for key, value in env.items(): env_override += f"{key}={value}\n" env_override += f"export {key}\n" return env_override def _get_repo_options_env(env): """ Get repo environment overrides dictionary to use in repo options process env A dictionary of variables to define the repository options Example: .. code-block:: yaml - env: - OPTIONS : 'ask-passphrase' .. warning:: The above illustrates a common PyYAML pitfall, that **yes**, **no**, **on**, **off**, **true**, and **false** are all loaded as boolean ``True`` and ``False`` values, and must be enclosed in quotes to be used as strings. More info on this (and other) PyYAML idiosyncrasies can be found :ref:`here <yaml-idiosyncrasies>`. """ env_options = "" if env is None: return env_options if not isinstance(env, dict): raise SaltInvocationError("'env' must be a Python dictionary") for key, value in env.items(): if key == "OPTIONS": env_options += f"{value}\n" return env_options def _get_repo_dists_env(env): """ Get repo environment overrides dictionary to use in repo distributions process env A dictionary of variables to define the repository distributions Example: .. code-block:: yaml - env: - ORIGIN : 'jessie' - LABEL : 'salt debian' - SUITE : 'main' - VERSION : '8.1' - CODENAME : 'jessie' - ARCHS : 'amd64 i386 source' - COMPONENTS : 'main' - DESCRIPTION : 'SaltStack Debian package repo' .. warning:: The above illustrates a common PyYAML pitfall, that **yes**, **no**, **on**, **off**, **true**, and **false** are all loaded as boolean ``True`` and ``False`` values, and must be enclosed in quotes to be used as strings. More info on this (and other) PyYAML idiosyncrasies can be found :ref:`here <yaml-idiosyncrasies>`. """ # env key with tuple of control information for handling input env dictionary # 0 | M - Mandatory, O - Optional, I - Ignore # 1 | 'text string for repo field' # 2 | 'default value' dflts_dict = { "OPTIONS": ("I", "", "processed by _get_repo_options_env"), "ORIGIN": ("O", "Origin", "SaltStack"), "LABEL": ("O", "Label", "salt_debian"), "SUITE": ("O", "Suite", "stable"), "VERSION": ("O", "Version", "9.0"), "CODENAME": ("M", "Codename", "stretch"), "ARCHS": ("M", "Architectures", "i386 amd64 source"), "COMPONENTS": ("M", "Components", "main"), "DESCRIPTION": ("O", "Description", "SaltStack debian package repo"), } env_dists = "" codename = "" dflts_keys = list(dflts_dict.keys()) if env is None: for key, value in dflts_dict.items(): if dflts_dict[key][0] == "M": env_dists += f"{dflts_dict[key][1]}: {dflts_dict[key][2]}\n" if key == "CODENAME": codename = dflts_dict[key][2] return (codename, env_dists) if not isinstance(env, dict): raise SaltInvocationError("'env' must be a Python dictionary") env_man_seen = [] for key, value in env.items(): if key in dflts_keys: if dflts_dict[key][0] == "M": env_man_seen.append(key) if key == "CODENAME": codename = value if dflts_dict[key][0] != "I": env_dists += f"{dflts_dict[key][1]}: {value}\n" else: env_dists += f"{key}: {value}\n" # ensure mandatories are included env_keys = list(env.keys()) for key in env_keys: if key in dflts_keys and dflts_dict[key][0] == "M" and key not in env_man_seen: env_dists += f"{dflts_dict[key][1]}: {dflts_dict[key][2]}\n" if key == "CODENAME": codename = value return (codename, env_dists) def _create_pbuilders(env, runas="root"): """ Create the .pbuilder family of files in user's home directory env A list or dictionary of environment variables to be set prior to execution. Example: .. code-block:: yaml - env: - DEB_BUILD_OPTIONS: 'nocheck' .. warning:: The above illustrates a common PyYAML pitfall, that **yes**, **no**, **on**, **off**, **true**, and **false** are all loaded as boolean ``True`` and ``False`` values, and must be enclosed in quotes to be used as strings. More info on this (and other) PyYAML idiosyncrasies can be found :ref:`here <yaml-idiosyncrasies>`. runas : root .. versionadded:: 2019.2.1 User to create the files and directories .. note:: Ensure the user has correct permissions to any files and directories which are to be utilized. """ home = os.path.expanduser(f"~{runas}") pbuilderrc = os.path.join(home, ".pbuilderrc") if not os.path.isfile(pbuilderrc): raise SaltInvocationError("pbuilderrc environment is incorrectly setup") env_overrides = _get_build_env(env) if env_overrides and not env_overrides.isspace(): with salt.utils.files.fopen(pbuilderrc, "a") as fow: fow.write(salt.utils.stringutils.to_str(env_overrides)) cmd = "chown {0}:{0} {1}".format(runas, pbuilderrc) retrc = __salt__["cmd.retcode"](cmd, runas="root") if retrc != 0: raise SaltInvocationError( "Create pbuilderrc in home directory failed with return error '{}', " "check logs for further details".format(retrc) ) def _mk_tree(): """ Create the debian build area """ basedir = tempfile.mkdtemp() return basedir def _get_spec(tree_base, spec, saltenv="base"): """ Get the spec file (tarball of the debian sub-dir to use) and place it in build area """ spec_tgt = os.path.basename(spec) dest = os.path.join(tree_base, spec_tgt) return __salt__["cp.get_url"](spec, dest, saltenv=saltenv) def _get_src(tree_base, source, saltenv="base"): """ Get the named sources and place them into the tree_base """ parsed = urllib.parse.urlparse(source) sbase = os.path.basename(source) dest = os.path.join(tree_base, sbase) if parsed.scheme: __salt__["cp.get_url"](source, dest, saltenv=saltenv) else: shutil.copy(source, dest) def make_src_pkg(dest_dir, spec, sources, env=None, saltenv="base", runas="root"): """ Create a platform specific source package from the given platform spec/control file and sources CLI Example: **Debian** .. code-block:: bash salt '*' pkgbuild.make_src_pkg /var/www/html/ https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/deb/python-libnacl.control.tar.xz https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz This example command should build the libnacl SOURCE package and place it in /var/www/html/ on the minion dest_dir Absolute path for directory to write source package spec Absolute path to spec file or equivalent sources Absolute path to source files to build source package from env : None A list or dictionary of environment variables to be set prior to execution. Example: .. code-block:: yaml - env: - DEB_BUILD_OPTIONS: 'nocheck' .. warning:: The above illustrates a common PyYAML pitfall, that **yes**, **no**, **on**, **off**, **true**, and **false** are all loaded as boolean ``True`` and ``False`` values, and must be enclosed in quotes to be used as strings. More info on this (and other) PyYAML idiosyncrasies can be found :ref:`here <yaml-idiosyncrasies>`. saltenv: base Salt environment variables runas : root .. versionadded:: 2019.2.1 User to create the files and directories .. note:: Ensure the user has correct permissions to any files and directories which are to be utilized. """ _create_pbuilders(env, runas) tree_base = _mk_tree() ret = [] if not os.path.isdir(dest_dir): os.makedirs(dest_dir) # ensure directories are writable root_user = "root" retrc = 0 cmd = "chown {0}:{0} {1}".format(runas, tree_base) retrc = __salt__["cmd.retcode"](cmd, runas="root") if retrc != 0: raise SaltInvocationError( "make_src_pkg ensuring tree_base '{}' ownership failed with return error" " '{}', check logs for further details".format(tree_base, retrc) ) cmd = "chown {0}:{0} {1}".format(runas, dest_dir) retrc = __salt__["cmd.retcode"](cmd, runas=root_user) if retrc != 0: raise SaltInvocationError( "make_src_pkg ensuring dest_dir '{}' ownership failed with return error" " '{}', check logs for further details".format(dest_dir, retrc) ) spec_pathfile = _get_spec(tree_base, spec, saltenv) # build salt equivalents from scratch if isinstance(sources, str): sources = sources.split(",") for src in sources: _get_src(tree_base, src, saltenv) # .dsc then assumes sources already build if spec_pathfile.endswith(".dsc"): for efile in os.listdir(tree_base): full = os.path.join(tree_base, efile) trgt = os.path.join(dest_dir, efile) shutil.copy(full, trgt) ret.append(trgt) return ret # obtain name of 'sdist' generated tarball, extract the version # and manipulate the name for debian use (convert minix and add '+ds') salttarball = None for afile in os.listdir(tree_base): if afile.startswith("salt-") and afile.endswith(".tar.gz"): salttarball = afile break else: return ret frontname = salttarball.split(".tar.gz") salttar_name = frontname[0] k = salttar_name.rfind("-") debname = salttar_name[:k] + "_" + salttar_name[k + 1 :] debname += "+ds" debname_orig = debname + ".orig.tar.gz" abspath_debname = os.path.join(tree_base, debname) cmd = f"tar -xvzf {salttarball}" retrc = __salt__["cmd.retcode"](cmd, cwd=tree_base, runas=root_user) cmd = f"mv {salttar_name} {debname}" retrc |= __salt__["cmd.retcode"](cmd, cwd=tree_base, runas=root_user) cmd = f"tar -cvzf {os.path.join(tree_base, debname_orig)} {debname}" retrc |= __salt__["cmd.retcode"](cmd, cwd=tree_base, runas=root_user) cmd = f"rm -f {salttarball}" retrc |= __salt__["cmd.retcode"](cmd, cwd=tree_base, runas=root_user, env=env) cmd = f"cp {spec_pathfile} {abspath_debname}" retrc |= __salt__["cmd.retcode"](cmd, cwd=abspath_debname, runas=root_user) cmd = f"tar -xvJf {spec_pathfile}" retrc |= __salt__["cmd.retcode"](cmd, cwd=abspath_debname, runas=root_user, env=env) cmd = f"rm -f {os.path.basename(spec_pathfile)}" retrc |= __salt__["cmd.retcode"](cmd, cwd=abspath_debname, runas=root_user) cmd = "debuild -S -uc -us -sa" retrc |= __salt__["cmd.retcode"]( cmd, cwd=abspath_debname, runas=root_user, python_shell=True, env=env ) cmd = f"rm -fR {abspath_debname}" retrc |= __salt__["cmd.retcode"](cmd, runas=root_user) if retrc != 0: raise SaltInvocationError( "Make source package for destination directory {}, spec {}, sources {}," " failed with return error {}, check logs for further details".format( dest_dir, spec, sources, retrc ) ) for dfile in os.listdir(tree_base): if not dfile.endswith(".build"): full = os.path.join(tree_base, dfile) trgt = os.path.join(dest_dir, dfile) shutil.copy(full, trgt) ret.append(trgt) return ret def build( runas, tgt, dest_dir, spec, sources, deps, env, template, saltenv="base", log_dir="/var/log/salt/pkgbuild", ): # pylint: disable=unused-argument """ Given the package destination directory, the tarball containing debian files (e.g. control) and package sources, use pbuilder to safely build the platform package CLI Example: **Debian** .. code-block:: bash salt '*' pkgbuild.make_src_pkg deb-8-x86_64 /var/www/html https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/deb/python-libnacl.control https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz This example command should build the libnacl package for Debian using pbuilder and place it in /var/www/html/ on the minion """ ret = {} retrc = 0 try: os.makedirs(dest_dir) except OSError as exc: if exc.errno != errno.EEXIST: raise dsc_dir = tempfile.mkdtemp() try: dscs = make_src_pkg(dsc_dir, spec, sources, env, saltenv, runas) except Exception as exc: # pylint: disable=broad-except shutil.rmtree(dsc_dir) log.error("Failed to make src package, exception '%s'", exc) return ret root_user = "root" # ensure pbuilder setup from runas if other than root if runas != root_user: user_home = os.path.expanduser(f"~{runas}") root_home = os.path.expanduser("~root") cmd = f"cp {user_home}/.pbuilderrc {root_home}/" retrc = __salt__["cmd.retcode"]( cmd, runas=root_user, python_shell=True, env=env ) cmd = f"cp -R {user_home}/.pbuilder-hooks {root_home}/" retrc = __salt__["cmd.retcode"]( cmd, runas=root_user, python_shell=True, env=env ) if retrc != 0: raise SaltInvocationError( "build copy pbuilder files from '{}' to '{}' returned error '{}', " "check logs for further details".format(user_home, root_home, retrc) ) cmd = "/usr/sbin/pbuilder --create" retrc = __salt__["cmd.retcode"](cmd, runas=root_user, python_shell=True, env=env) if retrc != 0: raise SaltInvocationError( "pbuilder create failed with return error '{}', " "check logs for further details".format(retrc) ) # use default /var/cache/pbuilder/result results_dir = "/var/cache/pbuilder/result" # ensure clean cmd = f"rm -fR {results_dir}" retrc |= __salt__["cmd.retcode"](cmd, runas=root_user, python_shell=True, env=env) # dscs should only contain salt orig and debian tarballs and dsc file for dsc in dscs: afile = os.path.basename(dsc) os.path.join(dest_dir, afile) if dsc.endswith(".dsc"): dbase = os.path.dirname(dsc) try: cmd = "chown {0}:{0} -R {1}".format(runas, dbase) retrc |= __salt__["cmd.retcode"]( cmd, runas=root_user, python_shell=True, env=env ) cmd = "/usr/sbin/pbuilder update --override-config" retrc |= __salt__["cmd.retcode"]( cmd, runas=root_user, python_shell=True, env=env ) cmd = f'/usr/sbin/pbuilder build --debbuildopts "-sa" {dsc}' retrc |= __salt__["cmd.retcode"]( cmd, runas=root_user, python_shell=True, env=env ) if retrc != 0: raise SaltInvocationError( "pbuilder build or update failed with return error {}, " "check logs for further details".format(retrc) ) # ignore local deps generated package file for bfile in os.listdir(results_dir): if bfile != "Packages": full = os.path.join(results_dir, bfile) bdist = os.path.join(dest_dir, bfile) shutil.copy(full, bdist) ret.setdefault("Packages", []).append(bdist) except Exception as exc: # pylint: disable=broad-except log.error("Error building from '%s', execption '%s'", dsc, exc) # remove any Packages file created for local dependency processing for pkgzfile in os.listdir(dest_dir): if pkgzfile == "Packages": pkgzabsfile = os.path.join(dest_dir, pkgzfile) os.remove(pkgzabsfile) cmd = "chown {0}:{0} -R {1}".format(runas, dest_dir) __salt__["cmd.retcode"](cmd, runas=root_user, python_shell=True, env=env) shutil.rmtree(dsc_dir) return ret def make_repo( repodir, keyid=None, env=None, use_passphrase=False, gnupghome="/etc/salt/gpgkeys", runas="root", timeout=15.0, ): """ Make a package repository and optionally sign it and packages present Given the repodir (directory to create repository in), create a Debian repository and optionally sign it and packages present. This state is best used with onchanges linked to your package building states. repodir The directory to find packages that will be in the repository. keyid .. versionchanged:: 2016.3.0 Optional Key ID to use in signing packages and repository. This consists of the last 8 hex digits of the GPG key ID. Utilizes Public and Private keys associated with keyid which have been loaded into the minion's Pillar data. Leverages gpg-agent and gpg-preset-passphrase for caching keys, etc. These pillar values are assumed to be filenames which are present in ``gnupghome``. The pillar keys shown below have to match exactly. For example, contents from a Pillar data file with named Public and Private keys as follows: .. code-block:: yaml gpg_pkg_priv_keyname: gpg_pkg_key.pem gpg_pkg_pub_keyname: gpg_pkg_key.pub env .. versionchanged:: 2016.3.0 A dictionary of environment variables to be utilized in creating the repository. use_passphrase : False .. versionadded:: 2016.3.0 Use a passphrase with the signing key presented in ``keyid``. Passphrase is received from Pillar data which could be passed on the command line with ``pillar`` parameter. For example: .. code-block:: bash pillar='{ "gpg_passphrase" : "my_passphrase" }' gnupghome : /etc/salt/gpgkeys .. versionadded:: 2016.3.0 Location where GPG related files are stored, used with ``keyid``. runas : root .. versionadded:: 2016.3.0 User to create the repository as, and optionally sign packages. .. note:: Ensure the user has correct permissions to any files and directories which are to be utilized. timeout : 15.0 .. versionadded:: 2016.3.4 Timeout in seconds to wait for the prompt for inputting the passphrase. CLI Example: .. code-block:: bash salt '*' pkgbuild.make_repo /var/www/html """ res = {"retcode": 1, "stdout": "", "stderr": "initialization value"} retrc = 0 if gnupghome and env is None: env = {} env["GNUPGHOME"] = gnupghome repoconf = os.path.join(repodir, "conf") if not os.path.isdir(repoconf): os.makedirs(repoconf) codename, repocfg_dists = _get_repo_dists_env(env) repoconfdist = os.path.join(repoconf, "distributions") with salt.utils.files.fopen(repoconfdist, "w") as fow: fow.write(salt.utils.stringutils.to_str(repocfg_dists)) repocfg_opts = _get_repo_options_env(env) repoconfopts = os.path.join(repoconf, "options") with salt.utils.files.fopen(repoconfopts, "w") as fow: fow.write(salt.utils.stringutils.to_str(repocfg_opts)) cmd = "chown {0}:{0} -R {1}".format(runas, repoconf) retrc = __salt__["cmd.retcode"](cmd, runas="root") if retrc != 0: raise SaltInvocationError( "failed to ensure rights to repoconf directory, error {}, " "check logs for further details".format(retrc) ) local_keygrip_to_use = None local_key_fingerprint = None local_keyid = None phrase = "" # preset passphase and interaction with gpg-agent gpg_info_file = f"{gnupghome}/gpg-agent-info-salt" gpg_tty_info_file = f"{gnupghome}/gpg-tty-info-salt" # if using older than gnupg 2.1, then env file exists older_gnupg = __salt__["file.file_exists"](gpg_info_file) if keyid is not None: with salt.utils.files.fopen(repoconfdist, "a") as fow: fow.write(salt.utils.stringutils.to_str(f"SignWith: {keyid}\n")) # import_keys pkg_pub_key_file = "{}/{}".format( gnupghome, __salt__["pillar.get"]("gpg_pkg_pub_keyname", None) ) pkg_priv_key_file = "{}/{}".format( gnupghome, __salt__["pillar.get"]("gpg_pkg_priv_keyname", None) ) if pkg_pub_key_file is None or pkg_priv_key_file is None: raise SaltInvocationError( "Pillar data should contain Public and Private keys associated with" " 'keyid'" ) try: __salt__["gpg.import_key"]( user=runas, filename=pkg_pub_key_file, gnupghome=gnupghome ) __salt__["gpg.import_key"]( user=runas, filename=pkg_priv_key_file, gnupghome=gnupghome ) except SaltInvocationError: raise SaltInvocationError( "Public and Private key files associated with Pillar data and 'keyid' " "{} could not be found".format(keyid) ) # gpg keys should have been loaded as part of setup # retrieve specified key, obtain fingerprint and preset passphrase local_keys = __salt__["gpg.list_keys"](user=runas, gnupghome=gnupghome) for gpg_key in local_keys: if keyid == gpg_key["keyid"][8:]: local_keygrip_to_use = gpg_key["fingerprint"] local_key_fingerprint = gpg_key["fingerprint"] local_keyid = gpg_key["keyid"] break if not older_gnupg: try: _check_repo_sign_utils_support("gpg2") cmd = "gpg2 --with-keygrip --list-secret-keys" except CommandExecutionError: # later gpg versions have dispensed with gpg2 - Ubuntu 18.04 cmd = "gpg --with-keygrip --list-secret-keys" local_keys2_keygrip = __salt__["cmd.run"](cmd, runas=runas, env=env) local_keys2 = iter(local_keys2_keygrip.splitlines()) try: for line in local_keys2: if line.startswith("sec"): line_fingerprint = next(local_keys2).lstrip().rstrip() if local_key_fingerprint == line_fingerprint: lkeygrip = next(local_keys2).split("=") local_keygrip_to_use = lkeygrip[1].lstrip().rstrip() break except StopIteration: raise SaltInvocationError( "unable to find keygrip associated with fingerprint '{}' for keyid" " '{}'".format(local_key_fingerprint, local_keyid) ) if local_keyid is None: raise SaltInvocationError( "The key ID '{}' was not found in GnuPG keyring at '{}'".format( keyid, gnupghome ) ) _check_repo_sign_utils_support("debsign") if older_gnupg: with salt.utils.files.fopen(gpg_info_file, "r") as fow: gpg_raw_info = fow.readlines() for gpg_info_line in gpg_raw_info: gpg_info_line = salt.utils.stringutils.to_unicode(gpg_info_line) gpg_info = gpg_info_line.split("=") env[gpg_info[0]] = gpg_info[1] break else: with salt.utils.files.fopen(gpg_tty_info_file, "r") as fow: gpg_raw_info = fow.readlines() for gpg_tty_info_line in gpg_raw_info: gpg_tty_info_line = salt.utils.stringutils.to_unicode(gpg_tty_info_line) gpg_tty_info = gpg_tty_info_line.split("=") env[gpg_tty_info[0]] = gpg_tty_info[1] break if use_passphrase: _check_repo_gpg_phrase_utils() phrase = __salt__["pillar.get"]("gpg_passphrase") cmd = ( "/usr/lib/gnupg2/gpg-preset-passphrase --verbose --preset --passphrase" ' "{}" {}'.format(phrase, local_keygrip_to_use) ) retrc |= __salt__["cmd.retcode"](cmd, runas=runas, env=env) for debfile in os.listdir(repodir): abs_file = os.path.join(repodir, debfile) if debfile.endswith(".changes"): os.remove(abs_file) if debfile.endswith(".dsc"): # sign_it_here if older_gnupg: if local_keyid is not None: cmd = f"debsign --re-sign -k {keyid} {abs_file}" retrc |= __salt__["cmd.retcode"]( cmd, runas=runas, cwd=repodir, use_vt=True, env=env ) cmd = ( "reprepro --ignore=wrongdistribution --component=main -Vb ." " includedsc {} {}".format(codename, abs_file) ) retrc |= __salt__["cmd.retcode"]( cmd, runas=runas, cwd=repodir, use_vt=True, env=env ) else: # interval of 0.125 is really too fast on some systems interval = 0.5 if local_keyid is not None: number_retries = timeout / interval times_looped = 0 error_msg = f"Failed to debsign file {abs_file}" if ( __grains__["os"] in ["Ubuntu"] and __grains__["osmajorrelease"] < 18 ) or ( __grains__["os"] in ["Debian"] and __grains__["osmajorrelease"] <= 8 ): cmd = f"debsign --re-sign -k {keyid} {abs_file}" try: proc = salt.utils.vt.Terminal( cmd, env=env, shell=True, stream_stdout=True, stream_stderr=True, ) while proc.has_unread_data: stdout, _ = proc.recv() if stdout and SIGN_PROMPT_RE.search(stdout): # have the prompt for inputting the passphrase proc.sendline(phrase) else: times_looped += 1 if times_looped > number_retries: raise SaltInvocationError( "Attempting to sign file {} failed, timed out" " after {} seconds".format( abs_file, int(times_looped * interval) ) ) time.sleep(interval) proc_exitstatus = proc.exitstatus if proc_exitstatus != 0: raise SaltInvocationError( "Signing file {} failed with proc.status {}".format( abs_file, proc_exitstatus ) ) except salt.utils.vt.TerminalException as err: trace = traceback.format_exc() log.error(error_msg, err, trace) res = {"retcode": 1, "stdout": "", "stderr": trace} finally: proc.close(terminate=True, kill=True) else: cmd = "debsign --re-sign -k {} {}".format( local_key_fingerprint, abs_file ) retrc |= __salt__["cmd.retcode"]( cmd, runas=runas, cwd=repodir, use_vt=True, env=env ) number_retries = timeout / interval times_looped = 0 error_msg = f"Failed to reprepro includedsc file {abs_file}" cmd = ( "reprepro --ignore=wrongdistribution --component=main -Vb ." " includedsc {} {}".format(codename, abs_file) ) if ( __grains__["os"] in ["Ubuntu"] and __grains__["osmajorrelease"] < 18 ) or ( __grains__["os"] in ["Debian"] and __grains__["osmajorrelease"] <= 8 ): try: proc = salt.utils.vt.Terminal( cmd, env=env, shell=True, cwd=repodir, stream_stdout=True, stream_stderr=True, ) while proc.has_unread_data: stdout, _ = proc.recv() if stdout and REPREPRO_SIGN_PROMPT_RE.search(stdout): # have the prompt for inputting the passphrase proc.sendline(phrase) else: times_looped += 1 if times_looped > number_retries: raise SaltInvocationError( "Attempting to reprepro includedsc for file {}" " failed, timed out after {} loops".format( abs_file, times_looped ) ) time.sleep(interval) proc_exitstatus = proc.exitstatus if proc_exitstatus != 0: raise SaltInvocationError( "Reprepro includedsc for codename {} and file {} failed" " with proc.status {}".format( codename, abs_file, proc_exitstatus ) ) except salt.utils.vt.TerminalException as err: trace = traceback.format_exc() log.error(error_msg, err, trace) res = {"retcode": 1, "stdout": "", "stderr": trace} finally: proc.close(terminate=True, kill=True) else: retrc |= __salt__["cmd.retcode"]( cmd, runas=runas, cwd=repodir, use_vt=True, env=env ) if retrc != 0: raise SaltInvocationError( "Making a repo encountered errors, return error {}, check logs for" " further details".format(retrc) ) if debfile.endswith(".deb"): cmd = ( "reprepro --ignore=wrongdistribution --component=main -Vb . includedeb" " {} {}".format(codename, abs_file) ) res = __salt__["cmd.run_all"]( cmd, runas=runas, cwd=repodir, use_vt=True, env=env ) return res