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/cloud/clouds
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/cloud/clouds/aliyun.py
""" AliYun ECS Cloud Module ======================= .. versionadded:: 2014.7.0 The Aliyun cloud module is used to control access to the aliyun ECS. http://www.aliyun.com/ Use of this module requires the ``id`` and ``key`` parameter to be set. Set up the cloud configuration at ``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/aliyun.conf``: .. code-block:: yaml my-aliyun-config: # aliyun Access Key ID id: wFGEwgregeqw3435gDger # aliyun Access Key Secret key: GDE43t43REGTrkilg43934t34qT43t4dgegerGEgg location: cn-qingdao driver: aliyun :depends: requests """ import base64 import hmac import logging import pprint import sys import time import urllib.parse import uuid from hashlib import sha1 import salt.config as config import salt.utils.cloud import salt.utils.data import salt.utils.json from salt.exceptions import ( SaltCloudExecutionFailure, SaltCloudExecutionTimeout, SaltCloudNotFound, SaltCloudSystemExit, ) from salt.utils.stringutils import to_bytes try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False # Get logging started log = logging.getLogger(__name__) ALIYUN_LOCATIONS = { # 'us-west-2': 'ec2_us_west_oregon', "cn-hangzhou": "AliYun HangZhou Region", "cn-beijing": "AliYun BeiJing Region", "cn-hongkong": "AliYun HongKong Region", "cn-qingdao": "AliYun QingDao Region", "cn-shanghai": "AliYun ShangHai Region", "cn-shenzhen": "AliYun ShenZheng Region", "ap-northeast-1": "AliYun DongJing Region", "ap-southeast-1": "AliYun XinJiaPo Region", "ap-southeast-2": "AliYun XiNi Region", "eu-central-1": "EU FalaKeFu Region", "me-east-1": "ME DiBai Region", "us-east-1": "US FuJiNiYa Region", "us-west-1": "US GuiGu Region", } DEFAULT_LOCATION = "cn-hangzhou" DEFAULT_ALIYUN_API_VERSION = "2014-05-26" __virtualname__ = "aliyun" # Only load in this module if the aliyun configurations are in place def __virtual__(): """ Check for aliyun configurations """ if get_configured_provider() is False: return False if get_dependencies() is False: return False return __virtualname__ def _get_active_provider_name(): try: return __active_provider_name__.value() except AttributeError: return __active_provider_name__ def get_configured_provider(): """ Return the first configured instance. """ return config.is_provider_configured( __opts__, _get_active_provider_name() or __virtualname__, ("id", "key") ) def get_dependencies(): """ Warn if dependencies aren't met. """ return config.check_driver_dependencies(__virtualname__, {"requests": HAS_REQUESTS}) def avail_locations(call=None): """ Return a dict of all available VM locations on the cloud provider with relevant data """ if call == "action": raise SaltCloudSystemExit( "The avail_locations function must be called with " "-f or --function, or with the --list-locations option" ) params = {"Action": "DescribeRegions"} items = query(params=params) ret = {} for region in items["Regions"]["Region"]: ret[region["RegionId"]] = {} for item in region: ret[region["RegionId"]][item] = str(region[item]) return ret def avail_images(kwargs=None, call=None): """ Return a list of the images that are on the provider """ if call == "action": raise SaltCloudSystemExit( "The avail_images function must be called with " "-f or --function, or with the --list-images option" ) if not isinstance(kwargs, dict): kwargs = {} provider = get_configured_provider() location = provider.get("location", DEFAULT_LOCATION) if "location" in kwargs: location = kwargs["location"] params = { "Action": "DescribeImages", "RegionId": location, "PageSize": "100", } items = query(params=params) ret = {} for image in items["Images"]["Image"]: ret[image["ImageId"]] = {} for item in image: ret[image["ImageId"]][item] = str(image[item]) return ret def avail_sizes(call=None): """ Return a list of the image sizes that are on the provider """ if call == "action": raise SaltCloudSystemExit( "The avail_sizes function must be called with " "-f or --function, or with the --list-sizes option" ) params = {"Action": "DescribeInstanceTypes"} items = query(params=params) ret = {} for image in items["InstanceTypes"]["InstanceType"]: ret[image["InstanceTypeId"]] = {} for item in image: ret[image["InstanceTypeId"]][item] = str(image[item]) return ret def get_location(vm_=None): """ Return the aliyun region to use, in this order: - CLI parameter - VM parameter - Cloud profile setting """ return __opts__.get( "location", config.get_cloud_config_value( "location", vm_ or get_configured_provider(), __opts__, default=DEFAULT_LOCATION, search_global=False, ), ) def list_availability_zones(call=None): """ List all availability zones in the current region """ ret = {} params = {"Action": "DescribeZones", "RegionId": get_location()} items = query(params) for zone in items["Zones"]["Zone"]: ret[zone["ZoneId"]] = {} for item in zone: ret[zone["ZoneId"]][item] = str(zone[item]) return ret def list_nodes_min(call=None): """ Return a list of the VMs that are on the provider. Only a list of VM names, and their state, is returned. This is the minimum amount of information needed to check for existing VMs. """ if call == "action": raise SaltCloudSystemExit( "The list_nodes_min function must be called with -f or --function." ) ret = {} location = get_location() params = { "Action": "DescribeInstanceStatus", "RegionId": location, } nodes = query(params) log.debug("Total %s instance found in Region %s", nodes["TotalCount"], location) if "Code" in nodes or nodes["TotalCount"] == 0: return ret for node in nodes["InstanceStatuses"]["InstanceStatus"]: ret[node["InstanceId"]] = {} for item in node: ret[node["InstanceId"]][item] = node[item] return ret def list_nodes(call=None): """ Return a list of the VMs that are on the provider """ if call == "action": raise SaltCloudSystemExit( "The list_nodes function must be called with -f or --function." ) nodes = list_nodes_full() ret = {} for instanceId in nodes: node = nodes[instanceId] ret[node["name"]] = { "id": node["id"], "name": node["name"], "public_ips": node["public_ips"], "private_ips": node["private_ips"], "size": node["size"], "state": str(node["state"]), } return ret def list_nodes_full(call=None): """ Return a list of the VMs that are on the provider """ if call == "action": raise SaltCloudSystemExit( "The list_nodes_full function must be called with -f or --function." ) ret = {} location = get_location() params = { "Action": "DescribeInstanceStatus", "RegionId": location, "PageSize": "50", } result = query(params=params) log.debug("Total %s instance found in Region %s", result["TotalCount"], location) if "Code" in result or result["TotalCount"] == 0: return ret # aliyun max 100 top instance in api result_instancestatus = result["InstanceStatuses"]["InstanceStatus"] if result["TotalCount"] > 50: params["PageNumber"] = "2" result = query(params=params) result_instancestatus.update(result["InstanceStatuses"]["InstanceStatus"]) for node in result_instancestatus: instanceId = node.get("InstanceId", "") params = {"Action": "DescribeInstanceAttribute", "InstanceId": instanceId} items = query(params=params) if "Code" in items: log.warning("Query instance:%s attribute failed", instanceId) continue name = items["InstanceName"] ret[name] = { "id": items["InstanceId"], "name": name, "image": items["ImageId"], "size": "TODO", "state": items["Status"], } for item in items: value = items[item] if value is not None: value = str(value) if item == "PublicIpAddress": ret[name]["public_ips"] = items[item]["IpAddress"] if item == "InnerIpAddress" and "private_ips" not in ret[name]: ret[name]["private_ips"] = items[item]["IpAddress"] if item == "VpcAttributes": vpc_ips = items[item]["PrivateIpAddress"]["IpAddress"] if vpc_ips: ret[name]["private_ips"] = vpc_ips ret[name][item] = value provider = _get_active_provider_name() or "aliyun" if ":" in provider: comps = provider.split(":") provider = comps[0] __opts__["update_cachedir"] = True __utils__["cloud.cache_node_list"](ret, provider, __opts__) return ret def list_nodes_select(call=None): """ Return a list of the VMs that are on the provider, with select fields """ return salt.utils.cloud.list_nodes_select( list_nodes_full("function"), __opts__["query.selection"], call, ) def list_securitygroup(call=None): """ Return a list of security group """ if call == "action": raise SaltCloudSystemExit( "The list_nodes function must be called with -f or --function." ) params = { "Action": "DescribeSecurityGroups", "RegionId": get_location(), "PageSize": "50", } result = query(params) if "Code" in result: return {} ret = {} for sg in result["SecurityGroups"]["SecurityGroup"]: ret[sg["SecurityGroupId"]] = {} for item in sg: ret[sg["SecurityGroupId"]][item] = sg[item] return ret def get_image(vm_): """ Return the image object to use """ images = avail_images() vm_image = str( config.get_cloud_config_value("image", vm_, __opts__, search_global=False) ) if not vm_image: raise SaltCloudNotFound("No image specified for this VM.") if vm_image and str(vm_image) in images: return images[vm_image]["ImageId"] raise SaltCloudNotFound(f"The specified image, '{vm_image}', could not be found.") def get_securitygroup(vm_): """ Return the security group """ sgs = list_securitygroup() securitygroup = config.get_cloud_config_value( "securitygroup", vm_, __opts__, search_global=False ) if not securitygroup: raise SaltCloudNotFound("No securitygroup ID specified for this VM.") if securitygroup and str(securitygroup) in sgs: return sgs[securitygroup]["SecurityGroupId"] raise SaltCloudNotFound( f"The specified security group, '{securitygroup}', could not be found." ) def get_size(vm_): """ Return the VM's size. Used by create_node(). """ sizes = avail_sizes() vm_size = str( config.get_cloud_config_value("size", vm_, __opts__, search_global=False) ) if not vm_size: raise SaltCloudNotFound("No size specified for this VM.") if vm_size and str(vm_size) in sizes: return sizes[vm_size]["InstanceTypeId"] raise SaltCloudNotFound(f"The specified size, '{vm_size}', could not be found.") def __get_location(vm_): """ Return the VM's location """ locations = avail_locations() vm_location = str( config.get_cloud_config_value("location", vm_, __opts__, search_global=False) ) if not vm_location: raise SaltCloudNotFound("No location specified for this VM.") if vm_location and str(vm_location) in locations: return locations[vm_location]["RegionId"] raise SaltCloudNotFound( f"The specified location, '{vm_location}', could not be found." ) def start(name, call=None): """ Start a node CLI Examples: .. code-block:: bash salt-cloud -a start myinstance """ if call != "action": raise SaltCloudSystemExit("The stop action must be called with -a or --action.") log.info("Starting node %s", name) instanceId = _get_node(name)["InstanceId"] params = {"Action": "StartInstance", "InstanceId": instanceId} result = query(params) return result def stop(name, force=False, call=None): """ Stop a node CLI Examples: .. code-block:: bash salt-cloud -a stop myinstance salt-cloud -a stop myinstance force=True """ if call != "action": raise SaltCloudSystemExit("The stop action must be called with -a or --action.") log.info("Stopping node %s", name) instanceId = _get_node(name)["InstanceId"] params = { "Action": "StopInstance", "InstanceId": instanceId, "ForceStop": str(force).lower(), } result = query(params) return result def reboot(name, call=None): """ Reboot a node CLI Examples: .. code-block:: bash salt-cloud -a reboot myinstance """ if call != "action": raise SaltCloudSystemExit("The stop action must be called with -a or --action.") log.info("Rebooting node %s", name) instance_id = _get_node(name)["InstanceId"] params = {"Action": "RebootInstance", "InstanceId": instance_id} result = query(params) return result def create_node(kwargs): """ Convenience function to make the rest api call for node creation. """ if not isinstance(kwargs, dict): kwargs = {} # Required parameters params = { "Action": "CreateInstance", "InstanceType": kwargs.get("size_id", ""), "RegionId": kwargs.get("region_id", DEFAULT_LOCATION), "ImageId": kwargs.get("image_id", ""), "SecurityGroupId": kwargs.get("securitygroup_id", ""), "InstanceName": kwargs.get("name", ""), } # Optional parameters' optional = [ "InstanceName", "InternetChargeType", "InternetMaxBandwidthIn", "InternetMaxBandwidthOut", "HostName", "Password", "SystemDisk.Category", "VSwitchId", # 'DataDisk.n.Size', 'DataDisk.n.Category', 'DataDisk.n.SnapshotId' ] for item in optional: if item in kwargs: params.update({item: kwargs[item]}) # invoke web call result = query(params) return result["InstanceId"] def create(vm_): """ Create a single VM from a data dict """ try: # Check for required profile parameters before sending any API calls. if ( vm_["profile"] and config.is_profile_configured( __opts__, _get_active_provider_name() or "aliyun", vm_["profile"], vm_=vm_, ) is False ): return False except AttributeError: pass __utils__["cloud.fire_event"]( "event", "starting create", "salt/cloud/{}/creating".format(vm_["name"]), args=__utils__["cloud.filter_event"]( "creating", vm_, ["name", "profile", "provider", "driver"] ), sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) log.info("Creating Cloud VM %s", vm_["name"]) kwargs = { "name": vm_["name"], "size_id": get_size(vm_), "image_id": get_image(vm_), "region_id": __get_location(vm_), "securitygroup_id": get_securitygroup(vm_), } if "vswitch_id" in vm_: kwargs["VSwitchId"] = vm_["vswitch_id"] if "internet_chargetype" in vm_: kwargs["InternetChargeType"] = vm_["internet_chargetype"] if "internet_maxbandwidthin" in vm_: kwargs["InternetMaxBandwidthIn"] = str(vm_["internet_maxbandwidthin"]) if "internet_maxbandwidthout" in vm_: kwargs["InternetMaxBandwidthOut"] = str(vm_["internet_maxbandwidthOut"]) if "hostname" in vm_: kwargs["HostName"] = vm_["hostname"] if "password" in vm_: kwargs["Password"] = vm_["password"] if "instance_name" in vm_: kwargs["InstanceName"] = vm_["instance_name"] if "systemdisk_category" in vm_: kwargs["SystemDisk.Category"] = vm_["systemdisk_category"] __utils__["cloud.fire_event"]( "event", "requesting instance", "salt/cloud/{}/requesting".format(vm_["name"]), args=__utils__["cloud.filter_event"]("requesting", kwargs, list(kwargs)), sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) try: ret = create_node(kwargs) except Exception as exc: # pylint: disable=broad-except log.error( "Error creating %s on Aliyun ECS\n\n" "The following exception was thrown when trying to " "run the initial deployment: %s", vm_["name"], str(exc), # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG, ) return False # repair ip address error and start vm time.sleep(8) params = {"Action": "StartInstance", "InstanceId": ret} query(params) def __query_node_data(vm_name): data = show_instance(vm_name, call="action") if not data: # Trigger an error in the wait_for_ip function return False if data.get("PublicIpAddress", None) is not None: return data try: data = salt.utils.cloud.wait_for_ip( __query_node_data, update_args=(vm_["name"],), timeout=config.get_cloud_config_value( "wait_for_ip_timeout", vm_, __opts__, default=10 * 60 ), interval=config.get_cloud_config_value( "wait_for_ip_interval", vm_, __opts__, default=10 ), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_["name"]) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) if data["public_ips"]: ssh_ip = data["public_ips"][0] elif data["private_ips"]: ssh_ip = data["private_ips"][0] else: log.info("No available ip:cant connect to salt") return False log.debug("VM %s is now running", ssh_ip) vm_["ssh_host"] = ssh_ip # The instance is booted and accessible, let's Salt it! ret = __utils__["cloud.bootstrap"](vm_, __opts__) ret.update(data) log.info("Created Cloud VM '%s'", vm_["name"]) log.debug("'%s' VM creation details:\n%s", vm_["name"], pprint.pformat(data)) __utils__["cloud.fire_event"]( "event", "created instance", "salt/cloud/{}/created".format(vm_["name"]), args=__utils__["cloud.filter_event"]( "created", vm_, ["name", "profile", "provider", "driver"] ), sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) return ret def _compute_signature(parameters, access_key_secret): """ Generate aliyun request signature """ def percent_encode(line): if not isinstance(line, str): return line s = line if sys.stdin.encoding is None: s = line.decode().encode("utf8") else: s = line.decode(sys.stdin.encoding).encode("utf8") res = urllib.parse.quote(s, "") res = res.replace("+", "%20") res = res.replace("*", "%2A") res = res.replace("%7E", "~") return res sortedParameters = sorted(list(parameters.items()), key=lambda items: items[0]) canonicalizedQueryString = "" for k, v in sortedParameters: canonicalizedQueryString += "&" + percent_encode(k) + "=" + percent_encode(v) # All aliyun API only support GET method stringToSign = "GET&%2F&" + percent_encode(canonicalizedQueryString[1:]) h = hmac.new(to_bytes(access_key_secret + "&"), stringToSign, sha1) signature = base64.encodestring(h.digest()).strip() return signature def query(params=None): """ Make a web call to aliyun ECS REST API """ path = "https://ecs-cn-hangzhou.aliyuncs.com" access_key_id = config.get_cloud_config_value( "id", get_configured_provider(), __opts__, search_global=False ) access_key_secret = config.get_cloud_config_value( "key", get_configured_provider(), __opts__, search_global=False ) timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) # public interface parameters parameters = { "Format": "JSON", "Version": DEFAULT_ALIYUN_API_VERSION, "AccessKeyId": access_key_id, "SignatureVersion": "1.0", "SignatureMethod": "HMAC-SHA1", "SignatureNonce": str(uuid.uuid1()), "TimeStamp": timestamp, } # include action or function parameters if params: parameters.update(params) # Calculate the string for Signature signature = _compute_signature(parameters, access_key_secret) parameters["Signature"] = signature request = requests.get(path, params=parameters, verify=True, timeout=120) if request.status_code != 200: raise SaltCloudSystemExit( "An error occurred while querying aliyun ECS. HTTP Code: {} " "Error: '{}'".format(request.status_code, request.text) ) log.debug(request.url) content = request.text result = salt.utils.json.loads(content) if "Code" in result: raise SaltCloudSystemExit(pprint.pformat(result.get("Message", {}))) return result def script(vm_): """ Return the script deployment object """ deploy_script = salt.utils.cloud.os_script( config.get_cloud_config_value("script", vm_, __opts__), vm_, __opts__, salt.utils.cloud.salt_config_to_yaml( salt.utils.cloud.minion_config(__opts__, vm_) ), ) return deploy_script def show_disk(name, call=None): """ Show the disk details of the instance CLI Examples: .. code-block:: bash salt-cloud -a show_disk aliyun myinstance """ if call != "action": raise SaltCloudSystemExit( "The show_disks action must be called with -a or --action." ) ret = {} params = {"Action": "DescribeInstanceDisks", "InstanceId": name} items = query(params=params) for disk in items["Disks"]["Disk"]: ret[disk["DiskId"]] = {} for item in disk: ret[disk["DiskId"]][item] = str(disk[item]) return ret def list_monitor_data(kwargs=None, call=None): """ Get monitor data of the instance. If instance name is missing, will show all the instance monitor data on the region. CLI Examples: .. code-block:: bash salt-cloud -f list_monitor_data aliyun salt-cloud -f list_monitor_data aliyun name=AY14051311071990225bd """ if call != "function": raise SaltCloudSystemExit( "The list_monitor_data must be called with -f or --function." ) if not isinstance(kwargs, dict): kwargs = {} ret = {} params = {"Action": "GetMonitorData", "RegionId": get_location()} if "name" in kwargs: params["InstanceId"] = kwargs["name"] items = query(params=params) monitorData = items["MonitorData"] for data in monitorData["InstanceMonitorData"]: ret[data["InstanceId"]] = {} for item in data: ret[data["InstanceId"]][item] = str(data[item]) return ret def show_instance(name, call=None): """ Show the details from aliyun instance """ if call != "action": raise SaltCloudSystemExit( "The show_instance action must be called with -a or --action." ) return _get_node(name) def _get_node(name): attempts = 5 while attempts >= 0: try: return list_nodes_full()[name] except KeyError: attempts -= 1 log.debug( "Failed to get the data for node '%s'. Remaining attempts: %s", name, attempts, ) # Just a little delay between attempts... time.sleep(0.5) raise SaltCloudNotFound(f"The specified instance {name} not found") def show_image(kwargs, call=None): """ Show the details from aliyun image """ if call != "function": raise SaltCloudSystemExit( "The show_images function must be called with -f or --function" ) if not isinstance(kwargs, dict): kwargs = {} location = get_location() if "location" in kwargs: location = kwargs["location"] params = { "Action": "DescribeImages", "RegionId": location, "ImageId": kwargs["image"], } ret = {} items = query(params=params) # DescribeImages so far support input multi-image. And # if not found certain image, the response will include # blank image list other than 'not found' error message if "Code" in items or not items["Images"]["Image"]: raise SaltCloudNotFound("The specified image could not be found.") log.debug("Total %s image found in Region %s", items["TotalCount"], location) for image in items["Images"]["Image"]: ret[image["ImageId"]] = {} for item in image: ret[image["ImageId"]][item] = str(image[item]) return ret def destroy(name, call=None): """ Destroy a node. CLI Example: .. code-block:: bash salt-cloud -a destroy myinstance salt-cloud -d myinstance """ if call == "function": raise SaltCloudSystemExit( "The destroy action must be called with -d, --destroy, -a or --action." ) __utils__["cloud.fire_event"]( "event", "destroying instance", f"salt/cloud/{name}/destroying", args={"name": name}, sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) instanceId = _get_node(name)["InstanceId"] # have to stop instance before del it stop_params = {"Action": "StopInstance", "InstanceId": instanceId} query(stop_params) params = {"Action": "DeleteInstance", "InstanceId": instanceId} node = query(params) __utils__["cloud.fire_event"]( "event", "destroyed instance", f"salt/cloud/{name}/destroyed", args={"name": name}, sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) return node