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/states
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/states/keystone.py
""" Management of Keystone users ============================ :depends: - keystoneclient Python module :configuration: See :py:mod:`salt.modules.keystone` for setup instructions. .. code-block:: yaml Keystone tenants: keystone.tenant_present: - names: - admin - demo - service Keystone roles: keystone.role_present: - names: - admin - Member admin: keystone.user_present: - password: R00T_4CC3SS - email: admin@domain.com - roles: admin: # tenants - admin # roles service: - admin - Member - require: - keystone: Keystone tenants - keystone: Keystone roles nova: keystone.user_present: - password: '$up3rn0v4' - email: nova@domain.com - tenant: service - roles: service: - admin - require: - keystone: Keystone tenants - keystone: Keystone roles demo: keystone.user_present: - password: 'd3m0n$trati0n' - email: demo@domain.com - tenant: demo - roles: demo: - Member - require: - keystone: Keystone tenants - keystone: Keystone roles nova service: keystone.service_present: - name: nova - service_type: compute - description: OpenStack Compute Service """ def __virtual__(): """ Only load if the keystone module is in __salt__ """ if "keystone.auth" in __salt__: return "keystone" return (False, "keystone module could not be loaded") _OS_IDENTITY_API_VERSION = 2 _TENANT_ID = "tenant_id" def _api_version(profile=None, **connection_args): """ Sets global variables _OS_IDENTITY_API_VERSION and _TENANT_ID depending on API version. """ global _TENANT_ID global _OS_IDENTITY_API_VERSION try: if ( float( __salt__["keystone.api_version"]( profile=profile, **connection_args ).strip("v") ) >= 3 ): _TENANT_ID = "project_id" _OS_IDENTITY_API_VERSION = 3 except KeyError: pass def user_present( name, password, email, tenant=None, enabled=True, roles=None, profile=None, password_reset=True, project=None, **connection_args, ): """ Ensure that the keystone user is present with the specified properties. name The name of the user to manage password The password to use for this user. .. note:: If the user already exists and a different password was set for the user than the one specified here, the password for the user will be updated. Please set the ``password_reset`` option to ``False`` if this is not the desired behavior. password_reset Whether or not to reset password after initial set. Defaults to ``True``. email The email address for this user tenant The tenant (name) for this user project The project (name) for this user (overrides tenant in api v3) enabled Availability state for this user roles The roles the user should have under given tenants. Passed as a dictionary mapping tenant names to a list of roles in this tenant, i.e.:: roles: admin: # tenant - admin # role service: - admin - Member """ ret = { "name": name, "changes": {}, "result": True, "comment": f'User "{name}" will be updated', } _api_version(profile=profile, **connection_args) if project and not tenant: tenant = project # Validate tenant if set if tenant is not None: tenantdata = __salt__["keystone.tenant_get"]( name=tenant, profile=profile, **connection_args ) if "Error" in tenantdata: ret["result"] = False ret["comment"] = f'Tenant / project "{tenant}" does not exist' return ret tenant_id = tenantdata[tenant]["id"] else: tenant_id = None # Check if user is already present user = __salt__["keystone.user_get"](name=name, profile=profile, **connection_args) if "Error" not in user: change_email = False change_enabled = False change_tenant = False change_password = False if user[name].get("email", None) != email: change_email = True if user[name].get("enabled", None) != enabled: change_enabled = True if tenant and ( _TENANT_ID not in user[name] or user[name].get(_TENANT_ID, None) != tenant_id ): change_tenant = True if password_reset is True and not __salt__["keystone.user_verify_password"]( name=name, password=password, profile=profile, **connection_args ): change_password = True if __opts__.get("test") and ( change_email or change_enabled or change_tenant or change_password ): ret["result"] = None ret["comment"] = f'User "{name}" will be updated' if change_email is True: ret["changes"]["Email"] = "Will be updated" if change_enabled is True: ret["changes"]["Enabled"] = "Will be True" if change_tenant is True: ret["changes"]["Tenant"] = f'Will be added to "{tenant}" tenant' if change_password is True: ret["changes"]["Password"] = "Will be updated" return ret ret["comment"] = f'User "{name}" is already present' if change_email: __salt__["keystone.user_update"]( name=name, email=email, profile=profile, **connection_args ) ret["comment"] = f'User "{name}" has been updated' ret["changes"]["Email"] = "Updated" if change_enabled: __salt__["keystone.user_update"]( name=name, enabled=enabled, profile=profile, **connection_args ) ret["comment"] = f'User "{name}" has been updated' ret["changes"]["Enabled"] = f"Now {enabled}" if change_tenant: __salt__["keystone.user_update"]( name=name, tenant=tenant, profile=profile, **connection_args ) ret["comment"] = f'User "{name}" has been updated' ret["changes"]["Tenant"] = f'Added to "{tenant}" tenant' if change_password: __salt__["keystone.user_password_update"]( name=name, password=password, profile=profile, **connection_args ) ret["comment"] = f'User "{name}" has been updated' ret["changes"]["Password"] = "Updated" if roles: for tenant in roles: args = dict( {"user_name": name, "tenant_name": tenant, "profile": profile}, **connection_args, ) tenant_roles = __salt__["keystone.user_role_list"](**args) for role in roles[tenant]: if role not in tenant_roles: if __opts__.get("test"): ret["result"] = None ret["comment"] = 'User roles "{}" will been updated'.format( name ) return ret addargs = dict( { "user": name, "role": role, "tenant": tenant, "profile": profile, }, **connection_args, ) newrole = __salt__["keystone.user_role_add"](**addargs) if "roles" in ret["changes"]: ret["changes"]["roles"].append(newrole) else: ret["changes"]["roles"] = [newrole] roles_to_remove = list(set(tenant_roles) - set(roles[tenant])) for role in roles_to_remove: if __opts__.get("test"): ret["result"] = None ret["comment"] = 'User roles "{}" will been updated'.format( name ) return ret addargs = dict( { "user": name, "role": role, "tenant": tenant, "profile": profile, }, **connection_args, ) oldrole = __salt__["keystone.user_role_remove"](**addargs) if "roles" in ret["changes"]: ret["changes"]["roles"].append(oldrole) else: ret["changes"]["roles"] = [oldrole] else: # Create that user! if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Keystone user "{name}" will be added' ret["changes"]["User"] = "Will be created" return ret __salt__["keystone.user_create"]( name=name, password=password, email=email, tenant_id=tenant_id, enabled=enabled, profile=profile, **connection_args, ) if roles: for tenant in roles: for role in roles[tenant]: __salt__["keystone.user_role_add"]( user=name, role=role, tenant=tenant, profile=profile, **connection_args, ) ret["comment"] = f"Keystone user {name} has been added" ret["changes"]["User"] = "Created" return ret def user_absent(name, profile=None, **connection_args): """ Ensure that the keystone user is absent. name The name of the user that should not exist """ ret = { "name": name, "changes": {}, "result": True, "comment": f'User "{name}" is already absent', } # Check if user is present user = __salt__["keystone.user_get"](name=name, profile=profile, **connection_args) if "Error" not in user: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'User "{name}" will be deleted' return ret # Delete that user! __salt__["keystone.user_delete"](name=name, profile=profile, **connection_args) ret["comment"] = f'User "{name}" has been deleted' ret["changes"]["User"] = "Deleted" return ret def tenant_present( name, description=None, enabled=True, profile=None, **connection_args ): """ Ensures that the keystone tenant exists name The name of the tenant to manage description The description to use for this tenant enabled Availability state for this tenant """ ret = { "name": name, "changes": {}, "result": True, "comment": f'Tenant / project "{name}" already exists', } _api_version(profile=profile, **connection_args) # Check if tenant is already present tenant = __salt__["keystone.tenant_get"]( name=name, profile=profile, **connection_args ) if "Error" not in tenant: if tenant[name].get("description", None) != description: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Tenant / project "{name}" will be updated' ret["changes"]["Description"] = "Will be updated" return ret __salt__["keystone.tenant_update"]( name=name, description=description, enabled=enabled, profile=profile, **connection_args, ) ret["comment"] = f'Tenant / project "{name}" has been updated' ret["changes"]["Description"] = "Updated" if tenant[name].get("enabled", None) != enabled: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Tenant / project "{name}" will be updated' ret["changes"]["Enabled"] = f"Will be {enabled}" return ret __salt__["keystone.tenant_update"]( name=name, description=description, enabled=enabled, profile=profile, **connection_args, ) ret["comment"] = f'Tenant / project "{name}" has been updated' ret["changes"]["Enabled"] = f"Now {enabled}" else: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Tenant / project "{name}" will be added' ret["changes"]["Tenant"] = "Will be created" return ret # Create tenant if _OS_IDENTITY_API_VERSION > 2: created = __salt__["keystone.project_create"]( name=name, domain="default", description=description, enabled=enabled, profile=profile, **connection_args, ) else: created = __salt__["keystone.tenant_create"]( name=name, description=description, enabled=enabled, profile=profile, **connection_args, ) ret["changes"]["Tenant"] = "Created" if created is True else "Failed" ret["result"] = created ret["comment"] = f'Tenant / project "{name}" has been added' return ret def tenant_absent(name, profile=None, **connection_args): """ Ensure that the keystone tenant is absent. name The name of the tenant that should not exist """ ret = { "name": name, "changes": {}, "result": True, "comment": f'Tenant / project "{name}" is already absent', } # Check if tenant is present tenant = __salt__["keystone.tenant_get"]( name=name, profile=profile, **connection_args ) if "Error" not in tenant: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Tenant / project "{name}" will be deleted' return ret # Delete tenant __salt__["keystone.tenant_delete"]( name=name, profile=profile, **connection_args ) ret["comment"] = f'Tenant / project "{name}" has been deleted' ret["changes"]["Tenant/Project"] = "Deleted" return ret def project_present( name, description=None, enabled=True, profile=None, **connection_args ): """ Ensures that the keystone project exists Alias for tenant_present from V2 API to fulfill V3 API naming convention. .. versionadded:: 2016.11.0 name The name of the project to manage description The description to use for this project enabled Availability state for this project .. code-block:: yaml nova: keystone.project_present: - enabled: True - description: 'Nova Compute Service' """ return tenant_present( name, description=description, enabled=enabled, profile=profile, **connection_args, ) def project_absent(name, profile=None, **connection_args): """ Ensure that the keystone project is absent. Alias for tenant_absent from V2 API to fulfill V3 API naming convention. .. versionadded:: 2016.11.0 name The name of the project that should not exist .. code-block:: yaml delete_nova: keystone.project_absent: - name: nova """ return tenant_absent(name, profile=profile, **connection_args) def role_present(name, profile=None, **connection_args): """' Ensures that the keystone role exists name The name of the role that should be present """ ret = { "name": name, "changes": {}, "result": True, "comment": f'Role "{name}" already exists', } # Check if role is already present role = __salt__["keystone.role_get"](name=name, profile=profile, **connection_args) if "Error" not in role: return ret else: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Role "{name}" will be added' return ret # Create role __salt__["keystone.role_create"](name, profile=profile, **connection_args) ret["comment"] = f'Role "{name}" has been added' ret["changes"]["Role"] = "Created" return ret def role_absent(name, profile=None, **connection_args): """ Ensure that the keystone role is absent. name The name of the role that should not exist """ ret = { "name": name, "changes": {}, "result": True, "comment": f'Role "{name}" is already absent', } # Check if role is present role = __salt__["keystone.role_get"](name=name, profile=profile, **connection_args) if "Error" not in role: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Role "{name}" will be deleted' return ret # Delete role __salt__["keystone.role_delete"](name=name, profile=profile, **connection_args) ret["comment"] = f'Role "{name}" has been deleted' ret["changes"]["Role"] = "Deleted" return ret def service_present( name, service_type, description=None, profile=None, **connection_args ): """ Ensure service present in Keystone catalog name The name of the service service_type The type of Openstack Service description (optional) Description of the service """ ret = { "name": name, "changes": {}, "result": True, "comment": f'Service "{name}" already exists', } # Check if service is already present role = __salt__["keystone.service_get"]( name=name, profile=profile, **connection_args ) if "Error" not in role: return ret else: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Service "{name}" will be added' return ret # Create service __salt__["keystone.service_create"]( name, service_type, description, profile=profile, **connection_args ) ret["comment"] = f'Service "{name}" has been added' ret["changes"]["Service"] = "Created" return ret def service_absent(name, profile=None, **connection_args): """ Ensure that the service doesn't exist in Keystone catalog name The name of the service that should not exist """ ret = { "name": name, "changes": {}, "result": True, "comment": f'Service "{name}" is already absent', } # Check if service is present role = __salt__["keystone.service_get"]( name=name, profile=profile, **connection_args ) if "Error" not in role: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Service "{name}" will be deleted' return ret # Delete service __salt__["keystone.service_delete"]( name=name, profile=profile, **connection_args ) ret["comment"] = f'Service "{name}" has been deleted' ret["changes"]["Service"] = "Deleted" return ret def endpoint_present( name, publicurl=None, internalurl=None, adminurl=None, region=None, profile=None, url=None, interface=None, **connection_args, ): """ Ensure the specified endpoints exists for service name The Service name publicurl The public url of service endpoint (for V2 API) internalurl The internal url of service endpoint (for V2 API) adminurl The admin url of the service endpoint (for V2 API) region The region of the endpoint url The endpoint URL (for V3 API) interface The interface type, which describes the visibility of the endpoint. (for V3 API) """ ret = {"name": name, "changes": {}, "result": True, "comment": ""} _api_version(profile=profile, **connection_args) endpoint = __salt__["keystone.endpoint_get"]( name, region, profile=profile, interface=interface, **connection_args ) def _changes(desc): return ret.get("comment", "") + desc + "\n" def _create_endpoint(): if _OS_IDENTITY_API_VERSION > 2: ret["changes"] = __salt__["keystone.endpoint_create"]( name, region=region, url=url, interface=interface, profile=profile, **connection_args, ) else: ret["changes"] = __salt__["keystone.endpoint_create"]( name, region=region, publicurl=publicurl, adminurl=adminurl, internalurl=internalurl, profile=profile, **connection_args, ) if endpoint and "Error" not in endpoint and endpoint.get("region") == region: if _OS_IDENTITY_API_VERSION > 2: change_url = False change_interface = False if endpoint.get("url", None) != url: ret["comment"] = _changes( 'URL changes from "{}" to "{}"'.format( endpoint.get("url", None), url ) ) change_url = True if endpoint.get("interface", None) != interface: ret["comment"] = _changes( 'Interface changes from "{}" to "{}"'.format( endpoint.get("interface", None), interface ) ) change_interface = True if __opts__.get("test") and (change_url or change_interface): ret["result"] = None ret["changes"]["Endpoint"] = "Will be updated" ret["comment"] += 'Endpoint for service "{}" will be updated'.format( name ) return ret if change_url: ret["changes"]["url"] = url if change_interface: ret["changes"]["interface"] = interface else: change_publicurl = False change_adminurl = False change_internalurl = False if endpoint.get("publicurl", None) != publicurl: change_publicurl = True ret["comment"] = _changes( 'Public URL changes from "{}" to "{}"'.format( endpoint.get("publicurl", None), publicurl ) ) if endpoint.get("adminurl", None) != adminurl: change_adminurl = True ret["comment"] = _changes( 'Admin URL changes from "{}" to "{}"'.format( endpoint.get("adminurl", None), adminurl ) ) if endpoint.get("internalurl", None) != internalurl: change_internalurl = True ret["comment"] = _changes( 'Internal URL changes from "{}" to "{}"'.format( endpoint.get("internalurl", None), internalurl ) ) if __opts__.get("test") and ( change_publicurl or change_adminurl or change_internalurl ): ret["result"] = None ret["comment"] += 'Endpoint for service "{}" will be updated'.format( name ) ret["changes"]["Endpoint"] = "Will be updated" return ret if change_publicurl: ret["changes"]["publicurl"] = publicurl if change_adminurl: ret["changes"]["adminurl"] = adminurl if change_internalurl: ret["changes"]["internalurl"] = internalurl if ret["comment"]: # changed __salt__["keystone.endpoint_delete"]( name, region, profile=profile, interface=interface, **connection_args ) _create_endpoint() ret["comment"] += f'Endpoint for service "{name}" has been updated' else: # Add new endpoint if __opts__.get("test"): ret["result"] = None ret["changes"]["Endpoint"] = "Will be created" ret["comment"] = f'Endpoint for service "{name}" will be added' return ret _create_endpoint() ret["comment"] = f'Endpoint for service "{name}" has been added' if ret["comment"] == "": # => no changes ret["comment"] = f'Endpoint for service "{name}" already exists' return ret def endpoint_absent(name, region=None, profile=None, interface=None, **connection_args): """ Ensure that the endpoint for a service doesn't exist in Keystone catalog name The name of the service whose endpoints should not exist region (optional) The region of the endpoint. Defaults to ``RegionOne``. interface The interface type, which describes the visibility of the endpoint. (for V3 API) """ ret = { "name": name, "changes": {}, "result": True, "comment": 'Endpoint for service "{}"{} is already absent'.format( name, f', interface "{interface}",' if interface is not None else "", ), } # Check if service is present endpoint = __salt__["keystone.endpoint_get"]( name, region, profile=profile, interface=interface, **connection_args ) if not endpoint: return ret else: if __opts__.get("test"): ret["result"] = None ret["comment"] = f'Endpoint for service "{name}" will be deleted' return ret # Delete service __salt__["keystone.endpoint_delete"]( name, region, profile=profile, interface=interface, **connection_args ) ret["comment"] = 'Endpoint for service "{}"{} has been deleted'.format( name, f', interface "{interface}",' if interface is not None else "", ) ret["changes"]["endpoint"] = "Deleted" return ret