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/vagrant.py
""" Work with virtual machines managed by Vagrant. .. versionadded:: 2018.3.0 Mapping between a Salt node id and the Vagrant machine name (and the path to the Vagrantfile where it is defined) is stored in a Salt sdb database on the Vagrant host (minion) machine. In order to use this module, sdb must be configured. An SQLite database is the recommended storage method. The URI used for the sdb lookup is "sdb://vagrant_sdb_data". requirements: - the VM host machine must have salt-minion, Vagrant and a vm provider installed. - the VM host must have a valid definition for `sdb://vagrant_sdb_data` Configuration example: .. code-block:: yaml # file /etc/salt/minion.d/vagrant_sdb.conf vagrant_sdb_data: driver: sqlite3 database: /var/cache/salt/vagrant.sqlite table: sdb create_table: True """ import logging import os import salt.utils.files import salt.utils.path import salt.utils.stringutils from salt._compat import ipaddress from salt.exceptions import CommandExecutionError, SaltInvocationError log = logging.getLogger(__name__) __virtualname__ = "vagrant" VAGRANT_SDB_URL = "sdb://vagrant_sdb_data/" def __virtual__(): """ run Vagrant commands if possible """ if salt.utils.path.which("vagrant") is None: return ( False, "The vagrant module could not be loaded: vagrant command not found", ) return __virtualname__ def _build_sdb_uri(key): """ returns string used to fetch data for "key" from the sdb store. Salt node id's are used as the key for vm_ dicts. """ return f"{VAGRANT_SDB_URL}{key}" def _build_machine_uri(machine, cwd): """ returns string used to fetch id names from the sdb store. the cwd and machine name are concatenated with '?' which should never collide with a Salt node id -- which is important since we will be storing both in the same table. """ key = f"{machine}?{os.path.abspath(cwd)}" return _build_sdb_uri(key) def _update_vm_info(name, vm_): """store the vm_ information keyed by name""" __utils__["sdb.sdb_set"](_build_sdb_uri(name), vm_, __opts__) # store machine-to-name mapping, too if vm_["machine"]: __utils__["sdb.sdb_set"]( _build_machine_uri(vm_["machine"], vm_.get("cwd", ".")), name, __opts__ ) def get_vm_info(name): """ get the information for a VM. :param name: salt_id name :return: dictionary of {'machine': x, 'cwd': y, ...}. """ try: vm_ = __utils__["sdb.sdb_get"](_build_sdb_uri(name), __opts__) except KeyError: raise SaltInvocationError( "Probable sdb driver not found. Check your configuration." ) if vm_ is None or "machine" not in vm_: raise SaltInvocationError(f"No Vagrant machine defined for Salt_id {name}") return vm_ def get_machine_id(machine, cwd): """ returns the salt_id name of the Vagrant VM :param machine: the Vagrant machine name :param cwd: the path to Vagrantfile :return: salt_id name """ name = __utils__["sdb.sdb_get"](_build_machine_uri(machine, cwd), __opts__) return name def _erase_vm_info(name): """ erase the information for a VM the we are destroying. some sdb drivers (such as the SQLite driver we expect to use) do not have a `delete` method, so if the delete fails, we have to replace the with a blank entry. """ try: # delete the machine record vm_ = get_vm_info(name) if vm_["machine"]: key = _build_machine_uri(vm_["machine"], vm_.get("cwd", ".")) try: __utils__["sdb.sdb_delete"](key, __opts__) except KeyError: # no delete method found -- load a blank value __utils__["sdb.sdb_set"](key, None, __opts__) except Exception: # pylint: disable=broad-except pass uri = _build_sdb_uri(name) try: # delete the name record __utils__["sdb.sdb_delete"](uri, __opts__) except KeyError: # no delete method found -- load an empty dictionary __utils__["sdb.sdb_set"](uri, {}, __opts__) except Exception: # pylint: disable=broad-except pass def _vagrant_ssh_config(vm_): """ get the information for ssh communication from the new VM :param vm_: the VM's info as we have it now :return: dictionary of ssh stuff """ machine = vm_["machine"] log.info("requesting vagrant ssh-config for VM %s", machine or "(default)") cmd = f"vagrant ssh-config {machine}" reply = __salt__["cmd.shell"]( cmd, runas=vm_.get("runas"), cwd=vm_.get("cwd"), ignore_retcode=True ) ssh_config = {} for line in reply.split("\n"): # build a dictionary of the text reply tokens = line.strip().split() if len(tokens) == 2: # each two-token line becomes a key:value pair ssh_config[tokens[0]] = tokens[1] log.debug("ssh_config=%s", repr(ssh_config)) return ssh_config def version(): """ Return the version of Vagrant on the minion CLI Example: .. code-block:: bash salt '*' vagrant.version """ cmd = "vagrant -v" return __salt__["cmd.shell"](cmd) def list_domains(): """ Return a list of the salt_id names of all available Vagrant VMs on this host without regard to the path where they are defined. CLI Example: .. code-block:: bash salt '*' vagrant.list_domains --log-level=info The log shows information about all known Vagrant environments on this machine. This data is cached and may not be completely up-to-date. """ vms = [] cmd = "vagrant global-status" reply = __salt__["cmd.shell"](cmd) log.debug("--->\n%s", reply) for line in reply.split("\n"): # build a list of the text reply tokens = line.strip().split() try: _ = int(tokens[0], 16) # valid id numbers are hexadecimal except (ValueError, IndexError): continue # skip lines without valid id numbers machine = tokens[1] cwd = tokens[-1] name = get_machine_id(machine, cwd) if name: vms.append(name) return vms def list_active_vms(cwd=None): """ Return a list of machine names for active virtual machine on the host, which are defined in the Vagrantfile at the indicated path. CLI Example: .. code-block:: bash salt '*' vagrant.list_active_vms cwd=/projects/project_1 """ vms = [] cmd = "vagrant status" reply = __salt__["cmd.shell"](cmd, cwd=cwd) log.info("--->\n%s", reply) for line in reply.split("\n"): # build a list of the text reply tokens = line.strip().split() if len(tokens) > 1: if tokens[1] == "running": vms.append(tokens[0]) return vms def list_inactive_vms(cwd=None): """ Return a list of machine names for inactive virtual machine on the host, which are defined in the Vagrantfile at the indicated path. CLI Example: .. code-block:: bash salt '*' virt.list_inactive_vms cwd=/projects/project_1 """ vms = [] cmd = "vagrant status" reply = __salt__["cmd.shell"](cmd, cwd=cwd) log.info("--->\n%s", reply) for line in reply.split("\n"): # build a list of the text reply tokens = line.strip().split() if len(tokens) > 1 and tokens[-1].endswith(")"): if tokens[1] != "running": vms.append(tokens[0]) return vms def vm_state(name="", cwd=None): """ Return list of information for all the vms indicating their state. If you pass a VM name in as an argument then it will return info for just the named VM, otherwise it will return all VMs defined by the Vagrantfile in the `cwd` directory. CLI Example: .. code-block:: bash salt '*' vagrant.vm_state <name> cwd=/projects/project_1 returns a list of dictionaries with machine name, state, provider, and salt_id name. .. code-block:: python datum = {'machine': _, # Vagrant machine name, 'state': _, # string indicating machine state, like 'running' 'provider': _, # the Vagrant VM provider 'name': _} # salt_id name Known bug: if there are multiple machines in your Vagrantfile, and you request the status of the ``primary`` machine, which you defined by leaving the ``machine`` parameter blank, then you may receive the status of all of them. Please specify the actual machine name for each VM if there are more than one. """ if name: vm_ = get_vm_info(name) machine = vm_["machine"] cwd = vm_["cwd"] or cwd # usually ignore passed-in cwd else: if not cwd: raise SaltInvocationError( f"Path to Vagranfile must be defined, but cwd={cwd}" ) machine = "" info = [] cmd = f"vagrant status {machine}" reply = __salt__["cmd.shell"](cmd, cwd) log.info("--->\n%s", reply) for line in reply.split("\n"): # build a list of the text reply tokens = line.strip().split() if len(tokens) > 1 and tokens[-1].endswith(")"): try: datum = { "machine": tokens[0], "state": " ".join(tokens[1:-1]), "provider": tokens[-1].lstrip("(").rstrip(")"), "name": get_machine_id(tokens[0], cwd), } info.append(datum) except IndexError: pass return info def init( name, # Salt_id for created VM cwd=None, # path to find Vagrantfile machine="", # name of machine in Vagrantfile runas=None, # username who owns Vagrant box start=False, # start the machine when initialized vagrant_provider="", # vagrant provider (default=virtualbox) vm=None, # a dictionary of VM configuration settings ): """ Initialize a new Vagrant VM. This inputs all the information needed to start a Vagrant VM. These settings are stored in a Salt sdb database on the Vagrant host minion and used to start, control, and query the guest VMs. The salt_id assigned here is the key field for that database and must be unique. :param name: The salt_id name you will use to control this VM :param cwd: The path to the directory where the Vagrantfile is located :param machine: The machine name in the Vagrantfile. If blank, the primary machine will be used. :param runas: The username on the host who owns the Vagrant work files. :param start: (default: False) Start the virtual machine now. :param vagrant_provider: The name of a Vagrant VM provider (if not the default). :param vm: Optionally, all the above information may be supplied in this dictionary. :return: A string indicating success, or False. CLI Example: .. code-block:: bash salt <host> vagrant.init <salt_id> /path/to/Vagrantfile salt my_laptop vagrant.init x1 /projects/bevy_master machine=quail1 """ vm_ = {} if vm is None else vm.copy() # passed configuration data vm_["name"] = name # passed-in keyword arguments overwrite vm dictionary values vm_["cwd"] = cwd or vm_.get("cwd") if not vm_["cwd"]: raise SaltInvocationError( 'Path to Vagrantfile must be defined by "cwd" argument' ) vm_["machine"] = machine or vm_.get("machine", machine) vm_["runas"] = runas or vm_.get("runas", runas) vm_["vagrant_provider"] = vagrant_provider or vm_.get("vagrant_provider", "") _update_vm_info(name, vm_) if start: log.debug("Starting VM %s", name) ret = _start(name, vm_) else: ret = "Name {} defined using VM {}".format(name, vm_["machine"] or "(default)") return ret def start(name): """ Start (vagrant up) a virtual machine defined by salt_id name. The machine must have been previously defined using "vagrant.init". CLI Example: .. code-block:: bash salt <host> vagrant.start <salt_id> """ vm_ = get_vm_info(name) return _start(name, vm_) def _start( name, vm_ ): # internal call name, because "start" is a keyword argument to vagrant.init try: machine = vm_["machine"] except KeyError: raise SaltInvocationError(f"No Vagrant machine defined for Salt_id {name}") vagrant_provider = vm_.get("vagrant_provider", "") provider_ = f"--provider={vagrant_provider}" if vagrant_provider else "" cmd = f"vagrant up {machine} {provider_}" ret = __salt__["cmd.run_all"]( cmd, runas=vm_.get("runas"), cwd=vm_.get("cwd"), output_loglevel="info" ) if machine == "": # we were called using the default machine for line in ret["stdout"].split("\n"): # find its actual Vagrant name if line.startswith("==>"): machine = line.split()[1].rstrip(":") vm_["machine"] = machine _update_vm_info(name, vm_) # and remember the true name break if ret["retcode"] == 0: return f'Started "{name}" using Vagrant machine "{machine}".' return False def shutdown(name): """ Send a soft shutdown (vagrant halt) signal to the named vm. This does the same thing as vagrant.stop. Other-VM control modules use "stop" and "shutdown" to differentiate between hard and soft shutdowns. CLI Example: .. code-block:: bash salt <host> vagrant.shutdown <salt_id> """ return stop(name) def stop(name): """ Hard shutdown the virtual machine. (vagrant halt) CLI Example: .. code-block:: bash salt <host> vagrant.stop <salt_id> """ vm_ = get_vm_info(name) machine = vm_["machine"] cmd = f"vagrant halt {machine}" ret = __salt__["cmd.retcode"](cmd, runas=vm_.get("runas"), cwd=vm_.get("cwd")) return ret == 0 def pause(name): """ Pause (vagrant suspend) the named VM. CLI Example: .. code-block:: bash salt <host> vagrant.pause <salt_id> """ vm_ = get_vm_info(name) machine = vm_["machine"] cmd = f"vagrant suspend {machine}" ret = __salt__["cmd.retcode"](cmd, runas=vm_.get("runas"), cwd=vm_.get("cwd")) return ret == 0 def reboot(name, provision=False): """ Reboot a VM. (vagrant reload) CLI Example: .. code-block:: bash salt <host> vagrant.reboot <salt_id> provision=True :param name: The salt_id name you will use to control this VM :param provision: (False) also re-run the Vagrant provisioning scripts. """ vm_ = get_vm_info(name) machine = vm_["machine"] prov = "--provision" if provision else "" cmd = f"vagrant reload {machine} {prov}" ret = __salt__["cmd.retcode"](cmd, runas=vm_.get("runas"), cwd=vm_.get("cwd")) return ret == 0 def destroy(name): """ Destroy and delete a virtual machine. (vagrant destroy -f) This also removes the salt_id name defined by vagrant.init. CLI Example: .. code-block:: bash salt <host> vagrant.destroy <salt_id> """ vm_ = get_vm_info(name) machine = vm_["machine"] cmd = f"vagrant destroy -f {machine}" ret = __salt__["cmd.run_all"]( cmd, runas=vm_.get("runas"), cwd=vm_.get("cwd"), output_loglevel="info" ) if ret["retcode"] == 0: _erase_vm_info(name) return f"Destroyed VM {name}" return False def get_ssh_config(name, network_mask="", get_private_key=False): r""" Retrieve hints of how you might connect to a Vagrant VM. :param name: the salt_id of the machine :param network_mask: a CIDR mask to search for the VM's address :param get_private_key: (default: False) return the key used for ssh login :return: a dict of ssh login information for the VM CLI Example: .. code-block:: bash salt <host> vagrant.get_ssh_config <salt_id> salt my_laptop vagrant.get_ssh_config quail1 network_mask=10.0.0.0/8 get_private_key=True The returned dictionary contains: - key_filename: the name of the private key file on the VM host computer - ssh_username: the username to be used to log in to the VM - ssh_host: the IP address used to log in to the VM. (This will usually be `127.0.0.1`) - ssh_port: the TCP port used to log in to the VM. (This will often be `2222`) - \[ip_address:\] (if `network_mask` is defined. see below) - \[private_key:\] (if `get_private_key` is True) the private key for ssh_username About `network_mask`: Vagrant usually uses a redirected TCP port on its host computer to log in to a VM using ssh. This redirected port and its IP address are "ssh_port" and "ssh_host". The ssh_host is usually the localhost (127.0.0.1). This makes it impossible for a third machine (such as a salt-cloud master) to contact the VM unless the VM has another network interface defined. You will usually want a bridged network defined by having a `config.vm.network "public_network"` statement in your `Vagrantfile`. The IP address of the bridged adapter will typically be assigned by DHCP and unknown to you, but you should be able to determine what IP network the address will be chosen from. If you enter a CIDR network mask, Salt will attempt to find the VM's address for you. The host machine will send an "ifconfig" command to the VM (using ssh to `ssh_host`:`ssh_port`) and return the IP address of the first interface it can find which matches your mask. """ vm_ = get_vm_info(name) ssh_config = _vagrant_ssh_config(vm_) try: ans = { "key_filename": ssh_config["IdentityFile"], "ssh_username": ssh_config["User"], "ssh_host": ssh_config["HostName"], "ssh_port": ssh_config["Port"], } except KeyError: raise CommandExecutionError( "Insufficient SSH information to contact VM {}. Is it running?".format( vm_.get("machine", "(default)") ) ) if network_mask: # ask the new VM to report its network address command = ( "ssh -i {IdentityFile} -p {Port} " "-oStrictHostKeyChecking={StrictHostKeyChecking} " "-oUserKnownHostsFile={UserKnownHostsFile} " "-oControlPath=none " "{User}@{HostName} ifconfig".format(**ssh_config) ) log.info("Trying ssh -p %(Port)s %(User)s@%(HostName)s ifconfig", ssh_config) reply = __salt__["cmd.shell"](command) log.info("--->\n%s", reply) target_network_range = ipaddress.ip_network(network_mask, strict=False) for line in reply.split("\n"): try: # try to find a bridged network address # the lines we are looking for appear like: # "inet addr:10.124.31.185 Bcast:10.124.31.255 Mask:255.255.248.0" # or "inet6 addr: fe80::a00:27ff:fe04:7aac/64 Scope:Link" tokens = line.replace( "addr:", "", 1 ).split() # remove "addr:" if it exists, then split found_address = None if "inet" in tokens: nxt = tokens.index("inet") + 1 found_address = ipaddress.ip_address(tokens[nxt]) elif "inet6" in tokens: nxt = tokens.index("inet6") + 1 found_address = ipaddress.ip_address(tokens[nxt].split("/")[0]) if found_address in target_network_range: ans["ip_address"] = str(found_address) break # we have located a good matching address except (IndexError, AttributeError, TypeError): pass # all syntax and type errors loop here # falling out if the loop leaves us remembering the last candidate log.info( "Network IP address in %s detected as: %s", target_network_range, ans.get("ip_address", "(not found)"), ) if get_private_key: # retrieve the Vagrant private key from the host try: with salt.utils.files.fopen(ssh_config["IdentityFile"]) as pks: ans["private_key"] = salt.utils.stringutils.to_unicode(pks.read()) except OSError as e: raise CommandExecutionError( f"Error processing Vagrant private key file: {e}" ) return ans