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_kinesis.py
""" Connection module for Amazon Kinesis .. versionadded:: 2017.7.0 :configuration: This module accepts explicit Kinesis credentials but can also utilize IAM roles assigned to the instance trough 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 kinesis.keyid: GKTADJGHEIQSXMKKRBJ08H kinesis.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs A region may also be specified in the configuration: .. code-block:: yaml kinesis.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 # pylint: disable=E0602 import logging import random import time import salt.utils.versions # pylint: disable=unused-import try: import boto3 import botocore logging.getLogger("boto3").setLevel(logging.CRITICAL) HAS_BOTO = True except ImportError: HAS_BOTO = False # pylint: enable=unused-import log = logging.getLogger(__name__) __virtualname__ = "boto_kinesis" def __virtual__(): """ Only load if boto3 libraries exist. """ has_boto_reqs = salt.utils.versions.check_boto_reqs() if has_boto_reqs is True: __utils__["boto3.assign_funcs"](__name__, "kinesis") return __virtualname__ return has_boto_reqs def _get_basic_stream(stream_name, conn): """ Stream info from AWS, via describe_stream Only returns the first "page" of shards (up to 100); use _get_full_stream() for all shards. CLI Example: .. code-block:: bash salt myminion boto_kinesis._get_basic_stream my_stream existing_conn """ return _execute_with_retries(conn, "describe_stream", StreamName=stream_name) def _get_full_stream(stream_name, region=None, key=None, keyid=None, profile=None): """ Get complete stream info from AWS, via describe_stream, including all shards. CLI Example: .. code-block:: bash salt myminion boto_kinesis._get_full_stream my_stream region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = {} stream = _get_basic_stream(stream_name, conn)["result"] full_stream = stream # iterate through if there are > 100 shards (max that AWS will return from describe_stream) while stream["StreamDescription"]["HasMoreShards"]: stream = _execute_with_retries( conn, "describe_stream", StreamName=stream_name, ExclusiveStartShardId=stream["StreamDescription"]["Shards"][-1]["ShardId"], ) stream = stream["result"] full_stream["StreamDescription"]["Shards"] += stream["StreamDescription"][ "Shards" ] r["result"] = full_stream return r def get_stream_when_active( stream_name, region=None, key=None, keyid=None, profile=None ): """ Get complete stream info from AWS, returning only when the stream is in the ACTIVE state. Continues to retry when stream is updating or creating. If the stream is deleted during retries, the loop will catch the error and break. CLI Example: .. code-block:: bash salt myminion boto_kinesis.get_stream_when_active my_stream region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) stream_status = None # only get basic stream until it's active, # so we don't pull the full list of shards repeatedly (in case of very large stream) attempt = 0 max_retry_delay = 10 while stream_status != "ACTIVE": time.sleep(_jittered_backoff(attempt, max_retry_delay)) attempt += 1 stream_response = _get_basic_stream(stream_name, conn) if "error" in stream_response: return stream_response stream_status = stream_response["result"]["StreamDescription"]["StreamStatus"] # now it's active, get the full stream if necessary if stream_response["result"]["StreamDescription"]["HasMoreShards"]: stream_response = _get_full_stream(stream_name, region, key, keyid, profile) return stream_response def exists(stream_name, region=None, key=None, keyid=None, profile=None): """ Check if the stream exists. Returns False and the error if it does not. CLI Example: .. code-block:: bash salt myminion boto_kinesis.exists my_stream region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = {} stream = _get_basic_stream(stream_name, conn) if "error" in stream: r["result"] = False r["error"] = stream["error"] else: r["result"] = True return r def create_stream( stream_name, num_shards, region=None, key=None, keyid=None, profile=None ): """ Create a stream with name stream_name and initial number of shards num_shards. CLI Example: .. code-block:: bash salt myminion boto_kinesis.create_stream my_stream N region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = _execute_with_retries( conn, "create_stream", ShardCount=num_shards, StreamName=stream_name ) if "error" not in r: r["result"] = True return r def delete_stream(stream_name, region=None, key=None, keyid=None, profile=None): """ Delete the stream with name stream_name. This cannot be undone! All data will be lost!! CLI Example: .. code-block:: bash salt myminion boto_kinesis.delete_stream my_stream region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = _execute_with_retries(conn, "delete_stream", StreamName=stream_name) if "error" not in r: r["result"] = True return r def increase_stream_retention_period( stream_name, retention_hours, region=None, key=None, keyid=None, profile=None ): """ Increase stream retention period to retention_hours CLI Example: .. code-block:: bash salt myminion boto_kinesis.increase_stream_retention_period my_stream N region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = _execute_with_retries( conn, "increase_stream_retention_period", StreamName=stream_name, RetentionPeriodHours=retention_hours, ) if "error" not in r: r["result"] = True return r def decrease_stream_retention_period( stream_name, retention_hours, region=None, key=None, keyid=None, profile=None ): """ Decrease stream retention period to retention_hours CLI Example: .. code-block:: bash salt myminion boto_kinesis.decrease_stream_retention_period my_stream N region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = _execute_with_retries( conn, "decrease_stream_retention_period", StreamName=stream_name, RetentionPeriodHours=retention_hours, ) if "error" not in r: r["result"] = True return r def enable_enhanced_monitoring( stream_name, metrics, region=None, key=None, keyid=None, profile=None ): """ Enable enhanced monitoring for the specified shard-level metrics on stream stream_name CLI Example: .. code-block:: bash salt myminion boto_kinesis.enable_enhanced_monitoring my_stream ["metrics", "to", "enable"] region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = _execute_with_retries( conn, "enable_enhanced_monitoring", StreamName=stream_name, ShardLevelMetrics=metrics, ) if "error" not in r: r["result"] = True return r def disable_enhanced_monitoring( stream_name, metrics, region=None, key=None, keyid=None, profile=None ): """ Disable enhanced monitoring for the specified shard-level metrics on stream stream_name CLI Example: .. code-block:: bash salt myminion boto_kinesis.disable_enhanced_monitoring my_stream ["metrics", "to", "disable"] region=us-east-1 """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = _execute_with_retries( conn, "disable_enhanced_monitoring", StreamName=stream_name, ShardLevelMetrics=metrics, ) if "error" not in r: r["result"] = True return r def get_info_for_reshard(stream_details): """ Collect some data: number of open shards, key range, etc. Modifies stream_details to add a sorted list of OpenShards. Returns (min_hash_key, max_hash_key, stream_details) CLI Example: .. code-block:: bash salt myminion boto_kinesis.get_info_for_reshard existing_stream_details """ min_hash_key = 0 max_hash_key = 0 stream_details["OpenShards"] = [] for shard in stream_details["Shards"]: shard_id = shard["ShardId"] if "EndingSequenceNumber" in shard["SequenceNumberRange"]: # EndingSequenceNumber is null for open shards, so this shard must be closed log.debug("skipping closed shard %s", shard_id) continue stream_details["OpenShards"].append(shard) shard["HashKeyRange"]["StartingHashKey"] = long_int( shard["HashKeyRange"]["StartingHashKey"] ) shard["HashKeyRange"]["EndingHashKey"] = long_int( shard["HashKeyRange"]["EndingHashKey"] ) if shard["HashKeyRange"]["StartingHashKey"] < min_hash_key: min_hash_key = shard["HashKeyRange"]["StartingHashKey"] if shard["HashKeyRange"]["EndingHashKey"] > max_hash_key: max_hash_key = shard["HashKeyRange"]["EndingHashKey"] stream_details["OpenShards"].sort( key=lambda shard: long_int(shard["HashKeyRange"]["StartingHashKey"]) ) return min_hash_key, max_hash_key, stream_details def long_int(hash_key): """ The hash key is a 128-bit int, sent as a string. It's necessary to convert to int/long for comparison operations. This helper method handles python 2/3 incompatibility CLI Example: .. code-block:: bash salt myminion boto_kinesis.long_int some_MD5_hash_as_string :return: long object if python 2.X, int object if python 3.X """ return int(hash_key) def reshard( stream_name, desired_size, force=False, region=None, key=None, keyid=None, profile=None, ): """ Reshard a kinesis stream. Each call to this function will wait until the stream is ACTIVE, then make a single split or merge operation. This function decides where to split or merge with the assumption that the ultimate goal is a balanced partition space. For safety, user must past in force=True; otherwise, the function will dry run. CLI Example: .. code-block:: bash salt myminion boto_kinesis.reshard my_stream N True region=us-east-1 :return: True if a split or merge was found/performed, False if nothing is needed """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) r = {} stream_response = get_stream_when_active(stream_name, region, key, keyid, profile) if "error" in stream_response: return stream_response stream_details = stream_response["result"]["StreamDescription"] min_hash_key, max_hash_key, stream_details = get_info_for_reshard(stream_details) log.debug( "found %s open shards, min_hash_key %s max_hash_key %s", len(stream_details["OpenShards"]), min_hash_key, max_hash_key, ) # find the first open shard that doesn't match the desired pattern. When we find it, # either split or merge (depending on if it's too big or too small), and then return. for shard_num, shard in enumerate(stream_details["OpenShards"]): shard_id = shard["ShardId"] if "EndingSequenceNumber" in shard["SequenceNumberRange"]: # something went wrong, there's a closed shard in our open shard list log.debug("this should never happen! closed shard %s", shard_id) continue starting_hash_key = shard["HashKeyRange"]["StartingHashKey"] ending_hash_key = shard["HashKeyRange"]["EndingHashKey"] # this weird math matches what AWS does when you create a kinesis stream # with an initial number of shards. expected_starting_hash_key = ( max_hash_key - min_hash_key ) / desired_size * shard_num + shard_num expected_ending_hash_key = (max_hash_key - min_hash_key) / desired_size * ( shard_num + 1 ) + shard_num # fix an off-by-one at the end if expected_ending_hash_key > max_hash_key: expected_ending_hash_key = max_hash_key log.debug( "Shard %s (%s) should start at %s: %s", shard_num, shard_id, expected_starting_hash_key, starting_hash_key == expected_starting_hash_key, ) log.debug( "Shard %s (%s) should end at %s: %s", shard_num, shard_id, expected_ending_hash_key, ending_hash_key == expected_ending_hash_key, ) if starting_hash_key != expected_starting_hash_key: r["error"] = "starting hash keys mismatch, don't know what to do!" return r if ending_hash_key == expected_ending_hash_key: continue if ending_hash_key > expected_ending_hash_key + 1: # split at expected_ending_hash_key if force: log.debug( "%s should end at %s, actual %s, splitting", shard_id, expected_ending_hash_key, ending_hash_key, ) r = _execute_with_retries( conn, "split_shard", StreamName=stream_name, ShardToSplit=shard_id, NewStartingHashKey=str(expected_ending_hash_key + 1), ) else: log.debug( "%s should end at %s, actual %s would split", shard_id, expected_ending_hash_key, ending_hash_key, ) if "error" not in r: r["result"] = True return r else: # merge next_shard_id = _get_next_open_shard(stream_details, shard_id) if not next_shard_id: r["error"] = f"failed to find next shard after {shard_id}" return r if force: log.debug( "%s should continue past %s, merging with %s", shard_id, ending_hash_key, next_shard_id, ) r = _execute_with_retries( conn, "merge_shards", StreamName=stream_name, ShardToMerge=shard_id, AdjacentShardToMerge=next_shard_id, ) else: log.debug( "%s should continue past %s, would merge with %s", shard_id, ending_hash_key, next_shard_id, ) if "error" not in r: r["result"] = True return r log.debug("No split or merge action necessary") r["result"] = False return r def list_streams(region=None, key=None, keyid=None, profile=None): """ Return a list of all streams visible to the current account CLI Example: .. code-block:: bash salt myminion boto_kinesis.list_streams """ conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) streams = [] exclusive_start_stream_name = "" while exclusive_start_stream_name is not None: args = ( {"ExclusiveStartStreamName": exclusive_start_stream_name} if exclusive_start_stream_name else {} ) ret = _execute_with_retries(conn, "list_streams", **args) if "error" in ret: return ret ret = ret["result"] if ret and ret.get("result") else {} streams += ret.get("StreamNames", []) exclusive_start_stream_name = ( streams[-1] if ret.get("HasMoreStreams", False) in (True, "true") else None ) return {"result": streams} def _get_next_open_shard(stream_details, shard_id): """ Return the next open shard after shard_id CLI Example: .. code-block:: bash salt myminion boto_kinesis._get_next_open_shard existing_stream_details shard_id """ found = False for shard in stream_details["OpenShards"]: current_shard_id = shard["ShardId"] if current_shard_id == shard_id: found = True continue if found: return current_shard_id def _execute_with_retries(conn, function, **kwargs): """ Retry if we're rate limited by AWS or blocked by another call. Give up and return error message if resource not found or argument is invalid. conn The connection established by the calling method via _get_conn() function The function to call on conn. i.e. create_stream **kwargs Any kwargs required by the above function, with their keywords i.e. StreamName=stream_name Returns: The result dict with the HTTP response and JSON data if applicable as 'result', or an error as 'error' CLI Example: .. code-block:: bash salt myminion boto_kinesis._execute_with_retries existing_conn function_name function_kwargs """ r = {} max_attempts = 18 max_retry_delay = 10 for attempt in range(max_attempts): log.info("attempt: %s function: %s", attempt, function) try: fn = getattr(conn, function) r["result"] = fn(**kwargs) return r except botocore.exceptions.ClientError as e: error_code = e.response["Error"]["Code"] if ( "LimitExceededException" in error_code or "ResourceInUseException" in error_code ): # could be rate limited by AWS or another command is blocking, # retry with exponential backoff log.debug("Retrying due to AWS exception", exc_info=True) time.sleep(_jittered_backoff(attempt, max_retry_delay)) else: # ResourceNotFoundException or InvalidArgumentException r["error"] = e.response["Error"] log.error(r["error"]) r["result"] = None return r r["error"] = "Tried to execute function {} {} times, but was unable".format( function, max_attempts ) log.error(r["error"]) return r def _jittered_backoff(attempt, max_retry_delay): """ Basic exponential backoff CLI Example: .. code-block:: bash salt myminion boto_kinesis._jittered_backoff current_attempt_number max_delay_in_seconds """ return min(random.random() * (2**attempt), max_retry_delay)