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/qingcloud.py
""" QingCloud Cloud Module ====================== .. versionadded:: 2015.8.0 The QingCloud cloud module is used to control access to the QingCloud. http://www.qingcloud.com/ Use of this module requires the ``access_key_id``, ``secret_access_key``, ``zone`` and ``key_filename`` parameter to be set. Set up the cloud configuration at ``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/qingcloud.conf``: .. code-block:: yaml my-qingcloud: driver: qingcloud access_key_id: AKIDMRTGYONNLTFFRBQJ secret_access_key: clYwH21U5UOmcov4aNV2V2XocaHCG3JZGcxEczFu zone: pek2 key_filename: /path/to/your.pem :depends: requests """ import base64 import hmac import logging import pprint import time import urllib.parse from hashlib import sha256 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, ) try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False # Get logging started log = logging.getLogger(__name__) __virtualname__ = "qingcloud" DEFAULT_QINGCLOUD_API_VERSION = 1 DEFAULT_QINGCLOUD_SIGNATURE_VERSION = 1 # Only load in this module if the qingcloud configurations are in place def __virtual__(): """ Check for QingCloud 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__, ("access_key_id", "secret_access_key", "zone", "key_filename"), ) def get_dependencies(): """ Warn if dependencies aren't met. """ return config.check_driver_dependencies(__virtualname__, {"requests": HAS_REQUESTS}) def _compute_signature(parameters, access_key_secret, method, path): """ Generate an API request signature. Detailed document can be found at: https://docs.qingcloud.com/api/common/signature.html """ parameters["signature_method"] = "HmacSHA256" string_to_sign = f"{method.upper()}\n{path}\n" keys = sorted(parameters.keys()) pairs = [] for key in keys: val = str(parameters[key]).encode("utf-8") pairs.append( urllib.parse.quote(key, safe="") + "=" + urllib.parse.quote(val, safe="-_~") ) qs = "&".join(pairs) string_to_sign += qs h = hmac.new(access_key_secret, digestmod=sha256) h.update(string_to_sign) signature = base64.b64encode(h.digest()).strip() return signature def query(params=None): """ Make a web call to QingCloud IaaS API. """ path = "https://api.qingcloud.com/iaas/" access_key_id = config.get_cloud_config_value( "access_key_id", get_configured_provider(), __opts__, search_global=False ) access_key_secret = config.get_cloud_config_value( "secret_access_key", get_configured_provider(), __opts__, search_global=False ) verify_ssl = config.get_cloud_config_value( "verify_ssl", get_configured_provider(), __opts__, default=True, search_global=False, ) # public interface parameters real_parameters = { "access_key_id": access_key_id, "signature_version": DEFAULT_QINGCLOUD_SIGNATURE_VERSION, "time_stamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "version": DEFAULT_QINGCLOUD_API_VERSION, } # include action or function parameters if params: for key, value in params.items(): if isinstance(value, list): for i in range(1, len(value) + 1): if isinstance(value[i - 1], dict): for sk, sv in value[i - 1].items(): if isinstance(sv, dict) or isinstance(sv, list): sv = salt.utils.json.dumps(sv, separators=(",", ":")) real_parameters[f"{key}.{i}.{sk}"] = sv else: real_parameters[f"{key}.{i}"] = value[i - 1] else: real_parameters[key] = value # Calculate the string for Signature signature = _compute_signature(real_parameters, access_key_secret, "GET", "/iaas/") real_parameters["signature"] = signature # print('parameters:') # pprint.pprint(real_parameters) request = requests.get(path, params=real_parameters, verify=verify_ssl, timeout=120) # print('url:') # print(request.url) if request.status_code != 200: raise SaltCloudSystemExit( "An error occurred while querying QingCloud. HTTP Code: {} " "Error: '{}'".format(request.status_code, request.text) ) log.debug(request.url) content = request.text result = salt.utils.json.loads(content) # print('response:') # pprint.pprint(result) if result["ret_code"] != 0: raise SaltCloudSystemExit(pprint.pformat(result.get("message", {}))) return result def avail_locations(call=None): """ Return a dict of all available locations on the provider with relevant data. CLI Examples: .. code-block:: bash salt-cloud --list-locations my-qingcloud """ if call == "action": raise SaltCloudSystemExit( "The avail_locations function must be called with " "-f or --function, or with the --list-locations option" ) params = { "action": "DescribeZones", } items = query(params=params) result = {} for region in items["zone_set"]: result[region["zone_id"]] = {} for key in region: result[region["zone_id"]][key] = str(region[key]) return result def _get_location(vm_=None): """ Return the VM's location. Used by create(). """ locations = avail_locations() vm_location = str( config.get_cloud_config_value("zone", vm_, __opts__, search_global=False) ) if not vm_location: raise SaltCloudNotFound("No location specified for this VM.") if vm_location in locations: return vm_location raise SaltCloudNotFound( f"The specified location, '{vm_location}', could not be found." ) def _get_specified_zone(kwargs=None, provider=None): if provider is None: provider = get_configured_provider() if isinstance(kwargs, dict): zone = kwargs.get("zone", None) if zone is not None: return zone zone = provider["zone"] return zone def avail_images(kwargs=None, call=None): """ Return a list of the images that are on the provider. CLI Examples: .. code-block:: bash salt-cloud --list-images my-qingcloud salt-cloud -f avail_images my-qingcloud zone=gd1 """ 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 = {} params = { "action": "DescribeImages", "provider": "system", "zone": _get_specified_zone(kwargs, get_configured_provider()), } items = query(params=params) result = {} for image in items["image_set"]: result[image["image_id"]] = {} for key in image: result[image["image_id"]][key] = image[key] return result def _get_image(vm_): """ Return the VM's image. Used by create(). """ 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 in images: return vm_image raise SaltCloudNotFound(f"The specified image, '{vm_image}', could not be found.") def show_image(kwargs, call=None): """ Show the details from QingCloud concerning an image. CLI Examples: .. code-block:: bash salt-cloud -f show_image my-qingcloud image=trustysrvx64c salt-cloud -f show_image my-qingcloud image=trustysrvx64c,coreos4 salt-cloud -f show_image my-qingcloud image=trustysrvx64c zone=ap1 """ if call != "function": raise SaltCloudSystemExit( "The show_images function must be called with -f or --function" ) if not isinstance(kwargs, dict): kwargs = {} images = kwargs["image"] images = images.split(",") params = { "action": "DescribeImages", "images": images, "zone": _get_specified_zone(kwargs, get_configured_provider()), } items = query(params=params) if not items["image_set"]: raise SaltCloudNotFound("The specified image could not be found.") result = {} for image in items["image_set"]: result[image["image_id"]] = {} for key in image: result[image["image_id"]][key] = image[key] return result # QingCloud doesn't provide an API of geting instance sizes QINGCLOUD_SIZES = { "pek2": { "c1m1": {"cpu": 1, "memory": "1G"}, "c1m2": {"cpu": 1, "memory": "2G"}, "c1m4": {"cpu": 1, "memory": "4G"}, "c2m2": {"cpu": 2, "memory": "2G"}, "c2m4": {"cpu": 2, "memory": "4G"}, "c2m8": {"cpu": 2, "memory": "8G"}, "c4m4": {"cpu": 4, "memory": "4G"}, "c4m8": {"cpu": 4, "memory": "8G"}, "c4m16": {"cpu": 4, "memory": "16G"}, }, "pek1": { "small_b": {"cpu": 1, "memory": "1G"}, "small_c": {"cpu": 1, "memory": "2G"}, "medium_a": {"cpu": 2, "memory": "2G"}, "medium_b": {"cpu": 2, "memory": "4G"}, "medium_c": {"cpu": 2, "memory": "8G"}, "large_a": {"cpu": 4, "memory": "4G"}, "large_b": {"cpu": 4, "memory": "8G"}, "large_c": {"cpu": 4, "memory": "16G"}, }, } QINGCLOUD_SIZES["ap1"] = QINGCLOUD_SIZES["pek2"] QINGCLOUD_SIZES["gd1"] = QINGCLOUD_SIZES["pek2"] def avail_sizes(kwargs=None, call=None): """ Return a list of the instance sizes that are on the provider. CLI Examples: .. code-block:: bash salt-cloud --list-sizes my-qingcloud salt-cloud -f avail_sizes my-qingcloud zone=pek2 """ if call == "action": raise SaltCloudSystemExit( "The avail_sizes function must be called with " "-f or --function, or with the --list-sizes option" ) zone = _get_specified_zone(kwargs, get_configured_provider()) result = {} for size_key in QINGCLOUD_SIZES[zone]: result[size_key] = {} for attribute_key in QINGCLOUD_SIZES[zone][size_key]: result[size_key][attribute_key] = QINGCLOUD_SIZES[zone][size_key][ attribute_key ] return result def _get_size(vm_): """ Return the VM's size. Used by create(). """ 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 instance.") if vm_size in sizes: return vm_size raise SaltCloudNotFound(f"The specified size, '{vm_size}', could not be found.") def _show_normalized_node(full_node): """ Normalize the QingCloud instance data. Used by list_nodes()-related functions. """ public_ips = full_node.get("eip", []) if public_ips: public_ip = public_ips["eip_addr"] public_ips = [ public_ip, ] private_ips = [] for vxnet in full_node.get("vxnets", []): private_ip = vxnet.get("private_ip", None) if private_ip: private_ips.append(private_ip) normalized_node = { "id": full_node["instance_id"], "image": full_node["image"]["image_id"], "size": full_node["instance_type"], "state": full_node["status"], "private_ips": private_ips, "public_ips": public_ips, } return normalized_node def list_nodes_full(call=None): """ Return a list of the instances that are on the provider. CLI Examples: .. code-block:: bash salt-cloud -F my-qingcloud """ if call == "action": raise SaltCloudSystemExit( "The list_nodes_full function must be called with -f or --function." ) zone = _get_specified_zone() params = { "action": "DescribeInstances", "zone": zone, "status": ["pending", "running", "stopped", "suspended"], } items = query(params=params) log.debug("Total %s instances found in zone %s", items["total_count"], zone) result = {} if items["total_count"] == 0: return result for node in items["instance_set"]: normalized_node = _show_normalized_node(node) node.update(normalized_node) result[node["instance_id"]] = node provider = _get_active_provider_name() or "qingcloud" if ":" in provider: comps = provider.split(":") provider = comps[0] __opts__["update_cachedir"] = True __utils__["cloud.cache_node_list"](result, provider, __opts__) return result def list_nodes(call=None): """ Return a list of the instances that are on the provider. CLI Examples: .. code-block:: bash salt-cloud -Q my-qingcloud """ if call == "action": raise SaltCloudSystemExit( "The list_nodes function must be called with -f or --function." ) nodes = list_nodes_full() ret = {} for instance_id, full_node in nodes.items(): ret[instance_id] = { "id": full_node["id"], "image": full_node["image"], "size": full_node["size"], "state": full_node["state"], "public_ips": full_node["public_ips"], "private_ips": full_node["private_ips"], } return ret def list_nodes_min(call=None): """ Return a list of the instances that are on the provider. Only a list of instances names, and their state, is returned. CLI Examples: .. code-block:: bash salt-cloud -f list_nodes_min my-qingcloud """ if call != "function": raise SaltCloudSystemExit( "The list_nodes_min function must be called with -f or --function." ) nodes = list_nodes_full() result = {} for instance_id, full_node in nodes.items(): result[instance_id] = { "name": full_node["instance_name"], "status": full_node["status"], } return result def list_nodes_select(call=None): """ Return a list of the instances that are on the provider, with selected fields. CLI Examples: .. code-block:: bash salt-cloud -S my-qingcloud """ return salt.utils.cloud.list_nodes_select( list_nodes_full("function"), __opts__["query.selection"], call, ) def show_instance(instance_id, call=None, kwargs=None): """ Show the details from QingCloud concerning an instance. CLI Examples: .. code-block:: bash salt-cloud -a show_instance i-2f733r5n """ if call != "action": raise SaltCloudSystemExit( "The show_instance action must be called with -a or --action." ) params = { "action": "DescribeInstances", "instances.1": instance_id, "zone": _get_specified_zone(kwargs=None, provider=get_configured_provider()), } items = query(params=params) if items["total_count"] == 0: raise SaltCloudNotFound( f"The specified instance, '{instance_id}', could not be found." ) full_node = items["instance_set"][0] normalized_node = _show_normalized_node(full_node) full_node.update(normalized_node) result = full_node return result def _query_node_data(instance_id): data = show_instance(instance_id, call="action") if not data: return False if data.get("private_ips", []): return data def create(vm_): """ Create a single instance from a data dict. CLI Examples: .. code-block:: bash salt-cloud -p qingcloud-ubuntu-c1m1 hostname1 salt-cloud -m /path/to/mymap.sls -P """ 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 "qingcloud", 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"]) # params params = { "action": "RunInstances", "instance_name": vm_["name"], "zone": _get_location(vm_), "instance_type": _get_size(vm_), "image_id": _get_image(vm_), "vxnets.1": vm_["vxnets"], "login_mode": vm_["login_mode"], "login_keypair": vm_["login_keypair"], } __utils__["cloud.fire_event"]( "event", "requesting instance", "salt/cloud/{}/requesting".format(vm_["name"]), args={ "kwargs": __utils__["cloud.filter_event"]( "requesting", params, list(params) ), }, sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) result = query(params) new_instance_id = result["instances"][0] try: data = salt.utils.cloud.wait_for_ip( _query_node_data, update_args=(new_instance_id,), 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)) private_ip = data["private_ips"][0] log.debug("VM %s is now running", private_ip) vm_["ssh_host"] = private_ip # The instance is booted and accessible, let's Salt it! __utils__["cloud.bootstrap"](vm_, __opts__) 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 data 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 start(instance_id, call=None): """ Start an instance. CLI Examples: .. code-block:: bash salt-cloud -a start i-2f733r5n """ if call != "action": raise SaltCloudSystemExit("The stop action must be called with -a or --action.") log.info("Starting instance %s", instance_id) params = { "action": "StartInstances", "zone": _get_specified_zone(provider=get_configured_provider()), "instances.1": instance_id, } result = query(params) return result def stop(instance_id, force=False, call=None): """ Stop an instance. CLI Examples: .. code-block:: bash salt-cloud -a stop i-2f733r5n salt-cloud -a stop i-2f733r5n force=True """ if call != "action": raise SaltCloudSystemExit("The stop action must be called with -a or --action.") log.info("Stopping instance %s", instance_id) params = { "action": "StopInstances", "zone": _get_specified_zone(provider=get_configured_provider()), "instances.1": instance_id, "force": int(force), } result = query(params) return result def reboot(instance_id, call=None): """ Reboot an instance. CLI Examples: .. code-block:: bash salt-cloud -a reboot i-2f733r5n """ if call != "action": raise SaltCloudSystemExit("The stop action must be called with -a or --action.") log.info("Rebooting instance %s", instance_id) params = { "action": "RestartInstances", "zone": _get_specified_zone(provider=get_configured_provider()), "instances.1": instance_id, } result = query(params) return result def destroy(instance_id, call=None): """ Destroy an instance. CLI Example: .. code-block:: bash salt-cloud -a destroy i-2f733r5n salt-cloud -d i-2f733r5n """ if call == "function": raise SaltCloudSystemExit( "The destroy action must be called with -d, --destroy, -a or --action." ) instance_data = show_instance(instance_id, call="action") name = instance_data["instance_name"] __utils__["cloud.fire_event"]( "event", "destroying instance", f"salt/cloud/{name}/destroying", args={"name": name}, sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) params = { "action": "TerminateInstances", "zone": _get_specified_zone(provider=get_configured_provider()), "instances.1": instance_id, } result = 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 result