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_rds.py
""" Connection module for Amazon RDS .. versionadded:: 2015.8.0 :configuration: This module accepts explicit rds 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 rds.keyid: GKTADJGHEIQSXMKKRBJ08H rds.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs A region may also be specified in the configuration: .. code-block:: yaml rds.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 # pylint whinging perfectly valid code # pylint: disable=W0106 import logging import time import salt.utils.compat import salt.utils.odict as odict import salt.utils.versions from salt.exceptions import SaltInvocationError log = logging.getLogger(__name__) # pylint: disable=import-error try: # pylint: disable=unused-import import boto import boto3 # pylint: enable=unused-import from botocore.exceptions import ClientError logging.getLogger("boto").setLevel(logging.CRITICAL) logging.getLogger("boto3").setLevel(logging.CRITICAL) HAS_BOTO = True except ImportError: HAS_BOTO = False # pylint: enable=import-error boto3_param_map = { "allocated_storage": ("AllocatedStorage", int), "allow_major_version_upgrade": ("AllowMajorVersionUpgrade", bool), "apply_immediately": ("ApplyImmediately", bool), "auto_minor_version_upgrade": ("AutoMinorVersionUpgrade", bool), "availability_zone": ("AvailabilityZone", str), "backup_retention_period": ("BackupRetentionPeriod", int), "ca_certificate_identifier": ("CACertificateIdentifier", str), "character_set_name": ("CharacterSetName", str), "copy_tags_to_snapshot": ("CopyTagsToSnapshot", bool), "db_cluster_identifier": ("DBClusterIdentifier", str), "db_instance_class": ("DBInstanceClass", str), "db_name": ("DBName", str), "db_parameter_group_name": ("DBParameterGroupName", str), "db_port_number": ("DBPortNumber", int), "db_security_groups": ("DBSecurityGroups", list), "db_subnet_group_name": ("DBSubnetGroupName", str), "domain": ("Domain", str), "domain_iam_role_name": ("DomainIAMRoleName", str), "engine": ("Engine", str), "engine_version": ("EngineVersion", str), "iops": ("Iops", int), "kms_key_id": ("KmsKeyId", str), "license_model": ("LicenseModel", str), "master_user_password": ("MasterUserPassword", str), "master_username": ("MasterUsername", str), "monitoring_interval": ("MonitoringInterval", int), "monitoring_role_arn": ("MonitoringRoleArn", str), "multi_az": ("MultiAZ", bool), "name": ("DBInstanceIdentifier", str), "new_db_instance_identifier": ("NewDBInstanceIdentifier", str), "option_group_name": ("OptionGroupName", str), "port": ("Port", int), "preferred_backup_window": ("PreferredBackupWindow", str), "preferred_maintenance_window": ("PreferredMaintenanceWindow", str), "promotion_tier": ("PromotionTier", int), "publicly_accessible": ("PubliclyAccessible", bool), "storage_encrypted": ("StorageEncrypted", bool), "storage_type": ("StorageType", str), "tags": ("Tags", list), "tde_credential_arn": ("TdeCredentialArn", str), "tde_credential_password": ("TdeCredentialPassword", str), "vpc_security_group_ids": ("VpcSecurityGroupIds", list), } 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(boto3_ver="1.3.1") def __init__(opts): if HAS_BOTO: __utils__["boto3.assign_funcs"](__name__, "rds") def exists(name, tags=None, region=None, key=None, keyid=None, profile=None): """ Check to see if an RDS exists. CLI Example: .. code-block:: bash salt myminion boto_rds.exists myrds region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) try: rds = conn.describe_db_instances(DBInstanceIdentifier=name) return {"exists": bool(rds)} except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def option_group_exists( name, tags=None, region=None, key=None, keyid=None, profile=None ): """ Check to see if an RDS option group exists. CLI Example: .. code-block:: bash salt myminion boto_rds.option_group_exists myoptiongr region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) try: rds = conn.describe_option_groups(OptionGroupName=name) return {"exists": bool(rds)} except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def parameter_group_exists( name, tags=None, region=None, key=None, keyid=None, profile=None ): """ Check to see if an RDS parameter group exists. CLI Example: .. code-block:: bash salt myminion boto_rds.parameter_group_exists myparametergroup \ region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) try: rds = conn.describe_db_parameter_groups(DBParameterGroupName=name) return {"exists": bool(rds), "error": None} except ClientError as e: resp = {} if e.response["Error"]["Code"] == "DBParameterGroupNotFound": resp["exists"] = False resp["error"] = __utils__["boto3.get_error"](e) return resp def subnet_group_exists( name, tags=None, region=None, key=None, keyid=None, profile=None ): """ Check to see if an RDS subnet group exists. CLI Example: .. code-block:: bash salt myminion boto_rds.subnet_group_exists my-param-group \ region=us-east-1 """ try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"exists": bool(conn)} rds = conn.describe_db_subnet_groups(DBSubnetGroupName=name) return {"exists": bool(rds)} except ClientError as e: if "DBSubnetGroupNotFoundFault" in e.message: return {"exists": False} else: return {"error": __utils__["boto3.get_error"](e)} def create( name, allocated_storage, db_instance_class, engine, master_username, master_user_password, db_name=None, db_security_groups=None, vpc_security_group_ids=None, vpc_security_groups=None, availability_zone=None, db_subnet_group_name=None, preferred_maintenance_window=None, db_parameter_group_name=None, backup_retention_period=None, preferred_backup_window=None, port=None, multi_az=None, engine_version=None, auto_minor_version_upgrade=None, license_model=None, iops=None, option_group_name=None, character_set_name=None, publicly_accessible=None, wait_status=None, tags=None, db_cluster_identifier=None, storage_type=None, tde_credential_arn=None, tde_credential_password=None, storage_encrypted=None, kms_key_id=None, domain=None, copy_tags_to_snapshot=None, monitoring_interval=None, monitoring_role_arn=None, domain_iam_role_name=None, region=None, promotion_tier=None, key=None, keyid=None, profile=None, ): """ Create an RDS Instance CLI example to create an RDS Instance:: salt myminion boto_rds.create myrds 10 db.t2.micro MySQL sqlusr sqlpassw """ if not allocated_storage: raise SaltInvocationError("allocated_storage is required") if not db_instance_class: raise SaltInvocationError("db_instance_class is required") if not engine: raise SaltInvocationError("engine is required") if not master_username: raise SaltInvocationError("master_username is required") if not master_user_password: raise SaltInvocationError("master_user_password is required") if availability_zone and multi_az: raise SaltInvocationError( "availability_zone and multi_az are mutually exclusive arguments." ) if wait_status: wait_stati = ["available", "modifying", "backing-up"] if wait_status not in wait_stati: raise SaltInvocationError(f"wait_status can be one of: {wait_stati}") if vpc_security_groups: v_tmp = __salt__["boto_secgroup.convert_to_group_ids"]( groups=vpc_security_groups, region=region, key=key, keyid=keyid, profile=profile, ) vpc_security_group_ids = ( vpc_security_group_ids + v_tmp if vpc_security_group_ids else v_tmp ) try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} kwargs = {} boto_params = set(boto3_param_map.keys()) keys = set(locals().keys()) tags = _tag_doc(tags) for param_key in keys.intersection(boto_params): val = locals()[param_key] if val is not None: mapped = boto3_param_map[param_key] kwargs[mapped[0]] = mapped[1](val) # Validation doesn't want parameters that are None # https://github.com/boto/boto3/issues/400 kwargs = {k: v for k, v in kwargs.items() if v is not None} rds = conn.create_db_instance(**kwargs) if not rds: return {"created": False} if not wait_status: return { "created": True, "message": f"RDS instance {name} created.", } while True: jmespath = "DBInstances[*].DBInstanceStatus" status = describe_db_instances( name=name, jmespath=jmespath, region=region, key=key, keyid=keyid, profile=profile, ) if status: stat = status[0] else: # Whoops, something is horribly wrong... return { "created": False, "error": ( "RDS instance {} should have been created but" " now I can't find it.".format(name) ), } if stat == wait_status: return { "created": True, "message": "RDS instance {} created (current status {})".format( name, stat ), } time.sleep(10) log.info("Instance status after 10 seconds is: %s", stat) except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def create_read_replica( name, source_name, db_instance_class=None, availability_zone=None, port=None, auto_minor_version_upgrade=None, iops=None, option_group_name=None, publicly_accessible=None, tags=None, db_subnet_group_name=None, storage_type=None, copy_tags_to_snapshot=None, monitoring_interval=None, monitoring_role_arn=None, region=None, key=None, keyid=None, profile=None, ): """ Create an RDS read replica CLI example to create an RDS read replica:: salt myminion boto_rds.create_read_replica replicaname source_name """ if not backup_retention_period: raise SaltInvocationError("backup_retention_period is required") res = __salt__["boto_rds.exists"](source_name, tags, region, key, keyid, profile) if not res.get("exists"): return { "exists": bool(res), "message": f"RDS instance source {source_name} does not exists.", } res = __salt__["boto_rds.exists"](name, tags, region, key, keyid, profile) if res.get("exists"): return { "exists": bool(res), "message": f"RDS replica instance {name} already exists.", } try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) kwargs = {} for key in ("OptionGroupName", "MonitoringRoleArn"): if locals()[key] is not None: kwargs[key] = str(locals()[key]) for key in ("MonitoringInterval", "Iops", "Port"): if locals()[key] is not None: kwargs[key] = int(locals()[key]) for key in ("CopyTagsToSnapshot", "AutoMinorVersionUpgrade"): if locals()[key] is not None: kwargs[key] = bool(locals()[key]) taglist = _tag_doc(tags) rds_replica = conn.create_db_instance_read_replica( DBInstanceIdentifier=name, SourceDBInstanceIdentifier=source_name, DBInstanceClass=db_instance_class, AvailabilityZone=availability_zone, PubliclyAccessible=publicly_accessible, Tags=taglist, DBSubnetGroupName=db_subnet_group_name, StorageType=storage_type, **kwargs, ) return {"exists": bool(rds_replica)} except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def create_option_group( name, engine_name, major_engine_version, option_group_description, tags=None, region=None, key=None, keyid=None, profile=None, ): """ Create an RDS option group CLI example to create an RDS option group:: salt myminion boto_rds.create_option_group my-opt-group mysql 5.6 \ "group description" """ res = __salt__["boto_rds.option_group_exists"]( name, tags, region, key, keyid, profile ) if res.get("exists"): return {"exists": bool(res)} try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} taglist = _tag_doc(tags) rds = conn.create_option_group( OptionGroupName=name, EngineName=engine_name, MajorEngineVersion=major_engine_version, OptionGroupDescription=option_group_description, Tags=taglist, ) return {"exists": bool(rds)} except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def create_parameter_group( name, db_parameter_group_family, description, tags=None, region=None, key=None, keyid=None, profile=None, ): """ Create an RDS parameter group CLI example to create an RDS parameter group:: salt myminion boto_rds.create_parameter_group my-param-group mysql5.6 \ "group description" """ res = __salt__["boto_rds.parameter_group_exists"]( name, tags, region, key, keyid, profile ) if res.get("exists"): return {"exists": bool(res)} try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} taglist = _tag_doc(tags) rds = conn.create_db_parameter_group( DBParameterGroupName=name, DBParameterGroupFamily=db_parameter_group_family, Description=description, Tags=taglist, ) if not rds: return { "created": False, "message": f"Failed to create RDS parameter group {name}", } return { "exists": bool(rds), "message": f"Created RDS parameter group {name}", } except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def create_subnet_group( name, description, subnet_ids, tags=None, region=None, key=None, keyid=None, profile=None, ): """ Create an RDS subnet group CLI example to create an RDS subnet group:: salt myminion boto_rds.create_subnet_group my-subnet-group \ "group description" '[subnet-12345678, subnet-87654321]' \ region=us-east-1 """ res = __salt__["boto_rds.subnet_group_exists"]( name, tags, region, key, keyid, profile ) if res.get("exists"): return {"exists": bool(res)} try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} taglist = _tag_doc(tags) rds = conn.create_db_subnet_group( DBSubnetGroupName=name, DBSubnetGroupDescription=description, SubnetIds=subnet_ids, Tags=taglist, ) return {"created": bool(rds)} except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def update_parameter_group( name, parameters, apply_method="pending-reboot", tags=None, region=None, key=None, keyid=None, profile=None, ): """ Update an RDS parameter group. CLI Example: .. code-block:: bash salt myminion boto_rds.update_parameter_group my-param-group \ parameters='{"back_log":1, "binlog_cache_size":4096}' \ region=us-east-1 """ res = __salt__["boto_rds.parameter_group_exists"]( name, tags, region, key, keyid, profile ) if not res.get("exists"): return { "exists": bool(res), "message": f"RDS parameter group {name} does not exist.", } param_list = [] for key, value in parameters.items(): item = odict.OrderedDict() item.update({"ParameterName": key}) item.update({"ApplyMethod": apply_method}) if type(value) is bool: item.update({"ParameterValue": "on" if value else "off"}) else: item.update({"ParameterValue": str(value)}) param_list.append(item) if not param_list: return {"results": False} try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} res = conn.modify_db_parameter_group( DBParameterGroupName=name, Parameters=param_list ) return {"results": bool(res)} except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def describe(name, tags=None, region=None, key=None, keyid=None, profile=None): """ Return RDS instance details. CLI Example: .. code-block:: bash salt myminion boto_rds.describe myrds """ res = __salt__["boto_rds.exists"](name, tags, region, key, keyid, profile) if not res.get("exists"): return { "exists": bool(res), "message": f"RDS instance {name} does not exist.", } try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} rds = conn.describe_db_instances(DBInstanceIdentifier=name) rds = [ i for i in rds.get("DBInstances", []) if i.get("DBInstanceIdentifier") == name ].pop(0) if rds: keys = ( "DBInstanceIdentifier", "DBInstanceClass", "Engine", "DBInstanceStatus", "DBName", "AllocatedStorage", "PreferredBackupWindow", "BackupRetentionPeriod", "AvailabilityZone", "PreferredMaintenanceWindow", "LatestRestorableTime", "EngineVersion", "AutoMinorVersionUpgrade", "LicenseModel", "Iops", "CharacterSetName", "PubliclyAccessible", "StorageType", "TdeCredentialArn", "DBInstancePort", "DBClusterIdentifier", "StorageEncrypted", "KmsKeyId", "DbiResourceId", "CACertificateIdentifier", "CopyTagsToSnapshot", "MonitoringInterval", "MonitoringRoleArn", "PromotionTier", "DomainMemberships", ) return {"rds": {k: rds.get(k) for k in keys}} else: return {"rds": None} except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} except IndexError: return {"rds": None} def describe_db_instances( name=None, filters=None, jmespath="DBInstances", region=None, key=None, keyid=None, profile=None, ): """ Return a detailed listing of some, or all, DB Instances visible in the current scope. Arbitrary subelements or subsections of the returned dataset can be selected by passing in a valid JMSEPath filter as well. CLI Example: .. code-block:: bash salt myminion boto_rds.describe_db_instances jmespath='DBInstances[*].DBInstanceIdentifier' """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) pag = conn.get_paginator("describe_db_instances") args = {} args.update({"DBInstanceIdentifier": name}) if name else None args.update({"Filters": filters}) if filters else None pit = pag.paginate(**args) pit = pit.search(jmespath) if jmespath else pit try: return [p for p in pit] except ClientError as e: code = getattr(e, "response", {}).get("Error", {}).get("Code") if code != "DBInstanceNotFound": log.error(__utils__["boto3.get_error"](e)) return [] def describe_db_subnet_groups( name=None, filters=None, jmespath="DBSubnetGroups", region=None, key=None, keyid=None, profile=None, ): """ Return a detailed listing of some, or all, DB Subnet Groups visible in the current scope. Arbitrary subelements or subsections of the returned dataset can be selected by passing in a valid JMSEPath filter as well. CLI Example: .. code-block:: bash salt myminion boto_rds.describe_db_subnet_groups """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) pag = conn.get_paginator("describe_db_subnet_groups") args = {} args.update({"DBSubnetGroupName": name}) if name else None args.update({"Filters": filters}) if filters else None pit = pag.paginate(**args) pit = pit.search(jmespath) if jmespath else pit return [p for p in pit] def get_endpoint(name, tags=None, region=None, key=None, keyid=None, profile=None): """ Return the endpoint of an RDS instance. CLI Example: .. code-block:: bash salt myminion boto_rds.get_endpoint myrds """ endpoint = False res = __salt__["boto_rds.exists"](name, tags, region, key, keyid, profile) if res.get("exists"): try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if conn: rds = conn.describe_db_instances(DBInstanceIdentifier=name) if rds and "Endpoint" in rds["DBInstances"][0]: endpoint = rds["DBInstances"][0]["Endpoint"]["Address"] return endpoint except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} return endpoint def delete( name, skip_final_snapshot=None, final_db_snapshot_identifier=None, region=None, key=None, keyid=None, profile=None, tags=None, wait_for_deletion=True, timeout=180, ): """ Delete an RDS instance. CLI Example: .. code-block:: bash salt myminion boto_rds.delete myrds skip_final_snapshot=True \ region=us-east-1 """ if timeout == 180 and not skip_final_snapshot: timeout = 420 if not skip_final_snapshot and not final_db_snapshot_identifier: raise SaltInvocationError( "At least one of the following must" " be specified: skip_final_snapshot" " final_db_snapshot_identifier" ) try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"deleted": bool(conn)} kwargs = {} if locals()["skip_final_snapshot"] is not None: kwargs["SkipFinalSnapshot"] = bool(locals()["skip_final_snapshot"]) if locals()["final_db_snapshot_identifier"] is not None: kwargs["FinalDBSnapshotIdentifier"] = str( locals()["final_db_snapshot_identifier"] ) res = conn.delete_db_instance(DBInstanceIdentifier=name, **kwargs) if not wait_for_deletion: return { "deleted": bool(res), "message": f"Deleted RDS instance {name}.", } start_time = time.time() while True: res = __salt__["boto_rds.exists"]( name=name, tags=tags, region=region, key=key, keyid=keyid, profile=profile, ) if not res.get("exists"): return { "deleted": bool(res), "message": f"Deleted RDS instance {name} completely.", } if time.time() - start_time > timeout: raise SaltInvocationError( "RDS instance {} has not been " "deleted completely after {} " "seconds".format(name, timeout) ) log.info( "Waiting up to %s seconds for RDS instance %s to be deleted.", timeout, name, ) time.sleep(10) except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def delete_option_group(name, region=None, key=None, keyid=None, profile=None): """ Delete an RDS option group. CLI Example: .. code-block:: bash salt myminion boto_rds.delete_option_group my-opt-group \ region=us-east-1 """ try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"deleted": bool(conn)} res = conn.delete_option_group(OptionGroupName=name) if not res: return { "deleted": bool(res), "message": f"Failed to delete RDS option group {name}.", } return { "deleted": bool(res), "message": f"Deleted RDS option group {name}.", } except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def delete_parameter_group(name, region=None, key=None, keyid=None, profile=None): """ Delete an RDS parameter group. CLI Example: .. code-block:: bash salt myminion boto_rds.delete_parameter_group my-param-group \ region=us-east-1 """ try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} r = conn.delete_db_parameter_group(DBParameterGroupName=name) return { "deleted": bool(r), "message": f"Deleted RDS parameter group {name}.", } except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def delete_subnet_group(name, region=None, key=None, keyid=None, profile=None): """ Delete an RDS subnet group. CLI Example: .. code-block:: bash salt myminion boto_rds.delete_subnet_group my-subnet-group \ region=us-east-1 """ try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} r = conn.delete_db_subnet_group(DBSubnetGroupName=name) return { "deleted": bool(r), "message": f"Deleted RDS subnet group {name}.", } except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def describe_parameter_group( name, Filters=None, MaxRecords=None, Marker=None, region=None, key=None, keyid=None, profile=None, ): """ Returns a list of `DBParameterGroup` descriptions. CLI example to description of parameter group:: salt myminion boto_rds.describe_parameter_group parametergroupname\ region=us-east-1 """ res = __salt__["boto_rds.parameter_group_exists"]( name, tags=None, region=region, key=key, keyid=keyid, profile=profile ) if not res.get("exists"): return {"exists": bool(res)} try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"results": bool(conn)} kwargs = {} for key in ("Marker", "Filters"): if locals()[key] is not None: kwargs[key] = str(locals()[key]) if locals()["MaxRecords"] is not None: kwargs["MaxRecords"] = int(locals()["MaxRecords"]) info = conn.describe_db_parameter_groups(DBParameterGroupName=name, **kwargs) if not info: return { "results": bool(info), "message": f"Failed to get RDS description for group {name}.", } return { "results": bool(info), "message": f"Got RDS descrition for group {name}.", } except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def describe_parameters( name, Source=None, MaxRecords=None, Marker=None, region=None, key=None, keyid=None, profile=None, ): """ Returns a list of `DBParameterGroup` parameters. CLI example to description of parameters :: salt myminion boto_rds.describe_parameters parametergroupname\ region=us-east-1 """ res = __salt__["boto_rds.parameter_group_exists"]( name, tags=None, region=region, key=key, keyid=keyid, profile=profile ) if not res.get("exists"): return { "result": False, "message": f"Parameter group {name} does not exist", } try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return { "result": False, "message": "Could not establish a connection to RDS", } kwargs = {} kwargs.update({"DBParameterGroupName": name}) for key in ("Marker", "Source"): if locals()[key] is not None: kwargs[key] = str(locals()[key]) if locals()["MaxRecords"] is not None: kwargs["MaxRecords"] = int(locals()["MaxRecords"]) pag = conn.get_paginator("describe_db_parameters") pit = pag.paginate(**kwargs) keys = [ "ParameterName", "ParameterValue", "Description", "Source", "ApplyType", "DataType", "AllowedValues", "IsModifieable", "MinimumEngineVersion", "ApplyMethod", ] parameters = odict.OrderedDict() ret = {"result": True} for p in pit: for result in p["Parameters"]: data = odict.OrderedDict() for k in keys: data[k] = result.get(k) parameters[result.get("ParameterName")] = data ret["parameters"] = parameters return ret except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def modify_db_instance( name, allocated_storage=None, allow_major_version_upgrade=None, apply_immediately=None, auto_minor_version_upgrade=None, backup_retention_period=None, ca_certificate_identifier=None, character_set_name=None, copy_tags_to_snapshot=None, db_cluster_identifier=None, db_instance_class=None, db_name=None, db_parameter_group_name=None, db_port_number=None, db_security_groups=None, db_subnet_group_name=None, domain=None, domain_iam_role_name=None, engine_version=None, iops=None, kms_key_id=None, license_model=None, master_user_password=None, monitoring_interval=None, monitoring_role_arn=None, multi_az=None, new_db_instance_identifier=None, option_group_name=None, preferred_backup_window=None, preferred_maintenance_window=None, promotion_tier=None, publicly_accessible=None, storage_encrypted=None, storage_type=None, tde_credential_arn=None, tde_credential_password=None, vpc_security_group_ids=None, region=None, key=None, keyid=None, profile=None, ): """ Modify settings for a DB instance. CLI example to description of parameters :: salt myminion boto_rds.modify_db_instance db_instance_identifier region=us-east-1 """ res = __salt__["boto_rds.exists"]( name, tags=None, region=region, key=key, keyid=keyid, profile=profile ) if not res.get("exists"): return { "modified": False, "message": f"RDS db instance {name} does not exist.", } try: conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return {"modified": False} kwargs = {} excluded = {"name"} boto_params = set(boto3_param_map.keys()) keys = set(locals().keys()) for key in keys.intersection(boto_params).difference(excluded): val = locals()[key] if val is not None: mapped = boto3_param_map[key] kwargs[mapped[0]] = mapped[1](val) info = conn.modify_db_instance(DBInstanceIdentifier=name, **kwargs) if not info: return { "modified": bool(info), "message": f"Failed to modify RDS db instance {name}.", } return { "modified": bool(info), "message": f"Modified RDS db instance {name}.", "results": dict(info), } except ClientError as e: return {"error": __utils__["boto3.get_error"](e)} def _tag_doc(tags): taglist = [] if tags is not None: for k, v in tags.items(): if str(k).startswith("__"): continue taglist.append({"Key": str(k), "Value": str(v)}) return taglist