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/github.py
""" Github User State Module .. versionadded:: 2016.3.0 This state is used to ensure presence of users in the Organization. .. code-block:: yaml ensure user test is present in github: github.present: - name: 'Example TestUser1' - email: example@domain.com - username: 'gitexample' """ import datetime import logging import time from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) def __virtual__(): """ Only load if the github module is available in __salt__ """ if "github.list_users" in __salt__: return "github" return (False, "github module could not be loaded") def present(name, profile="github", **kwargs): """ Ensure a user is present .. code-block:: yaml ensure user test is present in github: github.present: - name: 'gitexample' The following parameters are required: name This is the github handle of the user in the organization """ ret = {"name": name, "changes": {}, "result": None, "comment": ""} target = __salt__["github.get_user"](name, profile=profile, **kwargs) # If the user has a valid github handle and is not in the org already if not target: ret["result"] = False ret["comment"] = f"Couldnt find user {name}" elif isinstance(target, bool) and target: ret["comment"] = f"User {name} is already in the org " ret["result"] = True elif ( not target.get("in_org", False) and target.get("membership_state") != "pending" ): if __opts__["test"]: ret["comment"] = f"User {name} will be added to the org" return ret # add the user result = __salt__["github.add_user"](name, profile=profile, **kwargs) if result: ret["changes"].setdefault("old", None) ret["changes"].setdefault("new", f"User {name} exists in the org now") ret["result"] = True else: ret["result"] = False ret["comment"] = f"Failed to add user {name} to the org" else: ret["comment"] = f"User {name} has already been invited." ret["result"] = True return ret def absent(name, profile="github", **kwargs): """ Ensure a github user is absent .. code-block:: yaml ensure user test is absent in github: github.absent: - name: 'Example TestUser1' - email: example@domain.com - username: 'gitexample' The following parameters are required: name Github handle of the user in organization """ email = kwargs.get("email") full_name = kwargs.get("fullname") ret = { "name": name, "changes": {}, "result": None, "comment": f"User {name} is absent.", } target = __salt__["github.get_user"](name, profile=profile, **kwargs) if target: if isinstance(target, bool) or target.get("in_org", False): if __opts__["test"]: ret["comment"] = f"User {name} will be deleted" ret["result"] = None return ret result = __salt__["github.remove_user"](name, profile=profile, **kwargs) if result: ret["comment"] = f"Deleted user {name}" ret["changes"].setdefault("old", f"User {name} exists") ret["changes"].setdefault("new", f"User {name} deleted") ret["result"] = True else: ret["comment"] = f"Failed to delete {name}" ret["result"] = False else: ret["comment"] = f"User {name} has already been deleted!" ret["result"] = True else: ret["comment"] = f"User {name} does not exist" ret["result"] = True return ret return ret def team_present( name, description=None, repo_names=None, privacy="secret", permission="pull", members=None, enforce_mfa=False, no_mfa_grace_seconds=0, profile="github", **kwargs, ): """ Ensure a team is present name This is the name of the team in the organization. description The description of the team. repo_names The names of repositories to add the team to. privacy The level of privacy for the team, can be 'secret' or 'closed'. Defaults to secret. permission The default permission for new repositories added to the team, can be 'pull', 'push' or 'admin'. Defaults to pull. members The members belonging to the team, specified as a dict of member name to optional configuration. Options include 'enforce_mfa_from' and 'mfa_exempt'. enforce_mfa Whether to enforce MFA requirements on members of the team. If True then all members without `mfa_exempt: True` configured will be removed from the team. Note that `no_mfa_grace_seconds` may be set to allow members a grace period. no_mfa_grace_seconds The number of seconds of grace time that a member will have to enable MFA before being removed from the team. The grace period will begin from `enforce_mfa_from` on the member configuration, which defaults to 1970/01/01. Example: .. code-block:: yaml Ensure team test is present in github: github.team_present: - name: 'test' - members: user1: {} user2: {} Ensure team test_mfa is present in github: github.team_present: - name: 'test_mfa' - members: user1: enforce_mfa_from: 2016/06/15 - enforce_mfa: True .. versionadded:: 2016.11.0 """ ret = {"name": name, "changes": {}, "result": True, "comment": ""} target = __salt__["github.get_team"](name, profile=profile, **kwargs) test_comments = [] if target: # Team already exists parameters = {} if description is not None and target["description"] != description: parameters["description"] = description if permission is not None and target["permission"] != permission: parameters["permission"] = permission if privacy is not None and target["privacy"] != privacy: parameters["privacy"] = privacy if len(parameters) > 0: if __opts__["test"]: test_comments.append( f"Team properties are set to be edited: {parameters}" ) ret["result"] = None else: result = __salt__["github.edit_team"]( name, profile=profile, **parameters ) if result: ret["changes"]["team"] = { "old": f"Team properties were {target}", "new": "Team properties (that changed) are {}".format( parameters ), } else: ret["result"] = False ret["comment"] = "Failed to update team properties." return ret manage_repos = repo_names is not None current_repos = set( __salt__["github.list_team_repos"](name, profile=profile).keys() ) repo_names = set(repo_names or []) repos_to_add = repo_names - current_repos repos_to_remove = current_repos - repo_names if repo_names else [] if repos_to_add: if __opts__["test"]: test_comments.append( "Team {} will have the following repos added: {}.".format( name, list(repos_to_add) ) ) ret["result"] = None else: for repo_name in repos_to_add: result = __salt__["github.add_team_repo"]( repo_name, name, profile=profile, **kwargs ) if result: ret["changes"][repo_name] = { "old": f"Repo {repo_name} is not in team {name}", "new": f"Repo {repo_name} is in team {name}", } else: ret["result"] = False ret["comment"] = "Failed to add repo {} to team {}.".format( repo_name, name ) return ret if repos_to_remove: if __opts__["test"]: test_comments.append( "Team {} will have the following repos removed: {}.".format( name, list(repos_to_remove) ) ) ret["result"] = None else: for repo_name in repos_to_remove: result = __salt__["github.remove_team_repo"]( repo_name, name, profile=profile, **kwargs ) if result: ret["changes"][repo_name] = { "old": f"Repo {repo_name} is in team {name}", "new": f"Repo {repo_name} is not in team {name}", } else: ret["result"] = False ret["comment"] = ( "Failed to remove repo {} from team {}.".format( repo_name, name ) ) return ret else: # Team does not exist - it will be created. if __opts__["test"]: ret["comment"] = f"Team {name} is set to be created." ret["result"] = None return ret result = __salt__["github.add_team"]( name, description=description, repo_names=repo_names, permission=permission, privacy=privacy, profile=profile, **kwargs, ) if result: ret["changes"]["team"] = {} ret["changes"]["team"]["old"] = None ret["changes"]["team"]["new"] = f"Team {name} has been created" else: ret["result"] = False ret["comment"] = f"Failed to create team {name}." return ret manage_members = members is not None mfa_deadline = datetime.datetime.utcnow() - datetime.timedelta( seconds=no_mfa_grace_seconds ) members_no_mfa = __salt__["github.list_members_without_mfa"](profile=profile) members_lower = {} for member_name, info in members or {}.items(): members_lower[member_name.lower()] = info member_change = False current_members = __salt__["github.list_team_members"](name, profile=profile) for member, member_info in members or {}.items(): log.info("Checking member %s in team %s", member, name) if member.lower() not in current_members: if enforce_mfa and _member_violates_mfa( member, member_info, mfa_deadline, members_no_mfa ): if __opts__["test"]: test_comments.append( "User {} will not be added to the " "team because they do not have MFA." "".format(member) ) else: # Add to team member_change = True if __opts__["test"]: test_comments.append(f"User {member} set to be added to the team.") ret["result"] = None else: result = __salt__["github.add_team_member"]( member, name, profile=profile, **kwargs ) if result: ret["changes"][member] = {} ret["changes"][member][ "old" ] = f"User {member} is not in team {name}" ret["changes"][member]["new"] = "User {} is in team {}".format( member, name ) else: ret["result"] = False ret["comment"] = "Failed to add user {} to team {}.".format( member, name ) return ret for member in current_members: mfa_violation = False if member in members_lower: mfa_violation = _member_violates_mfa( member, members_lower[member], mfa_deadline, members_no_mfa ) if ( manage_members and member not in members_lower or (enforce_mfa and mfa_violation) ): # Remove from team member_change = True if __opts__["test"]: if mfa_violation: test_comments.append( "User {} set to be removed from the " "team because they do not have MFA.".format(member) ) else: test_comments.append( f"User {member} set to be removed from the team." ) ret["result"] = None else: result = __salt__["github.remove_team_member"]( member, name, profile=profile, **kwargs ) if result: extra_changes = " due to MFA violation" if mfa_violation else "" ret["changes"][member] = { "old": f"User {member} is in team {name}", "new": "User {} is not in team {}{}".format( member, name, extra_changes ), } else: ret["result"] = False ret["comment"] = "Failed to remove user {} from team {}.".format( member, name ) return ret if member_change: # Refresh team cache __salt__["github.list_team_members"]( name, profile=profile, ignore_cache=False, **kwargs ) if len(test_comments) > 0: ret["comment"] = "\n".join(test_comments) return ret def _member_violates_mfa(member, member_info, mfa_deadline, members_without_mfa): if member_info.get("mfa_exempt", False): return False enforce_mfa_from = datetime.datetime.strptime( member_info.get("enforce_mfa_from", "1970/01/01"), "%Y/%m/%d" ) return member.lower() in members_without_mfa and (mfa_deadline > enforce_mfa_from) def team_absent(name, profile="github", **kwargs): """ Ensure a team is absent. Example: .. code-block:: yaml ensure team test is present in github: github.team_absent: - name: 'test' The following parameters are required: name This is the name of the team in the organization. .. versionadded:: 2016.11.0 """ ret = {"name": name, "changes": {}, "result": None, "comment": ""} target = __salt__["github.get_team"](name, profile=profile, **kwargs) if not target: ret["comment"] = f"Team {name} does not exist" ret["result"] = True return ret else: if __opts__["test"]: ret["comment"] = f"Team {name} will be deleted" ret["result"] = None return ret result = __salt__["github.remove_team"](name, profile=profile, **kwargs) if result: ret["comment"] = f"Deleted team {name}" ret["changes"].setdefault("old", f"Team {name} exists") ret["changes"].setdefault("new", f"Team {name} deleted") ret["result"] = True else: ret["comment"] = f"Failed to delete {name}" ret["result"] = False return ret def repo_present( name, description=None, homepage=None, private=None, has_issues=None, has_wiki=None, has_downloads=None, auto_init=False, gitignore_template=None, license_template=None, teams=None, profile="github", **kwargs, ): """ Ensure a repository is present name This is the name of the repository. description The description of the repository. homepage The URL with more information about the repository. private The visiblity of the repository. Note that private repositories require a paid GitHub account. has_issues Whether to enable issues for this repository. has_wiki Whether to enable the wiki for this repository. has_downloads Whether to enable downloads for this repository. auto_init Whether to create an initial commit with an empty README. gitignore_template The desired language or platform for a .gitignore, e.g "Haskell". license_template The desired LICENSE template to apply, e.g "mit" or "mozilla". teams The teams for which this repo should belong to, specified as a dict of team name to permission ('pull', 'push' or 'admin'). .. versionadded:: 2017.7.0 Example: .. code-block:: yaml Ensure repo my-repo is present in github: github.repo_present: - name: 'my-repo' - description: 'My very important repository' .. versionadded:: 2016.11.0 """ ret = {"name": name, "changes": {}, "result": True, "comment": ""} # This is an optimization to cache all repos in the organization up front. # The first use of this state will collect all of the repos and save a bunch # of API calls for future use. __salt__["github.list_repos"](profile=profile) try: target = __salt__["github.get_repo_info"](name, profile=profile, **kwargs) except CommandExecutionError: target = None given_params = { "description": description, "homepage": homepage, "private": private, "has_issues": has_issues, "has_wiki": has_wiki, "has_downloads": has_downloads, "auto_init": auto_init, "gitignore_template": gitignore_template, "license_template": license_template, } # Keep track of current_teams if we've fetched them after creating a new repo current_teams = None if target: # Repo already exists # Some params are only valid on repo creation ignore_params = ["auto_init", "gitignore_template", "license_template"] parameters = {} old_parameters = {} for param_name, param_value in given_params.items(): if ( param_value is not None and param_name not in ignore_params and target[param_name] is not param_value and target[param_name] != param_value ): parameters[param_name] = param_value old_parameters[param_name] = target[param_name] if len(parameters) > 0: repo_change = { "old": f"Repo properties were {old_parameters}", "new": f"Repo properties (that changed) are {parameters}", } if __opts__["test"]: ret["changes"]["repo"] = repo_change ret["result"] = None else: result = __salt__["github.edit_repo"]( name, profile=profile, **parameters ) if result: ret["changes"]["repo"] = repo_change else: ret["result"] = False ret["comment"] = "Failed to update repo properties." return ret else: # Repo does not exist - it will be created. repo_change = {"old": None, "new": f"Repo {name} has been created"} if __opts__["test"]: ret["changes"]["repo"] = repo_change ret["result"] = None else: add_params = dict(given_params) add_params.update(kwargs) result = __salt__["github.add_repo"](name, **add_params) if not result: ret["result"] = False ret["comment"] = f"Failed to create repo {name}." return ret # Turns out that trying to fetch teams for a new repo can 404 immediately # after repo creation, so this waits until we can fetch teams successfully # before continuing. for attempt in range(3): time.sleep(1) try: current_teams = __salt__["github.get_repo_teams"]( name, profile=profile, **kwargs ) break except CommandExecutionError as e: log.info("Attempt %s to fetch new repo %s failed", attempt, name) if current_teams is None: ret["result"] = False ret["comment"] = f"Failed to verify repo {name} after creation." return ret ret["changes"]["repo"] = repo_change if teams is not None: if __opts__["test"] and not target: # Assume no teams if we're in test mode and the repo doesn't exist current_teams = [] elif current_teams is None: current_teams = __salt__["github.get_repo_teams"](name, profile=profile) current_team_names = {t["name"] for t in current_teams} # First remove any teams that aren't present for team_name in current_team_names: if team_name not in teams: team_change = { "old": f"Repo {name} is in team {team_name}", "new": f"Repo {name} is not in team {team_name}", } if __opts__["test"]: ret["changes"][team_name] = team_change ret["result"] = None else: result = __salt__["github.remove_team_repo"]( name, team_name, profile=profile ) if result: ret["changes"][team_name] = team_change else: ret["result"] = False ret["comment"] = ( "Failed to remove repo {} from team {}.".format( name, team_name ) ) return ret # Next add or modify any necessary teams for team_name, permission in teams.items(): if team_name not in current_team_names: # Need to add repo to team team_change = { "old": f"Repo {name} is not in team {team_name}", "new": f"Repo {name} is in team {team_name}", } if __opts__["test"]: ret["changes"][team_name] = team_change ret["result"] = None else: result = __salt__["github.add_team_repo"]( name, team_name, profile=profile, permission=permission ) if result: ret["changes"][team_name] = team_change else: ret["result"] = False ret["comment"] = ( "Failed to remove repo {} from team {}.".format( name, team_name ) ) return ret else: current_permission = ( __salt__["github.list_team_repos"](team_name, profile=profile) .get(name.lower(), {}) .get("permission") ) if not current_permission: ret["result"] = False ret["comment"] = ( "Failed to determine current permission for team " "{} in repo {}".format(team_name, name) ) return ret elif current_permission != permission: team_change = { "old": "Repo {} in team {} has permission {}".format( name, team_name, current_permission ), "new": "Repo {} in team {} has permission {}".format( name, team_name, permission ), } if __opts__["test"]: ret["changes"][team_name] = team_change ret["result"] = None else: result = __salt__["github.add_team_repo"]( name, team_name, profile=profile, permission=permission ) if result: ret["changes"][team_name] = team_change else: ret["result"] = False ret["comment"] = ( "Failed to set permission on repo {} from " "team {} to {}.".format(name, team_name, permission) ) return ret return ret def repo_absent(name, profile="github", **kwargs): """ Ensure a repo is absent. Example: .. code-block:: yaml ensure repo test is absent in github: github.repo_absent: - name: 'test' The following parameters are required: name This is the name of the repository in the organization. .. versionadded:: 2016.11.0 """ ret = {"name": name, "changes": {}, "result": None, "comment": ""} try: target = __salt__["github.get_repo_info"](name, profile=profile, **kwargs) except CommandExecutionError: target = None if not target: ret["comment"] = f"Repo {name} does not exist" ret["result"] = True return ret else: if __opts__["test"]: ret["comment"] = f"Repo {name} will be deleted" ret["result"] = None return ret result = __salt__["github.remove_repo"](name, profile=profile, **kwargs) if result: ret["comment"] = f"Deleted repo {name}" ret["changes"].setdefault("old", f"Repo {name} exists") ret["changes"].setdefault("new", f"Repo {name} deleted") ret["result"] = True else: ret["comment"] = ( "Failed to delete repo {}. Ensure the delete_repo " "scope is enabled if using OAuth.".format(name) ) ret["result"] = False return ret