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/imh-python/lib/python3.9/site-packages/libcloud/dns/drivers
Viewing File: /opt/imh-python/lib/python3.9/site-packages/libcloud/dns/drivers/auroradns.py
# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ AuroraDNS DNS Driver """ import base64 import json import hmac import datetime from hashlib import sha256 from libcloud.utils.py3 import httplib from libcloud.utils.py3 import b from libcloud.common.base import ConnectionUserAndKey, JsonResponse from libcloud.common.types import InvalidCredsError, ProviderError from libcloud.common.types import LibcloudError from libcloud.dns.base import DNSDriver, Zone, Record from libcloud.dns.types import RecordType, ZoneDoesNotExistError from libcloud.dns.types import ZoneAlreadyExistsError, RecordDoesNotExistError API_HOST = 'api.auroradns.eu' # Default TTL required by libcloud, but doesn't do anything in AuroraDNS DEFAULT_ZONE_TTL = 3600 DEFAULT_ZONE_TYPE = 'master' VALID_RECORD_PARAMS_EXTRA = ['ttl', 'prio', 'health_check_id', 'disabled'] class AuroraDNSHealthCheckType(object): """ Healthcheck type. """ HTTP = 'HTTP' HTTPS = 'HTTPS' TCP = 'TCP' class HealthCheckError(LibcloudError): error_type = 'HealthCheckError' def __init__(self, value, driver, health_check_id): self.health_check_id = health_check_id super(HealthCheckError, self).__init__(value=value, driver=driver) def __str__(self): return self.__repr__() def __repr__(self): return ('<%s in %s, health_check_id=%s, value=%s>' % (self.error_type, repr(self.driver), self.health_check_id, self.value)) class HealthCheckDoesNotExistError(HealthCheckError): error_type = 'HealthCheckDoesNotExistError' class AuroraDNSHealthCheck(object): """ AuroraDNS Healthcheck resource. """ def __init__(self, id, type, hostname, ipaddress, port, interval, path, threshold, health, enabled, zone, driver, extra=None): """ :param id: Healthcheck id :type id: ``str`` :param hostname: Hostname or FQDN of the target :type hostname: ``str`` :param ipaddress: IPv4 or IPv6 address of the target :type ipaddress: ``str`` :param port: The port on the target to monitor :type port: ``int`` :param interval: The interval of the health check :type interval: ``int`` :param path: The path to monitor on the target :type path: ``str`` :param threshold: The threshold of before marking a check as failed :type threshold: ``int`` :param health: The current health of the health check :type health: ``bool`` :param enabled: If the health check is currently enabled :type enabled: ``bool`` :param zone: Zone instance. :type zone: :class:`Zone` :param driver: DNSDriver instance. :type driver: :class:`DNSDriver` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` """ self.id = str(id) if id else None self.type = type self.hostname = hostname self.ipaddress = ipaddress self.port = int(port) if port else None self.interval = int(interval) self.path = path self.threshold = int(threshold) self.health = bool(health) self.enabled = bool(enabled) self.zone = zone self.driver = driver self.extra = extra or {} def update(self, type=None, hostname=None, ipaddress=None, port=None, interval=None, path=None, threshold=None, enabled=None, extra=None): return self.driver.ex_update_healthcheck(healthcheck=self, type=type, hostname=hostname, ipaddress=ipaddress, port=port, path=path, interval=interval, threshold=threshold, enabled=enabled, extra=extra) def delete(self): return self.driver.ex_delete_healthcheck(healthcheck=self) def __repr__(self): return ('<AuroraDNSHealthCheck: zone=%s, id=%s, type=%s, hostname=%s, ' 'ipaddress=%s, port=%d, interval=%d, health=%s, provider=%s' '...>' % (self.zone.id, self.id, self.type, self.hostname, self.ipaddress, self.port, self.interval, self.health, self.driver.name)) class AuroraDNSResponse(JsonResponse): def success(self): return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] def parse_error(self): status = int(self.status) error = {'driver': self, 'value': ''} if status == httplib.UNAUTHORIZED: error['value'] = 'Authentication failed' raise InvalidCredsError(**error) elif status == httplib.FORBIDDEN: error['value'] = 'Authorization failed' error['http_status'] = status raise ProviderError(**error) elif status == httplib.NOT_FOUND: context = self.connection.context if context['resource'] == 'zone': error['zone_id'] = context['id'] raise ZoneDoesNotExistError(**error) elif context['resource'] == 'record': error['record_id'] = context['id'] raise RecordDoesNotExistError(**error) elif context['resource'] == 'healthcheck': error['health_check_id'] = context['id'] raise HealthCheckDoesNotExistError(**error) elif status == httplib.CONFLICT: context = self.connection.context if context['resource'] == 'zone': error['zone_id'] = context['id'] raise ZoneAlreadyExistsError(**error) elif status == httplib.BAD_REQUEST: context = self.connection.context body = self.parse_body() raise ProviderError(value=body['errormsg'], http_code=status, driver=self) class AuroraDNSConnection(ConnectionUserAndKey): host = API_HOST responseCls = AuroraDNSResponse def calculate_auth_signature(self, secret_key, method, url, timestamp): b64_hmac = base64.b64encode( hmac.new(b(secret_key), b(method) + b(url) + b(timestamp), digestmod=sha256).digest() ) return b64_hmac.decode('utf-8') def gen_auth_header(self, api_key, secret_key, method, url, timestamp): signature = self.calculate_auth_signature(secret_key, method, url, timestamp) auth_b64 = base64.b64encode(b('%s:%s' % (api_key, signature))) return 'AuroraDNSv1 %s' % (auth_b64.decode('utf-8')) def request(self, action, params=None, data='', headers=None, method='GET'): if not headers: headers = {} if not params: params = {} if method in ("POST", "PUT"): headers = {'Content-Type': 'application/json; charset=UTF-8'} t = datetime.datetime.utcnow() timestamp = t.strftime('%Y%m%dT%H%M%SZ') headers['X-AuroraDNS-Date'] = timestamp headers['Authorization'] = self.gen_auth_header(self.user_id, self.key, method, action, timestamp) return super(AuroraDNSConnection, self).request(action=action, params=params, data=data, method=method, headers=headers) class AuroraDNSDriver(DNSDriver): name = 'AuroraDNS' website = 'https://www.pcextreme.nl/en/aurora/dns' connectionCls = AuroraDNSConnection RECORD_TYPE_MAP = { RecordType.A: 'A', RecordType.AAAA: 'AAAA', RecordType.CNAME: 'CNAME', RecordType.MX: 'MX', RecordType.NS: 'NS', RecordType.SOA: 'SOA', RecordType.SRV: 'SRV', RecordType.TXT: 'TXT', RecordType.DS: 'DS', RecordType.PTR: 'PTR', RecordType.SSHFP: 'SSHFP', RecordType.TLSA: 'TLSA' } HEALTHCHECK_TYPE_MAP = { AuroraDNSHealthCheckType.HTTP: 'HTTP', AuroraDNSHealthCheckType.HTTPS: 'HTTPS', AuroraDNSHealthCheckType.TCP: 'TCP' } def iterate_zones(self): res = self.connection.request('/zones') for zone in res.parse_body(): yield self.__res_to_zone(zone) def iterate_records(self, zone): self.connection.set_context({'resource': 'zone', 'id': zone.id}) res = self.connection.request('/zones/%s/records' % zone.id) for record in res.parse_body(): yield self.__res_to_record(zone, record) def get_zone(self, zone_id): self.connection.set_context({'resource': 'zone', 'id': zone_id}) res = self.connection.request('/zones/%s' % zone_id) zone = res.parse_body() return self.__res_to_zone(zone) def get_record(self, zone_id, record_id): self.connection.set_context({'resource': 'record', 'id': record_id}) res = self.connection.request('/zones/%s/records/%s' % (zone_id, record_id)) record = res.parse_body() zone = self.get_zone(zone_id) return self.__res_to_record(zone, record) def create_zone(self, domain, type='master', ttl=None, extra=None): self.connection.set_context({'resource': 'zone', 'id': domain}) res = self.connection.request('/zones', method='POST', data=json.dumps({'name': domain})) zone = res.parse_body() return self.__res_to_zone(zone) def create_record(self, name, zone, type, data, extra=None): if name is None: name = "" rdata = { 'name': name, 'type': self.RECORD_TYPE_MAP[type], 'content': data } rdata = self.__merge_extra_data(rdata, extra) if 'ttl' not in rdata: rdata['ttl'] = DEFAULT_ZONE_TTL self.connection.set_context({'resource': 'zone', 'id': zone.id}) res = self.connection.request('/zones/%s/records' % zone.id, method='POST', data=json.dumps(rdata)) record = res.parse_body() return self.__res_to_record(zone, record) def delete_zone(self, zone): self.connection.set_context({'resource': 'zone', 'id': zone.id}) self.connection.request('/zones/%s' % zone.id, method='DELETE') return True def delete_record(self, record): self.connection.set_context({'resource': 'record', 'id': record.id}) self.connection.request('/zones/%s/records/%s' % (record.zone.id, record.id), method='DELETE') return True def list_record_types(self): types = [] for record_type in self.RECORD_TYPE_MAP.keys(): types.append(record_type) return types def update_record(self, record, name, type, data, extra=None): rdata = {} if name is not None: rdata['name'] = name if type is not None: rdata['type'] = self.RECORD_TYPE_MAP[type] if data is not None: rdata['content'] = data rdata = self.__merge_extra_data(rdata, extra) self.connection.set_context({'resource': 'record', 'id': record.id}) self.connection.request('/zones/%s/records/%s' % (record.zone.id, record.id), method='PUT', data=json.dumps(rdata)) return self.get_record(record.zone.id, record.id) def ex_list_healthchecks(self, zone): """ List all Health Checks in a zone. :param zone: Zone to list health checks for. :type zone: :class:`Zone` :return: ``list`` of :class:`AuroraDNSHealthCheck` """ healthchecks = [] self.connection.set_context({'resource': 'zone', 'id': zone.id}) res = self.connection.request('/zones/%s/health_checks' % zone.id) for healthcheck in res.parse_body(): healthchecks.append(self.__res_to_healthcheck(zone, healthcheck)) return healthchecks def ex_get_healthcheck(self, zone, health_check_id): """ Get a single Health Check from a zone :param zone: Zone in which the health check is :type zone: :class:`Zone` :param health_check_id: ID of the required health check :type health_check_id: ``str`` :return: :class:`AuroraDNSHealthCheck` """ self.connection.set_context({'resource': 'healthcheck', 'id': health_check_id}) res = self.connection.request('/zones/%s/health_checks/%s' % (zone.id, health_check_id)) check = res.parse_body() return self.__res_to_healthcheck(zone, check) def ex_create_healthcheck(self, zone, type, hostname, port, path, interval, threshold, ipaddress=None, enabled=True, extra=None): """ Create a new Health Check in a zone :param zone: Zone in which the health check should be created :type zone: :class:`Zone` :param type: The type of health check to be created :type type: :class:`AuroraDNSHealthCheckType` :param hostname: The hostname of the target to monitor :type hostname: ``str`` :param port: The port of the target to monitor. E.g. 80 for HTTP :type port: ``int`` :param path: The path of the target to monitor. Only used by HTTP at this moment. Usually this is simple /. :type path: ``str`` :param interval: The interval of checks. 10, 30 or 60 seconds. :type interval: ``int`` :param threshold: The threshold of failures before the healthcheck is marked as failed. :type threshold: ``int`` :param ipaddress: (optional) The IP Address of the target to monitor. You can pass a empty string if this is not required. :type ipaddress: ``str`` :param enabled: (optional) If this healthcheck is enabled to run :type enabled: ``bool`` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :return: :class:`AuroraDNSHealthCheck` """ cdata = { 'type': self.HEALTHCHECK_TYPE_MAP[type], 'hostname': hostname, 'ipaddress': ipaddress, 'port': int(port), 'interval': int(interval), 'path': path, 'threshold': int(threshold), 'enabled': enabled } self.connection.set_context({'resource': 'zone', 'id': zone.id}) res = self.connection.request('/zones/%s/health_checks' % zone.id, method='POST', data=json.dumps(cdata)) healthcheck = res.parse_body() return self.__res_to_healthcheck(zone, healthcheck) def ex_update_healthcheck(self, healthcheck, type=None, hostname=None, ipaddress=None, port=None, path=None, interval=None, threshold=None, enabled=None, extra=None): """ Update an existing Health Check :param zone: The healthcheck which has to be updated :type zone: :class:`AuroraDNSHealthCheck` :param type: (optional) The type of health check to be created :type type: :class:`AuroraDNSHealthCheckType` :param hostname: (optional) The hostname of the target to monitor :type hostname: ``str`` :param ipaddress: (optional) The IP Address of the target to monitor. You can pass a empty string if this is not required. :type ipaddress: ``str`` :param port: (optional) The port of the target to monitor. E.g. 80 for HTTP :type port: ``int`` :param path: (optional) The path of the target to monitor. Only used by HTTP at this moment. Usually just '/'. :type path: ``str`` :param interval: (optional) The interval of checks. 10, 30 or 60 seconds. :type interval: ``int`` :param threshold: (optional) The threshold of failures before the healthcheck is marked as failed. :type threshold: ``int`` :param enabled: (optional) If this healthcheck is enabled to run :type enabled: ``bool`` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :return: :class:`AuroraDNSHealthCheck` """ cdata = {} if type is not None: cdata['type'] = self.HEALTHCHECK_TYPE_MAP[type] if hostname is not None: cdata['hostname'] = hostname if ipaddress is not None: if len(ipaddress) == 0: cdata['ipaddress'] = None else: cdata['ipaddress'] = ipaddress if port is not None: cdata['port'] = int(port) if path is not None: cdata['path'] = path if interval is not None: cdata['interval'] = int(interval) if threshold is not None: cdata['threshold'] = threshold if enabled is not None: cdata['enabled'] = bool(enabled) self.connection.set_context({'resource': 'healthcheck', 'id': healthcheck.id}) self.connection.request('/zones/%s/health_checks/%s' % (healthcheck.zone.id, healthcheck.id), method='PUT', data=json.dumps(cdata)) return self.ex_get_healthcheck(healthcheck.zone, healthcheck.id) def ex_delete_healthcheck(self, healthcheck): """ Remove an existing Health Check :param zone: The healthcheck which has to be removed :type zone: :class:`AuroraDNSHealthCheck` """ self.connection.set_context({'resource': 'healthcheck', 'id': healthcheck.id}) self.connection.request('/zones/%s/health_checks/%s' % (healthcheck.zone.id, healthcheck.id), method='DELETE') return True def __res_to_record(self, zone, record): if len(record['name']) == 0: name = None else: name = record['name'] extra = {} extra['created'] = record['created'] extra['modified'] = record['modified'] extra['disabled'] = record['disabled'] extra['ttl'] = record['ttl'] extra['priority'] = record['prio'] return Record(id=record['id'], name=name, type=record['type'], data=record['content'], zone=zone, driver=self.connection.driver, ttl=record['ttl'], extra=extra) def __res_to_zone(self, zone): return Zone(id=zone['id'], domain=zone['name'], type=DEFAULT_ZONE_TYPE, ttl=DEFAULT_ZONE_TTL, driver=self.connection.driver, extra={'created': zone['created'], 'servers': zone['servers'], 'account_id': zone['account_id'], 'cluster_id': zone['cluster_id']}) def __res_to_healthcheck(self, zone, healthcheck): return AuroraDNSHealthCheck(id=healthcheck['id'], type=healthcheck['type'], hostname=healthcheck['hostname'], ipaddress=healthcheck['ipaddress'], health=healthcheck['health'], threshold=healthcheck['threshold'], path=healthcheck['path'], interval=healthcheck['interval'], port=healthcheck['port'], enabled=healthcheck['enabled'], zone=zone, driver=self.connection.driver) def __merge_extra_data(self, rdata, extra): if extra is not None: for param in VALID_RECORD_PARAMS_EXTRA: if param in extra: rdata[param] = extra[param] return rdata