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/boto_asg.py
""" Connection module for Amazon Autoscale Groups .. versionadded:: 2014.7.0 :configuration: This module accepts explicit autoscale credentials but can also utilize IAM roles assigned to the instance through Instance Profiles. Dynamic credentials are then automatically obtained from AWS API and no further configuration is necessary. More Information available at: .. code-block:: text http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html If IAM roles are not used you need to specify them either in a pillar or in the minion's config file: .. code-block:: yaml asg.keyid: GKTADJGHEIQSXMKKRBJ08H asg.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs A region may also be specified in the configuration: .. code-block:: yaml asg.region: us-east-1 If a region is not specified, the default is us-east-1. It's also possible to specify key, keyid and region via a profile, either as a passed in dict, or as a string to pull from pillars or minion config: .. code-block:: yaml myprofile: keyid: GKTADJGHEIQSXMKKRBJ08H key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs region: us-east-1 :depends: boto :depends: boto3 """ # keep lint from choking on _get_conn and _cache_id # pylint: disable=E0602 import datetime import email.mime.multipart import logging import sys import time import salt.utils.compat import salt.utils.json import salt.utils.odict as odict import salt.utils.versions log = logging.getLogger(__name__) DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" try: import boto import boto.ec2 import boto.ec2.autoscale as autoscale import boto.ec2.blockdevicemapping as blockdevicemapping import boto.ec2.instance logging.getLogger("boto").setLevel(logging.CRITICAL) import boto3 # pylint: disable=unused-import from botocore.exceptions import ClientError logging.getLogger("boto3").setLevel(logging.CRITICAL) HAS_BOTO = True except ImportError: HAS_BOTO = False def __virtual__(): """ Only load if boto libraries exist. """ has_boto_reqs = salt.utils.versions.check_boto_reqs() if has_boto_reqs is True: __utils__["boto.assign_funcs"]( __name__, "asg", module="ec2.autoscale", pack=__salt__ ) setattr( sys.modules[__name__], "_get_ec2_conn", __utils__["boto.get_connection_func"]("ec2"), ) return has_boto_reqs def __init__(opts): if HAS_BOTO: __utils__["boto3.assign_funcs"]( __name__, "autoscaling", get_conn_funcname="_get_conn_autoscaling_boto3" ) def exists(name, region=None, key=None, keyid=None, profile=None): """ Check to see if an autoscale group exists. CLI Example: .. code-block:: bash salt myminion boto_asg.exists myasg region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: _conn = conn.get_all_groups(names=[name]) if _conn: return True else: msg = f"The autoscale group does not exist in region {region}" log.debug(msg) return False except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) return False def get_config(name, region=None, key=None, keyid=None, profile=None): """ Get the configuration for an autoscale group. CLI Example: .. code-block:: bash salt myminion boto_asg.get_config myasg region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: asg = conn.get_all_groups(names=[name]) if asg: asg = asg[0] else: return {} ret = odict.OrderedDict() attrs = [ "name", "availability_zones", "default_cooldown", "desired_capacity", "health_check_period", "health_check_type", "launch_config_name", "load_balancers", "max_size", "min_size", "placement_group", "vpc_zone_identifier", "tags", "termination_policies", "suspended_processes", ] for attr in attrs: # Tags are objects, so we need to turn them into dicts. if attr == "tags": _tags = [] for tag in asg.tags: _tag = odict.OrderedDict() _tag["key"] = tag.key _tag["value"] = tag.value _tag["propagate_at_launch"] = tag.propagate_at_launch _tags.append(_tag) ret["tags"] = _tags # Boto accepts a string or list as input for vpc_zone_identifier, # but always returns a comma separated list. We require lists in # states. elif attr == "vpc_zone_identifier": ret[attr] = getattr(asg, attr).split(",") # convert SuspendedProcess objects to names elif attr == "suspended_processes": suspended_processes = getattr(asg, attr) ret[attr] = sorted(x.process_name for x in suspended_processes) else: ret[attr] = getattr(asg, attr) # scaling policies policies = conn.get_all_policies(as_group=name) ret["scaling_policies"] = [] for policy in policies: ret["scaling_policies"].append( dict( [ ("name", policy.name), ("adjustment_type", policy.adjustment_type), ("scaling_adjustment", policy.scaling_adjustment), ("min_adjustment_step", policy.min_adjustment_step), ("cooldown", policy.cooldown), ] ) ) # scheduled actions actions = conn.get_all_scheduled_actions(as_group=name) ret["scheduled_actions"] = {} for action in actions: end_time = None if action.end_time: end_time = action.end_time.isoformat() ret["scheduled_actions"][action.name] = dict( [ ("min_size", action.min_size), ("max_size", action.max_size), # AWS bug ("desired_capacity", int(action.desired_capacity)), ("start_time", action.start_time.isoformat()), ("end_time", end_time), ("recurrence", action.recurrence), ] ) return ret except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) return {} def create( name, launch_config_name, availability_zones, min_size, max_size, desired_capacity=None, load_balancers=None, default_cooldown=None, health_check_type=None, health_check_period=None, placement_group=None, vpc_zone_identifier=None, tags=None, termination_policies=None, suspended_processes=None, scaling_policies=None, scheduled_actions=None, region=None, notification_arn=None, notification_types=None, key=None, keyid=None, profile=None, ): """ Create an autoscale group. CLI Example: .. code-block:: bash salt myminion boto_asg.create myasg mylc '["us-east-1a", "us-east-1e"]' 1 10 load_balancers='["myelb", "myelb2"]' tags='[{"key": "Name", value="myasg", "propagate_at_launch": True}]' """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if isinstance(availability_zones, str): availability_zones = salt.utils.json.loads(availability_zones) if isinstance(load_balancers, str): load_balancers = salt.utils.json.loads(load_balancers) if isinstance(vpc_zone_identifier, str): vpc_zone_identifier = salt.utils.json.loads(vpc_zone_identifier) if isinstance(tags, str): tags = salt.utils.json.loads(tags) # Make a list of tag objects from the dict. _tags = [] if tags: for tag in tags: try: key = tag.get("key") except KeyError: log.error("Tag missing key.") return False try: value = tag.get("value") except KeyError: log.error("Tag missing value.") return False propagate_at_launch = tag.get("propagate_at_launch", False) _tag = autoscale.Tag( key=key, value=value, resource_id=name, propagate_at_launch=propagate_at_launch, ) _tags.append(_tag) if isinstance(termination_policies, str): termination_policies = salt.utils.json.loads(termination_policies) if isinstance(suspended_processes, str): suspended_processes = salt.utils.json.loads(suspended_processes) if isinstance(scheduled_actions, str): scheduled_actions = salt.utils.json.loads(scheduled_actions) retries = 30 while True: try: _asg = autoscale.AutoScalingGroup( name=name, launch_config=launch_config_name, availability_zones=availability_zones, min_size=min_size, max_size=max_size, desired_capacity=desired_capacity, load_balancers=load_balancers, default_cooldown=default_cooldown, health_check_type=health_check_type, health_check_period=health_check_period, placement_group=placement_group, tags=_tags, vpc_zone_identifier=vpc_zone_identifier, termination_policies=termination_policies, suspended_processes=suspended_processes, ) conn.create_auto_scaling_group(_asg) # create scaling policies _create_scaling_policies(conn, name, scaling_policies) # create scheduled actions _create_scheduled_actions(conn, name, scheduled_actions) # create notifications if notification_arn and notification_types: conn.put_notification_configuration( _asg, notification_arn, notification_types ) log.info("Created ASG %s", name) return True except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) msg = "Failed to create ASG %s", name log.error(msg) return False def update( name, launch_config_name, availability_zones, min_size, max_size, desired_capacity=None, load_balancers=None, default_cooldown=None, health_check_type=None, health_check_period=None, placement_group=None, vpc_zone_identifier=None, tags=None, termination_policies=None, suspended_processes=None, scaling_policies=None, scheduled_actions=None, notification_arn=None, notification_types=None, region=None, key=None, keyid=None, profile=None, ): """ Update an autoscale group. CLI Example: .. code-block:: bash salt myminion boto_asg.update myasg mylc '["us-east-1a", "us-east-1e"]' 1 10 load_balancers='["myelb", "myelb2"]' tags='[{"key": "Name", value="myasg", "propagate_at_launch": True}]' """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) conn3 = _get_conn_autoscaling_boto3( region=region, key=key, keyid=keyid, profile=profile ) if not conn: return False, "failed to connect to AWS" if isinstance(availability_zones, str): availability_zones = salt.utils.json.loads(availability_zones) if isinstance(load_balancers, str): load_balancers = salt.utils.json.loads(load_balancers) if isinstance(vpc_zone_identifier, str): vpc_zone_identifier = salt.utils.json.loads(vpc_zone_identifier) if isinstance(tags, str): tags = salt.utils.json.loads(tags) if isinstance(termination_policies, str): termination_policies = salt.utils.json.loads(termination_policies) if isinstance(suspended_processes, str): suspended_processes = salt.utils.json.loads(suspended_processes) if isinstance(scheduled_actions, str): scheduled_actions = salt.utils.json.loads(scheduled_actions) # Massage our tagset into add / remove lists # Use a boto3 call here b/c the boto2 call doeesn't implement filters current_tags = conn3.describe_tags( Filters=[{"Name": "auto-scaling-group", "Values": [name]}] ).get("Tags", []) current_tags = [ { "key": t["Key"], "value": t["Value"], "resource_id": t["ResourceId"], "propagate_at_launch": t.get("PropagateAtLaunch", False), } for t in current_tags ] add_tags = [] desired_tags = [] if tags: tags = __utils__["boto3.ordered"](tags) for tag in tags: try: key = tag.get("key") except KeyError: log.error("Tag missing key.") return False, f"Tag {tag} missing key" try: value = tag.get("value") except KeyError: log.error("Tag missing value.") return False, f"Tag {tag} missing value" propagate_at_launch = tag.get("propagate_at_launch", False) _tag = { "key": key, "value": value, "resource_id": name, "propagate_at_launch": propagate_at_launch, } if _tag not in current_tags: add_tags.append(_tag) desired_tags.append(_tag) delete_tags = [t for t in current_tags if t not in desired_tags] retries = 30 while True: try: _asg = autoscale.AutoScalingGroup( connection=conn, name=name, launch_config=launch_config_name, availability_zones=availability_zones, min_size=min_size, max_size=max_size, desired_capacity=desired_capacity, load_balancers=load_balancers, default_cooldown=default_cooldown, health_check_type=health_check_type, health_check_period=health_check_period, placement_group=placement_group, tags=add_tags, vpc_zone_identifier=vpc_zone_identifier, termination_policies=termination_policies, ) if notification_arn and notification_types: conn.put_notification_configuration( _asg, notification_arn, notification_types ) _asg.update() # Seems the update call doesn't handle tags, so we'll need to update # that separately. if add_tags: log.debug("Adding/updating tags from ASG: %s", add_tags) conn.create_or_update_tags([autoscale.Tag(**t) for t in add_tags]) if delete_tags: log.debug("Deleting tags from ASG: %s", delete_tags) conn.delete_tags([autoscale.Tag(**t) for t in delete_tags]) # update doesn't handle suspended_processes either # Resume all processes _asg.resume_processes() # suspend any that are specified. Note that the boto default of empty # list suspends all; don't do that. if suspended_processes: _asg.suspend_processes(suspended_processes) log.info("Updated ASG %s", name) # ### scaling policies # delete all policies, then recreate them for policy in conn.get_all_policies(as_group=name): conn.delete_policy(policy.name, autoscale_group=name) _create_scaling_policies(conn, name, scaling_policies) # ### scheduled actions # delete all scheduled actions, then recreate them for scheduled_action in conn.get_all_scheduled_actions(as_group=name): conn.delete_scheduled_action( scheduled_action.name, autoscale_group=name ) _create_scheduled_actions(conn, name, scheduled_actions) return True, "" except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) msg = f"Failed to update ASG {name}" log.error(msg) return False, str(e) def _create_scaling_policies(conn, as_name, scaling_policies): "helper function to create scaling policies" if scaling_policies: for policy in scaling_policies: policy = autoscale.policy.ScalingPolicy( name=policy["name"], as_name=as_name, adjustment_type=policy["adjustment_type"], scaling_adjustment=policy["scaling_adjustment"], min_adjustment_step=policy.get("min_adjustment_step", None), cooldown=policy["cooldown"], ) conn.create_scaling_policy(policy) def _create_scheduled_actions(conn, as_name, scheduled_actions): """ Helper function to create scheduled actions """ if scheduled_actions: for name, action in scheduled_actions.items(): if "start_time" in action and isinstance(action["start_time"], str): action["start_time"] = datetime.datetime.strptime( action["start_time"], DATE_FORMAT ) if "end_time" in action and isinstance(action["end_time"], str): action["end_time"] = datetime.datetime.strptime( action["end_time"], DATE_FORMAT ) conn.create_scheduled_group_action( as_name, name, desired_capacity=action.get("desired_capacity"), min_size=action.get("min_size"), max_size=action.get("max_size"), start_time=action.get("start_time"), end_time=action.get("end_time"), recurrence=action.get("recurrence"), ) def delete(name, force=False, region=None, key=None, keyid=None, profile=None): """ Delete an autoscale group. CLI Example: .. code-block:: bash salt myminion boto_asg.delete myasg region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: conn.delete_auto_scaling_group(name, force) msg = f"Deleted autoscale group {name}." log.info(msg) return True except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) msg = f"Failed to delete autoscale group {name}" log.error(msg) return False def get_cloud_init_mime(cloud_init): """ Get a mime multipart encoded string from a cloud-init dict. Currently supports boothooks, scripts and cloud-config. CLI Example: .. code-block:: bash salt myminion boto.get_cloud_init_mime <cloud init> """ if isinstance(cloud_init, str): cloud_init = salt.utils.json.loads(cloud_init) _cloud_init = email.mime.multipart.MIMEMultipart() if "boothooks" in cloud_init: for script_name, script in cloud_init["boothooks"].items(): _script = email.mime.text.MIMEText(script, "cloud-boothook") _cloud_init.attach(_script) if "scripts" in cloud_init: for script_name, script in cloud_init["scripts"].items(): _script = email.mime.text.MIMEText(script, "x-shellscript") _cloud_init.attach(_script) if "cloud-config" in cloud_init: cloud_config = cloud_init["cloud-config"] _cloud_config = email.mime.text.MIMEText( salt.utils.yaml.safe_dump(cloud_config, default_flow_style=False), "cloud-config", ) _cloud_init.attach(_cloud_config) return _cloud_init.as_string() def launch_configuration_exists(name, region=None, key=None, keyid=None, profile=None): """ Check for a launch configuration's existence. CLI Example: .. code-block:: bash salt myminion boto_asg.launch_configuration_exists mylc """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: lc = conn.get_all_launch_configurations(names=[name]) if lc: return True else: msg = "The launch configuration does not exist in region {}".format( region ) log.debug(msg) return False except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) return False def get_all_launch_configurations(region=None, key=None, keyid=None, profile=None): """ Fetch and return all Launch Configuration with details. CLI Example: .. code-block:: bash salt myminion boto_asg.get_all_launch_configurations """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: return conn.get_all_launch_configurations() except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) return [] def list_launch_configurations(region=None, key=None, keyid=None, profile=None): """ List all Launch Configurations. CLI Example: .. code-block:: bash salt myminion boto_asg.list_launch_configurations """ ret = get_all_launch_configurations(region, key, keyid, profile) return [r.name for r in ret] def describe_launch_configuration( name, region=None, key=None, keyid=None, profile=None ): """ Dump details of a given launch configuration. CLI Example: .. code-block:: bash salt myminion boto_asg.describe_launch_configuration mylc """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: lc = conn.get_all_launch_configurations(names=[name]) if lc: return lc[0] else: msg = "The launch configuration does not exist in region {}".format( region ) log.debug(msg) return None except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) return None def create_launch_configuration( name, image_id, key_name=None, vpc_id=None, vpc_name=None, security_groups=None, user_data=None, instance_type="m1.small", kernel_id=None, ramdisk_id=None, block_device_mappings=None, instance_monitoring=False, spot_price=None, instance_profile_name=None, ebs_optimized=False, associate_public_ip_address=None, volume_type=None, delete_on_termination=True, iops=None, use_block_device_types=False, region=None, key=None, keyid=None, profile=None, ): """ Create a launch configuration. CLI Example: .. code-block:: bash salt myminion boto_asg.create_launch_configuration mylc image_id=ami-0b9c9f62 key_name='mykey' security_groups='["mygroup"]' instance_type='c3.2xlarge' """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if isinstance(security_groups, str): security_groups = salt.utils.json.loads(security_groups) if isinstance(block_device_mappings, str): block_device_mappings = salt.utils.json.loads(block_device_mappings) _bdms = [] if block_device_mappings: # Boto requires objects for the mappings and the devices. _block_device_map = blockdevicemapping.BlockDeviceMapping() for block_device_dict in block_device_mappings: for block_device, attributes in block_device_dict.items(): _block_device = blockdevicemapping.EBSBlockDeviceType() for attribute, value in attributes.items(): setattr(_block_device, attribute, value) _block_device_map[block_device] = _block_device _bdms = [_block_device_map] # If a VPC is specified, then determine the secgroup id's within that VPC, not # within the default VPC. If a security group id is already part of the list, # convert_to_group_ids leaves that entry without attempting a lookup on it. if security_groups and (vpc_id or vpc_name): security_groups = __salt__["boto_secgroup.convert_to_group_ids"]( security_groups, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) lc = autoscale.LaunchConfiguration( name=name, image_id=image_id, key_name=key_name, security_groups=security_groups, user_data=user_data, instance_type=instance_type, kernel_id=kernel_id, ramdisk_id=ramdisk_id, block_device_mappings=_bdms, instance_monitoring=instance_monitoring, spot_price=spot_price, instance_profile_name=instance_profile_name, ebs_optimized=ebs_optimized, associate_public_ip_address=associate_public_ip_address, volume_type=volume_type, delete_on_termination=delete_on_termination, iops=iops, use_block_device_types=use_block_device_types, ) retries = 30 while True: try: conn.create_launch_configuration(lc) log.info("Created LC %s", name) return True except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) msg = f"Failed to create LC {name}" log.error(msg) return False def delete_launch_configuration(name, region=None, key=None, keyid=None, profile=None): """ Delete a launch configuration. CLI Example: .. code-block:: bash salt myminion boto_asg.delete_launch_configuration mylc """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: conn.delete_launch_configuration(name) log.info("Deleted LC %s", name) return True except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) msg = f"Failed to delete LC {name}" log.error(msg) return False def get_scaling_policy_arn( as_group, scaling_policy_name, region=None, key=None, keyid=None, profile=None ): """ Return the arn for a scaling policy in a specific autoscale group or None if not found. Mainly used as a helper method for boto_cloudwatch_alarm, for linking alarms to scaling policies. CLI Example: .. code-block:: bash salt '*' boto_asg.get_scaling_policy_arn mygroup mypolicy """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while retries > 0: retries -= 1 try: policies = conn.get_all_policies(as_group=as_group) for policy in policies: if policy.name == scaling_policy_name: return policy.policy_arn log.error("Could not convert: %s", as_group) return None except boto.exception.BotoServerError as e: if e.error_code != "Throttling": raise log.debug("Throttled by API, will retry in 5 seconds") time.sleep(5) log.error("Maximum number of retries exceeded") return None def get_all_groups(region=None, key=None, keyid=None, profile=None): """ Return all AutoScale Groups visible in the account (as a list of boto.ec2.autoscale.group.AutoScalingGroup). .. versionadded:: 2016.11.0 CLI Example: .. code-block:: bash salt-call boto_asg.get_all_groups region=us-east-1 --output yaml """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: next_token = "" asgs = [] while next_token is not None: ret = conn.get_all_groups(next_token=next_token) asgs += [a for a in ret] next_token = ret.next_token return asgs except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) return [] def list_groups(region=None, key=None, keyid=None, profile=None): """ Return all AutoScale Groups visible in the account (as a list of names). .. versionadded:: 2016.11.0 CLI Example: .. code-block:: bash salt-call boto_asg.list_groups region=us-east-1 """ return [ a.name for a in get_all_groups(region=region, key=key, keyid=keyid, profile=profile) ] def get_instances( name, lifecycle_state="InService", health_status="Healthy", attribute="private_ip_address", attributes=None, region=None, key=None, keyid=None, profile=None, ): """ return attribute of all instances in the named autoscale group. CLI Example: .. code-block:: bash salt-call boto_asg.get_instances my_autoscale_group_name """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) ec2_conn = _get_ec2_conn(region=region, key=key, keyid=keyid, profile=profile) retries = 30 while True: try: asgs = conn.get_all_groups(names=[name]) break except boto.exception.BotoServerError as e: if retries and e.code == "Throttling": log.debug("Throttled by AWS API, retrying in 5 seconds...") time.sleep(5) retries -= 1 continue log.error(e) return False if len(asgs) != 1: log.debug( "name '%s' returns multiple ASGs: %s", name, [asg.name for asg in asgs] ) return False asg = asgs[0] instance_ids = [] # match lifecycle_state and health_status for i in asg.instances: if lifecycle_state is not None and i.lifecycle_state != lifecycle_state: continue if health_status is not None and i.health_status != health_status: continue instance_ids.append(i.instance_id) # get full instance info, so that we can return the attribute instances = ec2_conn.get_only_instances(instance_ids=instance_ids) if attributes: return [ [_convert_attribute(instance, attr) for attr in attributes] for instance in instances ] else: # properly handle case when not all instances have the requested attribute return [ _convert_attribute(instance, attribute) for instance in instances if getattr(instance, attribute) ] def _convert_attribute(instance, attribute): if attribute == "tags": tags = dict(getattr(instance, attribute)) return { key.encode("utf-8"): value.encode("utf-8") for key, value in tags.items() } return getattr(instance, attribute).encode("ascii") def enter_standby( name, instance_ids, should_decrement_desired_capacity=False, region=None, key=None, keyid=None, profile=None, ): """ Switch desired instances to StandBy mode .. versionadded:: 2016.11.0 CLI Example: .. code-block:: bash salt-call boto_asg.enter_standby my_autoscale_group_name '["i-xxxxxx"]' """ conn = _get_conn_autoscaling_boto3( region=region, key=key, keyid=keyid, profile=profile ) try: response = conn.enter_standby( InstanceIds=instance_ids, AutoScalingGroupName=name, ShouldDecrementDesiredCapacity=should_decrement_desired_capacity, ) except ClientError as e: err = __utils__["boto3.get_error"](e) if e.response.get("Error", {}).get("Code") == "ResourceNotFoundException": return {"exists": False} return {"error": err} return all( activity["StatusCode"] != "Failed" for activity in response["Activities"] ) def exit_standby( name, instance_ids, should_decrement_desired_capacity=False, region=None, key=None, keyid=None, profile=None, ): """ Exit desired instances from StandBy mode .. versionadded:: 2016.11.0 CLI Example: .. code-block:: bash salt-call boto_asg.exit_standby my_autoscale_group_name '["i-xxxxxx"]' """ conn = _get_conn_autoscaling_boto3( region=region, key=key, keyid=keyid, profile=profile ) try: response = conn.exit_standby( InstanceIds=instance_ids, AutoScalingGroupName=name ) except ClientError as e: err = __utils__["boto3.get_error"](e) if e.response.get("Error", {}).get("Code") == "ResourceNotFoundException": return {"exists": False} return {"error": err} return all( activity["StatusCode"] != "Failed" for activity in response["Activities"] )