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/heat.py
""" Module for handling OpenStack Heat calls .. versionadded:: 2017.7.0 :depends: - heatclient Python module :configuration: This module is not usable until the user, password, tenant, and auth URL are specified either in a pillar or in the minion's config file. For example:: keystone.user: admin keystone.password: verybadpass keystone.tenant: admin keystone.insecure: False #(optional) keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' # Optional keystone.region_name: 'RegionOne' If configuration for multiple OpenStack accounts is required, they can be set up as different configuration profiles: For example:: openstack1: keystone.user: admin keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' openstack2: keystone.user: admin keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.2:5000/v2.0/' With this configuration in place, any of the heat functions can make use of a configuration profile by declaring it explicitly. For example:: salt '*' heat.flavor_list profile=openstack1 """ import logging import time import salt.utils.files import salt.utils.json import salt.utils.stringutils import salt.utils.versions import salt.utils.yaml from salt.exceptions import SaltInvocationError # pylint: disable=import-error HAS_HEAT = False try: import heatclient HAS_HEAT = True except ImportError: pass HAS_OSLO = False try: from oslo_serialization import jsonutils HAS_OSLO = True except ImportError: pass SECTIONS = (PARAMETER_DEFAULTS, PARAMETERS, RESOURCE_REGISTRY, EVENT_SINKS) = ( "parameter_defaults", "parameters", "resource_registry", "event_sinks", ) logging.basicConfig(level=logging.DEBUG) log = logging.getLogger(__name__) def __virtual__(): """ Only load this module if heat is installed on this minion. """ if HAS_HEAT and HAS_OSLO: return "heat" return ( False, "The heat execution module cannot be loaded: " "the heatclient and oslo_serialization" # 'the heatclient and keystoneclient and oslo_serialization' " python library is not available.", ) def _auth(profile=None, api_version=1, **connection_args): """ Set up heat credentials, returns `heatclient.client.Client`. Optional parameter "api_version" defaults to 1. Only intended to be used within heat-enabled modules """ if profile: prefix = profile + ":keystone." else: prefix = "keystone." def get(key, default=None): """ Checks connection_args, then salt-minion config, falls back to specified default value. """ return connection_args.get( "connection_" + key, __salt__["config.get"](prefix + key, default) ) user = get("user", "admin") password = get("password", None) tenant = get("tenant", "admin") tenant_id = get("tenant_id") auth_url = get("auth_url", "http://127.0.0.1:35357/v2.0") insecure = get("insecure", False) admin_token = get("token") region_name = get("region_name", None) if admin_token and api_version != 1 and not password: # If we had a password we could just # ignore the admin-token and move on... raise SaltInvocationError( "Only can use keystone admin token " + "with Heat API v1" ) elif password: # Can't use the admin-token anyway kwargs = { "username": user, "password": password, "tenant_id": tenant_id, "auth_url": auth_url, "region_name": region_name, "tenant_name": tenant, } # 'insecure' keyword not supported by all v2.0 keystone clients # this ensures it's only passed in when defined if insecure: kwargs["insecure"] = True elif api_version == 1 and admin_token: kwargs = {"token": admin_token, "auth_url": auth_url} else: raise SaltInvocationError("No credentials to authenticate with.") token = __salt__["keystone.token_get"](profile) kwargs["token"] = token["id"] # This doesn't realy prevent the password to show up # in the minion log as keystoneclient.session is # logging it anyway when in debug-mode kwargs.pop("password") try: heat_endpoint = __salt__["keystone.endpoint_get"]("heat", profile)["url"] except KeyError: heat_endpoint = __salt__["keystone.endpoint_get"]("heat", profile)["publicurl"] heat_endpoint = heat_endpoint % token log.debug( "Calling heatclient.client.Client(%s, %s, **%s)", api_version, heat_endpoint, kwargs, ) # may raise exc.HTTPUnauthorized, exc.HTTPNotFound # but we deal with those elsewhere return heatclient.client.Client(api_version, endpoint=heat_endpoint, **kwargs) def _parse_template(tmpl_str): """ Parsing template """ tmpl_str = tmpl_str.strip() if tmpl_str.startswith("{"): tpl = salt.utils.json.loads(tmpl_str) else: try: tpl = salt.utils.yaml.safe_load(tmpl_str) except salt.utils.yaml.YAMLError as exc: raise ValueError(str(exc)) else: if tpl is None: tpl = {} if not ( "HeatTemplateFormatVersion" in tpl or "heat_template_version" in tpl or "AWSTemplateFormatVersion" in tpl ): raise ValueError("Template format version not found.") return tpl def _parse_environment(env_str): """ Parsing template """ try: env = salt.utils.yaml.safe_load(env_str) except salt.utils.yaml.YAMLError as exc: raise ValueError(str(exc)) else: if env is None: env = {} elif not isinstance(env, dict): raise ValueError("The environment is not a valid YAML mapping data type.") for param in env: if param not in SECTIONS: raise ValueError(f'environment has wrong section "{param}"') return env def _get_stack_events(h_client, stack_id, event_args): """ Get event for stack """ event_args["stack_id"] = stack_id event_args["resource_name"] = None try: events = h_client.events.list(**event_args) except heatclient.exc.HTTPNotFound as exc: raise heatclient.exc.CommandError(str(exc)) else: for event in events: event.stack_name = stack_id.split("/")[0] return events def _poll_for_events( h_client, stack_name, action=None, poll_period=5, timeout=60, marker=None ): """ Polling stack events """ if action: stop_status = (f"{action}_FAILED", f"{action}_COMPLETE") def stop_check(a): return a in stop_status else: def stop_check(a): return a.endswith("_COMPLETE") or a.endswith("_FAILED") timeout_sec = timeout * 60 no_event_polls = 0 msg_template = "\n Stack %(name)s %(status)s \n" while True: events = _get_stack_events( h_client, stack_id=stack_name, event_args={"sort_dir": "asc", "marker": marker}, ) if not events: no_event_polls += 1 else: no_event_polls = 0 # set marker to last event that was received. marker = getattr(events[-1], "id", None) for event in events: # check if stack event was also received if getattr(event, "resource_name", "") == stack_name: stack_status = getattr(event, "resource_status", "") msg = msg_template % dict(name=stack_name, status=stack_status) if stop_check(stack_status): return stack_status, msg if no_event_polls >= 2: # after 2 polls with no events, fall back to a stack get stack = h_client.stacks.get(stack_name) stack_status = stack.stack_status msg = msg_template % dict(name=stack_name, status=stack_status) if stop_check(stack_status): return stack_status, msg # go back to event polling again no_event_polls = 0 time.sleep(poll_period) timeout_sec -= poll_period if timeout_sec <= 0: stack_status = f"{action}_FAILED" msg = "Timeout expired" return stack_status, msg def list_stack(profile=None): """ Return a list of available stack (heat stack-list) profile Profile to use CLI Example: .. code-block:: bash salt '*' heat.list_stack profile=openstack1 """ ret = {} h_client = _auth(profile) for stack in h_client.stacks.list(): links = {} for link in stack.links: links[link["rel"]] = link["href"] ret[stack.stack_name] = { "status": stack.stack_status, "id": stack.id, "name": stack.stack_name, "creation": stack.creation_time, "owner": stack.stack_owner, "reason": stack.stack_status_reason, "links": links, } return ret def show_stack(name=None, profile=None): """ Return details about a specific stack (heat stack-show) name Name of the stack profile Profile to use CLI Example: .. code-block:: bash salt '*' heat.show_stack name=mystack profile=openstack1 """ h_client = _auth(profile) if not name: return {"result": False, "comment": "Parameter name missing or None"} try: ret = {} stack = h_client.stacks.get(name) links = {} for link in stack.links: links[link["rel"]] = link["href"] ret[stack.stack_name] = { "status": stack.stack_status, "id": stack.id, "name": stack.stack_name, "creation": stack.creation_time, "owner": stack.stack_owner, "reason": stack.stack_status_reason, "parameters": stack.parameters, "links": links, } ret["result"] = True except heatclient.exc.HTTPNotFound: return {"result": False, "comment": f"No stack {name}"} return ret def delete_stack(name=None, poll=0, timeout=60, profile=None): """ Delete a stack (heat stack-delete) name Name of the stack poll Poll and report events until stack complete timeout Stack creation timeout in minute profile Profile to use CLI Examples: .. code-block:: bash salt '*' heat.delete_stack name=mystack poll=5 \\ profile=openstack1 """ h_client = _auth(profile) ret = {"result": True, "comment": ""} if not name: ret["result"] = False ret["comment"] = "Parameter name missing or None" return ret try: h_client.stacks.delete(name) except heatclient.exc.HTTPNotFound: ret["result"] = False ret["comment"] = f"No stack {name}" except heatclient.exc.HTTPForbidden as forbidden: log.exception(forbidden) ret["result"] = False ret["comment"] = str(forbidden) if ret["result"] is False: return ret if poll > 0: try: stack_status, msg = _poll_for_events( h_client, name, action="DELETE", poll_period=poll, timeout=timeout ) except heatclient.exc.CommandError: ret["comment"] = f"Deleted stack {name}." return ret except Exception as ex: # pylint: disable=W0703 log.exception("Delete failed %s", ex) ret["result"] = False ret["comment"] = f"{ex}" return ret if stack_status == "DELETE_FAILED": ret["result"] = False ret["comment"] = f"Deleted stack FAILED'{name}'{msg}." else: ret["comment"] = f"Deleted stack {name}." return ret def create_stack( name=None, template_file=None, environment=None, parameters=None, poll=0, rollback=False, timeout=60, profile=None, ): """ Create a stack (heat stack-create) name Name of the new stack template_file File of template environment File of environment parameters Parameter dict used to create the stack poll Poll and report events until stack complete rollback Enable rollback on create failure timeout Stack creation timeout in minutes profile Profile to build on CLI Example: .. code-block:: bash salt '*' heat.create_stack name=mystack \\ template_file=salt://template.yaml \\ environment=salt://environment.yaml \\ parameters="{"image": "Debian 8", "flavor": "m1.small"}" \\ poll=5 rollback=False timeout=60 profile=openstack1 .. versionadded:: 2017.7.5,2018.3.1 The spelling mistake in parameter `enviroment` was corrected to `environment`. The `enviroment` spelling mistake has been removed in Salt 3000. """ h_client = _auth(profile) ret = {"result": True, "comment": ""} if not parameters: parameters = {} if template_file: template_tmp_file = salt.utils.files.mkstemp() tsfn, source_sum, comment_ = __salt__["file.get_managed"]( name=template_tmp_file, template=None, source=template_file, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) template_manage_result = __salt__["file.manage_file"]( name=template_tmp_file, sfn=tsfn, ret=None, source=template_file, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if template_manage_result["result"]: with salt.utils.files.fopen(template_tmp_file, "r") as tfp_: tpl = salt.utils.stringutils.to_unicode(tfp_.read()) salt.utils.files.safe_rm(template_tmp_file) try: template = _parse_template(tpl) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open template: {} {}".format( template_file, comment_ ) else: ret["result"] = False ret["comment"] = "Can not open template" if ret["result"] is False: return ret kwargs = {} kwargs["template"] = template try: h_client.stacks.validate(**kwargs) except Exception as ex: # pylint: disable=W0703 log.exception("Template not valid %s", ex) ret["result"] = False ret["comment"] = f"Template not valid {ex}" return ret env = {} if environment: environment_tmp_file = salt.utils.files.mkstemp() esfn, source_sum, comment_ = __salt__["file.get_managed"]( name=environment_tmp_file, template=None, source=environment, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) environment_manage_result = __salt__["file.manage_file"]( name=environment_tmp_file, sfn=esfn, ret=None, source=environment, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if environment_manage_result["result"]: with salt.utils.files.fopen(environment_tmp_file, "r") as efp_: env_str = salt.utils.stringutils.to_unicode(efp_.read()) salt.utils.files.safe_rm(environment_tmp_file) try: env = _parse_environment(env_str) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open environment: {}, {}".format( environment, comment_ ) if ret["result"] is False: return ret fields = { "stack_name": name, "disable_rollback": not rollback, "parameters": parameters, "template": template, "environment": env, "timeout_mins": timeout, } # If one or more environments is found, pass the listing to the server try: h_client.stacks.create(**fields) except Exception as ex: # pylint: disable=W0703 log.exception("Create failed %s", ex) ret["result"] = False ret["comment"] = f"{ex}" return ret if poll > 0: stack_status, msg = _poll_for_events( h_client, name, action="CREATE", poll_period=poll, timeout=timeout ) if stack_status == "CREATE_FAILED": ret["result"] = False ret["comment"] = f"Created stack FAILED'{name}'{msg}." if ret["result"] is True: ret["comment"] = f"Created stack '{name}'." return ret def update_stack( name=None, template_file=None, environment=None, parameters=None, poll=0, rollback=False, timeout=60, profile=None, ): """ Update a stack (heat stack-template) name Name of the stack template_file File of template environment File of environment parameters Parameter dict used to update the stack poll Poll and report events until stack complete rollback Enable rollback on update failure timeout Stack creation timeout in minutes profile Profile to build on CLI Example: .. code-block:: bash salt '*' heat.update_stack name=mystack \\ template_file=salt://template.yaml \\ environment=salt://environment.yaml \\ parameters="{"image": "Debian 8", "flavor": "m1.small"}" \\ poll=5 rollback=False timeout=60 profile=openstack1 .. versionadded:: 2017.7.5,2018.3.1 The spelling mistake in parameter `enviroment` was corrected to `environment`. The `enviroment` spelling mistake has been removed in Salt 3000. """ h_client = _auth(profile) ret = {"result": True, "comment": ""} if not name: ret["result"] = False ret["comment"] = "Parameter name missing or None" return ret if not parameters: parameters = {} if template_file: template_tmp_file = salt.utils.files.mkstemp() tsfn, source_sum, comment_ = __salt__["file.get_managed"]( name=template_tmp_file, template=None, source=template_file, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) template_manage_result = __salt__["file.manage_file"]( name=template_tmp_file, sfn=tsfn, ret=None, source=template_file, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if template_manage_result["result"]: with salt.utils.files.fopen(template_tmp_file, "r") as tfp_: tpl = salt.utils.stringutils.to_unicode(tfp_.read()) salt.utils.files.safe_rm(template_tmp_file) try: template = _parse_template(tpl) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open template: {} {}".format( template_file, comment_ ) else: ret["result"] = False ret["comment"] = "Can not open template" if ret["result"] is False: return ret kwargs = {} kwargs["template"] = template try: h_client.stacks.validate(**kwargs) except Exception as ex: # pylint: disable=W0703 log.exception("Template not valid %s", ex) ret["result"] = False ret["comment"] = f"Template not valid {ex}" return ret env = {} if environment: environment_tmp_file = salt.utils.files.mkstemp() esfn, source_sum, comment_ = __salt__["file.get_managed"]( name=environment_tmp_file, template=None, source=environment, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) environment_manage_result = __salt__["file.manage_file"]( name=environment_tmp_file, sfn=esfn, ret=None, source=environment, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if environment_manage_result["result"]: with salt.utils.files.fopen(environment_tmp_file, "r") as efp_: env_str = salt.utils.stringutils.to_unicode(efp_.read()) salt.utils.files.safe_rm(environment_tmp_file) try: env = _parse_environment(env_str) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open environment: {}, {}".format( environment, comment_ ) if ret["result"] is False: return ret fields = { "disable_rollback": not rollback, "parameters": parameters, "template": template, "environment": env, "timeout_mins": timeout, } try: h_client.stacks.update(name, **fields) except Exception as ex: # pylint: disable=W0703 log.exception("Update failed %s", ex) ret["result"] = False ret["comment"] = f"Update failed {ex}" return ret if poll > 0: stack_status, msg = _poll_for_events( h_client, name, action="UPDATE", poll_period=poll, timeout=timeout ) if stack_status == "UPDATE_FAILED": ret["result"] = False ret["comment"] = f"Updated stack FAILED'{name}'{msg}." if ret["result"] is True: ret["comment"] = (f"Updated stack '{name}'.",) return ret def template_stack(name=None, profile=None): """ Return template a specific stack (heat stack-template) name Name of the stack profile Profile to use CLI Example: .. code-block:: bash salt '*' heat.template_stack name=mystack profile=openstack1 """ h_client = _auth(profile) if not name: return {"result": False, "comment": "Parameter name missing or None"} try: get_template = h_client.stacks.template(name) except heatclient.exc.HTTPNotFound: return {"result": False, "comment": f"No stack with {name}"} except heatclient.exc.BadRequest: return {"result": False, "comment": f"Bad request fot stack {name}"} if "heat_template_version" in get_template: template = salt.utils.yaml.safe_dump(get_template) else: template = jsonutils.dumps(get_template, indent=2, ensure_ascii=False) checksum = __salt__["hashutil.digest"](template) ret = {"template": template, "result": True, "checksum": checksum} return ret