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/boto3_elasticache.py
""" Execution module for Amazon Elasticache using boto3 =================================================== .. versionadded:: 2017.7.0 :configuration: This module accepts explicit elasticache 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 elasticache.keyid: GKTADJGHEIQSXMKKRBJ08H elasticache.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs A region may also be specified in the configuration: .. code-block:: yaml elasticache.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: boto3 """ # keep lint from choking on _get_conn and _cache_id # pylint: disable=E0602 import logging import time import salt.utils.compat import salt.utils.versions from salt.exceptions import CommandExecutionError, SaltInvocationError log = logging.getLogger(__name__) try: # pylint: disable=unused-import import boto3 import botocore # pylint: enable=unused-import logging.getLogger("boto3").setLevel(logging.CRITICAL) HAS_BOTO3 = True except ImportError: HAS_BOTO3 = False def __virtual__(): """ Only load if boto libraries exist and if boto libraries are greater than a given version. """ return salt.utils.versions.check_boto_reqs() def __init__(opts): if HAS_BOTO3: __utils__["boto3.assign_funcs"]( __name__, "elasticache", get_conn_funcname="_get_conn", cache_id_funcname="_cache_id", exactly_one_funcname=None, ) def _collect_results(func, item, args, marker="Marker"): ret = [] Marker = args[marker] if marker in args else "" while Marker is not None: r = func(**args) ret += r.get(item) Marker = r.get(marker) args.update({marker: Marker}) return ret def _describe_resource( name=None, name_param=None, res_type=None, info_node=None, conn=None, region=None, key=None, keyid=None, profile=None, **args, ): if conn is None: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) try: func = "describe_" + res_type + "s" f = getattr(conn, func) except (AttributeError, KeyError) as e: raise SaltInvocationError(f"No function '{func}()' found: {e.message}") # Undocumented, but you can't pass 'Marker' if searching for a specific resource... args.update({name_param: name} if name else {"Marker": ""}) args = {k: v for k, v in args.items() if not k.startswith("_")} try: return _collect_results(f, info_node, args) except botocore.exceptions.ClientError as e: log.debug(e) return None def _delete_resource( name, name_param, desc, res_type, wait=0, status_param=None, status_gone="deleted", region=None, key=None, keyid=None, profile=None, **args, ): """ Delete a generic Elasticache resource. """ try: wait = int(wait) except Exception: # pylint: disable=broad-except raise SaltInvocationError( "Bad value ('{}') passed for 'wait' param - must be an " "int or boolean.".format(wait) ) conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if name_param in args: log.info( "'name: %s' param being overridden by explicitly provided '%s: %s'", name, name_param, args[name_param], ) name = args[name_param] else: args[name_param] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: func = "delete_" + res_type f = getattr(conn, func) if wait: func = "describe_" + res_type + "s" s = globals()[func] except (AttributeError, KeyError) as e: raise SaltInvocationError(f"No function '{func}()' found: {e.message}") try: f(**args) if not wait: log.info("%s %s deletion requested.", desc.title(), name) return True log.info("Waiting up to %s seconds for %s %s to be deleted.", wait, desc, name) orig_wait = wait while wait > 0: r = s(name=name, conn=conn) if not r or r[0].get(status_param) == status_gone: log.info("%s %s deleted.", desc.title(), name) return True sleep = wait if wait % 60 == wait else 60 log.info("Sleeping %s seconds for %s %s to be deleted.", sleep, desc, name) time.sleep(sleep) wait -= sleep log.error("%s %s not deleted after %s seconds!", desc.title(), name, orig_wait) return False except botocore.exceptions.ClientError as e: log.error("Failed to delete %s %s: %s", desc, name, e) return False def _create_resource( name, name_param=None, desc=None, res_type=None, wait=0, status_param=None, status_good="available", region=None, key=None, keyid=None, profile=None, **args, ): try: wait = int(wait) except Exception: # pylint: disable=broad-except raise SaltInvocationError( "Bad value ('{}') passed for 'wait' param - must be an " "int or boolean.".format(wait) ) conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if name_param in args: log.info( "'name: %s' param being overridden by explicitly provided '%s: %s'", name, name_param, args[name_param], ) name = args[name_param] else: args[name_param] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: func = "create_" + res_type f = getattr(conn, func) if wait: func = "describe_" + res_type + "s" s = globals()[func] except (AttributeError, KeyError) as e: raise SaltInvocationError(f"No function '{func}()' found: {e.message}") try: f(**args) if not wait: log.info("%s %s created.", desc.title(), name) return True log.info( "Waiting up to %s seconds for %s %s to be become available.", wait, desc, name, ) orig_wait = wait while wait > 0: r = s(name=name, conn=conn) if r and r[0].get(status_param) == status_good: log.info("%s %s created and available.", desc.title(), name) return True sleep = wait if wait % 60 == wait else 60 log.info( "Sleeping %s seconds for %s %s to become available.", sleep, desc, name ) time.sleep(sleep) wait -= sleep log.error( "%s %s not available after %s seconds!", desc.title(), name, orig_wait ) return False except botocore.exceptions.ClientError as e: msg = f"Failed to create {desc} {name}: {e}" log.error(msg) return False def _modify_resource( name, name_param=None, desc=None, res_type=None, wait=0, status_param=None, status_good="available", region=None, key=None, keyid=None, profile=None, **args, ): try: wait = int(wait) except Exception: # pylint: disable=broad-except raise SaltInvocationError( "Bad value ('{}') passed for 'wait' param - must be an " "int or boolean.".format(wait) ) conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if name_param in args: log.info( "'name: %s' param being overridden by explicitly provided '%s: %s'", name, name_param, args[name_param], ) name = args[name_param] else: args[name_param] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: func = "modify_" + res_type f = getattr(conn, func) if wait: func = "describe_" + res_type + "s" s = globals()[func] except (AttributeError, KeyError) as e: raise SaltInvocationError(f"No function '{func}()' found: {e.message}") try: f(**args) if not wait: log.info("%s %s modification requested.", desc.title(), name) return True log.info( "Waiting up to %s seconds for %s %s to be become available.", wait, desc, name, ) orig_wait = wait while wait > 0: r = s(name=name, conn=conn) if r and r[0].get(status_param) == status_good: log.info("%s %s modified and available.", desc.title(), name) return True sleep = wait if wait % 60 == wait else 60 log.info( "Sleeping %s seconds for %s %s to become available.", sleep, desc, name ) time.sleep(sleep) wait -= sleep log.error( "%s %s not available after %s seconds!", desc.title(), name, orig_wait ) return False except botocore.exceptions.ClientError as e: msg = f"Failed to modify {desc} {name}: {e}" log.error(msg) return False def describe_cache_clusters( name=None, conn=None, region=None, key=None, keyid=None, profile=None, **args ): """ Return details about all (or just one) Elasticache cache clusters. Example: .. code-block:: bash salt myminion boto3_elasticache.describe_cache_clusters salt myminion boto3_elasticache.describe_cache_clusters myelasticache """ return _describe_resource( name=name, name_param="CacheClusterId", res_type="cache_cluster", info_node="CacheClusters", conn=conn, region=region, key=key, keyid=keyid, profile=profile, **args, ) def cache_cluster_exists( name, conn=None, region=None, key=None, keyid=None, profile=None ): """ Check to see if a cache cluster exists. Example: .. code-block:: bash salt myminion boto3_elasticache.cache_cluster_exists myelasticache """ return bool( describe_cache_clusters( name=name, conn=conn, region=region, key=key, keyid=keyid, profile=profile ) ) def create_cache_cluster( name, wait=600, security_groups=None, region=None, key=None, keyid=None, profile=None, **args, ): """ Create a cache cluster. Example: .. code-block:: bash salt myminion boto3_elasticache.create_cache_cluster name=myCacheCluster \ Engine=redis \ CacheNodeType=cache.t2.micro \ NumCacheNodes=1 \ SecurityGroupIds='[sg-11223344]' \ CacheSubnetGroupName=myCacheSubnetGroup """ if security_groups: if not isinstance(security_groups, list): security_groups = [security_groups] sgs = __salt__["boto_secgroup.convert_to_group_ids"]( groups=security_groups, region=region, key=key, keyid=keyid, profile=profile ) if "SecurityGroupIds" not in args: args["SecurityGroupIds"] = [] args["SecurityGroupIds"] += sgs args = {k: v for k, v in args.items() if not k.startswith("_")} return _create_resource( name, name_param="CacheClusterId", desc="cache cluster", res_type="cache_cluster", wait=wait, status_param="CacheClusterStatus", region=region, key=key, keyid=keyid, profile=profile, **args, ) def modify_cache_cluster( name, wait=600, security_groups=None, region=None, key=None, keyid=None, profile=None, **args, ): """ Update a cache cluster in place. Notes: {ApplyImmediately: False} is pretty danged silly in the context of salt. You can pass it, but for fairly obvious reasons the results over multiple runs will be undefined and probably contrary to your desired state. Reducing the number of nodes requires an EXPLICIT CacheNodeIdsToRemove be passed, which until a reasonable heuristic for programmatically deciding which nodes to remove has been established, MUST be decided and populated intentionally before a state call, and removed again before the next. In practice this is not particularly useful and should probably be avoided. Example: .. code-block:: bash salt myminion boto3_elasticache.create_cache_cluster name=myCacheCluster \ NotificationTopicStatus=inactive """ if security_groups: if not isinstance(security_groups, list): security_groups = [security_groups] sgs = __salt__["boto_secgroup.convert_to_group_ids"]( groups=security_groups, region=region, key=key, keyid=keyid, profile=profile ) if "SecurityGroupIds" not in args: args["SecurityGroupIds"] = [] args["SecurityGroupIds"] += sgs args = {k: v for k, v in args.items() if not k.startswith("_")} return _modify_resource( name, name_param="CacheClusterId", desc="cache cluster", res_type="cache_cluster", wait=wait, status_param="CacheClusterStatus", region=region, key=key, keyid=keyid, profile=profile, **args, ) def delete_cache_cluster( name, wait=600, region=None, key=None, keyid=None, profile=None, **args ): """ Delete a cache cluster. Example: .. code-block:: bash salt myminion boto3_elasticache.delete myelasticache """ return _delete_resource( name, name_param="CacheClusterId", desc="cache cluster", res_type="cache_cluster", wait=wait, status_param="CacheClusterStatus", region=region, key=key, keyid=keyid, profile=profile, **args, ) def describe_replication_groups( name=None, conn=None, region=None, key=None, keyid=None, profile=None ): """ Return details about all (or just one) Elasticache replication groups. Example: .. code-block:: bash salt myminion boto3_elasticache.describe_replication_groups salt myminion boto3_elasticache.describe_replication_groups myelasticache """ return _describe_resource( name=name, name_param="ReplicationGroupId", res_type="replication_group", info_node="ReplicationGroups", conn=conn, region=region, key=key, keyid=keyid, profile=profile, ) def replication_group_exists(name, region=None, key=None, keyid=None, profile=None): """ Check to see if a replication group exists. Example: .. code-block:: bash salt myminion boto3_elasticache.replication_group_exists myelasticache """ return bool( describe_replication_groups( name=name, region=region, key=key, keyid=keyid, profile=profile ) ) def create_replication_group( name, wait=600, security_groups=None, region=None, key=None, keyid=None, profile=None, **args, ): """ Create a replication group. Params are extensive and variable - see http://boto3.readthedocs.io/en/latest/reference/services/elasticache.html?#ElastiCache.Client.create_replication_group for in-depth usage documentation. Example: .. code-block:: bash salt myminion boto3_elasticache.create_replication_group \ name=myelasticache \ ReplicationGroupDescription=description """ if security_groups: if not isinstance(security_groups, list): security_groups = [security_groups] sgs = __salt__["boto_secgroup.convert_to_group_ids"]( groups=security_groups, region=region, key=key, keyid=keyid, profile=profile ) if "SecurityGroupIds" not in args: args["SecurityGroupIds"] = [] args["SecurityGroupIds"] += sgs args = {k: v for k, v in args.items() if not k.startswith("_")} return _create_resource( name, name_param="ReplicationGroupId", desc="replication group", res_type="replication_group", wait=wait, status_param="Status", region=region, key=key, keyid=keyid, profile=profile, **args, ) def modify_replication_group( name, wait=600, security_groups=None, region=None, key=None, keyid=None, profile=None, **args, ): """ Modify a replication group. Example: .. code-block:: bash salt myminion boto3_elasticache.modify_replication_group \ name=myelasticache \ ReplicationGroupDescription=newDescription """ if security_groups: if not isinstance(security_groups, list): security_groups = [security_groups] sgs = __salt__["boto_secgroup.convert_to_group_ids"]( groups=security_groups, region=region, key=key, keyid=keyid, profile=profile ) if "SecurityGroupIds" not in args: args["SecurityGroupIds"] = [] args["SecurityGroupIds"] += sgs args = {k: v for k, v in args.items() if not k.startswith("_")} return _modify_resource( name, name_param="ReplicationGroupId", desc="replication group", res_type="replication_group", wait=wait, status_param="Status", region=region, key=key, keyid=keyid, profile=profile, **args, ) def delete_replication_group( name, wait=600, region=None, key=None, keyid=None, profile=None, **args ): """ Delete an ElastiCache replication group, optionally taking a snapshot first. Example: .. code-block:: bash salt myminion boto3_elasticache.delete_replication_group my-replication-group """ return _delete_resource( name, name_param="ReplicationGroupId", desc="replication group", res_type="replication_group", wait=wait, status_param="Status", region=region, key=key, keyid=keyid, profile=profile, **args, ) def describe_cache_subnet_groups( name=None, conn=None, region=None, key=None, keyid=None, profile=None ): """ Return details about all (or just one) Elasticache replication groups. Example: .. code-block:: bash salt myminion boto3_elasticache.describe_cache_subnet_groups region=us-east-1 """ return _describe_resource( name=name, name_param="CacheSubnetGroupName", res_type="cache_subnet_group", info_node="CacheSubnetGroups", conn=conn, region=region, key=key, keyid=keyid, profile=profile, ) def cache_subnet_group_exists(name, region=None, key=None, keyid=None, profile=None): """ Check to see if an ElastiCache subnet group exists. Example: .. code-block:: bash salt myminion boto3_elasticache.cache_subnet_group_exists my-subnet-group """ return bool( describe_cache_subnet_groups( name=name, region=region, key=key, keyid=keyid, profile=profile ) ) def list_cache_subnet_groups(region=None, key=None, keyid=None, profile=None): """ Return a list of all cache subnet group names Example: .. code-block:: bash salt myminion boto3_elasticache.list_cache_subnet_groups region=us-east-1 """ return [ g["CacheSubnetGroupName"] for g in describe_cache_subnet_groups(None, region, key, keyid, profile) ] def create_cache_subnet_group( name, subnets=None, region=None, key=None, keyid=None, profile=None, **args ): """ Create an ElastiCache subnet group Example: .. code-block:: bash salt myminion boto3_elasticache.create_cache_subnet_group name=my-subnet-group \ CacheSubnetGroupDescription="description" \ subnets='[myVPCSubnet1,myVPCSubnet2]' """ if subnets: if "SubnetIds" not in args: args["SubnetIds"] = [] if not isinstance(subnets, list): subnets = [subnets] for subnet in subnets: if subnet.startswith("subnet-"): # Moderately safe assumption... :) Will be caught further down if incorrect. args["SubnetIds"] += [subnet] continue sn = __salt__["boto_vpc.describe_subnets"]( subnet_names=subnet, region=region, key=key, keyid=keyid, profile=profile, ).get("subnets") if not sn: raise SaltInvocationError( f"Could not resolve Subnet Name {subnet} to an ID." ) if len(sn) == 1: args["SubnetIds"] += [sn[0]["id"]] elif len(sn) > 1: raise CommandExecutionError( f"Subnet Name {subnet} returned more than one ID." ) args = {k: v for k, v in args.items() if not k.startswith("_")} return _create_resource( name, name_param="CacheSubnetGroupName", desc="cache subnet group", res_type="cache_subnet_group", region=region, key=key, keyid=keyid, profile=profile, **args, ) def modify_cache_subnet_group( name, subnets=None, region=None, key=None, keyid=None, profile=None, **args ): """ Modify an ElastiCache subnet group Example: .. code-block:: bash salt myminion boto3_elasticache.modify_cache_subnet_group \ name=my-subnet-group \ subnets='[myVPCSubnet3]' """ if subnets: if "SubnetIds" not in args: args["SubnetIds"] = [] if not isinstance(subnets, list): subnets = [subnets] for subnet in subnets: sn = __salt__["boto_vpc.describe_subnets"]( subnet_names=subnet, region=region, key=key, keyid=keyid, profile=profile, ).get("subnets") if len(sn) == 1: args["SubnetIds"] += [sn[0]["id"]] elif len(sn) > 1: raise CommandExecutionError( f"Subnet Name {subnet} returned more than one ID." ) elif subnet.startswith("subnet-"): # Moderately safe assumption... :) Will be caught later if incorrect. args["SubnetIds"] += [subnet] else: raise SaltInvocationError( f"Could not resolve Subnet Name {subnet} to an ID." ) args = {k: v for k, v in args.items() if not k.startswith("_")} return _modify_resource( name, name_param="CacheSubnetGroupName", desc="cache subnet group", res_type="cache_subnet_group", region=region, key=key, keyid=keyid, profile=profile, **args, ) def delete_cache_subnet_group( name, region=None, key=None, keyid=None, profile=None, **args ): """ Delete an ElastiCache subnet group. Example: .. code-block:: bash salt myminion boto3_elasticache.delete_subnet_group my-subnet-group region=us-east-1 """ return _delete_resource( name, name_param="CacheSubnetGroupName", desc="cache subnet group", res_type="cache_subnet_group", region=region, key=key, keyid=keyid, profile=profile, **args, ) def describe_cache_security_groups( name=None, conn=None, region=None, key=None, keyid=None, profile=None ): """ Return details about all (or just one) Elasticache cache clusters. Example: .. code-block:: bash salt myminion boto3_elasticache.describe_cache_security_groups salt myminion boto3_elasticache.describe_cache_security_groups mycachesecgrp """ return _describe_resource( name=name, name_param="CacheSecurityGroupName", res_type="cache_security_group", info_node="CacheSecurityGroups", conn=conn, region=region, key=key, keyid=keyid, profile=profile, ) def cache_security_group_exists(name, region=None, key=None, keyid=None, profile=None): """ Check to see if an ElastiCache security group exists. Example: .. code-block:: bash salt myminion boto3_elasticache.cache_security_group_exists mysecuritygroup """ return bool( describe_cache_security_groups( name=name, region=region, key=key, keyid=keyid, profile=profile ) ) def create_cache_security_group( name, region=None, key=None, keyid=None, profile=None, **args ): """ Create a cache security group. Example: .. code-block:: bash salt myminion boto3_elasticache.create_cache_security_group mycachesecgrp Description='My Cache Security Group' """ return _create_resource( name, name_param="CacheSecurityGroupName", desc="cache security group", res_type="cache_security_group", region=region, key=key, keyid=keyid, profile=profile, **args, ) def delete_cache_security_group( name, region=None, key=None, keyid=None, profile=None, **args ): """ Delete a cache security group. Example: .. code-block:: bash salt myminion boto3_elasticache.delete_cache_security_group myelasticachesg """ return _delete_resource( name, name_param="CacheSecurityGroupName", desc="cache security group", res_type="cache_security_group", region=region, key=key, keyid=keyid, profile=profile, **args, ) def authorize_cache_security_group_ingress( name, region=None, key=None, keyid=None, profile=None, **args ): """ Authorize network ingress from an ec2 security group to a cache security group. Example: .. code-block:: bash salt myminion boto3_elasticache.authorize_cache_security_group_ingress \ mycachesecgrp \ EC2SecurityGroupName=someEC2sg \ EC2SecurityGroupOwnerId=SOMEOWNERID """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if "CacheSecurityGroupName" in args: log.info( "'name: %s' param being overridden by explicitly provided " "'CacheSecurityGroupName: %s'", name, args["CacheSecurityGroupName"], ) name = args["CacheSecurityGroupName"] else: args["CacheSubnetGroupName"] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: conn.authorize_cache_security_group_ingress(**args) log.info( "Authorized %s to cache security group %s.", args["EC2SecurityGroupName"], name, ) return True except botocore.exceptions.ClientError as e: log.error("Failed to update security group %s: %s", name, e) return False def revoke_cache_security_group_ingress( name, region=None, key=None, keyid=None, profile=None, **args ): """ Revoke network ingress from an ec2 security group to a cache security group. Example: .. code-block:: bash salt myminion boto3_elasticache.revoke_cache_security_group_ingress \ mycachesecgrp \ EC2SecurityGroupName=someEC2sg \ EC2SecurityGroupOwnerId=SOMEOWNERID """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if "CacheSecurityGroupName" in args: log.info( "'name: %s' param being overridden by explicitly provided " "'CacheSecurityGroupName: %s'", name, args["CacheSecurityGroupName"], ) name = args["CacheSecurityGroupName"] else: args["CacheSubnetGroupName"] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: conn.revoke_cache_security_group_ingress(**args) log.info( "Revoked %s from cache security group %s.", args["EC2SecurityGroupName"], name, ) return True except botocore.exceptions.ClientError as e: log.error("Failed to update security group %s: %s", name, e) return False def list_tags_for_resource( name, region=None, key=None, keyid=None, profile=None, **args ): """ List tags on an Elasticache resource. Note that this function is essentially useless as it requires a full AWS ARN for the resource being operated on, but there is no provided API or programmatic way to find the ARN for a given object from its name or ID alone. It requires specific knowledge about the account number, AWS partition, and other magic details to generate. If you happen to have those handy, feel free to utilize this however... Example: .. code-block:: bash salt myminion boto3_elasticache.list_tags_for_resource \ name'=arn:aws:elasticache:us-west-2:0123456789:snapshot:mySnapshot' """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if "ResourceName" in args: log.info( "'name: %s' param being overridden by explicitly provided " "'ResourceName: %s'", name, args["ResourceName"], ) name = args["ResourceName"] else: args["ResourceName"] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: r = conn.list_tags_for_resource(**args) if r and "Taglist" in r: return r["TagList"] return [] except botocore.exceptions.ClientError as e: log.error("Failed to list tags for resource %s: %s", name, e) return [] def add_tags_to_resource(name, region=None, key=None, keyid=None, profile=None, **args): """ Add tags to an Elasticache resource. Note that this function is essentially useless as it requires a full AWS ARN for the resource being operated on, but there is no provided API or programmatic way to find the ARN for a given object from its name or ID alone. It requires specific knowledge about the account number, AWS partition, and other magic details to generate. If you happen to have those at hand though, feel free to utilize this function... Example: .. code-block:: bash salt myminion boto3_elasticache.add_tags_to_resource \ name'=arn:aws:elasticache:us-west-2:0123456789:snapshot:mySnapshot' \ Tags="[{'Key': 'TeamOwner', 'Value': 'infrastructure'}]" """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if "ResourceName" in args: log.info( "'name: %s' param being overridden by explicitly provided " "'ResourceName: %s'", name, args["ResourceName"], ) name = args["ResourceName"] else: args["ResourceName"] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: conn.add_tags_to_resource(**args) log.info("Added tags %s to %s.", args["Tags"], name) return True except botocore.exceptions.ClientError as e: log.error("Failed to add tags to %s: %s", name, e) return False def remove_tags_from_resource( name, region=None, key=None, keyid=None, profile=None, **args ): """ Remove tags from an Elasticache resource. Note that this function is essentially useless as it requires a full AWS ARN for the resource being operated on, but there is no provided API or programmatic way to find the ARN for a given object from its name or ID alone. It requires specific knowledge about the account number, AWS partition, and other magic details to generate. If you happen to have those at hand though, feel free to utilize this function... Example: .. code-block:: bash salt myminion boto3_elasticache.remove_tags_from_resource \ name'=arn:aws:elasticache:us-west-2:0123456789:snapshot:mySnapshot' \ TagKeys="['TeamOwner']" """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if "ResourceName" in args: log.info( "'name: %s' param being overridden by explicitly provided " "'ResourceName: %s'", name, args["ResourceName"], ) name = args["ResourceName"] else: args["ResourceName"] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: conn.remove_tags_from_resource(**args) log.info("Added tags %s to %s.", args["Tags"], name) return True except botocore.exceptions.ClientError as e: log.error("Failed to add tags to %s: %s", name, e) return False def copy_snapshot(name, region=None, key=None, keyid=None, profile=None, **args): """ Make a copy of an existing snapshot. Example: .. code-block:: bash salt myminion boto3_elasticache.copy_snapshot name=mySnapshot \ TargetSnapshotName=copyOfMySnapshot """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if "SourceSnapshotName" in args: log.info( "'name: %s' param being overridden by explicitly provided " "'SourceSnapshotName: %s'", name, args["SourceSnapshotName"], ) name = args["SourceSnapshotName"] else: args["SourceSnapshotName"] = name args = {k: v for k, v in args.items() if not k.startswith("_")} try: conn.copy_snapshot(**args) log.info("Snapshot %s copied to %s.", name, args["TargetSnapshotName"]) return True except botocore.exceptions.ClientError as e: log.error("Failed to copy snapshot %s: %s", name, e) return False def describe_cache_parameter_groups( name=None, conn=None, region=None, key=None, keyid=None, profile=None ): """ Return details about all (or just one) Elasticache cache clusters. Example: .. code-block:: bash salt myminion boto3_elasticache.describe_cache_parameter_groups salt myminion boto3_elasticache.describe_cache_parameter_groups myParameterGroup """ return _describe_resource( name=name, name_param="CacheParameterGroupName", res_type="cache_parameter_group", info_node="CacheParameterGroups", conn=conn, region=region, key=key, keyid=keyid, profile=profile, ) def create_cache_parameter_group( name, region=None, key=None, keyid=None, profile=None, **args ): """ Create a cache parameter group. Example: .. code-block:: bash salt myminion boto3_elasticache.create_cache_parameter_group \ name=myParamGroup \ CacheParameterGroupFamily=redis2.8 \ Description="My Parameter Group" """ return _create_resource( name, name_param="CacheParameterGroupName", desc="cache parameter group", res_type="cache_parameter_group", region=region, key=key, keyid=keyid, profile=profile, **args, ) def delete_cache_parameter_group( name, region=None, key=None, keyid=None, profile=None, **args ): """ Delete a cache parameter group. Example: .. code-block:: bash salt myminion boto3_elasticache.delete_cache_parameter_group myParamGroup """ return _delete_resource( name, name_param="CacheParameterGroupName", desc="cache parameter group", res_type="cache_parameter_group", region=region, key=key, keyid=keyid, profile=profile, **args, )