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
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/version.py
""" Set up the version of Salt """ import argparse import operator import os import platform import re import sys from collections import namedtuple from functools import total_ordering MAX_SIZE = sys.maxsize VERSION_LIMIT = MAX_SIZE - 200 # ----- ATTENTION ---------------------------------------------------------------------------------------------------> # # ALL major version bumps, new release codenames, MUST be defined in the SaltStackVersion.NAMES dictionary, i.e.: # # class SaltStackVersion: # # NAMES = { # 'Hydrogen': (2014, 1), # <- This is the tuple to bump versions # ( ... ) # } # # # ONLY UPDATE CODENAMES AFTER BRANCHING # # As an example, The Helium codename must only be properly defined with "(2014, 7)" after Hydrogen, "(2014, 1)", has # been branched out into its own branch. # # ALL OTHER VERSION INFORMATION IS EXTRACTED FROM THE GIT TAGS # # <---- ATTENTION ---------------------------------------------------------------------------------------------------- @total_ordering class SaltVersion(namedtuple("SaltVersion", "name, info, released")): __slots__ = () def __new__(cls, name, info, released=False): if isinstance(info, int): info = (info,) return super().__new__(cls, name, info, released) def __eq__(self, other): return self.info == other.info def __gt__(self, other): return self.info > other.info class SaltVersionsInfo(type): _sorted_versions = () _current_release = None _previous_release = None _next_release = None # pylint: disable=bad-whitespace # ----- Please refrain from fixing whitespace ----------------------------------> # The idea is to keep this readable. # ------------------------------------------------------------------------------- # fmt: off HYDROGEN = SaltVersion("Hydrogen" , info=(2014, 1), released=True) HELIUM = SaltVersion("Helium" , info=(2014, 7), released=True) LITHIUM = SaltVersion("Lithium" , info=(2015, 5), released=True) BERYLLIUM = SaltVersion("Beryllium" , info=(2015, 8), released=True) BORON = SaltVersion("Boron" , info=(2016, 3), released=True) CARBON = SaltVersion("Carbon" , info=(2016, 11), released=True) NITROGEN = SaltVersion("Nitrogen" , info=(2017, 7), released=True) OXYGEN = SaltVersion("Oxygen" , info=(2018, 3), released=True) FLUORINE = SaltVersion("Fluorine" , info=(2019, 2), released=True) NEON = SaltVersion("Neon" , info=3000, released=True) SODIUM = SaltVersion("Sodium" , info=3001, released=True) MAGNESIUM = SaltVersion("Magnesium" , info=3002, released=True) ALUMINIUM = SaltVersion("Aluminium" , info=3003, released=True) SILICON = SaltVersion("Silicon" , info=3004, released=True) PHOSPHORUS = SaltVersion("Phosphorus" , info=3005, released=True) SULFUR = SaltVersion("Sulfur" , info=3006, released=True) CHLORINE = SaltVersion("Chlorine" , info=3007) ARGON = SaltVersion("Argon" , info=3008) POTASSIUM = SaltVersion("Potassium" , info=3009) CALCIUM = SaltVersion("Calcium" , info=3010) SCANDIUM = SaltVersion("Scandium" , info=3011) TITANIUM = SaltVersion("Titanium" , info=3012) VANADIUM = SaltVersion("Vanadium" , info=3013) CHROMIUM = SaltVersion("Chromium" , info=3014) MANGANESE = SaltVersion("Manganese" , info=3015) IRON = SaltVersion("Iron" , info=3016) COBALT = SaltVersion("Cobalt" , info=3017) NICKEL = SaltVersion("Nickel" , info=3018) COPPER = SaltVersion("Copper" , info=3019) ZINC = SaltVersion("Zinc" , info=3020) GALLIUM = SaltVersion("Gallium" , info=3021) GERMANIUM = SaltVersion("Germanium" , info=3022) ARSENIC = SaltVersion("Arsenic" , info=3023) SELENIUM = SaltVersion("Selenium" , info=3024) BROMINE = SaltVersion("Bromine" , info=3025) KRYPTON = SaltVersion("Krypton" , info=3026) RUBIDIUM = SaltVersion("Rubidium" , info=3027) STRONTIUM = SaltVersion("Strontium" , info=3028) YTTRIUM = SaltVersion("Yttrium" , info=3029) ZIRCONIUM = SaltVersion("Zirconium" , info=3030) NIOBIUM = SaltVersion("Niobium" , info=3031) MOLYBDENUM = SaltVersion("Molybdenum" , info=3032) TECHNETIUM = SaltVersion("Technetium" , info=3033) RUTHENIUM = SaltVersion("Ruthenium" , info=3034) RHODIUM = SaltVersion("Rhodium" , info=3035) PALLADIUM = SaltVersion("Palladium" , info=3036) SILVER = SaltVersion("Silver" , info=3037) CADMIUM = SaltVersion("Cadmium" , info=3038) INDIUM = SaltVersion("Indium" , info=3039) TIN = SaltVersion("Tin" , info=3040) ANTIMONY = SaltVersion("Antimony" , info=3041) TELLURIUM = SaltVersion("Tellurium" , info=3042) IODINE = SaltVersion("Iodine" , info=3043) XENON = SaltVersion("Xenon" , info=3044) CESIUM = SaltVersion("Cesium" , info=3045) BARIUM = SaltVersion("Barium" , info=3046) LANTHANUM = SaltVersion("Lanthanum" , info=3047) CERIUM = SaltVersion("Cerium" , info=3048) PRASEODYMIUM = SaltVersion("Praseodymium" , info=3049) NEODYMIUM = SaltVersion("Neodymium" , info=3050) PROMETHIUM = SaltVersion("Promethium" , info=3051) SAMARIUM = SaltVersion("Samarium" , info=3052) EUROPIUM = SaltVersion("Europium" , info=3053) GADOLINIUM = SaltVersion("Gadolinium" , info=3054) TERBIUM = SaltVersion("Terbium" , info=3055) DYSPROSIUM = SaltVersion("Dysprosium" , info=3056) HOLMIUM = SaltVersion("Holmium" , info=3057) ERBIUM = SaltVersion("Erbium" , info=3058) THULIUM = SaltVersion("Thulium" , info=3059) YTTERBIUM = SaltVersion("Ytterbium" , info=3060) LUTETIUM = SaltVersion("Lutetium" , info=3061) HAFNIUM = SaltVersion("Hafnium" , info=3062) TANTALUM = SaltVersion("Tantalum" , info=3063) TUNGSTEN = SaltVersion("Tungsten" , info=3064) RHENIUM = SaltVersion("Rhenium" , info=3065) OSMIUM = SaltVersion("Osmium" , info=3066) IRIDIUM = SaltVersion("Iridium" , info=3067) PLATINUM = SaltVersion("Platinum" , info=3068) GOLD = SaltVersion("Gold" , info=3069) MERCURY = SaltVersion("Mercury" , info=3070) THALLIUM = SaltVersion("Thallium" , info=3071) LEAD = SaltVersion("Lead" , info=3072) BISMUTH = SaltVersion("Bismuth" , info=3073) POLONIUM = SaltVersion("Polonium" , info=3074) ASTATINE = SaltVersion("Astatine" , info=3075) RADON = SaltVersion("Radon" , info=3076) FRANCIUM = SaltVersion("Francium" , info=3077) RADIUM = SaltVersion("Radium" , info=3078) ACTINIUM = SaltVersion("Actinium" , info=3079) THORIUM = SaltVersion("Thorium" , info=3080) PROTACTINIUM = SaltVersion("Protactinium" , info=3081) URANIUM = SaltVersion("Uranium" , info=3082) NEPTUNIUM = SaltVersion("Neptunium" , info=3083) PLUTONIUM = SaltVersion("Plutonium" , info=3084) AMERICIUM = SaltVersion("Americium" , info=3085) CURIUM = SaltVersion("Curium" , info=3086) BERKELIUM = SaltVersion("Berkelium" , info=3087) CALIFORNIUM = SaltVersion("Californium" , info=3088) EINSTEINIUM = SaltVersion("Einsteinium" , info=3089) FERMIUM = SaltVersion("Fermium" , info=3090) MENDELEVIUM = SaltVersion("Mendelevium" , info=3091) NOBELIUM = SaltVersion("Nobelium" , info=3092) LAWRENCIUM = SaltVersion("Lawrencium" , info=3093) RUTHERFORDIUM = SaltVersion("Rutherfordium", info=3094) DUBNIUM = SaltVersion("Dubnium" , info=3095) SEABORGIUM = SaltVersion("Seaborgium" , info=3096) BOHRIUM = SaltVersion("Bohrium" , info=3097) HASSIUM = SaltVersion("Hassium" , info=3098) MEITNERIUM = SaltVersion("Meitnerium" , info=3099) DARMSTADTIUM = SaltVersion("Darmstadtium" , info=3100) ROENTGENIUM = SaltVersion("Roentgenium" , info=3101) COPERNICIUM = SaltVersion("Copernicium" , info=3102) NIHONIUM = SaltVersion("Nihonium" , info=3103) FLEROVIUM = SaltVersion("Flerovium" , info=3104) MOSCOVIUM = SaltVersion("Moscovium" , info=3105) LIVERMORIUM = SaltVersion("Livermorium" , info=3106) TENNESSINE = SaltVersion("Tennessine" , info=3107) OGANESSON = SaltVersion("Oganesson" , info=3108) # <---- Please refrain from fixing whitespace ----------------------------------- # The idea is to keep this readable. # ------------------------------------------------------------------------------- # pylint: enable=bad-whitespace # fmt: on @classmethod def versions(cls): if not cls._sorted_versions: cls._sorted_versions = sorted( (getattr(cls, name) for name in dir(cls) if name.isupper()), key=operator.attrgetter("info"), ) return cls._sorted_versions @classmethod def current_release(cls): if cls._current_release is None: for version in cls.versions(): if version.released is False: cls._current_release = version break return cls._current_release @classmethod def next_release(cls): if cls._next_release is None: next_release_ahead = False for version in cls.versions(): if next_release_ahead: cls._next_release = version break if version == cls.current_release(): next_release_ahead = True return cls._next_release @classmethod def previous_release(cls): if cls._previous_release is None: previous = None for version in cls.versions(): if version == cls.current_release(): break previous = version cls._previous_release = previous return cls._previous_release class SaltStackVersion: """ Handle SaltStack versions class. Knows how to parse ``git describe`` output, knows about release candidates and also supports version comparison. """ __slots__ = ( "name", "major", "minor", "bugfix", "mbugfix", "pre_type", "pre_num", "noc", "sha", ) git_sha_regex = r"(?P<sha>g?[a-f0-9]{7,40})" git_describe_regex = re.compile( r"(?:[^\d]+)?(?P<major>[\d]{1,4})" r"(?:\.(?P<minor>[\d]{1,2}))?" r"(?:\.(?P<bugfix>[\d]{0,2}))?" r"(?:\.(?P<mbugfix>[\d]{0,2}))?" r"(?:(?P<pre_type>rc|a|b|alpha|beta|nb)(?P<pre_num>[\d]+))?" r"(?:(?:.*)(?:\+|-)(?P<noc>(?:0na|[\d]+|n/a))(?:-|\.)" + git_sha_regex + r")?" ) git_sha_regex = r"^" + git_sha_regex git_sha_regex = re.compile(git_sha_regex) NAMES = {v.name: v.info for v in SaltVersionsInfo.versions()} LNAMES = {k.lower(): v for (k, v) in iter(NAMES.items())} VNAMES = {v: k for (k, v) in iter(NAMES.items())} RMATCH = {v[:2]: k for (k, v) in iter(NAMES.items())} def __init__( self, # pylint: disable=C0103 major, minor=None, bugfix=None, mbugfix=0, pre_type=None, pre_num=None, noc=0, sha=None, ): if isinstance(major, str): major = int(major) if isinstance(minor, str): if not minor: # Empty string minor = None else: minor = int(minor) if self.can_have_dot_zero(major): minor = minor if minor else 0 if bugfix is None and not self.new_version(major=major): bugfix = 0 elif isinstance(bugfix, str): if not bugfix: bugfix = None else: bugfix = int(bugfix) if mbugfix is None: mbugfix = 0 elif isinstance(mbugfix, str): mbugfix = int(mbugfix) if pre_type is None: pre_type = "" if pre_num is None: pre_num = 0 elif isinstance(pre_num, str): pre_num = int(pre_num) if noc is None: noc = 0 elif isinstance(noc, str) and noc in ("0na", "n/a"): noc = -1 elif isinstance(noc, str): noc = int(noc) self.major = major self.minor = minor self.bugfix = bugfix self.mbugfix = mbugfix self.pre_type = pre_type self.pre_num = pre_num if self.new_version(major): vnames_key = (major,) else: vnames_key = (major, minor) self.name = self.VNAMES.get(vnames_key) self.noc = noc self.sha = sha def new_version(self, major): """ determine if using new versioning scheme """ return bool(int(major) >= 3000 and int(major) < VERSION_LIMIT) def can_have_dot_zero(self, major): """ determine if using new versioning scheme """ return bool(int(major) >= 3006 and int(major) < VERSION_LIMIT) @classmethod def parse(cls, version_string): if version_string.lower() in cls.LNAMES: return cls.from_name(version_string) vstr = ( version_string.decode() if isinstance(version_string, bytes) else version_string ) match = cls.git_describe_regex.match(vstr) if not match: raise ValueError(f"Unable to parse version string: '{version_string}'") return cls(*match.groups()) @classmethod def from_name(cls, name): if name.lower() not in cls.LNAMES: raise ValueError(f"Named version '{name}' is not known") return cls(*cls.LNAMES[name.lower()]) @classmethod def from_last_named_version(cls): import salt.utils.versions salt.utils.versions.warn_until( SaltVersionsInfo.SULFUR, "The use of SaltStackVersion.from_last_named_version() is " "deprecated and set to be removed in {version}. Please use " "SaltStackVersion.current_release() instead.", ) return cls.current_release() @classmethod def current_release(cls): return cls(*SaltVersionsInfo.current_release().info) @classmethod def next_release(cls): return cls(*SaltVersionsInfo.next_release().info) @property def sse(self): # Higher than 0.17, lower than first date based return 0 < self.major < 2014 def min_info(self): info = [self.major] if self.new_version(self.major): if self.minor: info.append(self.minor) elif self.can_have_dot_zero(self.major): info.append(self.minor) else: info.extend([self.minor, self.bugfix, self.mbugfix]) return info @property def info(self): return tuple(self.min_info()) @property def pre_info(self): info = self.min_info() info.extend([self.pre_type, self.pre_num]) return tuple(info) @property def noc_info(self): info = self.min_info() info.extend([self.pre_type, self.pre_num, self.noc]) return tuple(info) @property def full_info(self): info = self.min_info() info.extend([self.pre_type, self.pre_num, self.noc, self.sha]) return tuple(info) @property def full_info_all_versions(self): """ Return the full info regardless of which versioning scheme we are using. """ info = [ self.major, self.minor, self.bugfix, self.mbugfix, self.pre_type, self.pre_num, self.noc, self.sha, ] return tuple(info) @property def string(self): if self.new_version(self.major): version_string = f"{self.major}" if self.minor: version_string = f"{self.major}.{self.minor}" if not self.minor and self.can_have_dot_zero(self.major): version_string = f"{self.major}.{self.minor}" else: version_string = f"{self.major}.{self.minor}.{self.bugfix}" if self.mbugfix: version_string += f".{self.mbugfix}" if self.pre_type: version_string += f"{self.pre_type}{self.pre_num}" if self.noc is not None and self.sha: noc = self.noc if noc < 0: noc = "0na" version_string += f"+{noc}.{self.sha}" return version_string @property def formatted_version(self): if self.name and self.major > 10000: version_string = self.name if self.sse: version_string += " Enterprise" version_string += " (Unreleased)" return version_string version_string = self.string if self.sse: version_string += " Enterprise" if self.new_version(self.major): rmatch_key = (self.major,) else: rmatch_key = (self.major, self.minor) if rmatch_key in self.RMATCH: version_string += f" ({self.RMATCH[rmatch_key]})" return version_string @property def pre_index(self): if self.new_version(self.major): pre_type = 2 if not isinstance(self.minor, int): pre_type = 1 else: pre_type = 4 return pre_type def __str__(self): return self.string def __compare__(self, other, method): if not isinstance(other, SaltStackVersion): if isinstance(other, str): other = SaltStackVersion.parse(other) elif isinstance(other, (list, tuple)): other = SaltStackVersion(*other) else: raise ValueError( f"Cannot instantiate Version from type '{type(other)}'" ) pre_type = self.pre_index other_pre_type = other.pre_index other_noc_info = list(other.noc_info) noc_info = list(self.noc_info) if self.new_version(self.major): if self.minor and not other.minor: # We have minor information, the other side does not if self.minor > 0: other_noc_info[1] = 0 if not self.minor and other.minor: # The other side has minor information, we don't if other.minor > 0: noc_info[1] = 0 if self.pre_type and not other.pre_type: # We have pre-release information, the other side doesn't other_noc_info[other_pre_type] = "zzzzz" if not self.pre_type and other.pre_type: # The other side has pre-release information, we don't noc_info[pre_type] = "zzzzz" return method(tuple(noc_info), tuple(other_noc_info)) def __lt__(self, other): return self.__compare__(other, lambda _self, _other: _self < _other) def __le__(self, other): return self.__compare__(other, lambda _self, _other: _self <= _other) def __eq__(self, other): return self.__compare__(other, lambda _self, _other: _self == _other) def __ne__(self, other): return self.__compare__(other, lambda _self, _other: _self != _other) def __ge__(self, other): return self.__compare__(other, lambda _self, _other: _self >= _other) def __gt__(self, other): return self.__compare__(other, lambda _self, _other: _self > _other) def __repr__(self): parts = [] if self.name: parts.append(f"name='{self.name}'") parts.extend([f"major={self.major}", f"minor={self.minor}"]) if self.new_version(self.major): if not self.can_have_dot_zero(self.major) and not self.minor: parts.remove("".join([x for x in parts if re.search("^minor*", x)])) else: parts.extend([f"bugfix={self.bugfix}"]) if self.mbugfix: parts.append(f"minor-bugfix={self.mbugfix}") if self.pre_type: parts.append(f"{self.pre_type}={self.pre_num}") noc = self.noc if noc == -1: noc = "0na" if noc and self.sha: parts.extend([f"noc={noc}", f"sha={self.sha}"]) return "<{} {}>".format(self.__class__.__name__, " ".join(parts)) # ----- Hardcoded Salt Codename Version Information -----------------------------------------------------------------> # # There's no need to do anything here. The last released codename will be picked up # -------------------------------------------------------------------------------------------------------------------- __saltstack_version__ = SaltStackVersion.current_release() # <---- Hardcoded Salt Version Information --------------------------------------------------------------------------- # ----- Dynamic/Runtime Salt Version Information --------------------------------------------------------------------> def __discover_version(saltstack_version): # This might be a 'python setup.py develop' installation type. Let's # discover the version information at runtime. import subprocess if "SETUP_DIRNAME" in globals(): # This is from the exec() call in Salt's setup.py cwd = SETUP_DIRNAME # pylint: disable=E0602 if not os.path.exists(os.path.join(cwd, ".git")): # This is not a Salt git checkout!!! Don't even try to parse... return saltstack_version else: cwd = os.path.abspath(os.path.dirname(__file__)) if not os.path.exists(os.path.join(os.path.dirname(cwd), ".git")): # This is not a Salt git checkout!!! Don't even try to parse... return saltstack_version try: kwargs = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) if not sys.platform.startswith("win"): # Let's not import `salt.utils` for the above check kwargs["close_fds"] = True process = subprocess.Popen( [ "git", "describe", "--tags", "--long", "--match", "v[0-9]*", "--always", ], **kwargs, ) out, err = process.communicate() out = out.decode().strip() err = err.decode().strip() if not out or err: return saltstack_version if SaltStackVersion.git_sha_regex.match(out): # We only define the parsed SHA and set NOC as ??? (unknown) saltstack_version.sha = out.strip() saltstack_version.noc = -1 return saltstack_version return SaltStackVersion.parse(out) except OSError as os_err: if os_err.errno != 2: # If the errno is not 2(The system cannot find the file # specified), raise the exception so it can be catch by the # developers raise return saltstack_version def __get_version(saltstack_version): """ If we can get a version provided at installation time or from Git, use that instead, otherwise we carry on. """ _hardcoded_version_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "_version.txt" ) if not os.path.exists(_hardcoded_version_file): return __discover_version(saltstack_version) with open( # pylint: disable=resource-leakage _hardcoded_version_file, encoding="utf-8" ) as rfh: return SaltStackVersion.parse(rfh.read().strip()) # Get additional version information if available __saltstack_version__ = __get_version(__saltstack_version__) if __saltstack_version__.name: # Set SaltVersionsInfo._current_release to avoid lookups when finding previous and next releases SaltVersionsInfo._current_release = getattr( SaltVersionsInfo, __saltstack_version__.name.upper() ) # This function has executed once, we're done with it. Delete it! del __get_version # <---- Dynamic/Runtime Salt Version Information --------------------------------------------------------------------- # ----- Common version related attributes - NO NEED TO CHANGE -------------------------------------------------------> __version_info__ = __saltstack_version__.info __version__ = __saltstack_version__.string # <---- Common version related attributes - NO NEED TO CHANGE -------------------------------------------------------- def salt_information(): """ Report version of salt. """ yield "Salt", __version__ def dependency_information(include_salt_cloud=False): """ Report versions of library dependencies. """ libs = [ ("Jinja2", "jinja2", "__version__"), ("M2Crypto", "M2Crypto", "version"), ("msgpack", "msgpack", "version"), ("msgpack-pure", "msgpack_pure", "version"), ("pycrypto", "Crypto", "__version__"), ("pycryptodome", "Cryptodome", "version_info"), ("cryptography", "cryptography", "__version__"), ("PyYAML", "yaml", "__version__"), ("PyZMQ", "zmq", "__version__"), ("ZMQ", "zmq", "zmq_version"), ("Mako", "mako", "__version__"), ("Tornado", "tornado", "version"), ("timelib", "timelib", "version"), ("dateutil", "dateutil", "__version__"), ("pygit2", "pygit2", "__version__"), ("libgit2", "pygit2", "LIBGIT2_VERSION"), ("smmap", "smmap", "__version__"), ("cffi", "cffi", "__version__"), ("pycparser", "pycparser", "__version__"), ("gitdb", "gitdb", "__version__"), ("gitpython", "git", "__version__"), ("python-gnupg", "gnupg", "__version__"), ("mysql-python", "MySQLdb", "__version__"), ("cherrypy", "cherrypy", "__version__"), ("docker-py", "docker", "__version__"), ("packaging", "packaging", "__version__"), ("looseversion", "looseversion", None), ("relenv", "relenv", "__version__"), ] if include_salt_cloud: libs.append( ("Apache Libcloud", "libcloud", "__version__"), ) def _sort_by_lowercased_name(entry): return entry[0].lower() for name, imp, attr in sorted(libs, key=_sort_by_lowercased_name): if imp is None: yield name, attr continue try: if attr is None: # Late import to reduce the needed available modules and libs # installed when running `python salt/version.py` from salt._compat import importlib_metadata version = importlib_metadata.version(imp) yield name, version continue imp = __import__(imp) version = getattr(imp, attr) if callable(version): version = version() if isinstance(version, (tuple, list)): version = ".".join(map(str, version)) yield name, version except Exception: # pylint: disable=broad-except yield name, None def system_information(): """ Report system versions. """ # Late import so that when getting called from setup.py does not break from salt.utils.platform import linux_distribution def system_version(): """ Return host system version. """ lin_ver = linux_distribution() mac_ver = platform.mac_ver() win_ver = platform.win32_ver() # linux_distribution() will return a # distribution on OS X and Windows. # Check mac_ver and win_ver first, # then lin_ver. if mac_ver[0]: if isinstance(mac_ver[1], (tuple, list)) and "".join(mac_ver[1]): return " ".join([mac_ver[0], ".".join(mac_ver[1]), mac_ver[2]]) else: return " ".join([mac_ver[0], mac_ver[2]]) elif win_ver[0]: return " ".join(win_ver) elif lin_ver[0]: return " ".join(lin_ver) else: return "" if platform.win32_ver()[0]: # Get the version and release info based on the Windows Operating # System Product Name. As long as Microsoft maintains a similar format # this should be future proof import win32api # pylint: disable=3rd-party-module-not-gated import win32con # pylint: disable=3rd-party-module-not-gated # Get the product name from the registry hkey = win32con.HKEY_LOCAL_MACHINE key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" value_name = "ProductName" reg_handle = win32api.RegOpenKey(hkey, key) # Returns a tuple of (product_name, value_type) product_name, _ = win32api.RegQueryValueEx(reg_handle, value_name) version = "Unknown" release = "" if "Server" in product_name: for item in product_name.split(" "): # If it's all digits, then it's version if re.match(r"\d+", item): version = item # If it starts with R and then numbers, it's the release # ie: R2 if re.match(r"^R\d+$", item): release = item release = f"{version}Server{release}" else: for item in product_name.split(" "): # If it's a number, decimal number, Thin or Vista, then it's the # version if re.match(r"^(\d+(\.\d+)?)|Thin|Vista$", item): version = item release = version _, ver, service_pack, extra = platform.win32_ver() version = " ".join([release, ver, service_pack, extra]) else: version = system_version() release = platform.release() system = [ ("system", platform.system()), ("dist", " ".join(linux_distribution(full_distribution_name=False))), ("release", release), ("machine", platform.machine()), ("version", version), ("locale", __salt_system_encoding__), ] for name, attr in system: yield name, attr continue def extensions_information(): """ Gather infomation about any installed salt extensions """ # Late import import salt.utils.entrypoints extensions = {} for entry_point in salt.utils.entrypoints.iter_entry_points("salt.loader"): dist_nv = salt.utils.entrypoints.name_and_version_from_entry_point(entry_point) if not dist_nv: continue if dist_nv.name in extensions: continue extensions[dist_nv.name] = dist_nv.version return extensions def versions_information(include_salt_cloud=False, include_extensions=True): """ Report the versions of dependent software. """ py_info = [ ("Python", sys.version.rsplit("\n", maxsplit=1)[0].strip()), ] salt_info = list(salt_information()) lib_info = list(dependency_information(include_salt_cloud)) sys_info = list(system_information()) info = { "Salt Version": dict(salt_info), "Python Version": dict(py_info), "Dependency Versions": dict(lib_info), "System Versions": dict(sys_info), } if include_extensions: extensions_info = extensions_information() if extensions_info: info["Salt Extensions"] = extensions_info return info def versions_report(include_salt_cloud=False, include_extensions=True): """ Yield each version properly formatted for console output. """ ver_info = versions_information( include_salt_cloud=include_salt_cloud, include_extensions=include_extensions ) not_installed = "Not Installed" ns_pad = len(not_installed) lib_pad = max(len(name) for name in ver_info["Dependency Versions"]) sys_pad = max(len(name) for name in ver_info["System Versions"]) if include_extensions and "Salt Extensions" in ver_info: ext_pad = max(len(name) for name in ver_info["Salt Extensions"]) else: ext_pad = 1 padding = max(lib_pad, sys_pad, ns_pad, ext_pad) + 1 fmt = "{0:>{pad}}: {1}" info = [] for ver_type in ( "Salt Version", "Python Version", "Dependency Versions", "Salt Extensions", "System Versions", ): if ver_type == "Salt Extensions" and ver_type not in ver_info: # No salt Extensions to report continue info.append(f"{ver_type}:") # List dependencies in alphabetical, case insensitive order for name in sorted(ver_info[ver_type], key=lambda x: x.lower()): ver = fmt.format( name, ver_info[ver_type][name] or not_installed, pad=padding ) info.append(ver) info.append(" ") yield from info def _parser(): parser = argparse.ArgumentParser() parser.add_argument( "--next-release", help="Return the next release", action="store_true" ) parser.add_argument("--parse", help="Parse the passed string as a salt version") # When pip installing we pass in other args to this script. # This allows us to catch those args but not use them parser.add_argument("unknown", nargs=argparse.REMAINDER) return parser.parse_args() if __name__ == "__main__": args = _parser() if args.next_release: print(__saltstack_version__.next_release()) elif args.parse: try: print(SaltStackVersion.parse(args.parse)) except Exception as exc: # pylint: disable=broad-except print(f"Failed to parse '{args.parse}' as a salt version: {exc}") sys.exit(1) else: print(__version__)