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/imh-python/lib/python3.9/site-packages/rads
Viewing File: /opt/imh-python/lib/python3.9/site-packages/rads/vz.py
"""VZ / HA functions""" import enum import json import shlex import subprocess from pathlib import Path from typing import Optional, TypedDict, Union from collections.abc import Iterable import distro class CT: """Create a CT object representing a VZ Container Args: ctid (str): container ID to collect information from. All vzctl/prlctl actions will interact with this container. Raises: VZError: if vzlist fails to run """ def __init__(self, ctid: str): self.ctid = str(ctid) # List of options for vzlist opts = [ "cpulimit", "cpus", "cpuunits", "ctid", "description", "device", "disabled", "diskinodes", "diskspace", "hostname", "ip", "laverage", "name", "netfilter", "numiptent", "numproc", "onboot", "ostemplate", "physpages", "private", "root", "status", "swappages", "uptime", "uuid", "veid", ] # Build vzlist command command = ["vzlist", "-jo", ",".join(opts), self.ctid] # Execute vzlist command try: result = subprocess.run( command, capture_output=True, encoding='utf-8', check=True ) except subprocess.CalledProcessError as exc: raise VZError(f"{exc.stderr}") from exc except OSError as exc: raise VZError(str(exc)) from exc # Parse vzlist command result data: dict = json.loads(result.stdout)[0] self.cpulimit: int = data.get("cpulimit", 0) self.cpus: int = data.get("cpus", 0) self.cpuunits: int = data.get("cpuunits", 0) self.ctid: Union[int, str] = data.get("ctid", "") self.description: str = data.get("description", "") self.device: str = data.get("device", "") self.disabled: bool = data.get("disabled", False) self.diskinodes: dict = data.get("diskinodes", {}) self.diskspace: dict = data.get("diskspace", {}) self.hostname: str = data.get("hostname", "") self.ip: list[str] = data.get("ip", []) self.laverage: list[float] = data.get("laverage", []) self.name: str = data.get("name", "") self.netfilter: str = data.get("netfilter", "") self.numiptent: dict = data.get("numiptent", {}) self.numproc: dict = data.get("numproc", {}) self.onboot: bool = data.get("onboot", False) self.ostemplate: str = data.get("ostemplate", "") self.physpages: dict = data.get("physpages", {}) self.private: str = data.get("private", "") self.root: str = data.get("root", "") self.status: str = data.get("status", "") self.swappages: dict = data.get("swappages", {}) self.uptime: float = data.get("uptime", 0.0) self.uuid: str = data.get("uuid", "") self.veid: str = data.get("veid", "") def __repr__(self): attrs = ", ".join( f"{key}={repr(getattr(self, key))}" for key in self.__dict__ ) return f"CT({attrs})" def start(self) -> subprocess.CompletedProcess: """Starts the container using 'vzctl start' Returns: subprocess.CompletedProcess: result of the 'vzctl start' command """ command = ["vzctl", "start", self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def stop(self) -> subprocess.CompletedProcess: """Stops the container using 'vzctl stop' Returns: subprocess.CompletedProcess: result of the 'vzctl stop' command """ command = ["vzctl", "stop", self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def restart(self) -> subprocess.CompletedProcess: """Restarts the container using 'vzctl restart' Returns: subprocess.CompletedProcess: result of the 'vzctl restart' command """ command = ["vzctl", "restart", self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def mount(self) -> subprocess.CompletedProcess: """Mounts the container's filesystem using the 'vzctl mount' command Returns: subprocess.CompletedProcess: result of the 'vzctl mount' command """ command = ["vzctl", "mount", self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def umount(self) -> subprocess.CompletedProcess: """Unmounts the container's filesystem using the 'vzctl umount' command Returns: subprocess.CompletedProcess: result of the 'vzctl umount' command """ command = ["vzctl", "umount", self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def destroy(self) -> subprocess.CompletedProcess: """Destroys the container using the 'vzctl destroy' command Returns: subprocess.CompletedProcess: result of the 'vzctl destroy' command """ command = ["vzctl", "destroy", self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def register(self, dst: str) -> subprocess.CompletedProcess: """Registers the container at the specified destination using the 'vzctl register' command Args: dst (str): The destination path where the container is to be registered Returns: subprocess.CompletedProcess: result of the 'vzctl register' command """ command = ["vzctl", "register", dst, self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def unregister(self) -> subprocess.CompletedProcess: """Unregisters the container using the 'vzctl unregister' command Returns: subprocess.CompletedProcess: result of the 'vzctl unregister' command """ command = ["vzctl", "unregister", self.ctid] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def clone(self, name: str) -> subprocess.CompletedProcess: """Clones the container with the specified name using the 'prlctl clone' Args: name (str): The name for the new cloned container Returns: subprocess.CompletedProcess: result of the 'prlctl clone' command """ command = ["prlctl", "clone", self.ctid, "--name", str(name)] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def exec2(self, cmd: str) -> subprocess.CompletedProcess: """Executes the specified command within the container using the 'vzctl exec2' command Args: cmd (str): The command to execute within the container. If args for the command come from user input, you may want to use shlex.join or shlex.quote on them Returns: subprocess.CompletedProcess: result of the 'vzctl exec2' command """ command = ["vzctl", "exec2", self.ctid, *shlex.split(cmd)] return subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) def set(self, **kwargs: str) -> subprocess.CompletedProcess: """Sets various configuration options for the container This method constructs a command to modify the container's configuration using the `vzctl set` command. It accepts keyword arguments where each key-value pair represents an option and its corresponding value to be set. The method appends "--save" to the command to save the changes permanently. After running the command, the method reinitializes the object to reflect the updated configuration. Args: **kwargs: Arbitrary keyword arguments representing configuration options and values. Returns: subprocess.CompletedProcess: The result of the `subprocess.run` call, containing information about the execution of the command """ arg_list = [] for key, value in kwargs.items(): arg_list.extend([f"--{key}", f"{value}"]) arg_list.append("--save") command = ["vzctl", "set", self.ctid, *arg_list] result = subprocess.run( command, capture_output=True, encoding='utf-8', check=False ) # re-initialize attributes self.__init__(self.ctid) # pylint:disable=unnecessary-dunder-call return result def fix_ctid(self, start: bool = True) -> None: """A tool for offline fixing CTID fields of VZ7 containers. Re-registers the container while offline. This method stops the container, unregisters it, renames the container directory from the current CTID to the container name, updates the instance attributes accordingly, and re-registers the container with the new details. Optionally, it can restart the container if the `start` parameter is set to True. Args: start (bool): If True, the container will be restarted after the CTID fix is applied. Defaults to True Returns: None """ self.stop() self.unregister() src = Path(f"/vz/private/{self.ctid}") dst = Path(f"/vz/private/{self.name}") src.rename(dst) self.private = str(dst) self.root = f"/vz/root/{self.name}" self.ctid = self.veid = self.name self.register(self.private) if start: self.start() class ListCmd(enum.Enum): """vz list base commands""" VZLIST = ["/usr/sbin/vzlist", "-H"] PRLCTL = ["/usr/bin/prlctl", "list", "-H"] class VZError(Exception): """Raised for errors with VZ and OpenVZ""" def is_vz() -> bool: """Checks if host is a Virtuozzo node""" return bool("Virtuozzo" in distro.name()) def is_vz7() -> bool: """Check if host is a Virtuozzo 7 node""" return bool(is_vz() and distro.major_version() == "7") def is_vps() -> bool: """Check if host is a Virtuozzo container""" try: with open("/proc/vz/veinfo", encoding="ascii") as handle: ve_data = handle.read().strip() except OSError: return False # if veinfo doesn't exist this can't be a vps if ve_data.count("\n") != 0: return False try: veid = int( ve_data.split()[0] ) # if veinfo contains >1 line, this is a CL or VZ node except ValueError: return True # veinfo contains a UUID return veid != 0 class VeInfoDict(TypedDict): """Values used in the return type of rads.vz.veinfo(). procs is the number of processes in the container; ips is a list of ip addresses""" procs: int ips: list[str] def veinfo() -> dict[str, VeInfoDict]: """Read running containers from /proc/vz/veinfo. VMs are not listed. Returns: dict[str, VeInfoDict]: container IDs mapped to dicts. Each dict contains "procs" (the number of processes in the container) and "ips" (all ip addresses assigned to the container) """ ret = {} # VZ Docs say the format of each line is # CT_ID reserved number_of_processes IP_address [IP_address ...] # where reserved isn't used and will always be 0 with open("/proc/vz/veinfo", encoding="ascii") as handle: for line in handle: ctid, _, procs, *ips = line.split() if ctid == '0': continue ret[ctid] = {'procs': int(procs), 'ips': ips} return ret def _exec(cmd: Iterable) -> subprocess.CompletedProcess[str]: """For executing prlctl or vzlist""" try: ret = subprocess.run( cmd, capture_output=True, encoding="utf-8", check=False ) except FileNotFoundError as exc: raise VZError(exc) from exc if ret.returncode: # nonzero raise VZError(f"Error running {cmd!r}. stderr={ret.stderr!r}") return ret def is_ct_running(ctid: Union[str, int]) -> bool: """Checks if a container is running Args: ctid: container ID to check Returns: True if the container is running on this node, False if it isn't or if some other error occurs """ try: ret = subprocess.run( ["/usr/bin/prlctl", "list", "-H", "-o", "status", str(ctid)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, encoding="utf-8", check=True, ) except FileNotFoundError: pass # retry with vzlist except subprocess.CalledProcessError: return False else: return ret.stdout.split()[0] == "running" try: ret = _exec(["/usr/sbin/vzlist", "-H", "-o", "status", str(ctid)]) except VZError: # CTID probably doesn't exist return False return ret.stdout.split()[0] == "running" def uuid2ctid(uuid: str) -> str: """get the legacy CTID of a container Args: uuid: VZ UUID to find the legacy CTID for Raises: VZError: if the prlctl command fails """ ret = _exec(["/usr/bin/prlctl", "list", "-H", "-o", "name", uuid]) return ret.stdout.split()[0] def ctid2uuid(ctid: Union[int, str]) -> str: """Obtain the UUID of a container from its legacy CTID Warning: This does not work on VZ4 Args: ctid: Legacy CTID to get the UUID for Raises: VZError: if the prlctl command fails """ ret = _exec(["/usr/bin/prlctl", "list", "-H", "-o", "uuid", str(ctid)]) return ret.stdout.split()[0].strip(r"{}") def get_envid(ctid: Union[int, str]) -> str: """Obtain the EnvID of a container Note: This determines what the subdirectory of /vz/root and /vz/private will be. This also has to run on VZ4 which lacks the envid field or prlctl, so we just return the CTID Args: ctid: legacy CTID to find the envid for Raises: VZError: if the prlctl command fails or /etc/virtuozzo-release is missing """ try: with open("/etc/virtuozzo-release", encoding="utf-8") as handle: if "Virtuozzo release 4" in handle.read(): return str(ctid) except FileNotFoundError as exc: raise VZError(exc) from exc ret = _exec(["/usr/bin/prlctl", "list", "-H", "-o", "envid", str(ctid)]) return ret.stdout.split()[0] def _list_cmd( opts: list, args: Optional[list], list_cmd: Optional[ListCmd] ) -> tuple[ListCmd, list[str]]: """Deterines the cmd to run based on VZ version for get_cts() Args: opts: items to send into ``-o/--output`` args: optional params to send such as ``--all`` list_cmd (ListCmd): set this to ListCmd.VZLIST to switch to that command """ if list_cmd == ListCmd.VZLIST: conv_opts = {x: ("veid" if x == "envid" else x) for x in opts} else: # prctl refers to 'ctid' as 'name' conv_opts = {x: ("name" if x == "ctid" else x) for x in opts} cmd = list_cmd.value.copy() if args is not None: cmd.extend(args) # forces opts's vals to be in the same order as args cmd_opts = ",".join([conv_opts[x] for x in opts]) cmd.extend(["-o", cmd_opts]) return list_cmd, cmd def _read_row( list_cmd: ListCmd, cmd: list[str], row: list[str], opts: list[str] ) -> dict[str, str]: # if number of rows matches requested options, return normally if len(row) == len(opts): return {x: row[i] for i, x in enumerate(opts)} # handle an edge case: prlctl can print missing ostemplates as '' while # vzlist prints it as '-', making the prlctl one harder to parse if ( list_cmd == ListCmd.PRLCTL and len(row) == len(opts) - 1 and "ostemplate" in opts ): opts = opts.copy() opts.remove("ostemplate") ret = {x: row[i] for i, x in enumerate(opts)} ret["ostemplate"] = "-" return ret raise VZError( f"{shlex.join(cmd)} expected {len(opts)} columns," f" but got {len(row)}: {row}" ) def get_cts( opts: Optional[list] = None, args: Optional[list] = None, list_cmd: ListCmd = ListCmd.PRLCTL, ) -> list[dict[str, str]]: """Returns containers according to platform as a list of dicts Args: opts: items to send into -o/--output (will default to ['ctid'] if None) args: optional params to send such as --all list_cmd (ListCmd): set this to ListCmd.VZLIST force using that command. Otherwise, we use prlctl. Raises: VZError: if the prlctl or vzlist command fails """ if not opts: opts = ["ctid"] ret = [] if not args and list_cmd == ListCmd.VZLIST and opts == ['ctid']: # if requesting just running ctids with vzlist, we can get that faster # from /proc/vz/veinfo for ctid in veinfo(): ret.append({'ctid': ctid}) return ret # process each line as a dict where keys are the arg and vals are the result list_cmd, cmd = _list_cmd(opts, args, list_cmd) for row in _exec(cmd).stdout.splitlines(): row = row.strip() if not row: continue # blank line ret.append(_read_row(list_cmd, cmd, row.split(), opts)) return ret